Просмотр исходного кода

feat: 增强注册和登录验证码流程,添加步骤标识和日志标签,优化 OAuth 刷新逻辑

祁连海 4 месяцев назад
Родитель
Сommit
f5b622d272

+ 5 - 1
background/signup-flow-helpers.js

@@ -167,7 +167,11 @@
         type: 'PREPARE_SIGNUP_VERIFICATION',
         type: 'PREPARE_SIGNUP_VERIFICATION',
         step,
         step,
         source: 'background',
         source: 'background',
-        payload: { password: password || '' },
+        payload: {
+          password: password || '',
+          prepareSource: 'step3_finalize',
+          prepareLogLabel: '步骤 3 收尾',
+        },
       }, {
       }, {
         timeoutMs: 30000,
         timeoutMs: 30000,
         retryDelayMs: 700,
         retryDelayMs: 700,

+ 0 - 20
background/steps/fetch-login-code.js

@@ -11,7 +11,6 @@
       executeStep7,
       executeStep7,
       getOAuthFlowRemainingMs,
       getOAuthFlowRemainingMs,
       getOAuthFlowStepTimeoutMs,
       getOAuthFlowStepTimeoutMs,
-      getPanelMode,
       getMailConfig,
       getMailConfig,
       getState,
       getState,
       getTabId,
       getTabId,
@@ -104,9 +103,6 @@
         }
         }
       }
       }
 
 
-      const shouldRefreshOAuthBeforeSubmit = getPanelMode(state) === 'cpa';
-      let step7ReplayCompleted = false;
-
       await resolveVerificationStep(8, state, mail, {
       await resolveVerificationStep(8, state, mail, {
         filterAfterTimestamp: stepStartedAt,
         filterAfterTimestamp: stepStartedAt,
         getRemainingTimeMs: getStep8RemainingTimeResolver(),
         getRemainingTimeMs: getStep8RemainingTimeResolver(),
@@ -114,22 +110,6 @@
         resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
         resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
           ? 0
           ? 0
           : STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
           : 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,
       });
       });
     }
     }
 
 

+ 5 - 1
background/steps/fetch-signup-code.js

@@ -39,7 +39,11 @@
           type: 'PREPARE_SIGNUP_VERIFICATION',
           type: 'PREPARE_SIGNUP_VERIFICATION',
           step: 4,
           step: 4,
           source: 'background',
           source: 'background',
