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

This commit is contained in:
祁连海
2026-04-17 22:30:12 +08:00
parent 8f290ed4fc
commit f5b622d272
8 changed files with 36 additions and 45 deletions
+5 -1
View File
@@ -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,
-20
View File
@@ -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,
});
}
+5 -1
View File
@@ -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,
+13 -10
View File
@@ -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}`);
@@ -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 收尾',
},
});
});
+5 -9
View File
@@ -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',
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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`:测试验证码流程模块已接入且导出工厂。