diff --git a/background/verification-flow.js b/background/verification-flow.js index 32f6192..d3c395f 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -1313,7 +1313,9 @@ disableTimeBudgetCap: Boolean(options.disableTimeBudgetCap), getRemainingTimeMs: options.getRemainingTimeMs, maxResendRequests: remainingAutomaticResendCount, - initialPollMaxAttempts: options.initialPollMaxAttempts, + initialPollMaxAttempts: mail.provider === '2925' && rejectedCodes.size > 0 + ? undefined + : options.initialPollMaxAttempts, resendIntervalMs, lastResendAt, onResendRequestedAt: updateFilterAfterTimestampForVerificationStep, diff --git a/content/mail-2925.js b/content/mail-2925.js index 13dfe4f..9f683ec 100644 --- a/content/mail-2925.js +++ b/content/mail-2925.js @@ -269,6 +269,48 @@ function findMailItems() { return []; } +function isLikelyMail2925MailboxPage() { + const pageText = getPageTextSample(3000); + if (!pageText) { + return /#\/mailList/i.test(location.hash || location.href || ''); + } + + if (findRefreshButton() || findInboxLink()) { + return true; + } + + return /#\/mailList/i.test(location.hash || location.href || '') + && /收件箱|刷新|删除|写信|邮件|mail/i.test(pageText) + && !(/欢迎使用邮箱|登录|login/i.test(pageText) && /密码|password/i.test(pageText)); +} + +async function waitForMailboxReady(timeoutMs = 12000) { + const startedAt = Date.now(); + while (Date.now() - startedAt <= timeoutMs) { + if (typeof throwIfMail2925LimitReached === 'function') { + throwIfMail2925LimitReached(); + } + const items = findMailItems(); + if (items.length > 0) { + return { ready: true, items, empty: false }; + } + if (getMail2925DisplayedMailboxEmail() || isLikelyMail2925MailboxPage()) { + return { ready: true, items: [], empty: true }; + } + await sleep(500); + } + + const items = findMailItems(); + if (items.length > 0) { + return { ready: true, items, empty: false }; + } + return { + ready: Boolean(getMail2925DisplayedMailboxEmail() || isLikelyMail2925MailboxPage()), + items, + empty: items.length === 0, + }; +} + function findActionBySelectors(selectors = []) { for (const selector of selectors) { const candidates = document.querySelectorAll(selector); @@ -518,7 +560,7 @@ function detectMail2925ViewState() { } const mailboxEmail = getMail2925DisplayedMailboxEmail(); - if (findMailItems().length > 0 || mailboxEmail) { + if (findMailItems().length > 0 || mailboxEmail || isLikelyMail2925MailboxPage()) { return { view: 'mailbox', limitMessage: '', mailboxEmail }; } @@ -924,7 +966,8 @@ async function sleepRandom(minMs, maxMs = minMs) { } async function returnToInbox() { - if (findMailItems().length > 0) { + const currentMailbox = await waitForMailboxReady(1500); + if (currentMailbox.ready) { return true; } @@ -936,7 +979,8 @@ async function returnToInbox() { simulateClick(inboxLink); for (let attempt = 0; attempt < 20; attempt += 1) { await sleep(250); - if (findMailItems().length > 0) { + const mailbox = await waitForMailboxReady(250); + if (mailbox.ready) { return true; } } @@ -1196,27 +1240,20 @@ async function handlePollEmail(step, payload) { let initialItems = []; let initialLoadUsedRefresh = false; - for (let i = 0; i < 20; i += 1) { - initialItems = findMailItems(); - if (initialItems.length > 0) { - break; - } - await sleep(500); - } - - if (initialItems.length === 0) { + const initialMailbox = await waitForMailboxReady(10000); + initialItems = initialMailbox.items; + if (!initialMailbox.ready || initialItems.length === 0) { initialLoadUsedRefresh = true; await returnToInbox(); await refreshInbox(); - await sleep(2000); + const refreshedMailbox = await waitForMailboxReady(12000); if (typeof throwIfMail2925LimitReached === 'function') { throwIfMail2925LimitReached(); } - initialItems = findMailItems(); - } - - if (initialItems.length === 0) { - throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。'); + if (!refreshedMailbox.ready) { + throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。'); + } + initialItems = refreshedMailbox.items; } log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`); @@ -1233,7 +1270,11 @@ async function handlePollEmail(step, payload) { await sleepRandom(900, 1500); } - const items = findMailItems(); + const mailbox = await waitForMailboxReady(3000); + if (!mailbox.ready) { + throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。'); + } + const items = mailbox.items; if (items.length > 0) { for (let index = 0; index < items.length; index += 1) { const item = items[index]; diff --git a/tests/mail-2925-content.test.js b/tests/mail-2925-content.test.js index 54ebb47..6f670d9 100644 --- a/tests/mail-2925-content.test.js +++ b/tests/mail-2925-content.test.js @@ -179,6 +179,10 @@ function extractVerificationCode(text) { async function sleep() {} async function sleepRandom() {} +async function waitForMailboxReady() { + const items = findMailItems(); + return { ready: true, items, empty: items.length === 0 }; +} async function returnToInbox() { clickOrder.push('inbox'); @@ -277,6 +281,10 @@ function extractVerificationCode(text) { async function sleep() {} async function sleepRandom() {} +async function waitForMailboxReady() { + const items = findMailItems(); + return { ready: true, items, empty: items.length === 0 }; +} async function returnToInbox() { return true; } @@ -370,6 +378,10 @@ function extractVerificationCode(text) { async function sleep() {} async function sleepRandom() {} +async function waitForMailboxReady() { + const items = findMailItems(); + return { ready: true, items, empty: items.length === 0 }; +} async function returnToInbox() { return true; } @@ -459,6 +471,10 @@ function extractVerificationCode(text) { async function sleep() {} async function sleepRandom() {} +async function waitForMailboxReady() { + const items = findMailItems(); + return { ready: true, items, empty: items.length === 0 }; +} async function returnToInbox() { return true; } @@ -633,6 +649,10 @@ function simulateClick(node) { async function sleep() {} async function sleepRandom() {} +async function waitForMailboxReady() { + const items = findMailItems(); + return { ready: items.length > 0, items, empty: items.length === 0 }; +} ${bundle} @@ -706,6 +726,10 @@ function simulateClick(node) { async function sleep() {} async function sleepRandom() {} +async function waitForMailboxReady() { + const items = findMailItems(); + return { ready: items.length > 0, items, empty: items.length === 0 }; +} const console = { warn() {} }; const MAIL2925_PREFIX = '[MultiPage:mail-2925]'; diff --git a/tests/verification-flow-polling.test.js b/tests/verification-flow-polling.test.js index d28aa41..447c9db 100644 --- a/tests/verification-flow-polling.test.js +++ b/tests/verification-flow-polling.test.js @@ -906,6 +906,80 @@ test('verification flow delays 2925 login resend until after the first full mail assert.equal(events.filter((event) => event === 'resend').length, 1); }); +test('verification flow uses full 2925 polling window after a rejected login code', async () => { + const pollMaxAttempts = []; + const submittedCodes = []; + let pollCalls = 0; + + const helpers = api.createVerificationFlowHelpers({ + addLog: async () => {}, + chrome: { tabs: { update: async () => {} } }, + CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email', + completeStepFromBackground: async () => {}, + confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }), + getHotmailVerificationPollConfig: () => ({}), + getHotmailVerificationRequestTimestamp: () => 0, + getState: async () => ({}), + getTabId: async () => 1, + HOTMAIL_PROVIDER: 'hotmail-api', + isStopError: () => false, + LUCKMAIL_PROVIDER: 'luckmail-api', + MAIL_2925_VERIFICATION_INTERVAL_MS: 15000, + MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15, + pollCloudflareTempEmailVerificationCode: async () => ({}), + pollHotmailVerificationCode: async () => ({}), + pollLuckmailVerificationCode: async () => ({}), + sendToContentScript: async (_source, message) => { + if (message.type === 'RESEND_VERIFICATION_CODE') { + return { resent: true }; + } + if (message.type === 'FILL_CODE') { + submittedCodes.push(message.payload.code); + return message.payload.code === '111111' + ? { invalidCode: true, errorText: 'Incorrect code' } + : { success: true }; + } + return {}; + }, + sendToContentScriptResilient: async () => ({}), + sendToMailContentScriptResilient: async (_mail, message) => { + if (message.type !== 'POLL_EMAIL') { + return {}; + } + pollCalls += 1; + pollMaxAttempts.push(message.payload.maxAttempts); + return pollCalls === 1 + ? { code: '111111', emailTimestamp: 1 } + : { code: '222222', emailTimestamp: 2 }; + }, + setState: async () => {}, + setStepStatus: async () => {}, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + VERIFICATION_POLL_MAX_ROUNDS: 5, + }); + + await helpers.resolveVerificationStep( + 8, + { + email: 'user@example.com', + mailProvider: '2925', + lastLoginCode: null, + }, + { provider: '2925', label: '2925 邮箱' }, + { + maxResendRequests: 0, + initialPollMaxAttempts: 5, + requestFreshCodeFirst: false, + filterAfterTimestamp: 123, + resendIntervalMs: 0, + } + ); + + assert.deepStrictEqual(pollMaxAttempts, [5, 15]); + assert.deepStrictEqual(submittedCodes, ['111111', '222222']); +}); + test('verification flow keeps Hotmail request timestamp filtering on the first poll', async () => { const pollPayloads = [];