Refactor workflow auto-run to node graph
This commit is contained in:
@@ -1,13 +1,58 @@
|
||||
const test = require('node:test');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/verification-flow.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundVerificationFlow;`)(globalScope);
|
||||
const rawCreateVerificationFlowHelpers = api.createVerificationFlowHelpers.bind(api);
|
||||
|
||||
const TEST_STEP_NODE_IDS = Object.freeze({
|
||||
4: 'fetch-signup-code',
|
||||
7: 'oauth-login',
|
||||
8: 'fetch-login-code',
|
||||
11: 'fetch-login-code',
|
||||
});
|
||||
|
||||
const TEST_NODE_STEP_IDS = Object.fromEntries(
|
||||
Object.entries(TEST_STEP_NODE_IDS).map(([step, nodeId]) => [nodeId, Number(step)])
|
||||
);
|
||||
|
||||
function getTestNodeIdByStepForState(step) {
|
||||
return TEST_STEP_NODE_IDS[Number(step)] || '';
|
||||
}
|
||||
|
||||
function getTestStepIdByNodeId(nodeId) {
|
||||
return TEST_NODE_STEP_IDS[String(nodeId || '').trim()] || null;
|
||||
}
|
||||
|
||||
function normalizeVerificationFlowTestOverrides(overrides = {}) {
|
||||
const normalized = { ...overrides };
|
||||
if (
|
||||
typeof normalized.completeNodeFromBackground !== 'function'
|
||||
&& typeof normalized.completeStepFromBackground === 'function'
|
||||
) {
|
||||
const completeStepFromBackground = normalized.completeStepFromBackground;
|
||||
normalized.completeNodeFromBackground = async (nodeId, payload) => (
|
||||
completeStepFromBackground(getTestStepIdByNodeId(nodeId), payload)
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof normalized.setNodeStatus !== 'function'
|
||||
&& typeof normalized.setStepStatus === 'function'
|
||||
) {
|
||||
const setStepStatus = normalized.setStepStatus;
|
||||
normalized.setNodeStatus = async (nodeId, status) => (
|
||||
setStepStatus(getTestStepIdByNodeId(nodeId), status)
|
||||
);
|
||||
}
|
||||
delete normalized.completeStepFromBackground;
|
||||
delete normalized.setStepStatus;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function createVerificationFlowTestHelpers(overrides = {}) {
|
||||
return api.createVerificationFlowHelpers({
|
||||
return rawCreateVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
buildVerificationPollPayload: null,
|
||||
chrome: {
|
||||
@@ -18,8 +63,9 @@ function createVerificationFlowTestHelpers(overrides = {}) {
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
CLOUD_MAIL_PROVIDER: 'cloudmail',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getNodeIdByStepForState: getTestNodeIdByStepForState,
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
getState: async () => ({}),
|
||||
@@ -36,14 +82,16 @@ function createVerificationFlowTestHelpers(overrides = {}) {
|
||||
sendToContentScript: async () => ({}),
|
||||
sendToMailContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
setNodeStatus: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||
...overrides,
|
||||
...normalizeVerificationFlowTestOverrides(overrides),
|
||||
});
|
||||
}
|
||||
|
||||
api.createVerificationFlowHelpers = createVerificationFlowTestHelpers;
|
||||
|
||||
test('verification flow prefers injected verification poll payload builder when provided', () => {
|
||||
const helpers = createVerificationFlowTestHelpers({
|
||||
buildVerificationPollPayload: (step, state, overrides = {}) => ({
|
||||
@@ -85,7 +133,7 @@ test('verification flow keeps 2925 polling cadence in the default payload', () =
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -184,7 +232,7 @@ test('verification flow only enables 2925 target email matching in receive mode'
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -233,7 +281,7 @@ test('verification flow runs beforeSubmit hook before filling the code', async (
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (_step, payload) => {
|
||||
completeNodeFromBackground: async (_step, payload) => {
|
||||
events.push(['complete', payload.code]);
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
@@ -299,7 +347,7 @@ test('verification flow skips 2925 mailbox preclear when using a fixed login mai
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -360,7 +408,7 @@ test('verification flow skips 2925 mailbox preclear when using a fixed signup ma
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -436,7 +484,7 @@ test('verification flow closes the tracked iCloud mail tab after a successful ve
|
||||
throw new Error('should not use family cleanup when tracked tab exists');
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -498,7 +546,7 @@ test('verification flow completes step 8 and flags phone verification when add-p
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (_step, payload) => {
|
||||
completeNodeFromBackground: async (_step, payload) => {
|
||||
events.push(['complete', payload.code]);
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
@@ -568,7 +616,7 @@ test('verification flow keeps step 8 successful when code submit transport fails
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (_step, payload) => {
|
||||
completeNodeFromBackground: async (_step, payload) => {
|
||||
events.push(['complete', payload.code]);
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
@@ -642,7 +690,7 @@ test('verification flow treats manual step 8 add-phone confirmation as the same
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {
|
||||
completeNodeFromBackground: async () => {
|
||||
throw new Error('should not complete step 8');
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({
|
||||
@@ -689,7 +737,7 @@ test('verification flow caps mail polling timeout to the remaining oauth budget'
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -753,7 +801,7 @@ test('verification flow keeps mail polling response timeout above minimum floor'
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -816,7 +864,7 @@ test('verification flow keeps 2925 mailbox polling at 15 refresh attempts even w
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -881,7 +929,7 @@ test('verification flow can run a 2/3/15 2925 resend polling plan', async () =>
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -953,7 +1001,7 @@ test('verification flow uses full 2925 polling window after a rejected login cod
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1026,7 +1074,7 @@ test('verification flow keeps Hotmail request timestamp filtering on the first p
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 87654,
|
||||
@@ -1081,7 +1129,7 @@ test('verification flow keeps fixed filter timestamp after step 4 resend', async
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: (_step, state) => Math.max(0, Number(state.signupVerificationRequestedAt || 0) - 15000),
|
||||
@@ -1147,7 +1195,7 @@ test('verification flow uses configured signup resend count for step 4', async (
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1206,7 +1254,7 @@ test('verification flow uses configured login resend count for step 8', async ()
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1265,8 +1313,8 @@ test('verification flow can complete Plus visible login-code step with shared st
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (step, payload) => {
|
||||
completed.push({ step, payload });
|
||||
completeNodeFromBackground: async (nodeId, payload) => {
|
||||
completed.push({ nodeId, payload });
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
@@ -1310,7 +1358,7 @@ test('verification flow can complete Plus visible login-code step with shared st
|
||||
assert.deepStrictEqual(fillMessages.map((message) => message.step), [8]);
|
||||
assert.deepStrictEqual(completed, [
|
||||
{
|
||||
step: 11,
|
||||
nodeId: 'fetch-login-code',
|
||||
payload: {
|
||||
emailTimestamp: 456,
|
||||
code: '654321',
|
||||
@@ -1328,7 +1376,7 @@ test('verification flow waits during resend cooldown instead of tight-looping',
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1389,7 +1437,7 @@ test('verification flow clicks resend before waiting for the next LuckMail /code
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1459,7 +1507,7 @@ test('verification flow notifies onResendRequestedAt when resend is triggered',
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1532,7 +1580,7 @@ test('verification flow uses resilient signup-page transport when submitting ver
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1582,7 +1630,7 @@ test('verification flow does not replay step 8 code submit after transient auth-
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1647,7 +1695,7 @@ test('verification flow requests a new code immediately after Cloudflare Temp Em
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (_step, payload) => {
|
||||
completeNodeFromBackground: async (_step, payload) => {
|
||||
completed.push(payload);
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
@@ -1726,7 +1774,7 @@ test('verification flow forwards optional signup profile payload when submitting
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1787,8 +1835,8 @@ test('verification flow keeps combined signup profile skip reason when completin
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (step, payload) => {
|
||||
completed.push({ step, payload });
|
||||
completeNodeFromBackground: async (nodeId, payload) => {
|
||||
completed.push({ nodeId, payload });
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
@@ -1843,7 +1891,7 @@ test('verification flow keeps combined signup profile skip reason when completin
|
||||
assert.equal(resilientCalls[0].payload.signupProfile.firstName, 'Ada');
|
||||
assert.deepStrictEqual(completed, [
|
||||
{
|
||||
step: 4,
|
||||
nodeId: 'fetch-signup-code',
|
||||
payload: {
|
||||
emailTimestamp: 123,
|
||||
code: '654321',
|
||||
@@ -1869,7 +1917,7 @@ test('verification flow treats retryable submit transport failure as success whe
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1919,7 +1967,7 @@ test('verification flow avoids resend storms when iCloud polling keeps hitting t
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -1982,7 +2030,7 @@ test('verification flow stops iCloud poll-only loop after repeated no-code round
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
@@ -2043,7 +2091,7 @@ test('verification flow derives iCloud polling response timeout from the configu
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
|
||||
Reference in New Issue
Block a user