feat: 增强注册和登录验证码流程,添加步骤标识和日志标签,优化 OAuth 刷新逻辑
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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');
|
log(`${prepareLogLabel}:页面已进入验证码后的下一阶段,本步骤按已完成处理。`, 'ok');
|
||||||
return { ready: true, alreadyVerified: true, retried: recoveryRound };
|
return { ready: true, alreadyVerified: true, retried: recoveryRound, prepareSource };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.state === 'verification') {
|
if (snapshot.state === 'verification') {
|
||||||
log(`步骤 4:验证码页面已就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok');
|
log(`${prepareLogLabel}:验证码页面已就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok');
|
||||||
return { ready: true, retried: recoveryRound };
|
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}`);
|
||||||
|
|||||||
@@ -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 收尾',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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.resolveOptions.beforeSubmit, undefined);
|
||||||
assert.equal(calls.ensureReady, 2);
|
assert.equal(calls.ensureReady, 1);
|
||||||
assert.equal(calls.executeStep7, 1);
|
assert.equal(calls.executeStep7, 0);
|
||||||
assert.deepStrictEqual(calls.sleep, [1200]);
|
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
@@ -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
@@ -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`:测试验证码流程模块已接入且导出工厂。
|
||||||
|
|||||||
Reference in New Issue
Block a user