修复2925号池不能自动登陆问题
This commit is contained in:
@@ -5394,6 +5394,7 @@ const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMa
|
|||||||
normalizeMail2925Accounts,
|
normalizeMail2925Accounts,
|
||||||
pickMail2925AccountForRun,
|
pickMail2925AccountForRun,
|
||||||
requestStop,
|
requestStop,
|
||||||
|
ensureContentScriptReadyOnTab,
|
||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
sendToMailContentScriptResilient,
|
sendToMailContentScriptResilient,
|
||||||
@@ -5402,6 +5403,7 @@ const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMa
|
|||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
upsertMail2925AccountInList,
|
upsertMail2925AccountInList,
|
||||||
|
waitForTabUrlMatch,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function upsertMail2925Account(input = {}) {
|
async function upsertMail2925Account(input = {}) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
pickMail2925AccountForRun,
|
pickMail2925AccountForRun,
|
||||||
getState,
|
getState,
|
||||||
isAutoRunLockedState,
|
isAutoRunLockedState,
|
||||||
|
ensureContentScriptReadyOnTab,
|
||||||
requestStop,
|
requestStop,
|
||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
@@ -24,11 +25,12 @@
|
|||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
upsertMail2925AccountInList,
|
upsertMail2925AccountInList,
|
||||||
|
waitForTabUrlMatch,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
const MAIL2925_SOURCE = 'mail-2925';
|
const MAIL2925_SOURCE = 'mail-2925';
|
||||||
const MAIL2925_URL = 'https://2925.com/#/mailList';
|
const MAIL2925_URL = 'https://2925.com/#/mailList';
|
||||||
const MAIL2925_LOGIN_URL = 'https://2925.com/';
|
const MAIL2925_LOGIN_URL = 'https://2925.com/login/';
|
||||||
const MAIL2925_INJECT = ['content/utils.js', 'content/mail-2925.js'];
|
const MAIL2925_INJECT = ['content/utils.js', 'content/mail-2925.js'];
|
||||||
const MAIL2925_INJECT_SOURCE = 'mail-2925';
|
const MAIL2925_INJECT_SOURCE = 'mail-2925';
|
||||||
const MAIL2925_COOKIE_DOMAINS = [
|
const MAIL2925_COOKIE_DOMAINS = [
|
||||||
@@ -387,7 +389,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
await reuseOrCreateTab(MAIL2925_SOURCE, forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL, {
|
const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL;
|
||||||
|
const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, {
|
||||||
inject: MAIL2925_INJECT,
|
inject: MAIL2925_INJECT,
|
||||||
injectSource: MAIL2925_INJECT_SOURCE,
|
injectSource: MAIL2925_INJECT_SOURCE,
|
||||||
});
|
});
|
||||||
@@ -497,13 +500,40 @@
|
|||||||
|
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
await addLog(`2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(forceRelogin=${forceRelogin ? 'true' : 'false'})`, 'info');
|
await addLog(`2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(forceRelogin=${forceRelogin ? 'true' : 'false'})`, 'info');
|
||||||
await reuseOrCreateTab(MAIL2925_SOURCE, forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL, {
|
const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL;
|
||||||
|
const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, {
|
||||||
inject: MAIL2925_INJECT,
|
inject: MAIL2925_INJECT,
|
||||||
injectSource: MAIL2925_INJECT_SOURCE,
|
injectSource: MAIL2925_INJECT_SOURCE,
|
||||||
});
|
});
|
||||||
const openedUrl = await getMail2925CurrentTabUrl();
|
const openedUrl = await getMail2925CurrentTabUrl();
|
||||||
await addLog(`2925:打开页后当前标签地址:${openedUrl || 'unknown'}`, 'info');
|
await addLog(`2925:打开页后当前标签地址:${openedUrl || 'unknown'}`, 'info');
|
||||||
|
|
||||||
|
if (forceRelogin && typeof waitForTabUrlMatch === 'function') {
|
||||||
|
const matchedLoginTab = await waitForTabUrlMatch(
|
||||||
|
tabId,
|
||||||
|
(url) => {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(String(url || ''));
|
||||||
|
return (parsed.hostname === '2925.com' || parsed.hostname === 'www.2925.com')
|
||||||
|
&& /^\/login\/?$/.test(parsed.pathname);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ timeoutMs: 15000, retryDelayMs: 300 }
|
||||||
|
);
|
||||||
|
await addLog(`2925:等待最终落到登录页结果:${matchedLoginTab?.url || 'timeout'}`, matchedLoginTab ? 'info' : 'warn');
|
||||||
|
if (matchedLoginTab && typeof ensureContentScriptReadyOnTab === 'function') {
|
||||||
|
await ensureContentScriptReadyOnTab(MAIL2925_SOURCE, tabId, {
|
||||||
|
inject: MAIL2925_INJECT,
|
||||||
|
injectSource: MAIL2925_INJECT_SOURCE,
|
||||||
|
timeoutMs: 20000,
|
||||||
|
retryDelayMs: 800,
|
||||||
|
logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (forceRelogin && typeof sleepWithStop === 'function') {
|
if (forceRelogin && typeof sleepWithStop === 'function') {
|
||||||
await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info');
|
await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info');
|
||||||
await sleepWithStop(3000);
|
await sleepWithStop(3000);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
(function attachBackgroundStep8(root, factory) {
|
(function attachBackgroundStep8(root, factory) {
|
||||||
root.MultiPageBackgroundStep8 = factory();
|
root.MultiPageBackgroundStep8 = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
|
||||||
|
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
function createStep8Executor(deps = {}) {
|
function createStep8Executor(deps = {}) {
|
||||||
const {
|
const {
|
||||||
addLog,
|
addLog,
|
||||||
@@ -61,6 +63,9 @@
|
|||||||
if (mail.error) throw new Error(mail.error);
|
if (mail.error) throw new Error(mail.error);
|
||||||
|
|
||||||
const stepStartedAt = Date.now();
|
const stepStartedAt = Date.now();
|
||||||
|
const verificationFilterAfterTimestamp = mail.provider === '2925'
|
||||||
|
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||||||
|
: stepStartedAt;
|
||||||
const verificationSessionKey = `8:${stepStartedAt}`;
|
const verificationSessionKey = `8:${stepStartedAt}`;
|
||||||
const authTabId = await getTabId('signup-page');
|
const authTabId = await getTabId('signup-page');
|
||||||
|
|
||||||
@@ -140,7 +145,7 @@
|
|||||||
...state,
|
...state,
|
||||||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||||
}, mail, {
|
}, mail, {
|
||||||
filterAfterTimestamp: mail.provider === '2925' ? 0 : stepStartedAt,
|
filterAfterTimestamp: verificationFilterAfterTimestamp,
|
||||||
sessionKey: verificationSessionKey,
|
sessionKey: verificationSessionKey,
|
||||||
disableTimeBudgetCap: mail.provider === '2925',
|
disableTimeBudgetCap: mail.provider === '2925',
|
||||||
getRemainingTimeMs: getStep8RemainingTimeResolver(state?.oauthUrl || ''),
|
getRemainingTimeMs: getStep8RemainingTimeResolver(state?.oauthUrl || ''),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
(function attachBackgroundStep4(root, factory) {
|
(function attachBackgroundStep4(root, factory) {
|
||||||
root.MultiPageBackgroundStep4 = factory();
|
root.MultiPageBackgroundStep4 = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep4Module() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep4Module() {
|
||||||
|
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
function createStep4Executor(deps = {}) {
|
function createStep4Executor(deps = {}) {
|
||||||
const {
|
const {
|
||||||
addLog,
|
addLog,
|
||||||
@@ -26,6 +28,9 @@
|
|||||||
const mail = getMailConfig(state);
|
const mail = getMailConfig(state);
|
||||||
if (mail.error) throw new Error(mail.error);
|
if (mail.error) throw new Error(mail.error);
|
||||||
const stepStartedAt = Date.now();
|
const stepStartedAt = Date.now();
|
||||||
|
const verificationFilterAfterTimestamp = mail.provider === '2925'
|
||||||
|
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||||||
|
: stepStartedAt;
|
||||||
const verificationSessionKey = `4:${stepStartedAt}`;
|
const verificationSessionKey = `4:${stepStartedAt}`;
|
||||||
const signupTabId = await getTabId('signup-page');
|
const signupTabId = await getTabId('signup-page');
|
||||||
if (!signupTabId) {
|
if (!signupTabId) {
|
||||||
@@ -101,7 +106,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await resolveVerificationStep(4, state, mail, {
|
await resolveVerificationStep(4, state, mail, {
|
||||||
filterAfterTimestamp: mail.provider === '2925' ? 0 : stepStartedAt,
|
filterAfterTimestamp: verificationFilterAfterTimestamp,
|
||||||
sessionKey: verificationSessionKey,
|
sessionKey: verificationSessionKey,
|
||||||
disableTimeBudgetCap: mail.provider === '2925',
|
disableTimeBudgetCap: mail.provider === '2925',
|
||||||
requestFreshCodeFirst: mail.provider === HOTMAIL_PROVIDER ? false : true,
|
requestFreshCodeFirst: mail.provider === HOTMAIL_PROVIDER ? false : true,
|
||||||
|
|||||||
@@ -239,12 +239,16 @@
|
|||||||
return requestedAt;
|
return requestedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldPreclear2925Mailbox(step, mail) {
|
function shouldPreclear2925Mailbox(step, mail, options = {}) {
|
||||||
return mail?.provider === '2925' && (step === 4 || step === 8);
|
if (mail?.provider !== '2925' || (step !== 4 && step !== 8)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(Number(options.filterAfterTimestamp) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clear2925MailboxBeforePolling(step, mail, options = {}) {
|
async function clear2925MailboxBeforePolling(step, mail, options = {}) {
|
||||||
if (!shouldPreclear2925Mailbox(step, mail)) {
|
if (!shouldPreclear2925Mailbox(step, mail, options)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -629,6 +629,13 @@ function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeMinuteTimestamp(timestamp) {
|
||||||
|
if (!Number.isFinite(timestamp) || timestamp <= 0) return 0;
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
date.setSeconds(0, 0);
|
||||||
|
return date.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
function parseMailItemTimestamp(item) {
|
function parseMailItemTimestamp(item) {
|
||||||
const timeText = getMailItemTimeText(item);
|
const timeText = getMailItemTimeText(item);
|
||||||
if (!timeText) return null;
|
if (!timeText) return null;
|
||||||
@@ -920,10 +927,12 @@ async function handlePollEmail(step, payload) {
|
|||||||
subjectFilters,
|
subjectFilters,
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
intervalMs,
|
intervalMs,
|
||||||
|
filterAfterTimestamp = 0,
|
||||||
excludeCodes = [],
|
excludeCodes = [],
|
||||||
strictChatGPTCodeOnly = false,
|
strictChatGPTCodeOnly = false,
|
||||||
} = payload || {};
|
} = payload || {};
|
||||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||||
|
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
|
||||||
if (typeof throwIfMail2925LimitReached === 'function') {
|
if (typeof throwIfMail2925LimitReached === 'function') {
|
||||||
throwIfMail2925LimitReached();
|
throwIfMail2925LimitReached();
|
||||||
}
|
}
|
||||||
@@ -975,6 +984,11 @@ async function handlePollEmail(step, payload) {
|
|||||||
for (let index = 0; index < items.length; index += 1) {
|
for (let index = 0; index < items.length; index += 1) {
|
||||||
const item = items[index];
|
const item = items[index];
|
||||||
const itemTimestamp = parseMailItemTimestamp(item);
|
const itemTimestamp = parseMailItemTimestamp(item);
|
||||||
|
const itemMinute = normalizeMinuteTimestamp(itemTimestamp || 0);
|
||||||
|
|
||||||
|
if (filterAfterMinute && (!itemMinute || itemMinute < filterAfterMinute)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const previewText = getMailItemText(item);
|
const previewText = getMailItemText(item);
|
||||||
if (!matchesMailFilters(previewText, senderFilters, subjectFilters)) {
|
if (!matchesMailFilters(previewText, senderFilters, subjectFilters)) {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ const assert = require('node:assert/strict');
|
|||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const mail2925Utils = require('../mail2925-utils.js');
|
const mail2925Utils = require('../mail2925-utils.js');
|
||||||
|
|
||||||
|
test('background mail2925 session uses /login/ as relogin entry url', () => {
|
||||||
|
const source = fs.readFileSync('background/mail-2925-session.js', 'utf8');
|
||||||
|
assert.match(source, /const MAIL2925_LOGIN_URL = 'https:\/\/2925\.com\/login\/';/);
|
||||||
|
});
|
||||||
|
|
||||||
test('ensureMail2925MailboxSession waits 3 seconds before and after opening login page on force relogin', async () => {
|
test('ensureMail2925MailboxSession waits 3 seconds before and after opening login page on force relogin', async () => {
|
||||||
const source = fs.readFileSync('background/mail-2925-session.js', 'utf8');
|
const source = fs.readFileSync('background/mail-2925-session.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
@@ -17,6 +22,8 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi
|
|||||||
};
|
};
|
||||||
const events = {
|
const events = {
|
||||||
sleeps: [],
|
sleeps: [],
|
||||||
|
openedUrls: [],
|
||||||
|
readyCalls: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const manager = api.createMail2925SessionManager({
|
const manager = api.createMail2925SessionManager({
|
||||||
@@ -41,7 +48,13 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi
|
|||||||
normalizeMail2925Accounts: mail2925Utils.normalizeMail2925Accounts,
|
normalizeMail2925Accounts: mail2925Utils.normalizeMail2925Accounts,
|
||||||
pickMail2925AccountForRun: mail2925Utils.pickMail2925AccountForRun,
|
pickMail2925AccountForRun: mail2925Utils.pickMail2925AccountForRun,
|
||||||
requestStop: async () => {},
|
requestStop: async () => {},
|
||||||
reuseOrCreateTab: async () => 1,
|
ensureContentScriptReadyOnTab: async () => {
|
||||||
|
events.readyCalls += 1;
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async (_source, url) => {
|
||||||
|
events.openedUrls.push(url);
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
sendToMailContentScriptResilient: async () => ({ loggedIn: true }),
|
sendToMailContentScriptResilient: async () => ({ loggedIn: true }),
|
||||||
setPersistentSettings: async (payload) => {
|
setPersistentSettings: async (payload) => {
|
||||||
currentState = { ...currentState, ...payload };
|
currentState = { ...currentState, ...payload };
|
||||||
@@ -54,6 +67,7 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi
|
|||||||
},
|
},
|
||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
upsertMail2925AccountInList: mail2925Utils.upsertMail2925AccountInList,
|
upsertMail2925AccountInList: mail2925Utils.upsertMail2925AccountInList,
|
||||||
|
waitForTabUrlMatch: async () => ({ url: 'https://2925.com/login/' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
await manager.ensureMail2925MailboxSession({
|
await manager.ensureMail2925MailboxSession({
|
||||||
@@ -62,5 +76,7 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi
|
|||||||
actionLabel: '步骤 4:确认 2925 邮箱登录态',
|
actionLabel: '步骤 4:确认 2925 邮箱登录态',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.openedUrls, ['https://2925.com/login/']);
|
||||||
assert.deepStrictEqual(events.sleeps, [3000, 3000]);
|
assert.deepStrictEqual(events.sleeps, [3000, 3000]);
|
||||||
|
assert.equal(events.readyCalls, 1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background/steps/fetch-signup-code.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep4;`)(globalScope);
|
||||||
|
|
||||||
|
test('step 4 passes a fixed 10-minute lookback window to 2925 mailbox polling', async () => {
|
||||||
|
let capturedOptions = null;
|
||||||
|
const realDateNow = Date.now;
|
||||||
|
Date.now = () => 700000;
|
||||||
|
|
||||||
|
const executor = api.createStep4Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
|
ensureMail2925MailboxSession: async () => {},
|
||||||
|
getMailConfig: () => ({
|
||||||
|
provider: '2925',
|
||||||
|
label: '2925 邮箱',
|
||||||
|
source: 'mail-2925',
|
||||||
|
url: 'https://2925.com',
|
||||||
|
}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
resolveVerificationStep: async (_step, _state, _mail, options) => {
|
||||||
|
capturedOptions = options;
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
sendToContentScriptResilient: async () => ({}),
|
||||||
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executor.executeStep4({
|
||||||
|
email: 'user@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
mail2925UseAccountPool: false,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Date.now = realDateNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(capturedOptions.filterAfterTimestamp, 100000);
|
||||||
|
assert.equal(capturedOptions.resendIntervalMs, 0);
|
||||||
|
});
|
||||||
@@ -89,8 +89,10 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 disables resend interval for 2925 mailbox polling', async () => {
|
test('step 8 uses a fixed 10-minute lookback window and disables resend interval for 2925 mailbox polling', async () => {
|
||||||
let capturedOptions = null;
|
let capturedOptions = null;
|
||||||
|
const realDateNow = Date.now;
|
||||||
|
Date.now = () => 900000;
|
||||||
|
|
||||||
const executor = api.createStep8Executor({
|
const executor = api.createStep8Executor({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
@@ -130,12 +132,17 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => {
|
|||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await executor.executeStep8({
|
try {
|
||||||
email: 'user@example.com',
|
await executor.executeStep8({
|
||||||
password: 'secret',
|
email: 'user@example.com',
|
||||||
oauthUrl: 'https://oauth.example/latest',
|
password: 'secret',
|
||||||
});
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Date.now = realDateNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(capturedOptions.filterAfterTimestamp, 300000);
|
||||||
assert.equal(capturedOptions.resendIntervalMs, 0);
|
assert.equal(capturedOptions.resendIntervalMs, 0);
|
||||||
assert.equal(capturedOptions.targetEmail, '');
|
assert.equal(capturedOptions.targetEmail, '');
|
||||||
assert.equal(capturedOptions.beforeSubmit, undefined);
|
assert.equal(capturedOptions.beforeSubmit, undefined);
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ function extractFunction(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test('handlePollEmail establishes a baseline after opening from detail view and only picks mail from a later refresh', async () => {
|
test('handlePollEmail establishes a baseline after opening from detail view and only picks mail from a later refresh', async () => {
|
||||||
const bundle = extractFunction('handlePollEmail');
|
const bundle = [
|
||||||
|
extractFunction('normalizeMinuteTimestamp'),
|
||||||
|
extractFunction('handlePollEmail'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
let state = 'detail';
|
let state = 'detail';
|
||||||
@@ -162,7 +165,10 @@ return {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handlePollEmail ignores targetEmail and still tests any matching ChatGPT mail', async () => {
|
test('handlePollEmail ignores targetEmail and still tests any matching ChatGPT mail', async () => {
|
||||||
const bundle = extractFunction('handlePollEmail');
|
const bundle = [
|
||||||
|
extractFunction('normalizeMinuteTimestamp'),
|
||||||
|
extractFunction('handlePollEmail'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
let state = 'empty';
|
let state = 'empty';
|
||||||
@@ -242,6 +248,94 @@ return {
|
|||||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-1']);
|
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('handlePollEmail only accepts 2925 mails inside the fixed lookback window', async () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizeMinuteTimestamp'),
|
||||||
|
extractFunction('handlePollEmail'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
let state = 'ready';
|
||||||
|
const seenCodes = new Set();
|
||||||
|
const readAndDeleteCalls = [];
|
||||||
|
const oldMail = {
|
||||||
|
id: 'mail-old',
|
||||||
|
text: 'OpenAI verification code 111111',
|
||||||
|
timestamp: 1000,
|
||||||
|
};
|
||||||
|
const windowMail = {
|
||||||
|
id: 'mail-window',
|
||||||
|
text: 'OpenAI verification code 222222',
|
||||||
|
timestamp: 301000,
|
||||||
|
};
|
||||||
|
|
||||||
|
function findMailItems() {
|
||||||
|
return state === 'ready' ? [oldMail, windowMail] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMailItemId(item) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMailIds(items = []) {
|
||||||
|
return new Set(items.map((item) => item.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMailItemTimestamp(item) {
|
||||||
|
return item.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesMailFilters(text) {
|
||||||
|
return /openai|verification/i.test(String(text || ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMailItemText(item) {
|
||||||
|
return item.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractVerificationCode(text) {
|
||||||
|
const match = String(text || '').match(/(\\d{6})/);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep() {}
|
||||||
|
async function sleepRandom() {}
|
||||||
|
async function returnToInbox() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
async function refreshInbox() {}
|
||||||
|
|
||||||
|
async function openMailAndDeleteAfterRead(item) {
|
||||||
|
readAndDeleteCalls.push(item.id);
|
||||||
|
return item.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureSeenCodesSession() {}
|
||||||
|
function persistSeenCodes() {}
|
||||||
|
function log() {}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePollEmail,
|
||||||
|
getReadAndDeleteCalls() {
|
||||||
|
return readAndDeleteCalls.slice();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = await api.handlePollEmail(4, {
|
||||||
|
senderFilters: ['openai'],
|
||||||
|
subjectFilters: ['verification'],
|
||||||
|
maxAttempts: 1,
|
||||||
|
intervalMs: 1,
|
||||||
|
filterAfterTimestamp: 120000,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.code, '222222');
|
||||||
|
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-window']);
|
||||||
|
});
|
||||||
|
|
||||||
test('ensureSeenCodesSession resets tried codes only when a new verification step session starts', async () => {
|
test('ensureSeenCodesSession resets tried codes only when a new verification step session starts', async () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('buildSeenCodeSessionKey'),
|
extractFunction('buildSeenCodeSessionKey'),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const source = fs.readFileSync('background/verification-flow.js', 'utf8');
|
|||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundVerificationFlow;`)(globalScope);
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundVerificationFlow;`)(globalScope);
|
||||||
|
|
||||||
test('verification flow extends 2925 polling window', () => {
|
test('verification flow keeps 2925 polling cadence in the default payload', () => {
|
||||||
const helpers = api.createVerificationFlowHelpers({
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
chrome: { tabs: { update: async () => {} } },
|
chrome: { tabs: { update: async () => {} } },
|
||||||
@@ -37,10 +37,8 @@ test('verification flow extends 2925 polling window', () => {
|
|||||||
const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
|
const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
|
||||||
const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
|
const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
|
||||||
|
|
||||||
assert.equal(step4Payload.filterAfterTimestamp, 0);
|
|
||||||
assert.equal(step4Payload.maxAttempts, 15);
|
assert.equal(step4Payload.maxAttempts, 15);
|
||||||
assert.equal(step4Payload.intervalMs, 15000);
|
assert.equal(step4Payload.intervalMs, 15000);
|
||||||
assert.equal(step8Payload.filterAfterTimestamp, 0);
|
|
||||||
assert.equal(step8Payload.maxAttempts, 15);
|
assert.equal(step8Payload.maxAttempts, 15);
|
||||||
assert.equal(step8Payload.intervalMs, 15000);
|
assert.equal(step8Payload.intervalMs, 15000);
|
||||||
});
|
});
|
||||||
@@ -111,7 +109,7 @@ test('verification flow runs beforeSubmit hook before filling the code', async (
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('verification flow clears 2925 mailbox before polling and after successful login code submission', async () => {
|
test('verification flow skips 2925 mailbox preclear when using a fixed login mail window and still clears after success', async () => {
|
||||||
const mailMessages = [];
|
const mailMessages = [];
|
||||||
|
|
||||||
const helpers = api.createVerificationFlowHelpers({
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
@@ -164,15 +162,15 @@ test('verification flow clears 2925 mailbox before polling and after successful
|
|||||||
lastLoginCode: null,
|
lastLoginCode: null,
|
||||||
},
|
},
|
||||||
{ provider: '2925', label: '2925 邮箱' },
|
{ provider: '2925', label: '2925 邮箱' },
|
||||||
{}
|
{ filterAfterTimestamp: 123456 }
|
||||||
);
|
);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|
||||||
assert.deepStrictEqual(mailMessages, ['DELETE_ALL_EMAILS', 'POLL_EMAIL', 'DELETE_ALL_EMAILS']);
|
assert.deepStrictEqual(mailMessages, ['POLL_EMAIL', 'DELETE_ALL_EMAILS']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('verification flow clears 2925 mailbox before polling and after successful signup code submission', async () => {
|
test('verification flow skips 2925 mailbox preclear when using a fixed signup mail window and still clears after success', async () => {
|
||||||
const mailMessages = [];
|
const mailMessages = [];
|
||||||
|
|
||||||
const helpers = api.createVerificationFlowHelpers({
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
@@ -229,13 +227,14 @@ test('verification flow clears 2925 mailbox before polling and after successful
|
|||||||
},
|
},
|
||||||
{ provider: '2925', label: '2925 邮箱' },
|
{ provider: '2925', label: '2925 邮箱' },
|
||||||
{
|
{
|
||||||
|
filterAfterTimestamp: 123456,
|
||||||
requestFreshCodeFirst: false,
|
requestFreshCodeFirst: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|
||||||
assert.deepStrictEqual(mailMessages, ['DELETE_ALL_EMAILS', 'POLL_EMAIL', 'DELETE_ALL_EMAILS']);
|
assert.deepStrictEqual(mailMessages, ['POLL_EMAIL', 'DELETE_ALL_EMAILS']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
|
test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
|
||||||
|
|||||||
@@ -620,3 +620,8 @@
|
|||||||
- Step 8 如果发现认证页已经进入登录超时报错/重试页,会直接报错并回到 Step 7 重新开始,而不是在 Step 8 内部点击 `重试`。
|
- Step 8 如果发现认证页已经进入登录超时报错/重试页,会直接报错并回到 Step 7 重新开始,而不是在 Step 8 内部点击 `重试`。
|
||||||
- 任意认证页重试页如果正文中出现 `max_check_attempts`,会被视为 Cloudflare 风控触发:后台立刻完全停止流程,侧边栏会复用现有确认弹窗提示等待 15~30 分钟后再试,避免继续刷新或反复重试加重风控,确认按钮显示为“我知道了”。
|
- 任意认证页重试页如果正文中出现 `max_check_attempts`,会被视为 Cloudflare 风控触发:后台立刻完全停止流程,侧边栏会复用现有确认弹窗提示等待 15~30 分钟后再试,避免继续刷新或反复重试加重风控,确认按钮显示为“我知道了”。
|
||||||
- Step 9 在点击 OAuth 同意页 `继续` 后,会额外检查是否进入认证页重试页;若命中则先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再重新执行当前轮的 `继续` 点击。
|
- Step 9 在点击 OAuth 同意页 `继续` 后,会额外检查是否进入认证页重试页;若命中则先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再重新执行当前轮的 `继续` 点击。
|
||||||
|
## 2026-04-21 2925 邮件时间窗补充
|
||||||
|
|
||||||
|
- `2925` 在 Step 4 / Step 8 现在会携带固定的步骤开始时间窗口,实际筛选下限为“步骤开始时间向前回看 10 分钟”。
|
||||||
|
- 为了保留这段固定时间窗内已经到达的验证码邮件,后台不再在轮询开始前预先清空 2925 邮箱。
|
||||||
|
- `2925` 验证码最终提交成功后,后台仍会异步发送 `DELETE_ALL_EMAILS` 做收尾清理。
|
||||||
|
|||||||
Reference in New Issue
Block a user