diff --git a/background.js b/background.js index 74003d3..fa997a4 100644 --- a/background.js +++ b/background.js @@ -5448,7 +5448,6 @@ async function withIcloudLoginHelp(actionLabel, action) { if (shouldEmitIcloudTransientLog(`${safeActionLabel}:final`)) { await addLog(`iCloud:${safeActionLabel}受网络/上下文波动影响:${getErrorMessage(err)}`, 'warn'); } - const safeActionLabel = String(actionLabel || '操作').trim() || '操作'; const transientError = new Error(`iCloud:${safeActionLabel}受网络/上下文波动影响,请稍后重试。`); transientError.code = 'ICLOUD_TRANSIENT_CONTEXT'; transientError.actionLabel = safeActionLabel; diff --git a/content/icloud-mail.js b/content/icloud-mail.js index e6befa7..8f434ad 100644 --- a/content/icloud-mail.js +++ b/content/icloud-mail.js @@ -307,9 +307,13 @@ if (shouldHandlePollEmailInCurrentFrame) { const items = collectThreadItems(); const useFallback = (fallbackCarry + attempt) > FALLBACK_AFTER; - for (const item of items) { + for (const [index, item] of items.entries()) { const signature = buildItemSignature(item); - if (!useFallback && existingSignatures.has(signature)) { + const allowInitialStep8VisibleCode = Number(step) === 8 + && attempt === 1 + && index === 0 + && !sessionBaseline.fromCache; + if (!useFallback && existingSignatures.has(signature) && !allowInitialStep8VisibleCode) { continue; } diff --git a/tests/background-icloud-login-help.test.js b/tests/background-icloud-login-help.test.js index 488cb1d..f3a008a 100644 --- a/tests/background-icloud-login-help.test.js +++ b/tests/background-icloud-login-help.test.js @@ -2,6 +2,47 @@ const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); +function extractFunctionBody(source, name) { + const start = source.indexOf(`function ${name}(`); + if (start < 0) { + throw new Error(`missing function ${name}`); + } + + let parenDepth = 0; + let signatureEnded = false; + let braceStart = -1; + for (let i = start; i < source.length; i += 1) { + const ch = source[i]; + if (ch === '(') { + parenDepth += 1; + } else if (ch === ')') { + parenDepth -= 1; + if (parenDepth === 0) { + signatureEnded = true; + } + } else if (ch === '{' && signatureEnded) { + braceStart = i; + break; + } + } + if (braceStart < 0) { + throw new Error(`missing body for function ${name}`); + } + + let depth = 0; + for (let i = braceStart; i < source.length; i += 1) { + const ch = source[i]; + if (ch === '{') depth += 1; + if (ch === '}') { + depth -= 1; + if (depth === 0) { + return source.slice(braceStart + 1, i); + } + } + } + throw new Error(`unterminated function ${name}`); +} + test('icloud login helper distinguishes auth-required errors from transient context errors', () => { const source = fs.readFileSync('background.js', 'utf8'); @@ -107,3 +148,20 @@ test('icloud login helper distinguishes auth-required errors from transient cont 'icloud auto-fetch should fallback to reusable aliases when create-new fails due transient session/context issues' ); }); + +test('icloud login helper does not redeclare safeActionLabel in transient branch', () => { + const source = fs.readFileSync('background.js', 'utf8'); + const body = extractFunctionBody(source, 'withIcloudLoginHelp'); + const declarations = body.match(/\bconst\s+safeActionLabel\b/g) || []; + + assert.equal( + declarations.length, + 1, + 'withIcloudLoginHelp should declare safeActionLabel once to avoid temporal-dead-zone crashes' + ); + assert.match( + body, + /const transientError = new Error\(`iCloud:\$\{safeActionLabel\}受网络\/上下文波动影响,请稍后重试。`\);/, + 'transient context errors should use the already-initialized safeActionLabel' + ); +}); diff --git a/tests/icloud-mail-content.test.js b/tests/icloud-mail-content.test.js index 29fecf4..9cfffbe 100644 --- a/tests/icloud-mail-content.test.js +++ b/tests/icloud-mail-content.test.js @@ -294,3 +294,152 @@ return { assert.equal(result.code, '556677'); }); + +test('icloud step8 polling finds a visible first-row code immediately', async () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('getThreadItemMetadata'), + extractFunction('buildItemSignature'), + extractFunction('extractVerificationCode'), + extractFunction('normalizePollSessionKey'), + extractFunction('getOrCreatePollSessionBaseline'), + extractFunction('persistPollSessionBaseline'), + extractFunction('handlePollEmail'), + ].join('\n'); + + const api = new Function(` +const ICLOUD_POLL_SESSION_CACHE = new Map(); +function log() {} +function throwIfStopped() {} +async function sleep() {} +async function waitForElement() { return true; } +async function refreshInbox() { return true; } +const currentThreadData = [ + { + signature: 'visible-code', + sender: 'noreply@tm.openai.com', + subject: '你的 OpenAI 代码为 576773', + preview: '输入此临时验证码以继续:576773', + timestamp: '21:05', + ariaLabel: 'visible-code', + }, + { + signature: 'older-code', + sender: 'noreply@tm.openai.com', + subject: '你的 OpenAI 代码为 697852', + preview: '输入此临时验证码以继续:697852', + timestamp: '21:04', + ariaLabel: 'older-code', + }, +]; +function collectThreadItems() { + return currentThreadData.map((entry) => ({ + getAttribute(name) { + if (name === 'aria-label') return entry.ariaLabel || entry.signature; + return ''; + }, + querySelector(selector) { + if (selector === '.thread-participants') return { textContent: entry.sender }; + if (selector === '.thread-subject') return { textContent: entry.subject }; + if (selector === '.thread-preview') return { textContent: entry.preview }; + if (selector === '.thread-timestamp') return { textContent: entry.timestamp }; + return null; + }, + })); +} +async function openMailItemAndRead(item) { + const meta = getThreadItemMetadata(item); + return { + sender: meta.sender, + recipients: '', + timestamp: meta.timestamp, + bodyText: meta.preview, + combinedText: meta.combinedText, + }; +} +${bundle} +return { handlePollEmail }; +`)(); + + const result = await api.handlePollEmail(8, { + senderFilters: ['openai', 'noreply', 'chatgpt'], + subjectFilters: ['code', '验证码', 'login'], + maxAttempts: 1, + intervalMs: 10, + excludeCodes: [], + sessionKey: '8:visible-first', + }); + + assert.equal(result.code, '576773'); +}); + +test('icloud step8 visible first-row code still respects excluded codes', async () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('getThreadItemMetadata'), + extractFunction('buildItemSignature'), + extractFunction('extractVerificationCode'), + extractFunction('normalizePollSessionKey'), + extractFunction('getOrCreatePollSessionBaseline'), + extractFunction('persistPollSessionBaseline'), + extractFunction('handlePollEmail'), + ].join('\n'); + + const api = new Function(` +const ICLOUD_POLL_SESSION_CACHE = new Map(); +function log() {} +function throwIfStopped() {} +async function sleep() {} +async function waitForElement() { return true; } +async function refreshInbox() { return true; } +const currentThreadData = [ + { + signature: 'visible-code', + sender: 'noreply@tm.openai.com', + subject: '你的 OpenAI 代码为 576773', + preview: '输入此临时验证码以继续:576773', + timestamp: '21:05', + ariaLabel: 'visible-code', + }, +]; +function collectThreadItems() { + return currentThreadData.map((entry) => ({ + getAttribute(name) { + if (name === 'aria-label') return entry.ariaLabel || entry.signature; + return ''; + }, + querySelector(selector) { + if (selector === '.thread-participants') return { textContent: entry.sender }; + if (selector === '.thread-subject') return { textContent: entry.subject }; + if (selector === '.thread-preview') return { textContent: entry.preview }; + if (selector === '.thread-timestamp') return { textContent: entry.timestamp }; + return null; + }, + })); +} +async function openMailItemAndRead(item) { + const meta = getThreadItemMetadata(item); + return { + sender: meta.sender, + recipients: '', + timestamp: meta.timestamp, + bodyText: meta.preview, + combinedText: meta.combinedText, + }; +} +${bundle} +return { handlePollEmail }; +`)(); + + await assert.rejects( + () => api.handlePollEmail(8, { + senderFilters: ['openai', 'noreply', 'chatgpt'], + subjectFilters: ['code', '验证码', 'login'], + maxAttempts: 1, + intervalMs: 10, + excludeCodes: ['576773'], + sessionKey: '8:excluded-visible-first', + }), + /仍未在 iCloud 邮箱中找到新的匹配邮件/ + ); +});