-          payload: { password: state.password || state.customPassword || '' },
+          payload: {
+            password: state.password || state.customPassword || '',
+            prepareSource: 'step4_execute',
+            prepareLogLabel: '步骤 4 执行',
+          },
         },
         },
         {
         {
           timeoutMs: 30000,
           timeoutMs: 30000,

+ 13 - 10
content/signup-page.js

@@ -1355,6 +1355,9 @@ async function waitForSignupVerificationTransition(timeout = 5000) {
 
 
 async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
 async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
   const { password } = payload;
   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();
   const start = Date.now();
   let recoveryRound = 0;
   let recoveryRound = 0;
   const maxRecoveryRounds = 3;
   const maxRecoveryRounds = 3;
@@ -1363,17 +1366,17 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
     throwIfStopped();
     throwIfStopped();
 
 
     const roundNo = recoveryRound + 1;
     const roundNo = recoveryRound + 1;
-    log(`步骤 4:等待页面进入验证码阶段(第 ${roundNo}/${maxRecoveryRounds} 轮,先等待 5 秒)...`, 'info');
+    log(`${prepareLogLabel}:等待页面进入验证码阶段(第 ${roundNo}/${maxRecoveryRounds} 轮,先等待 5 秒)...`, 'info');
     const snapshot = await waitForSignupVerificationTransition(5000);
     const snapshot = await waitForSignupVerificationTransition(5000);
 
 
     if (snapshot.state === 'step5') {
     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') {
     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') {
     if (snapshot.state === 'email_exists') {
@@ -1385,7 +1388,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
     if (snapshot.state === 'error') {
     if (snapshot.state === 'error') {
       await recoverCurrentAuthRetryPage({
       await recoverCurrentAuthRetryPage({
         flow: 'signup_password',
         flow: 'signup_password',
-        logLabel: `步骤 4:检测到密码页超时报错,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`,
+        logLabel: `${prepareLogLabel}:检测到密码页超时报错,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`,
         step: 4,
         step: 4,
         timeoutMs: 12000,
         timeoutMs: 12000,
       });
       });
@@ -1398,24 +1401,24 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
       }
       }
 
 
       if ((snapshot.passwordInput.value || '') !== password) {
       if ((snapshot.passwordInput.value || '') !== password) {
-        log('步骤 4:页面仍停留在密码页,正在重新填写密码...', 'warn');
+        log(`${prepareLogLabel}:页面仍停留在密码页,正在重新填写密码...`, 'warn');
         await humanPause(450, 1100);
         await humanPause(450, 1100);
         fillInput(snapshot.passwordInput, password);
         fillInput(snapshot.passwordInput, password);
       }
       }
 
 
       if (snapshot.submitButton && isActionEnabled(snapshot.submitButton)) {
       if (snapshot.submitButton && isActionEnabled(snapshot.submitButton)) {
-        log(`步骤 4:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn');
+        log(`${prepareLogLabel}:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn');
         await humanPause(350, 900);
         await humanPause(350, 900);
         simulateClick(snapshot.submitButton);
         simulateClick(snapshot.submitButton);
         await sleep(1200);
         await sleep(1200);
         continue;
         continue;
       }
       }
 
 
-      log(`步骤 4:页面仍停留在密码页,但“继续”按钮暂不可用,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn');
+      log(`${prepareLogLabel}:页面仍停留在密码页,但“继续”按钮暂不可用,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn');
       continue;
       continue;
     }
     }
 
 
-    log(`步骤 4:页面仍在切换中,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn');
+    log(`${prepareLogLabel}:页面仍在切换中,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn');
   }
   }
 
 
   throw new Error(`等待注册验证码页面就绪超时或自动恢复失败(已尝试 ${recoveryRound}/${maxRecoveryRounds} 轮)。URL: ${location.href}`);
   throw new Error(`等待注册验证码页面就绪超时或自动恢复失败(已尝试 ${recoveryRound}/${maxRecoveryRounds} 轮)。URL: ${location.href}`);

+ 5 - 1
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',
     type: 'PREPARE_SIGNUP_VERIFICATION',
     step: 3,
     step: 3,
     source: 'background',
     source: 'background',
-    payload: { password: 'Secret123!' },
+    payload: {
+      password: 'Secret123!',
+      prepareSource: 'step3_finalize',
+      prepareLogLabel: '步骤 3 收尾',
+    },
   });
   });
 });
 });

+ 5 - 9
tests/background-step7-recovery.test.js

@@ -6,7 +6,7 @@ const source = fs.readFileSync('background/steps/fetch-login-code.js', 'utf8');
 const globalScope = {};
 const globalScope = {};
 const api = new Function('self', `${source}; return self.MultiPageBackgroundStep8;`)(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 = {
   const calls = {
     ensureReady: 0,
     ensureReady: 0,
     ensureReadyOptions: [],
     ensureReadyOptions: [],
@@ -43,7 +43,6 @@ test('step 8 refreshes CPA oauth via step 7 replay before submitting verificatio
       url: 'https://mail.qq.com',
       url: 'https://mail.qq.com',
       navigateOnReuse: false,
       navigateOnReuse: false,
     }),
     }),
-    getPanelMode: () => 'cpa',
     getState: async () => ({ email: 'user@example.com', password: 'secret' }),
     getState: async () => ({ email: 'user@example.com', password: 'secret' }),
     getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
     getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
     HOTMAIL_PROVIDER: 'hotmail-api',
     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',
     LUCKMAIL_PROVIDER: 'luckmail-api',
     resolveVerificationStep: async (_step, _state, _mail, options) => {
     resolveVerificationStep: async (_step, _state, _mail, options) => {
       calls.resolveOptions = options;
       calls.resolveOptions = options;
-      await options.beforeSubmit({ code: '654321' });
     },
     },
     reuseOrCreateTab: async () => {},
     reuseOrCreateTab: async () => {},
     setState: async () => {},
     setState: async () => {},
