diff --git a/background/signup-flow-helpers.js b/background/signup-flow-helpers.js index 4e45f0d..59960ef 100644 --- a/background/signup-flow-helpers.js +++ b/background/signup-flow-helpers.js @@ -167,7 +167,11 @@ type: 'PREPARE_SIGNUP_VERIFICATION', step, source: 'background', - payload: { password: password || '' }, + payload: { + password: password || '', + prepareSource: 'step3_finalize', + prepareLogLabel: '步骤 3 收尾', + }, }, { timeoutMs: 30000, retryDelayMs: 700, diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index 1a33954..cd5832d 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -11,7 +11,6 @@ executeStep7, getOAuthFlowRemainingMs, getOAuthFlowStepTimeoutMs, - getPanelMode, getMailConfig, getState, getTabId, @@ -104,9 +103,6 @@ } } - const shouldRefreshOAuthBeforeSubmit = getPanelMode(state) === 'cpa'; - let step7ReplayCompleted = false; - await resolveVerificationStep(8, state, mail, { filterAfterTimestamp: stepStartedAt, getRemainingTimeMs: getStep8RemainingTimeResolver(), @@ -114,22 +110,6 @@ resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925') ? 0 : STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS, - beforeSubmit: shouldRefreshOAuthBeforeSubmit ? async (result) => { - if (step7ReplayCompleted) { - return; - } - - step7ReplayCompleted = true; - await addLog(`步骤 8:已拿到登录验证码 ${result.code},先刷新 CPA OAuth 链接并重走步骤 7,再回填验证码。`, 'warn'); - await rerunStep7ForStep8Recovery({ - logMessage: '步骤 8:正在重新获取最新 CPA OAuth 链接,并快速重走步骤 7...', - postStepDelayMs: 1200, - }); - await ensureStep8VerificationPageReady({ - timeoutMs: await getStep8ReadyTimeoutMs('重放步骤 7 后重新确认登录验证码页'), - }); - await addLog('步骤 8:登录验证码页面已重新就绪,开始回填刚才获取到的验证码。', 'info'); - } : undefined, }); } diff --git a/background/steps/fetch-signup-code.js b/background/steps/fetch-signup-code.js index 7484c1e..e7fe682 100644 --- a/background/steps/fetch-signup-code.js +++ b/background/steps/fetch-signup-code.js @@ -39,7 +39,11 @@ type: 'PREPARE_SIGNUP_VERIFICATION', step: 4, source: 'background', - payload: { password: state.password || state.customPassword || '' }, + payload: { + password: state.password || state.customPassword || '', + prepareSource: 'step4_execute', + prepareLogLabel: '步骤 4 执行', + }, }, { timeoutMs: 30000, diff --git a/content/signup-page.js b/content/signup-page.js index 8dc8833..958d99b 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -1355,6 +1355,9 @@ async function waitForSignupVerificationTransition(timeout = 5000) { async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { const { password } = payload; + const prepareSource = String(payload?.prepareSource || '').trim() || 'step4_execute'; + const prepareLogLabel = String(payload?.prepareLogLabel || '').trim() + || (prepareSource === 'step3_finalize' ? '步骤 3 收尾' : '步骤 4 执行'); const start = Date.now(); let recoveryRound = 0; const maxRecoveryRounds = 3; @@ -1363,17 +1366,17 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { throwIfStopped(); const roundNo = recoveryRound + 1; - log(`步骤 4:等待页面进入验证码阶段(第 ${roundNo}/${maxRecoveryRounds} 轮,先等待 5 秒)...`, 'info'); + log(`${prepareLogLabel}:等待页面进入验证码阶段(第 ${roundNo}/${maxRecoveryRounds} 轮,先等待 5 秒)...`, 'info'); const snapshot = await waitForSignupVerificationTransition(5000); if (snapshot.state === 'step5') { - log('步骤 4:页面已进入验证码后的下一阶段,本步骤按已完成处理。', 'ok'); - return { ready: true, alreadyVerified: true, retried: recoveryRound }; + log(`${prepareLogLabel}:页面已进入验证码后的下一阶段,本步骤按已完成处理。`, 'ok'); + return { ready: true, alreadyVerified: true, retried: recoveryRound, prepareSource }; } if (snapshot.state === 'verification') { - log(`步骤 4:验证码页面已就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok'); - return { ready: true, retried: recoveryRound }; + log(`${prepareLogLabel}:验证码页面已就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok'); + return { ready: true, retried: recoveryRound, prepareSource }; } if (snapshot.state === 'email_exists') { @@ -1385,7 +1388,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { if (snapshot.state === 'error') { await recoverCurrentAuthRetryPage({ flow: 'signup_password', - logLabel: `步骤 4:检测到密码页超时报错,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`, + logLabel: `${prepareLogLabel}:检测到密码页超时报错,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`, step: 4, timeoutMs: 12000, }); @@ -1398,24 +1401,24 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { } if ((snapshot.passwordInput.value || '') !== password) { - log('步骤 4:页面仍停留在密码页,正在重新填写密码...', 'warn'); + log(`${prepareLogLabel}:页面仍停留在密码页,正在重新填写密码...`, 'warn'); await humanPause(450, 1100); fillInput(snapshot.passwordInput, password); } if (snapshot.submitButton && isActionEnabled(snapshot.submitButton)) { - log(`步骤 4:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn'); + log(`${prepareLogLabel}:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn'); await humanPause(350, 900); simulateClick(snapshot.submitButton); await sleep(1200); continue; } - log(`步骤 4:页面仍停留在密码页,但“继续”按钮暂不可用,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); + log(`${prepareLogLabel}:页面仍停留在密码页,但“继续”按钮暂不可用,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); continue; } - log(`步骤 4:页面仍在切换中,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); + log(`${prepareLogLabel}:页面仍在切换中,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); } throw new Error(`等待注册验证码页面就绪超时或自动恢复失败(已尝试 ${recoveryRound}/${maxRecoveryRounds} 轮)。URL: ${location.href}`); diff --git a/tests/background-signup-step2-branching.test.js b/tests/background-signup-step2-branching.test.js index 24bc3bb..1d86de6 100644 --- a/tests/background-signup-step2-branching.test.js +++ b/tests/background-signup-step2-branching.test.js @@ -210,6 +210,10 @@ test('signup flow helper finalizes step 3 submit by reusing signup verification type: 'PREPARE_SIGNUP_VERIFICATION', step: 3, source: 'background', - payload: { password: 'Secret123!' }, + payload: { + password: 'Secret123!', + prepareSource: 'step3_finalize', + prepareLogLabel: '步骤 3 收尾', + }, }); }); diff --git a/tests/background-step7-recovery.test.js b/tests/background-step7-recovery.test.js index b9e74e0..1efb3e3 100644 --- a/tests/background-step7-recovery.test.js +++ b/tests/background-step7-recovery.test.js @@ -6,7 +6,7 @@ const source = fs.readFileSync('background/steps/fetch-login-code.js', 'utf8'); const globalScope = {}; const api = new Function('self', `${source}; return self.MultiPageBackgroundStep8;`)(globalScope); -test('step 8 refreshes CPA oauth via step 7 replay before submitting verification code', async () => { +test('step 8 submits login verification directly without replaying step 7', async () => { const calls = { ensureReady: 0, ensureReadyOptions: [], @@ -43,7 +43,6 @@ test('step 8 refreshes CPA oauth via step 7 replay before submitting verificatio url: 'https://mail.qq.com', navigateOnReuse: false, }), - getPanelMode: () => 'cpa', getState: async () => ({ email: 'user@example.com', password: 'secret' }), getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2), HOTMAIL_PROVIDER: 'hotmail-api', @@ -52,7 +51,6 @@ test('step 8 refreshes CPA oauth via step 7 replay before submitting verificatio LUCKMAIL_PROVIDER: 'luckmail-api', resolveVerificationStep: async (_step, _state, _mail, options) => { calls.resolveOptions = options; - await options.beforeSubmit({ code: '654321' }); }, reuseOrCreateTab: async () => {}, setState: async () => {}, @@ -77,17 +75,16 @@ test('step 8 refreshes CPA oauth via step 7 replay before submitting verificatio Date.now = realDateNow; } - assert.equal(typeof calls.resolveOptions.beforeSubmit, 'function'); - assert.equal(calls.ensureReady, 2); - assert.equal(calls.executeStep7, 1); - assert.deepStrictEqual(calls.sleep, [1200]); + assert.equal(calls.resolveOptions.beforeSubmit, undefined); + assert.equal(calls.ensureReady, 1); + assert.equal(calls.executeStep7, 0); + assert.deepStrictEqual(calls.sleep, []); assert.equal(calls.resolveOptions.filterAfterTimestamp, 123456); assert.equal(typeof calls.resolveOptions.getRemainingTimeMs, 'function'); assert.equal(await calls.resolveOptions.getRemainingTimeMs({ actionLabel: '登录验证码流程' }), 5000); assert.equal(calls.resolveOptions.resendIntervalMs, 25000); assert.deepStrictEqual(calls.ensureReadyOptions, [ { timeoutMs: 5000 }, - { timeoutMs: 5000 }, ]); }); @@ -114,7 +111,6 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => { url: 'https://2925.com', navigateOnReuse: false, }), - getPanelMode: () => 'sub2api', getState: async () => ({ email: 'user@example.com', password: 'secret' }), getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2), HOTMAIL_PROVIDER: 'hotmail-api', diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 19eccbb..34423ea 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -252,7 +252,6 @@ - `2925` provider 会拉长单轮轮询窗口,并关闭 Step 4 / 8 的自动重发间隔,减少因邮件延迟过早判负。 - Step 4 / 8 会在当前验证码步骤启动时固定一次邮件筛选时间窗;本步后续即使触发重新发送验证码,也继续沿用这一次启动时间作为筛选基准,不再随着重发时间前移。 - 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。 -- CPA 模式下,Step 8 在真正回填登录验证码前,会先刷新最新 OAuth 地址并快速重走一次 Step 7,降低验证码等待过久导致 OAuth 过期的概率。 - Auto 模式下,如果 Step 4 当前轮失败,后台会沿用当前邮箱回到 Step 1 重新开始当前轮,而不是立刻换邮箱开新尝试。 @@ -310,6 +309,7 @@ 3. 轮询登录验证码 4. 回填登录验证码 5. 如遇邮箱轮询类失败,则按有限次数回到 Step 7 重试 +6. 获取到登录验证码后不再触发“刷新 OAuth 并重走 Step 7”的前置回放,直接在当前验证码页提交并继续进入 Step 9 ### Step 9 diff --git a/项目文件结构说明.md b/项目文件结构说明.md index 583bdf0..093e634 100644 --- a/项目文件结构说明.md +++ b/项目文件结构说明.md @@ -55,7 +55,7 @@ - `background/steps/clear-login-cookies.js`:步骤 6 实现,负责登录前 Cookie 清理。 - `background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成。 -- `background/steps/fetch-login-code.js`:步骤 8 实现,负责登录验证码阶段的邮箱轮询、验证码回填与回退控制。 +- `background/steps/fetch-login-code.js`:步骤 8 实现,负责登录验证码阶段的邮箱轮询、验证码回填与回退控制;验证码获取后直接提交,不再在提交前回放步骤 7 刷新 OAuth。 - `background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口。 - `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交。 - `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。 @@ -143,7 +143,7 @@ - `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。 - `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。 - `tests/background-step6-retry-limit.test.js`:测试步骤 6 的 Cookie 清理,以及步骤 7 的有限重试上限。 -- `tests/background-step7-recovery.test.js`:测试步骤 8 在 CPA 模式下会先回放步骤 7 再提交登录验证码,并为 2925 关闭重发间隔。 +- `tests/background-step7-recovery.test.js`:测试步骤 8 获取登录验证码后直接提交(不再回放步骤 7),并为 2925 关闭重发间隔。 - `tests/background-step-registry.test.js`:测试后台步骤注册表和共享步骤定义已接入。 - `tests/background-tab-runtime-module.test.js`:测试标签运行时模块已接入且导出工厂。 - `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。