@@ -77,17 +75,16 @@ test('step 8 refreshes CPA oauth via step 7 replay before submitting verificatio
     Date.now = realDateNow;
     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(calls.resolveOptions.filterAfterTimestamp, 123456);
   assert.equal(typeof calls.resolveOptions.getRemainingTimeMs, 'function');
   assert.equal(typeof calls.resolveOptions.getRemainingTimeMs, 'function');
   assert.equal(await calls.resolveOptions.getRemainingTimeMs({ actionLabel: '登录验证码流程' }), 5000);
   assert.equal(await calls.resolveOptions.getRemainingTimeMs({ actionLabel: '登录验证码流程' }), 5000);
   assert.equal(calls.resolveOptions.resendIntervalMs, 25000);
   assert.equal(calls.resolveOptions.resendIntervalMs, 25000);
   assert.deepStrictEqual(calls.ensureReadyOptions, [
   assert.deepStrictEqual(calls.ensureReadyOptions, [
     { timeoutMs: 5000 },
     { timeoutMs: 5000 },
-    { timeoutMs: 5000 },
   ]);
   ]);
 });
 });
 
 
@@ -114,7 +111,6 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => {
       url: 'https://2925.com',
       url: 'https://2925.com',
       navigateOnReuse: false,
       navigateOnReuse: false,
     }),
     }),
-    getPanelMode: () => 'sub2api',
     getState: async () => ({ email: 'user@example.com', password: 'secret' }),
     getState: async () => ({ email: 'user@example.com', password: 'secret' }),
     getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
     getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
     HOTMAIL_PROVIDER: 'hotmail-api',
     HOTMAIL_PROVIDER: 'hotmail-api',

+ 1 - 1
项目完整链路说明.md

@@ -252,7 +252,6 @@
 - `2925` provider 会拉长单轮轮询窗口,并关闭 Step 4 / 8 的自动重发间隔,减少因邮件延迟过早判负。
 - `2925` provider 会拉长单轮轮询窗口,并关闭 Step 4 / 8 的自动重发间隔,减少因邮件延迟过早判负。
 - Step 4 / 8 会在当前验证码步骤启动时固定一次邮件筛选时间窗;本步后续即使触发重新发送验证码,也继续沿用这一次启动时间作为筛选基准,不再随着重发时间前移。
 - Step 4 / 8 会在当前验证码步骤启动时固定一次邮件筛选时间窗;本步后续即使触发重新发送验证码,也继续沿用这一次启动时间作为筛选基准,不再随着重发时间前移。
 - 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。
 - 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。
-- CPA 模式下,Step 8 在真正回填登录验证码前,会先刷新最新 OAuth 地址并快速重走一次 Step 7,降低验证码等待过久导致 OAuth 过期的概率。
 
 
 - Auto 模式下,如果 Step 4 当前轮失败,后台会沿用当前邮箱回到 Step 1 重新开始当前轮,而不是立刻换邮箱开新尝试。
 - Auto 模式下,如果 Step 4 当前轮失败,后台会沿用当前邮箱回到 Step 1 重新开始当前轮,而不是立刻换邮箱开新尝试。
 
 
@@ -310,6 +309,7 @@
 3. 轮询登录验证码
 3. 轮询登录验证码
 4. 回填登录验证码
 4. 回填登录验证码
 5. 如遇邮箱轮询类失败,则按有限次数回到 Step 7 重试
 5. 如遇邮箱轮询类失败,则按有限次数回到 Step 7 重试
+6. 获取到登录验证码后不再触发“刷新 OAuth 并重走 Step 7”的前置回放,直接在当前验证码页提交并继续进入 Step 9
 
 
 ### Step 9
 ### Step 9
 
 

+ 2 - 2
项目文件结构说明.md

@@ -55,7 +55,7 @@
 
 
 - `background/steps/clear-login-cookies.js`:步骤 6 实现,负责登录前 Cookie 清理。
 - `background/steps/clear-login-cookies.js`:步骤 6 实现,负责登录前 Cookie 清理。
 - `background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成。
 - `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/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口。
 - `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交。
 - `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交。
 - `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。
 - `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。
@@ -143,7 +143,7 @@
 - `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。
 - `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。
 - `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。
 - `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。
 - `tests/background-step6-retry-limit.test.js`:测试步骤 6 的 Cookie 清理,以及步骤 7 的有限重试上限。
 - `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-step-registry.test.js`:测试后台步骤注册表和共享步骤定义已接入。
 - `tests/background-tab-runtime-module.test.js`:测试标签运行时模块已接入且导出工厂。
 - `tests/background-tab-runtime-module.test.js`:测试标签运行时模块已接入且导出工厂。
 - `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。
 - `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。