修改登陆及后续逻辑,统一使用一个逻辑
This commit is contained in:
+121
-39
@@ -4420,7 +4420,6 @@ function getLoginAuthStateLabel(state) {
|
||||
if (typeof loggingStatus !== 'undefined' && loggingStatus?.getLoginAuthStateLabel) {
|
||||
return loggingStatus.getLoginAuthStateLabel(state);
|
||||
}
|
||||
state = state === 'oauth_consent_page' ? 'unknown' : state;
|
||||
switch (state) {
|
||||
case 'verification_page': return '登录验证码页';
|
||||
case 'password_page': return '密码页';
|
||||
@@ -4487,7 +4486,8 @@ function hasSavedProgress(statuses = {}, stateOverride = null) {
|
||||
return activeStepIds.some((step) => (merged[step] || 'pending') !== 'pending');
|
||||
}
|
||||
|
||||
function getDownstreamStateResets(step) {
|
||||
function getDownstreamStateResets(step, state = {}) {
|
||||
const stepKey = getStepExecutionKeyForState(step, state);
|
||||
const plusRuntimeResets = {
|
||||
plusCheckoutTabId: null,
|
||||
plusCheckoutUrl: null,
|
||||
@@ -4574,6 +4574,20 @@ function getDownstreamStateResets(step) {
|
||||
localhostUrl: null,
|
||||
};
|
||||
}
|
||||
if (stepKey === 'oauth-login' || stepKey === 'fetch-login-code') {
|
||||
return {
|
||||
lastLoginCode: null,
|
||||
loginVerificationRequestedAt: null,
|
||||
oauthFlowDeadlineAt: null,
|
||||
oauthFlowDeadlineSourceUrl: null,
|
||||
localhostUrl: null,
|
||||
};
|
||||
}
|
||||
if (stepKey === 'confirm-oauth') {
|
||||
return {
|
||||
localhostUrl: null,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -4609,7 +4623,7 @@ async function invalidateDownstreamAfterStepRestart(step, options = {}) {
|
||||
await addLog(`${logLabel},已重置后续步骤状态:${changedSteps.join(', ')}`, 'warn');
|
||||
}
|
||||
|
||||
const resets = getDownstreamStateResets(step);
|
||||
const resets = getDownstreamStateResets(step, state);
|
||||
if (Object.keys(resets).length) {
|
||||
await setState(resets);
|
||||
broadcastDataUpdate(resets);
|
||||
@@ -5436,6 +5450,24 @@ let resumeWaiter = null;
|
||||
const AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS = 120000;
|
||||
const AUTO_RUN_BACKGROUND_COMPLETED_STEPS = new Set([1, 2, 4, 6, 7, 8, 9]);
|
||||
const STEP_COMPLETION_SIGNAL_STEPS = new Set([3, 5, 10, 12]);
|
||||
const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([
|
||||
'open-chatgpt',
|
||||
'submit-signup-email',
|
||||
'fetch-signup-code',
|
||||
'clear-login-cookies',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'confirm-oauth',
|
||||
]);
|
||||
const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([
|
||||
'fill-password',
|
||||
'fill-profile',
|
||||
'platform-verify',
|
||||
]);
|
||||
|
||||
function waitForStepComplete(step, timeoutMs = 120000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -5457,7 +5489,26 @@ function waitForStepComplete(step, timeoutMs = 120000) {
|
||||
});
|
||||
}
|
||||
|
||||
function doesStepUseCompletionSignal(step) {
|
||||
function getStepExecutionKeyForState(step, state = {}) {
|
||||
if (typeof getStepDefinitionForState !== 'function') {
|
||||
return '';
|
||||
}
|
||||
return String(getStepDefinitionForState(step, state)?.key || '').trim();
|
||||
}
|
||||
|
||||
function doesStepUseBackgroundCompletion(step, state = {}) {
|
||||
const stepKey = getStepExecutionKeyForState(step, state);
|
||||
if (stepKey) {
|
||||
return AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS.has(stepKey);
|
||||
}
|
||||
return AUTO_RUN_BACKGROUND_COMPLETED_STEPS.has(step);
|
||||
}
|
||||
|
||||
function doesStepUseCompletionSignal(step, state = {}) {
|
||||
const stepKey = getStepExecutionKeyForState(step, state);
|
||||
if (stepKey) {
|
||||
return STEP_COMPLETION_SIGNAL_STEP_KEYS.has(stepKey);
|
||||
}
|
||||
return STEP_COMPLETION_SIGNAL_STEPS.has(step);
|
||||
}
|
||||
|
||||
@@ -5586,16 +5637,28 @@ async function waitForRunningStepsToFinish(payload = {}) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
const AUTH_CHAIN_STEP_IDS = new Set([7, 8, 9, 10, 11, 12]);
|
||||
const AUTH_CHAIN_STEP_IDS = new Set([7, 8, 9, 10, 11, 12, 13]);
|
||||
const AUTH_CHAIN_STEP_KEYS = new Set([
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]);
|
||||
let activeTopLevelAuthChainExecution = null;
|
||||
|
||||
function isAuthChainStep(step) {
|
||||
function isAuthChainStep(step, state = {}) {
|
||||
const stepKey = typeof getStepDefinitionForState === 'function'
|
||||
? String(getStepDefinitionForState(step, state)?.key || '').trim()
|
||||
: '';
|
||||
if (stepKey && typeof AUTH_CHAIN_STEP_KEYS !== 'undefined') {
|
||||
return AUTH_CHAIN_STEP_KEYS.has(stepKey);
|
||||
}
|
||||
return AUTH_CHAIN_STEP_IDS.has(Number(step));
|
||||
}
|
||||
|
||||
async function acquireTopLevelAuthChainExecution(step) {
|
||||
async function acquireTopLevelAuthChainExecution(step, state = {}) {
|
||||
const normalizedStep = Number(step);
|
||||
if (!isAuthChainStep(normalizedStep)) {
|
||||
if (!isAuthChainStep(normalizedStep, state)) {
|
||||
return {
|
||||
joined: false,
|
||||
release() {},
|
||||
@@ -5735,7 +5798,8 @@ async function requestStop(options = {}) {
|
||||
async function executeStep(step, options = {}) {
|
||||
const { deferRetryableTransportError = false } = options;
|
||||
console.log(LOG_PREFIX, `Executing step ${step}`);
|
||||
const authChainClaim = await acquireTopLevelAuthChainExecution(step);
|
||||
let state = await getState();
|
||||
const authChainClaim = await acquireTopLevelAuthChainExecution(step, state);
|
||||
if (authChainClaim.joined) {
|
||||
return;
|
||||
}
|
||||
@@ -5747,7 +5811,7 @@ async function executeStep(step, options = {}) {
|
||||
await addLog(`步骤 ${step} 开始执行`);
|
||||
await humanStepDelay();
|
||||
|
||||
const state = await getState();
|
||||
state = await getState();
|
||||
|
||||
// Set flow start time on first step
|
||||
if (step === 1 && !state.flowStartTime) {
|
||||
@@ -5765,11 +5829,11 @@ async function executeStep(step, options = {}) {
|
||||
});
|
||||
} catch (err) {
|
||||
executionError = err;
|
||||
const state = await getState();
|
||||
const errorState = await getState();
|
||||
if (isStopError(err)) {
|
||||
await setStepStatus(step, 'stopped');
|
||||
await addLog(`步骤 ${step} 已被用户停止`, 'warn');
|
||||
await appendManualAccountRunRecordIfNeeded(`step${step}_stopped`, state, getErrorMessage(err));
|
||||
await appendManualAccountRunRecordIfNeeded(`step${step}_stopped`, errorState, getErrorMessage(err));
|
||||
throw err;
|
||||
}
|
||||
if (isTerminalSecurityBlockedError(err)) {
|
||||
@@ -5780,10 +5844,10 @@ async function executeStep(step, options = {}) {
|
||||
await handleBrowserSwitchRequired(err);
|
||||
throw new Error(STOP_ERROR_MESSAGE);
|
||||
}
|
||||
if (!(deferRetryableTransportError && doesStepUseCompletionSignal(step) && isRetryableContentScriptTransportError(err))) {
|
||||
if (!(deferRetryableTransportError && doesStepUseCompletionSignal(step, errorState) && isRetryableContentScriptTransportError(err))) {
|
||||
await setStepStatus(step, 'failed');
|
||||
await addLog(`步骤 ${step} 失败:${err.message}`, 'error');
|
||||
await appendManualAccountRunRecordIfNeeded(`step${step}_failed`, state, getErrorMessage(err));
|
||||
await appendManualAccountRunRecordIfNeeded(`step${step}_failed`, errorState, getErrorMessage(err));
|
||||
} else {
|
||||
console.warn(
|
||||
LOG_PREFIX,
|
||||
@@ -5813,12 +5877,13 @@ async function executeStepAndWait(step, delayAfter = 2000) {
|
||||
await sleepWithStop(delaySeconds * 1000);
|
||||
}
|
||||
|
||||
if (AUTO_RUN_BACKGROUND_COMPLETED_STEPS.has(step)) {
|
||||
const executionState = await getState();
|
||||
if (doesStepUseBackgroundCompletion(step, executionState)) {
|
||||
await addLog(`自动运行:步骤 ${step} 由后台流程负责收尾,执行函数返回后将直接进入下一步。`, 'info');
|
||||
await executeStep(step);
|
||||
const latestState = await getState();
|
||||
await addLog(`自动运行:步骤 ${step} 已执行返回,当前状态为 ${latestState.stepStatuses?.[step] || 'pending'},准备继续后续步骤。`, 'info');
|
||||
} else if (doesStepUseCompletionSignal(step)) {
|
||||
} else if (doesStepUseCompletionSignal(step, executionState)) {
|
||||
await addLog(`自动运行:步骤 ${step} 已发起,正在等待完成信号(超时 ${AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS / 1000} 秒)。`, 'info');
|
||||
await executeStepViaCompletionSignal(step, AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS);
|
||||
await addLog(`自动运行:步骤 ${step} 已收到完成信号,准备继续后续步骤。`, 'info');
|
||||
@@ -6581,7 +6646,10 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
||||
|
||||
if (restartDecision.blockedByAddPhone) {
|
||||
const addPhoneUrl = restartDecision.authState?.url || 'https://auth.openai.com/add-phone';
|
||||
await addLog(`步骤 ${step}:检测到认证流程进入 add-phone(${addPhoneUrl}),停止自动回到步骤 7 重开。`, 'warn');
|
||||
const authChainStartStep = typeof getAuthChainStartStepId === 'function'
|
||||
? getAuthChainStartStepId(await getState())
|
||||
: FINAL_OAUTH_CHAIN_START_STEP;
|
||||
await addLog(`步骤 ${step}:检测到认证流程进入 add-phone(${addPhoneUrl}),停止自动回到步骤 ${authChainStartStep} 重开。`, 'warn');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
@@ -7440,12 +7508,13 @@ async function runPreStep6CookieCleanup() {
|
||||
// Step 7: Login and ensure the auth page reaches the login verification page
|
||||
// ============================================================
|
||||
|
||||
async function refreshOAuthUrlBeforeStep6(state) {
|
||||
async function refreshOAuthUrlBeforeStep6(state, options = {}) {
|
||||
const visibleStep = Number(options.visibleStep) || Number(state?.visibleStep) || 7;
|
||||
if (state?.contributionModeExpected && !state?.contributionMode) {
|
||||
throw new Error('步骤 7:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 链路。请重新进入贡献模式后再点击自动。');
|
||||
throw new Error(`步骤 ${visibleStep}:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 链路。请重新进入贡献模式后再点击自动。`);
|
||||
}
|
||||
if (state?.contributionMode && contributionOAuthManager?.startContributionFlow) {
|
||||
await addLog('步骤 7:contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...', 'info');
|
||||
await addLog(`步骤 ${visibleStep}:contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...`, 'info');
|
||||
const contributionState = await contributionOAuthManager.startContributionFlow({
|
||||
nickname: state.contributionNickname || '',
|
||||
openAuthTab: false,
|
||||
@@ -7458,9 +7527,9 @@ async function refreshOAuthUrlBeforeStep6(state) {
|
||||
await handleStepData(1, { oauthUrl });
|
||||
return oauthUrl;
|
||||
}
|
||||
await addLog(`步骤 7:contributionMode=false,走普通 CPA / SUB2API / Codex2API 链路(当前面板:${getPanelModeLabel(state)}),正在刷新 OAuth 登录地址...`, 'info');
|
||||
await addLog(`步骤 ${visibleStep}:contributionMode=false,走普通 CPA / SUB2API / Codex2API 链路(当前面板:${getPanelModeLabel(state)}),正在刷新 OAuth 登录地址...`, 'info');
|
||||
console.log(LOG_PREFIX, '[refreshOAuthUrlBeforeStep6] requesting fresh OAuth directly from panel');
|
||||
const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: '步骤 7' });
|
||||
const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: `步骤 ${visibleStep}` });
|
||||
await handleStepData(1, refreshResult);
|
||||
|
||||
if (!refreshResult?.oauthUrl) {
|
||||
@@ -7470,9 +7539,12 @@ async function refreshOAuthUrlBeforeStep6(state) {
|
||||
return refreshResult.oauthUrl;
|
||||
}
|
||||
|
||||
function buildOAuthFlowTimeoutError(step, actionLabel = '后续授权流程') {
|
||||
function buildOAuthFlowTimeoutError(step, actionLabel = '后续授权流程', state = {}) {
|
||||
const restartStep = typeof getAuthChainStartStepId === 'function'
|
||||
? getAuthChainStartStepId(state)
|
||||
: FINAL_OAUTH_CHAIN_START_STEP;
|
||||
return new Error(
|
||||
`步骤 ${step}:从拿到 OAuth 登录地址开始,${Math.round(OAUTH_FLOW_TIMEOUT_MS / 60000)} 分钟内未完成${actionLabel},结束当前链路,准备从步骤 7 重新开始。`
|
||||
`步骤 ${step}:从拿到 OAuth 登录地址开始,${Math.round(OAUTH_FLOW_TIMEOUT_MS / 60000)} 分钟内未完成${actionLabel},结束当前链路,准备从步骤 ${restartStep} 重新开始。`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7523,7 +7595,7 @@ async function getOAuthFlowRemainingMs(options = {}) {
|
||||
|
||||
const remainingMs = deadlineAt - Date.now();
|
||||
if (remainingMs <= 0) {
|
||||
throw buildOAuthFlowTimeoutError(step, actionLabel);
|
||||
throw buildOAuthFlowTimeoutError(step, actionLabel, state);
|
||||
}
|
||||
|
||||
return remainingMs;
|
||||
@@ -7539,9 +7611,11 @@ async function getOAuthFlowStepTimeoutMs(defaultTimeoutMs, options = {}) {
|
||||
|
||||
const budgetMs = remainingMs - reserveMs;
|
||||
if (budgetMs <= 0) {
|
||||
const stateForError = options.state || await getState();
|
||||
throw buildOAuthFlowTimeoutError(
|
||||
Number(options.step) || 7,
|
||||
String(options.actionLabel || '后续授权流程').trim() || '后续授权流程'
|
||||
String(options.actionLabel || '后续授权流程').trim() || '后续授权流程',
|
||||
stateForError
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7615,7 +7689,7 @@ async function getPostStep6AutoRestartDecision(step, error) {
|
||||
let authState = null;
|
||||
try {
|
||||
authState = await getLoginAuthStateFromContent({
|
||||
logMessage: `步骤 ${normalizedStep}:正在确认当前认证页状态,以决定是否回到步骤 7 重开...`,
|
||||
logMessage: `步骤 ${normalizedStep}:正在确认当前认证页状态,以决定是否回到步骤 ${authChainStartStep} 重开...`,
|
||||
});
|
||||
} catch (inspectError) {
|
||||
console.warn(LOG_PREFIX, '[AutoRun] failed to inspect login auth state after post-step6 error', {
|
||||
@@ -7671,6 +7745,8 @@ async function getLoginAuthStateFromContent(options = {}) {
|
||||
}
|
||||
|
||||
async function ensureStep8VerificationPageReady(options = {}) {
|
||||
const visibleStep = Number(options.visibleStep) || 8;
|
||||
const authLoginStep = Number(options.authLoginStep) || (visibleStep >= 11 ? 10 : 7);
|
||||
const pageState = await getLoginAuthStateFromContent(options);
|
||||
if (pageState.state === 'verification_page') {
|
||||
return pageState;
|
||||
@@ -7682,17 +7758,17 @@ async function ensureStep8VerificationPageReady(options = {}) {
|
||||
|
||||
if (pageState.state === 'login_timeout_error_page') {
|
||||
const urlPart = pageState.url ? ` URL: ${pageState.url}` : '';
|
||||
throw new Error(`STEP8_RESTART_STEP7::步骤 8:当前认证页进入登录超时报错页,请回到步骤 7 重新开始。${urlPart}`.trim());
|
||||
throw new Error(`STEP8_RESTART_STEP7::步骤 ${visibleStep}:当前认证页进入登录超时报错页,请回到步骤 ${authLoginStep} 重新开始。${urlPart}`.trim());
|
||||
}
|
||||
|
||||
if (pageState.state === 'add_phone_page') {
|
||||
const urlPart = pageState.url ? ` URL: ${pageState.url}` : '';
|
||||
throw new Error(`步骤 8:当前认证页进入手机号页面,当前流程无法继续自动授权。${urlPart}`.trim());
|
||||
throw new Error(`步骤 ${visibleStep}:当前认证页进入手机号页面,当前流程无法继续自动授权。${urlPart}`.trim());
|
||||
}
|
||||
|
||||
const stateLabel = getLoginAuthStateLabel(pageState.state);
|
||||
const urlPart = pageState.url ? ` URL: ${pageState.url}` : '';
|
||||
throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:${stateLabel}.${urlPart}`.trim());
|
||||
throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 ${authLoginStep}。当前状态:${stateLabel}.${urlPart}`.trim());
|
||||
}
|
||||
|
||||
async function rerunStep7ForStep8Recovery(options = {}) {
|
||||
@@ -7703,27 +7779,33 @@ async function rerunStep7ForStep8Recovery(options = {}) {
|
||||
|
||||
throwIfStopped();
|
||||
const initialState = await getState();
|
||||
const authLoginStep = typeof getAuthChainStartStepId === 'function'
|
||||
? getAuthChainStartStepId(initialState)
|
||||
: FINAL_OAUTH_CHAIN_START_STEP;
|
||||
await addLog(logMessage, 'warn');
|
||||
await setStepStatus(7, 'running');
|
||||
await addLog('步骤 7 开始执行');
|
||||
await setStepStatus(authLoginStep, 'running');
|
||||
await addLog(`步骤 ${authLoginStep} 开始执行`);
|
||||
|
||||
try {
|
||||
await step7Executor.executeStep7(initialState);
|
||||
await step7Executor.executeStep7({
|
||||
...initialState,
|
||||
visibleStep: authLoginStep,
|
||||
});
|
||||
} catch (err) {
|
||||
const latestState = await getState();
|
||||
if (isStopError(err)) {
|
||||
await setStepStatus(7, 'stopped');
|
||||
await addLog('步骤 7 已被用户停止', 'warn');
|
||||
await appendManualAccountRunRecordIfNeeded('step7_stopped', latestState, getErrorMessage(err));
|
||||
await setStepStatus(authLoginStep, 'stopped');
|
||||
await addLog(`步骤 ${authLoginStep} 已被用户停止`, 'warn');
|
||||
await appendManualAccountRunRecordIfNeeded(`step${authLoginStep}_stopped`, latestState, getErrorMessage(err));
|
||||
throw err;
|
||||
}
|
||||
if (isTerminalSecurityBlockedError(err)) {
|
||||
await handleCloudflareSecurityBlocked(err);
|
||||
throw new Error(STOP_ERROR_MESSAGE);
|
||||
}
|
||||
await setStepStatus(7, 'failed');
|
||||
await addLog(`步骤 7 失败:${getErrorMessage(err)}`, 'error');
|
||||
await appendManualAccountRunRecordIfNeeded('step7_failed', latestState, getErrorMessage(err));
|
||||
await setStepStatus(authLoginStep, 'failed');
|
||||
await addLog(`步骤 ${authLoginStep} 失败:${getErrorMessage(err)}`, 'error');
|
||||
await appendManualAccountRunRecordIfNeeded(`step${authLoginStep}_failed`, latestState, getErrorMessage(err));
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
}
|
||||
|
||||
function getLoginAuthStateLabel(state) {
|
||||
state = state === 'oauth_consent_page' ? 'unknown' : state;
|
||||
switch (state) {
|
||||
case 'verification_page':
|
||||
return '登录验证码页';
|
||||
|
||||
@@ -137,6 +137,31 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
function isStepProtectedFromAutoSkip(status) {
|
||||
return status === 'running'
|
||||
|| status === 'completed'
|
||||
|| status === 'manual_completed'
|
||||
|| status === 'skipped';
|
||||
}
|
||||
|
||||
function findStepByKeyAfter(currentStep, targetKey, state = {}) {
|
||||
const activeStepIds = typeof getStepIdsForState === 'function'
|
||||
? getStepIdsForState(state)
|
||||
: [];
|
||||
const candidates = activeStepIds.length ? activeStepIds : [Number(currentStep) + 1, 8];
|
||||
return candidates.find((stepId) => {
|
||||
const numericStep = Number(stepId);
|
||||
if (!Number.isFinite(numericStep) || numericStep <= Number(currentStep)) {
|
||||
return false;
|
||||
}
|
||||
const stepKey = getStepKeyForState(numericStep, state);
|
||||
if (stepKey) {
|
||||
return stepKey === targetKey;
|
||||
}
|
||||
return targetKey === 'fetch-login-code' && Number(currentStep) === 7 && numericStep === 8;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
async function handlePlatformVerifyStepData(payload) {
|
||||
if (payload.localhostUrl) {
|
||||
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||
@@ -180,7 +205,18 @@
|
||||
const stepKey = getStepKeyForState(step, stateForStep);
|
||||
|
||||
if (stepKey === 'oauth-login') {
|
||||
if (payload.loginVerificationRequestedAt) {
|
||||
if (payload.skipLoginVerificationStep) {
|
||||
await setState({ loginVerificationRequestedAt: null });
|
||||
const latestState = await getState();
|
||||
const loginCodeStep = findStepByKeyAfter(step, 'fetch-login-code', latestState);
|
||||
if (loginCodeStep) {
|
||||
const currentStatus = latestState.stepStatuses?.[loginCodeStep];
|
||||
if (!isStepProtectedFromAutoSkip(currentStatus)) {
|
||||
await setStepStatus(loginCodeStep, 'skipped');
|
||||
await addLog(`步骤 ${step}:认证页已直接进入 OAuth 授权页,已自动跳过步骤 ${loginCodeStep} 的登录验证码。`, 'warn');
|
||||
}
|
||||
}
|
||||
} else if (payload.loginVerificationRequestedAt) {
|
||||
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
|
||||
}
|
||||
return;
|
||||
@@ -484,7 +520,8 @@
|
||||
await setPersistentSettings({ emailPrefix: message.payload.emailPrefix });
|
||||
await setState({ emailPrefix: message.payload.emailPrefix });
|
||||
}
|
||||
if (doesStepUseCompletionSignal(step)) {
|
||||
const executionState = await getState();
|
||||
if (doesStepUseCompletionSignal(step, executionState)) {
|
||||
await executeStepViaCompletionSignal(step);
|
||||
} else {
|
||||
await executeStep(step);
|
||||
|
||||
@@ -38,10 +38,14 @@
|
||||
return visibleStep > 0 ? visibleStep : fallback;
|
||||
}
|
||||
|
||||
function getAuthLoginStepForVisibleStep(visibleStep) {
|
||||
return visibleStep >= 12 ? 10 : 7;
|
||||
}
|
||||
|
||||
async function executeStep9(state) {
|
||||
const visibleStep = getVisibleStep(state, 9);
|
||||
if (!state.oauthUrl) {
|
||||
const authLoginStep = visibleStep === 11 ? 10 : 7;
|
||||
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
|
||||
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}。`);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,25 +31,34 @@
|
||||
throwIfStopped,
|
||||
} = deps;
|
||||
|
||||
async function getStep8ReadyTimeoutMs(actionLabel, expectedOauthUrl = '') {
|
||||
function getVisibleStep(state, fallback = 8) {
|
||||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||
return visibleStep > 0 ? visibleStep : fallback;
|
||||
}
|
||||
|
||||
function getAuthLoginStepForVisibleStep(visibleStep) {
|
||||
return visibleStep >= 11 ? 10 : 7;
|
||||
}
|
||||
|
||||
async function getStep8ReadyTimeoutMs(actionLabel, expectedOauthUrl = '', visibleStep = 8) {
|
||||
if (typeof getOAuthFlowStepTimeoutMs !== 'function') {
|
||||
return 15000;
|
||||
}
|
||||
|
||||
return getOAuthFlowStepTimeoutMs(15000, {
|
||||
step: 8,
|
||||
step: visibleStep,
|
||||
actionLabel,
|
||||
oauthUrl: expectedOauthUrl,
|
||||
});
|
||||
}
|
||||
|
||||
function getStep8RemainingTimeResolver(expectedOauthUrl = '') {
|
||||
function getStep8RemainingTimeResolver(expectedOauthUrl = '', visibleStep = 8) {
|
||||
if (typeof getOAuthFlowRemainingMs !== 'function') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return async (details = {}) => getOAuthFlowRemainingMs({
|
||||
step: 8,
|
||||
step: visibleStep,
|
||||
actionLabel: details.actionLabel || '登录验证码流程',
|
||||
oauthUrl: expectedOauthUrl,
|
||||
});
|
||||
@@ -96,6 +105,7 @@
|
||||
}
|
||||
|
||||
async function runStep8Attempt(state) {
|
||||
const visibleStep = getVisibleStep(state, 8);
|
||||
const mail = getMailConfig(state);
|
||||
if (mail.error) throw new Error(mail.error);
|
||||
|
||||
@@ -110,14 +120,16 @@
|
||||
await chrome.tabs.update(authTabId, { active: true });
|
||||
} else {
|
||||
if (!state.oauthUrl) {
|
||||
throw new Error('缺少登录用 OAuth 链接,请先完成步骤 7。');
|
||||
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`);
|
||||
}
|
||||
await reuseOrCreateTab('signup-page', state.oauthUrl);
|
||||
}
|
||||
|
||||
throwIfStopped();
|
||||
const pageState = await ensureStep8VerificationPageReady({
|
||||
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || ''),
|
||||
visibleStep,
|
||||
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
|
||||
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
|
||||
});
|
||||
const shouldCompareVerificationEmail = mail.provider !== '2925';
|
||||
const displayedVerificationEmail = shouldCompareVerificationEmail
|
||||
@@ -131,22 +143,25 @@
|
||||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||
});
|
||||
|
||||
await addLog('步骤 8:登录验证码页面已就绪,开始获取验证码。', 'info');
|
||||
await addLog(`步骤 ${visibleStep}:登录验证码页面已就绪,开始获取验证码。`, 'info');
|
||||
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
|
||||
await addLog(`步骤 8:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
|
||||
await addLog(`步骤 ${visibleStep}:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
|
||||
}
|
||||
|
||||
if (shouldUseCustomRegistrationEmail(state)) {
|
||||
await confirmCustomVerificationStepBypass(8);
|
||||
await confirmCustomVerificationStepBypass(8, {
|
||||
completionStep: visibleStep,
|
||||
promptStep: visibleStep,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
|
||||
await addLog('步骤 8:正在确认 iCloud 邮箱登录态...', 'info');
|
||||
await addLog(`步骤 ${visibleStep}:正在确认 iCloud 邮箱登录态...`, 'info');
|
||||
await ensureIcloudMailSession({
|
||||
state,
|
||||
step: 8,
|
||||
actionLabel: '步骤 8:确认 iCloud 邮箱登录态',
|
||||
actionLabel: `步骤 ${visibleStep}:确认 iCloud 邮箱登录态`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,22 +171,22 @@
|
||||
|| mail.provider === LUCKMAIL_PROVIDER
|
||||
|| mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER
|
||||
) {
|
||||
await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`);
|
||||
await addLog(`步骤 ${visibleStep}:正在通过 ${mail.label} 轮询验证码...`);
|
||||
} else {
|
||||
await addLog(`步骤 8:正在打开${mail.label}...`);
|
||||
await addLog(`步骤 ${visibleStep}:正在打开${mail.label}...`);
|
||||
if (mail.provider === '2925' && typeof ensureMail2925MailboxSession === 'function') {
|
||||
await ensureMail2925MailboxSession({
|
||||
accountId: state.currentMail2925AccountId || null,
|
||||
forceRelogin: false,
|
||||
allowLoginWhenOnLoginPage: Boolean(state?.mail2925UseAccountPool),
|
||||
expectedMailboxEmail: getExpectedMail2925MailboxEmail(state),
|
||||
actionLabel: 'Step 8: ensure 2925 mailbox session',
|
||||
actionLabel: `Step ${visibleStep}: ensure 2925 mailbox session`,
|
||||
});
|
||||
} else {
|
||||
await focusOrOpenMailTab(mail);
|
||||
}
|
||||
if (mail.provider === '2925') {
|
||||
await addLog(`步骤 8:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info');
|
||||
await addLog(`步骤 ${visibleStep}:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,10 +194,11 @@
|
||||
...state,
|
||||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||
}, mail, {
|
||||
completionStep: visibleStep,
|
||||
filterAfterTimestamp: verificationFilterAfterTimestamp,
|
||||
sessionKey: verificationSessionKey,
|
||||
disableTimeBudgetCap: mail.provider === '2925',
|
||||
getRemainingTimeMs: getStep8RemainingTimeResolver(state?.oauthUrl || ''),
|
||||
getRemainingTimeMs: getStep8RemainingTimeResolver(state?.oauthUrl || '', visibleStep),
|
||||
requestFreshCodeFirst: false,
|
||||
targetEmail: fixedTargetEmail,
|
||||
resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
||||
@@ -206,6 +222,8 @@
|
||||
await runStep8Attempt(currentState);
|
||||
return;
|
||||
} catch (err) {
|
||||
const visibleStep = getVisibleStep(currentState, 8);
|
||||
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
|
||||
if (!isVerificationMailPollingError(err) && !isStep8RestartStep7Error(err)) {
|
||||
throw err;
|
||||
}
|
||||
@@ -218,26 +236,27 @@
|
||||
mailPollingAttempt += 1;
|
||||
await addLog(
|
||||
isStep8RestartStep7Error(err)
|
||||
? `步骤 8:检测到认证页进入重试/超时报错状态,准备从步骤 7 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`
|
||||
: `步骤 8:检测到邮箱轮询类失败,准备从步骤 7 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`,
|
||||
? `步骤 ${visibleStep}:检测到认证页进入重试/超时报错状态,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`
|
||||
: `步骤 ${visibleStep}:检测到邮箱轮询类失败,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`,
|
||||
'warn'
|
||||
);
|
||||
await rerunStep7ForStep8Recovery({
|
||||
logMessage: isStep8RestartStep7Error(err)
|
||||
? '步骤 8:认证页进入重试/超时报错状态,正在回到步骤 7 重新发起登录流程...'
|
||||
: '步骤 8:正在回到步骤 7,重新发起登录验证码流程...',
|
||||
? `步骤 ${visibleStep}:认证页进入重试/超时报错状态,正在回到步骤 ${authLoginStep} 重新发起登录流程...`
|
||||
: `步骤 ${visibleStep}:正在回到步骤 ${authLoginStep},重新发起登录验证码流程...`,
|
||||
});
|
||||
currentState = await getState();
|
||||
}
|
||||
}
|
||||
|
||||
const visibleStep = getVisibleStep(currentState, 8);
|
||||
if (lastMailPollingError) {
|
||||
throw new Error(
|
||||
`步骤 8:登录验证码流程在 ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} 轮邮箱轮询恢复后仍未成功。最后一次原因:${lastMailPollingError.message}`
|
||||
`步骤 ${visibleStep}:登录验证码流程在 ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} 轮邮箱轮询恢复后仍未成功。最后一次原因:${lastMailPollingError.message}`
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error('步骤 8:登录验证码流程未成功完成。');
|
||||
throw new Error(`步骤 ${visibleStep}:登录验证码流程未成功完成。`);
|
||||
}
|
||||
|
||||
return { executeStep8 };
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
try {
|
||||
const currentState = attempt === 1 ? state : await getState();
|
||||
const password = currentState.password || currentState.customPassword || '';
|
||||
const oauthUrl = await refreshOAuthUrlBeforeStep6(currentState);
|
||||
const oauthUrl = await refreshOAuthUrlBeforeStep6(currentState, { visibleStep });
|
||||
if (typeof startOAuthFlowTimeoutWindow === 'function') {
|
||||
await startOAuthFlowTimeoutWindow({ step: visibleStep, oauthUrl });
|
||||
}
|
||||
@@ -105,9 +105,16 @@
|
||||
}
|
||||
|
||||
if (isStep6SuccessResult(result)) {
|
||||
await completeStepFromBackground(visibleStep, {
|
||||
const completionPayload = {
|
||||
loginVerificationRequestedAt: result.loginVerificationRequestedAt || null,
|
||||
});
|
||||
};
|
||||
if (result.skipLoginVerificationStep) {
|
||||
completionPayload.skipLoginVerificationStep = true;
|
||||
}
|
||||
if (result.directOAuthConsentPage) {
|
||||
completionPayload.directOAuthConsentPage = true;
|
||||
}
|
||||
await completeStepFromBackground(visibleStep, completionPayload);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,21 +32,25 @@
|
||||
}
|
||||
|
||||
function getConfirmStepForVisibleStep(visibleStep) {
|
||||
return visibleStep === 12 ? 11 : 9;
|
||||
return visibleStep >= 13 ? 12 : 9;
|
||||
}
|
||||
|
||||
function parseLocalhostCallback(rawUrl) {
|
||||
function getAuthLoginStepForVisibleStep(visibleStep) {
|
||||
return visibleStep >= 13 ? 10 : 7;
|
||||
}
|
||||
|
||||
function parseLocalhostCallback(rawUrl, visibleStep = 10, confirmStep = 9) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(rawUrl);
|
||||
} catch {
|
||||
throw new Error('步骤 10 捕获到的 localhost OAuth 回调地址格式无效,请重新执行步骤 9。');
|
||||
throw new Error(`步骤 ${visibleStep} 捕获到的 localhost OAuth 回调地址格式无效,请重新执行步骤 ${confirmStep}。`);
|
||||
}
|
||||
|
||||
const code = normalizeString(parsed.searchParams.get('code'));
|
||||
const state = normalizeString(parsed.searchParams.get('state'));
|
||||
if (!code || !state) {
|
||||
throw new Error('步骤 10 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 9。');
|
||||
throw new Error(`步骤 ${visibleStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmStep}。`);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -191,16 +195,16 @@
|
||||
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmStep}。`);
|
||||
}
|
||||
if (!state.codex2apiSessionId) {
|
||||
throw new Error(`缺少 Codex2API 会话信息,请重新执行步骤 ${visibleStep === 12 ? 10 : 7}。`);
|
||||
throw new Error(`缺少 Codex2API 会话信息,请重新执行步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`);
|
||||
}
|
||||
if (!normalizeString(state.codex2apiAdminKey)) {
|
||||
throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。');
|
||||
}
|
||||
|
||||
const callback = parseLocalhostCallback(state.localhostUrl);
|
||||
const callback = parseLocalhostCallback(state.localhostUrl, visibleStep, confirmStep);
|
||||
const expectedState = normalizeString(state.codex2apiOAuthState);
|
||||
if (expectedState && expectedState !== callback.state) {
|
||||
throw new Error('Codex2API 回调 state 与当前授权会话不匹配,请重新执行步骤 7。');
|
||||
throw new Error(`Codex2API 回调 state 与当前授权会话不匹配,请重新执行步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`);
|
||||
}
|
||||
|
||||
const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl);
|
||||
|
||||
@@ -175,25 +175,32 @@
|
||||
return Math.max(0, Math.floor(Number(VERIFICATION_POLL_MAX_ROUNDS) || 1) - 1);
|
||||
}
|
||||
|
||||
async function confirmCustomVerificationStepBypass(step) {
|
||||
function getCompletionStep(step, options = {}) {
|
||||
const completionStep = Number(options.completionStep);
|
||||
return Number.isFinite(completionStep) && completionStep > 0 ? completionStep : step;
|
||||
}
|
||||
|
||||
async function confirmCustomVerificationStepBypass(step, options = {}) {
|
||||
const completionStep = getCompletionStep(step, options);
|
||||
const promptStep = getCompletionStep(step, { completionStep: options.promptStep ?? completionStep });
|
||||
const verificationLabel = getVerificationCodeLabel(step);
|
||||
await addLog(`步骤 ${step}:当前为自定义邮箱模式,请手动在页面中输入${verificationLabel}验证码并进入下一页面。`, 'warn');
|
||||
await addLog(`步骤 ${completionStep}:当前为自定义邮箱模式,请手动在页面中输入${verificationLabel}验证码并进入下一页面。`, 'warn');
|
||||
|
||||
let response = null;
|
||||
try {
|
||||
response = await confirmCustomVerificationStepBypassRequest(step);
|
||||
response = await confirmCustomVerificationStepBypassRequest(promptStep);
|
||||
} catch {
|
||||
throw new Error(`步骤 ${step}:无法打开确认弹窗,请先保持侧边栏打开后重试。`);
|
||||
throw new Error(`步骤 ${completionStep}:无法打开确认弹窗,请先保持侧边栏打开后重试。`);
|
||||
}
|
||||
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (step === 8 && response?.addPhoneDetected) {
|
||||
throw new Error('步骤 8:验证码提交后页面进入手机号页面,当前流程无法继续自动授权。 URL: https://auth.openai.com/add-phone');
|
||||
throw new Error(`步骤 ${completionStep}:验证码提交后页面进入手机号页面,当前流程无法继续自动授权。 URL: https://auth.openai.com/add-phone`);
|
||||
}
|
||||
if (!response?.confirmed) {
|
||||
throw new Error(`步骤 ${step}:已取消手动${verificationLabel}验证码确认。`);
|
||||
throw new Error(`步骤 ${completionStep}:已取消手动${verificationLabel}验证码确认。`);
|
||||
}
|
||||
|
||||
await setState({
|
||||
@@ -201,8 +208,8 @@
|
||||
signupVerificationRequestedAt: null,
|
||||
loginVerificationRequestedAt: null,
|
||||
});
|
||||
await deps.setStepStatus(step, 'skipped');
|
||||
await addLog(`步骤 ${step}:已确认手动完成${verificationLabel}验证码输入,当前步骤已跳过。`, 'warn');
|
||||
await deps.setStepStatus(completionStep, 'skipped');
|
||||
await addLog(`步骤 ${completionStep}:已确认手动完成${verificationLabel}验证码输入,当前步骤已跳过。`, 'warn');
|
||||
}
|
||||
|
||||
function getVerificationPollPayload(step, state, overrides = {}) {
|
||||
@@ -781,6 +788,7 @@
|
||||
}
|
||||
|
||||
async function resolveVerificationStep(step, state, mail, options = {}) {
|
||||
const completionStep = getCompletionStep(step, options);
|
||||
const stateKey = getVerificationCodeStateKey(step);
|
||||
const rejectedCodes = new Set();
|
||||
const hotmailPollConfig = mail.provider === HOTMAIL_PROVIDER
|
||||
@@ -918,7 +926,7 @@
|
||||
[stateKey]: result.code,
|
||||
});
|
||||
|
||||
await completeStepFromBackground(step, {
|
||||
await completeStepFromBackground(completionStep, {
|
||||
emailTimestamp: result.emailTimestamp,
|
||||
code: result.code,
|
||||
phoneVerificationRequired: Boolean(submitResult.addPhonePage),
|
||||
|
||||
+204
-274
@@ -1803,6 +1803,13 @@ function inspectLoginAuthState() {
|
||||
};
|
||||
}
|
||||
|
||||
if (consentReady) {
|
||||
return {
|
||||
...baseState,
|
||||
state: 'oauth_consent_page',
|
||||
};
|
||||
}
|
||||
|
||||
return baseState;
|
||||
}
|
||||
|
||||
@@ -1830,7 +1837,7 @@ function serializeLoginAuthState(snapshot) {
|
||||
}
|
||||
|
||||
function getLoginAuthStateLabel(snapshot) {
|
||||
const state = snapshot?.state === 'oauth_consent_page' ? 'unknown' : snapshot?.state;
|
||||
const state = snapshot?.state;
|
||||
switch (state) {
|
||||
case 'verification_page':
|
||||
return '登录验证码页';
|
||||
@@ -1887,13 +1894,32 @@ async function waitForLoginVerificationPageReady(timeout = 10000) {
|
||||
}
|
||||
|
||||
function createStep6SuccessResult(snapshot, options = {}) {
|
||||
return {
|
||||
const result = {
|
||||
step6Outcome: 'success',
|
||||
state: snapshot?.state || 'verification_page',
|
||||
url: snapshot?.url || location.href,
|
||||
via: options.via || '',
|
||||
loginVerificationRequestedAt: options.loginVerificationRequestedAt || null,
|
||||
};
|
||||
|
||||
if (options.skipLoginVerificationStep) {
|
||||
result.skipLoginVerificationStep = true;
|
||||
}
|
||||
if (options.directOAuthConsentPage) {
|
||||
result.directOAuthConsentPage = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function createStep6OAuthConsentSuccessResult(snapshot, options = {}) {
|
||||
return createStep6SuccessResult(snapshot, {
|
||||
...options,
|
||||
via: options.via || 'oauth_consent_page',
|
||||
loginVerificationRequestedAt: null,
|
||||
skipLoginVerificationStep: true,
|
||||
directOAuthConsentPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
function createStep6RecoverableResult(reason, snapshot, options = {}) {
|
||||
@@ -1948,6 +1974,15 @@ async function createStep6LoginTimeoutRecoveryTransition(reason, snapshot, messa
|
||||
};
|
||||
}
|
||||
|
||||
if (resolvedSnapshot.state === 'oauth_consent_page') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: createStep6OAuthConsentSuccessResult(resolvedSnapshot, {
|
||||
via,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (resolvedSnapshot.state === 'password_page') {
|
||||
log('步骤 7:登录超时报错页恢复后已进入密码页,继续当前登录流程。', 'warn');
|
||||
return { action: 'password', snapshot: resolvedSnapshot };
|
||||
@@ -2007,6 +2042,13 @@ async function finalizeStep6VerificationReady(options = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
log(`${logLabel}:认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。`, 'ok');
|
||||
return createStep6OAuthConsentSuccessResult(snapshot, {
|
||||
via: `${via}_oauth_consent`,
|
||||
});
|
||||
}
|
||||
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
log(`${logLabel}:页面进入登录超时报错页,准备自动恢复后重试步骤 7。`, 'warn');
|
||||
return createStep6LoginTimeoutRecoverableResult(
|
||||
@@ -2037,6 +2079,12 @@ async function finalizeStep6VerificationReady(options = {}) {
|
||||
loginVerificationRequestedAt,
|
||||
});
|
||||
}
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
log(`${logLabel}:认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。`, 'ok');
|
||||
return createStep6OAuthConsentSuccessResult(snapshot, {
|
||||
via: `${via}_oauth_consent`,
|
||||
});
|
||||
}
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
log(`${logLabel}:页面进入登录超时报错页,准备自动恢复后重试步骤 7。`, 'warn');
|
||||
return createStep6LoginTimeoutRecoverableResult(
|
||||
@@ -2059,21 +2107,14 @@ async function finalizeStep6VerificationReady(options = {}) {
|
||||
}
|
||||
|
||||
function normalizeStep6Snapshot(snapshot) {
|
||||
if (snapshot?.state !== 'oauth_consent_page') {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
return {
|
||||
...snapshot,
|
||||
state: 'unknown',
|
||||
};
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
function throwForStep6FatalState(snapshot) {
|
||||
snapshot = normalizeStep6Snapshot(snapshot);
|
||||
switch (snapshot?.state) {
|
||||
case 'oauth_consent_page':
|
||||
throw new Error(`当前页面已进入 OAuth 授权页,未经过登录验证码页,无法完成步骤 7。URL: ${snapshot.url}`);
|
||||
return;
|
||||
case 'add_phone_page':
|
||||
throw new Error(`当前页面已进入手机号页面,未经过登录验证码页,无法完成步骤 7。URL: ${snapshot.url}`);
|
||||
case 'unknown':
|
||||
@@ -2595,88 +2636,55 @@ async function fillVerificationCode(step, payload) {
|
||||
// Step 7: Login with registered account (on OAuth auth page)
|
||||
// ============================================================
|
||||
|
||||
async function waitForStep6EmailSubmitTransition(emailSubmittedAt, timeout = 12000) {
|
||||
const start = Date.now();
|
||||
let snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
function getStep6OptionMessage(value, snapshot) {
|
||||
return typeof value === 'function' ? value(snapshot) : String(value || '');
|
||||
}
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
throwIfStopped();
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
async function resolveStep6PostSubmitSnapshot(snapshot, options = {}) {
|
||||
const normalizedSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
|
||||
const {
|
||||
via = 'post_submit',
|
||||
loginVerificationRequestedAt = null,
|
||||
oauthConsentVia = `${via}_oauth_consent`,
|
||||
timeoutRecoveryReason = 'login_timeout_error_page',
|
||||
timeoutRecoveryMessage = '登录提交后进入登录超时报错页。',
|
||||
timeoutRecoveryVia = `${via}_timeout_recovered`,
|
||||
allowPasswordAction = false,
|
||||
allowEmailAction = false,
|
||||
allowFinalPasswordAction = false,
|
||||
allowFinalEmailAction = false,
|
||||
allowFinalSwitchAction = false,
|
||||
final = false,
|
||||
addPhoneMessage,
|
||||
} = options;
|
||||
|
||||
if (snapshot.state === 'verification_page') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: createStep6SuccessResult(snapshot, {
|
||||
via: 'email_submit',
|
||||
loginVerificationRequestedAt: emailSubmittedAt,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (snapshot.state === 'password_page') {
|
||||
return { action: 'password', snapshot };
|
||||
}
|
||||
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
const transition = await createStep6LoginTimeoutRecoveryTransition(
|
||||
'login_timeout_error_page',
|
||||
snapshot,
|
||||
'提交邮箱后进入登录超时报错页。',
|
||||
{
|
||||
loginVerificationRequestedAt: emailSubmittedAt,
|
||||
via: 'email_submit_timeout_recovered',
|
||||
}
|
||||
);
|
||||
if (transition.action === 'done') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: transition.result,
|
||||
};
|
||||
}
|
||||
if (transition.action === 'password') {
|
||||
return { action: 'password', snapshot: transition.snapshot };
|
||||
}
|
||||
if (transition.action === 'email') {
|
||||
return { action: 'email', snapshot: transition.snapshot };
|
||||
}
|
||||
return {
|
||||
action: 'recoverable',
|
||||
result: transition.result,
|
||||
};
|
||||
}
|
||||
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
throw new Error(`提交邮箱后页面直接进入 OAuth 授权页,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
|
||||
if (snapshot.state === 'add_phone_page') {
|
||||
throw new Error(`提交邮箱后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
|
||||
await sleep(250);
|
||||
}
|
||||
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
if (snapshot.state === 'verification_page') {
|
||||
if (normalizedSnapshot.state === 'verification_page') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: createStep6SuccessResult(snapshot, {
|
||||
via: 'email_submit',
|
||||
loginVerificationRequestedAt: emailSubmittedAt,
|
||||
result: createStep6SuccessResult(normalizedSnapshot, {
|
||||
via,
|
||||
loginVerificationRequestedAt,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (snapshot.state === 'password_page') {
|
||||
return { action: 'password', snapshot };
|
||||
|
||||
if (normalizedSnapshot.state === 'oauth_consent_page') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: createStep6OAuthConsentSuccessResult(normalizedSnapshot, {
|
||||
via: oauthConsentVia,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
|
||||
if (normalizedSnapshot.state === 'login_timeout_error_page') {
|
||||
const transition = await createStep6LoginTimeoutRecoveryTransition(
|
||||
'login_timeout_error_page',
|
||||
snapshot,
|
||||
'提交邮箱后进入登录超时报错页。',
|
||||
timeoutRecoveryReason,
|
||||
normalizedSnapshot,
|
||||
timeoutRecoveryMessage,
|
||||
{
|
||||
loginVerificationRequestedAt: emailSubmittedAt,
|
||||
via: 'email_submit_timeout_recovered',
|
||||
loginVerificationRequestedAt,
|
||||
via: timeoutRecoveryVia,
|
||||
}
|
||||
);
|
||||
if (transition.action === 'done') {
|
||||
@@ -2696,213 +2704,116 @@ async function waitForStep6EmailSubmitTransition(emailSubmittedAt, timeout = 120
|
||||
result: transition.result,
|
||||
};
|
||||
}
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
throw new Error(`提交邮箱后页面直接进入 OAuth 授权页,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
|
||||
if (normalizedSnapshot.state === 'password_page') {
|
||||
if (allowPasswordAction || (final && allowFinalPasswordAction)) {
|
||||
return { action: 'password', snapshot: normalizedSnapshot };
|
||||
}
|
||||
if (final && allowFinalSwitchAction && normalizedSnapshot.switchTrigger) {
|
||||
return { action: 'switch', snapshot: normalizedSnapshot };
|
||||
}
|
||||
}
|
||||
if (snapshot.state === 'add_phone_page') {
|
||||
throw new Error(`提交邮箱后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
|
||||
if (normalizedSnapshot.state === 'email_page' && (allowEmailAction || (final && allowFinalEmailAction))) {
|
||||
return { action: 'email', snapshot: normalizedSnapshot };
|
||||
}
|
||||
|
||||
if (normalizedSnapshot.state === 'add_phone_page') {
|
||||
const message = getStep6OptionMessage(addPhoneMessage, normalizedSnapshot)
|
||||
|| `登录提交后页面进入手机号页面。URL: ${normalizedSnapshot.url || location.href}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function waitForStep6PostSubmitTransition(options = {}) {
|
||||
const {
|
||||
timeout = 10000,
|
||||
stalledReason = 'post_submit_stalled',
|
||||
stalledMessage = '登录提交后未进入可识别的下一页。',
|
||||
} = options;
|
||||
const start = Date.now();
|
||||
let snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
throwIfStopped();
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
const transition = await resolveStep6PostSubmitSnapshot(snapshot, {
|
||||
...options,
|
||||
final: false,
|
||||
});
|
||||
if (transition) {
|
||||
return transition;
|
||||
}
|
||||
await sleep(250);
|
||||
}
|
||||
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
const transition = await resolveStep6PostSubmitSnapshot(snapshot, {
|
||||
...options,
|
||||
final: true,
|
||||
});
|
||||
if (transition) {
|
||||
return transition;
|
||||
}
|
||||
|
||||
return {
|
||||
action: 'recoverable',
|
||||
result: createStep6RecoverableResult('email_submit_stalled', snapshot, {
|
||||
message: '提交邮箱后长时间未进入密码页或登录验证码页。',
|
||||
result: createStep6RecoverableResult(stalledReason, snapshot, {
|
||||
message: stalledMessage,
|
||||
loginVerificationRequestedAt: options.loginVerificationRequestedAt || null,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForStep6EmailSubmitTransition(emailSubmittedAt, timeout = 12000) {
|
||||
return waitForStep6PostSubmitTransition({
|
||||
timeout,
|
||||
via: 'email_submit',
|
||||
oauthConsentVia: 'email_submit_oauth_consent',
|
||||
loginVerificationRequestedAt: emailSubmittedAt,
|
||||
timeoutRecoveryMessage: '提交邮箱后进入登录超时报错页。',
|
||||
timeoutRecoveryVia: 'email_submit_timeout_recovered',
|
||||
allowPasswordAction: true,
|
||||
stalledReason: 'email_submit_stalled',
|
||||
stalledMessage: '提交邮箱后长时间未进入密码页或登录验证码页。',
|
||||
addPhoneMessage: (snapshot) => `提交邮箱后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`,
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForStep6PasswordSubmitTransition(passwordSubmittedAt, timeout = 10000) {
|
||||
const start = Date.now();
|
||||
let snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
throwIfStopped();
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
|
||||
if (snapshot.state === 'verification_page') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: createStep6SuccessResult(snapshot, {
|
||||
via: 'password_submit',
|
||||
loginVerificationRequestedAt: passwordSubmittedAt,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
const transition = await createStep6LoginTimeoutRecoveryTransition(
|
||||
'login_timeout_error_page',
|
||||
snapshot,
|
||||
'提交密码后进入登录超时报错页。',
|
||||
{
|
||||
loginVerificationRequestedAt: passwordSubmittedAt,
|
||||
via: 'password_submit_timeout_recovered',
|
||||
}
|
||||
);
|
||||
if (transition.action === 'done') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: transition.result,
|
||||
};
|
||||
}
|
||||
if (transition.action === 'password') {
|
||||
return { action: 'password', snapshot: transition.snapshot };
|
||||
}
|
||||
if (transition.action === 'email') {
|
||||
return { action: 'email', snapshot: transition.snapshot };
|
||||
}
|
||||
return {
|
||||
action: 'recoverable',
|
||||
result: transition.result,
|
||||
};
|
||||
}
|
||||
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
throw new Error(`提交密码后页面直接进入 OAuth 授权页,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
|
||||
if (snapshot.state === 'add_phone_page') {
|
||||
throw new Error(`提交密码后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
|
||||
await sleep(250);
|
||||
}
|
||||
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
if (snapshot.state === 'verification_page') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: createStep6SuccessResult(snapshot, {
|
||||
via: 'password_submit',
|
||||
loginVerificationRequestedAt: passwordSubmittedAt,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
const transition = await createStep6LoginTimeoutRecoveryTransition(
|
||||
'login_timeout_error_page',
|
||||
snapshot,
|
||||
'提交密码后进入登录超时报错页。',
|
||||
{
|
||||
loginVerificationRequestedAt: passwordSubmittedAt,
|
||||
via: 'password_submit_timeout_recovered',
|
||||
}
|
||||
);
|
||||
if (transition.action === 'done') {
|
||||
return {
|
||||
action: 'done',
|
||||
result: transition.result,
|
||||
};
|
||||
}
|
||||
if (transition.action === 'password') {
|
||||
return { action: 'password', snapshot: transition.snapshot };
|
||||
}
|
||||
if (transition.action === 'email') {
|
||||
return { action: 'email', snapshot: transition.snapshot };
|
||||
}
|
||||
return {
|
||||
action: 'recoverable',
|
||||
result: transition.result,
|
||||
};
|
||||
}
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
throw new Error(`提交密码后页面直接进入 OAuth 授权页,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
if (snapshot.state === 'add_phone_page') {
|
||||
throw new Error(`提交密码后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
if (snapshot.state === 'password_page' && snapshot.switchTrigger) {
|
||||
return { action: 'switch', snapshot };
|
||||
}
|
||||
|
||||
return {
|
||||
action: 'recoverable',
|
||||
result: createStep6RecoverableResult('password_submit_stalled', snapshot, {
|
||||
message: '提交密码后仍未进入登录验证码页。',
|
||||
}),
|
||||
};
|
||||
return waitForStep6PostSubmitTransition({
|
||||
timeout,
|
||||
via: 'password_submit',
|
||||
oauthConsentVia: 'password_submit_oauth_consent',
|
||||
loginVerificationRequestedAt: passwordSubmittedAt,
|
||||
timeoutRecoveryMessage: '提交密码后进入登录超时报错页。',
|
||||
timeoutRecoveryVia: 'password_submit_timeout_recovered',
|
||||
allowFinalSwitchAction: true,
|
||||
stalledReason: 'password_submit_stalled',
|
||||
stalledMessage: '提交密码后仍未进入登录验证码页。',
|
||||
addPhoneMessage: (snapshot) => `提交密码后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`,
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForStep6SwitchTransition(loginVerificationRequestedAt, timeout = 10000) {
|
||||
const start = Date.now();
|
||||
let snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
const transition = await waitForStep6PostSubmitTransition({
|
||||
timeout,
|
||||
via: 'switch_to_one_time_code_login',
|
||||
oauthConsentVia: 'switch_to_one_time_code_oauth_consent',
|
||||
loginVerificationRequestedAt,
|
||||
timeoutRecoveryMessage: '切换到一次性验证码登录后进入登录超时报错页。',
|
||||
timeoutRecoveryVia: 'switch_to_one_time_code_timeout_recovered',
|
||||
stalledReason: 'one_time_code_switch_stalled',
|
||||
stalledMessage: '点击一次性验证码登录后仍未进入登录验证码页。',
|
||||
addPhoneMessage: (snapshot) => `切换到一次性验证码登录后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`,
|
||||
});
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
throwIfStopped();
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
|
||||
if (snapshot.state === 'verification_page') {
|
||||
return createStep6SuccessResult(snapshot, {
|
||||
via: 'switch_to_one_time_code_login',
|
||||
loginVerificationRequestedAt,
|
||||
});
|
||||
}
|
||||
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
const transition = await createStep6LoginTimeoutRecoveryTransition(
|
||||
'login_timeout_error_page',
|
||||
snapshot,
|
||||
'切换到一次性验证码登录后进入登录超时报错页。',
|
||||
{
|
||||
loginVerificationRequestedAt,
|
||||
via: 'switch_to_one_time_code_timeout_recovered',
|
||||
}
|
||||
);
|
||||
if (transition.action === 'done') {
|
||||
return transition.result;
|
||||
}
|
||||
if (transition.action === 'password' || transition.action === 'email') {
|
||||
return transition;
|
||||
}
|
||||
return transition.result;
|
||||
}
|
||||
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
throw new Error(`切换到一次性验证码登录后页面直接进入 OAuth 授权页,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
|
||||
if (snapshot.state === 'add_phone_page') {
|
||||
throw new Error(`切换到一次性验证码登录后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
|
||||
await sleep(250);
|
||||
}
|
||||
|
||||
snapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
if (snapshot.state === 'verification_page') {
|
||||
return createStep6SuccessResult(snapshot, {
|
||||
via: 'switch_to_one_time_code_login',
|
||||
loginVerificationRequestedAt,
|
||||
});
|
||||
}
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
const transition = await createStep6LoginTimeoutRecoveryTransition(
|
||||
'login_timeout_error_page',
|
||||
snapshot,
|
||||
'切换到一次性验证码登录后进入登录超时报错页。',
|
||||
{
|
||||
loginVerificationRequestedAt,
|
||||
via: 'switch_to_one_time_code_timeout_recovered',
|
||||
}
|
||||
);
|
||||
if (transition.action === 'done') {
|
||||
return transition.result;
|
||||
}
|
||||
if (transition.action === 'password' || transition.action === 'email') {
|
||||
return transition;
|
||||
}
|
||||
if (transition.action === 'done' || transition.action === 'recoverable') {
|
||||
return transition.result;
|
||||
}
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
throw new Error(`切换到一次性验证码登录后页面直接进入 OAuth 授权页,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
if (snapshot.state === 'add_phone_page') {
|
||||
throw new Error(`切换到一次性验证码登录后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`);
|
||||
}
|
||||
|
||||
return createStep6RecoverableResult('one_time_code_switch_stalled', snapshot, {
|
||||
message: '点击一次性验证码登录后仍未进入登录验证码页。',
|
||||
});
|
||||
return transition;
|
||||
}
|
||||
|
||||
async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
|
||||
@@ -2921,6 +2832,9 @@ async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
|
||||
await sleep(1200);
|
||||
const result = await waitForStep6SwitchTransition(loginVerificationRequestedAt);
|
||||
if (result?.step6Outcome === 'success') {
|
||||
if (result.skipLoginVerificationStep) {
|
||||
return result;
|
||||
}
|
||||
return finalizeStep6VerificationReady({
|
||||
logLabel: '步骤 7 收尾',
|
||||
loginVerificationRequestedAt: result.loginVerificationRequestedAt || loginVerificationRequestedAt,
|
||||
@@ -2964,6 +2878,9 @@ async function step6LoginFromPasswordPage(payload, snapshot) {
|
||||
|
||||
const transition = await waitForStep6PasswordSubmitTransition(passwordSubmittedAt);
|
||||
if (transition.action === 'done') {
|
||||
if (transition.result?.skipLoginVerificationStep) {
|
||||
return transition.result;
|
||||
}
|
||||
return finalizeStep6VerificationReady({
|
||||
logLabel: '步骤 7 收尾',
|
||||
loginVerificationRequestedAt: transition.result.loginVerificationRequestedAt || passwordSubmittedAt,
|
||||
@@ -3020,6 +2937,9 @@ async function step6LoginFromEmailPage(payload, snapshot) {
|
||||
|
||||
const transition = await waitForStep6EmailSubmitTransition(emailSubmittedAt);
|
||||
if (transition.action === 'done') {
|
||||
if (transition.result?.skipLoginVerificationStep) {
|
||||
return transition.result;
|
||||
}
|
||||
return finalizeStep6VerificationReady({
|
||||
logLabel: '步骤 7 收尾',
|
||||
loginVerificationRequestedAt: transition.result.loginVerificationRequestedAt || emailSubmittedAt,
|
||||
@@ -3057,6 +2977,13 @@ async function step6_login(payload) {
|
||||
});
|
||||
}
|
||||
|
||||
if (snapshot.state === 'oauth_consent_page') {
|
||||
log('步骤 7:认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。', 'ok');
|
||||
return createStep6OAuthConsentSuccessResult(snapshot, {
|
||||
via: 'already_on_oauth_consent_page',
|
||||
});
|
||||
}
|
||||
|
||||
if (snapshot.state === 'login_timeout_error_page') {
|
||||
log('步骤 7:检测到登录超时报错页,先尝试恢复当前页面。', 'warn');
|
||||
const transition = await createStep6LoginTimeoutRecoveryTransition(
|
||||
@@ -3069,6 +2996,9 @@ async function step6_login(payload) {
|
||||
}
|
||||
);
|
||||
if (transition.action === 'done') {
|
||||
if (transition.result?.skipLoginVerificationStep) {
|
||||
return transition.result;
|
||||
}
|
||||
return finalizeStep6VerificationReady({
|
||||
logLabel: '步骤 7 收尾',
|
||||
loginVerificationRequestedAt: transition.result.loginVerificationRequestedAt || null,
|
||||
|
||||
@@ -1012,7 +1012,7 @@ async function step1_getOAuthLink(payload, options = {}) {
|
||||
|
||||
async function step9_vpsVerify(payload) {
|
||||
const visibleStep = Number(payload?.visibleStep) || 10;
|
||||
const confirmStep = visibleStep === 12 ? 11 : 9;
|
||||
const confirmStep = visibleStep >= 13 ? 12 : 9;
|
||||
await ensureOAuthManagementPage(payload?.vpsPassword, confirmStep);
|
||||
|
||||
// 优先从 payload 读取 localhostUrl;没有时再回退到全局状态
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
{ id: 8, order: 80, key: 'paypal-approve', title: 'PayPal 登录与授权' },
|
||||
{ id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认' },
|
||||
{ id: 10, order: 100, key: 'oauth-login', title: '刷新 OAuth 并登录' },
|
||||
{ id: 11, order: 110, key: 'confirm-oauth', title: '自动确认 OAuth' },
|
||||
{ id: 12, order: 120, key: 'platform-verify', title: '平台回调验证' },
|
||||
{ id: 11, order: 110, key: 'fetch-login-code', title: '获取登录验证码' },
|
||||
{ id: 12, order: 120, key: 'confirm-oauth', title: '自动确认 OAuth' },
|
||||
{ id: 13, order: 130, key: 'platform-verify', title: '平台回调验证' },
|
||||
];
|
||||
|
||||
function isPlusModeEnabled(options = {}) {
|
||||
|
||||
@@ -2965,6 +2965,7 @@ function getCustomMailProviderUiCopy() {
|
||||
|
||||
function getCustomVerificationPromptCopy(step) {
|
||||
const verificationLabel = step === 4 ? '注册验证码' : '登录验证码';
|
||||
const isLoginVerificationStep = step === 8 || step === 11;
|
||||
return {
|
||||
title: `手动处理${verificationLabel}`,
|
||||
message: `当前邮箱服务为“自定义邮箱”。请先在页面中手动输入${verificationLabel},并确认已经进入下一页面后,再点击确认。`,
|
||||
@@ -2972,7 +2973,7 @@ function getCustomVerificationPromptCopy(step) {
|
||||
text: `点击确认后会跳过步骤 ${step}。`,
|
||||
tone: 'danger',
|
||||
},
|
||||
...(step === 8 ? {
|
||||
...(isLoginVerificationStep ? {
|
||||
phoneActionLabel: '出现手机号验证',
|
||||
phoneActionAlert: {
|
||||
text: '如果当前页面已经进入手机号验证,可直接标记为失败并继续下一个邮箱。',
|
||||
@@ -2984,7 +2985,7 @@ function getCustomVerificationPromptCopy(step) {
|
||||
|
||||
async function openCustomVerificationConfirmDialog(step) {
|
||||
const promptCopy = getCustomVerificationPromptCopy(step);
|
||||
if (step === 8) {
|
||||
if (step === 8 || step === 11) {
|
||||
return openActionModal({
|
||||
title: promptCopy.title,
|
||||
message: promptCopy.message,
|
||||
|
||||
@@ -44,4 +44,5 @@ test('logging/status add-phone detection ignores step 2 phone-entry switch failu
|
||||
true
|
||||
);
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('phone_verification_page'), '手机验证码页');
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('oauth_consent_page'), 'OAuth 授权页');
|
||||
});
|
||||
|
||||
@@ -59,6 +59,8 @@ function createRouter(overrides = {}) {
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getSourceLabel: () => '',
|
||||
getState: async () => overrides.state || { stepStatuses: { 3: 'pending' } },
|
||||
getStepDefinitionForState: overrides.getStepDefinitionForState,
|
||||
getStepIdsForState: overrides.getStepIdsForState,
|
||||
getTabId: overrides.getTabId || (async () => null),
|
||||
getStopRequested: () => false,
|
||||
handleAutoRunLoopUnhandledError: async () => {},
|
||||
@@ -184,6 +186,49 @@ test('message router skips step 5 when step 4 reports already logged-in transiti
|
||||
assert.equal(events.logs[0]?.message, '步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。');
|
||||
});
|
||||
|
||||
test('message router skips login-code step when oauth login lands on consent page', async () => {
|
||||
const stepKeys = {
|
||||
7: 'oauth-login',
|
||||
8: 'fetch-login-code',
|
||||
9: 'confirm-oauth',
|
||||
};
|
||||
const { router, events } = createRouter({
|
||||
state: { stepStatuses: { 7: 'completed', 8: 'pending', 9: 'pending' } },
|
||||
getStepDefinitionForState: (step) => ({ id: step, key: stepKeys[step] || '' }),
|
||||
getStepIdsForState: () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
});
|
||||
|
||||
await router.handleStepData(7, {
|
||||
skipLoginVerificationStep: true,
|
||||
directOAuthConsentPage: true,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(events.stepStatuses, [{ step: 8, status: 'skipped' }]);
|
||||
assert.equal(events.logs.some(({ message }) => /OAuth 授权页.*步骤 8/.test(message)), true);
|
||||
});
|
||||
|
||||
test('message router skips Plus login-code step when oauth login lands on consent page', async () => {
|
||||
const stepKeys = {
|
||||
10: 'oauth-login',
|
||||
11: 'fetch-login-code',
|
||||
12: 'confirm-oauth',
|
||||
13: 'platform-verify',
|
||||
};
|
||||
const { router, events } = createRouter({
|
||||
state: { plusModeEnabled: true, stepStatuses: { 10: 'completed', 11: 'pending', 12: 'pending', 13: 'pending' } },
|
||||
getStepDefinitionForState: (step) => ({ id: step, key: stepKeys[step] || '' }),
|
||||
getStepIdsForState: () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
|
||||
});
|
||||
|
||||
await router.handleStepData(10, {
|
||||
skipLoginVerificationStep: true,
|
||||
directOAuthConsentPage: true,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(events.stepStatuses, [{ step: 11, status: 'skipped' }]);
|
||||
assert.equal(events.logs.some(({ message }) => /OAuth 授权页.*步骤 11/.test(message)), true);
|
||||
});
|
||||
|
||||
test('message router finalizes step 3 before marking it completed', async () => {
|
||||
const { router, events } = createRouter();
|
||||
|
||||
|
||||
@@ -178,6 +178,55 @@ test('step 7 starts a new oauth timeout window for each refreshed oauth url', as
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 7 forwards direct OAuth consent skip metadata when completing', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
const events = {
|
||||
completions: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep7Executor({
|
||||
addLog: async () => {},
|
||||
completeStepFromBackground: async (step, payload) => {
|
||||
events.completions.push({ step, payload });
|
||||
},
|
||||
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||
getLoginAuthStateLabel: (state) => state || 'unknown',
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
isStep6RecoverableResult: (result) => result?.step6Outcome === 'recoverable',
|
||||
isStep6SuccessResult: (result) => result?.step6Outcome === 'success',
|
||||
refreshOAuthUrlBeforeStep6: async () => 'https://oauth.example/latest',
|
||||
reuseOrCreateTab: async () => {},
|
||||
sendToContentScriptResilient: async () => ({
|
||||
step6Outcome: 'success',
|
||||
state: 'oauth_consent_page',
|
||||
skipLoginVerificationStep: true,
|
||||
directOAuthConsentPage: true,
|
||||
}),
|
||||
STEP6_MAX_ATTEMPTS: 3,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep7({
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
visibleStep: 10,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(events.completions, [
|
||||
{
|
||||
step: 10,
|
||||
payload: {
|
||||
loginVerificationRequestedAt: null,
|
||||
skipLoginVerificationStep: true,
|
||||
directOAuthConsentPage: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 7 stops immediately when management secret is missing', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
|
||||
@@ -85,8 +85,78 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
{ step8VerificationTargetEmail: 'display.user@example.com' },
|
||||
]);
|
||||
assert.deepStrictEqual(calls.ensureReadyOptions, [
|
||||
{ timeoutMs: 5000 },
|
||||
{ visibleStep: 8, authLoginStep: 7, timeoutMs: 5000 },
|
||||
]);
|
||||
assert.equal(calls.resolveOptions.completionStep, 8);
|
||||
});
|
||||
|
||||
test('Plus login-code step reuses step 8 verification logic but completes visible step 11', async () => {
|
||||
let resolvedStep = null;
|
||||
let resolvedOptions = null;
|
||||
let readyOptions = null;
|
||||
const remainingStepCalls = [];
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async (options) => {
|
||||
readyOptions = options;
|
||||
return { state: 'verification_page', displayedEmail: 'plus.user@example.com' };
|
||||
},
|
||||
rerunStep7ForStep8Recovery: async () => {},
|
||||
getOAuthFlowRemainingMs: async (details) => {
|
||||
remainingStepCalls.push(details.step);
|
||||
return 9000;
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs, details) => {
|
||||
remainingStepCalls.push(details.step);
|
||||
return Math.min(defaultTimeoutMs, 9000);
|
||||
},
|
||||
getMailConfig: () => ({
|
||||
provider: 'qq',
|
||||
label: 'QQ 邮箱',
|
||||
source: 'mail-qq',
|
||||
url: 'https://mail.qq.com',
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret', plusModeEnabled: true }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
resolveVerificationStep: async (step, _state, _mail, options) => {
|
||||
resolvedStep = step;
|
||||
resolvedOptions = options;
|
||||
await options.getRemainingTimeMs({ actionLabel: '登录验证码流程' });
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
setState: async () => {},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep8({
|
||||
visibleStep: 11,
|
||||
plusModeEnabled: true,
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(resolvedStep, 8);
|
||||
assert.equal(resolvedOptions.completionStep, 11);
|
||||
assert.equal(resolvedOptions.targetEmail, 'plus.user@example.com');
|
||||
assert.deepStrictEqual(readyOptions, { visibleStep: 11, authLoginStep: 10, timeoutMs: 9000 });
|
||||
assert.deepStrictEqual(remainingStepCalls, [11, 11]);
|
||||
});
|
||||
|
||||
test('step 8 uses a fixed 10-minute lookback window and disables resend interval for 2925 mailbox polling', async () => {
|
||||
|
||||
@@ -216,3 +216,43 @@ return {
|
||||
assert.equal(modalPayload.actions[1].id, 'add_phone');
|
||||
assert.equal(modalPayload.actions[1].label, '出现手机号验证');
|
||||
});
|
||||
|
||||
test('sidepanel custom verification dialog exposes add-phone action for Plus login code step', async () => {
|
||||
const bundle = [
|
||||
extractFunction('getCustomVerificationPromptCopy'),
|
||||
extractFunction('openCustomVerificationConfirmDialog'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let openActionModalPayload = null;
|
||||
|
||||
async function openActionModal(options) {
|
||||
openActionModalPayload = options;
|
||||
return options.buildResult('add_phone');
|
||||
}
|
||||
|
||||
async function openConfirmModal() {
|
||||
throw new Error('Plus login code step should use action modal');
|
||||
}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
getCustomVerificationPromptCopy,
|
||||
openCustomVerificationConfirmDialog,
|
||||
getOpenActionModalPayload: () => openActionModalPayload,
|
||||
};
|
||||
`)();
|
||||
|
||||
const prompt = api.getCustomVerificationPromptCopy(11);
|
||||
assert.equal(prompt.phoneActionLabel, '出现手机号验证');
|
||||
|
||||
const result = await api.openCustomVerificationConfirmDialog(11);
|
||||
assert.deepEqual(result, {
|
||||
confirmed: false,
|
||||
addPhoneDetected: true,
|
||||
});
|
||||
|
||||
const modalPayload = api.getOpenActionModalPayload();
|
||||
assert.equal(modalPayload.actions[1].id, 'add_phone');
|
||||
});
|
||||
|
||||
@@ -44,14 +44,15 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
);
|
||||
assert.equal(plusSteps.some((step) => step.key === 'clear-login-cookies'), false);
|
||||
assert.equal(plusSteps.some((step) => step.key === 'fetch-login-code'), false);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true }), 12);
|
||||
assert.equal(plusSteps.some((step) => step.key === 'fetch-login-code'), true);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true }), 13);
|
||||
});
|
||||
|
||||
test('sidepanel html loads shared step definitions before sidepanel bootstrap', () => {
|
||||
|
||||
@@ -215,17 +215,20 @@ return {
|
||||
consentReady: true,
|
||||
});
|
||||
|
||||
const inspected = api.inspectLoginAuthState();
|
||||
assert.strictEqual(inspected.state, 'oauth_consent_page');
|
||||
|
||||
const snapshot = api.normalizeStep6Snapshot({
|
||||
state: 'oauth_consent_page',
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
});
|
||||
|
||||
assert.strictEqual(snapshot.state, 'unknown', '第六步应忽略 oauth_consent_page 状态');
|
||||
assert.strictEqual(snapshot.state, 'oauth_consent_page', '第六步应保留 oauth_consent_page 状态');
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
!extractFunction('inspectLoginAuthState').includes("state: 'oauth_consent_page'"),
|
||||
'inspectLoginAuthState 不应再产出 oauth_consent_page 状态'
|
||||
extractFunction('inspectLoginAuthState').includes("state: 'oauth_consent_page'"),
|
||||
'inspectLoginAuthState 应产出 oauth_consent_page 状态'
|
||||
);
|
||||
|
||||
console.log('step6 login state tests passed');
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
const start = markers
|
||||
.map((marker) => source.indexOf(marker))
|
||||
.find((index) => index >= 0);
|
||||
if (start < 0) {
|
||||
throw new Error(`missing function ${name}`);
|
||||
}
|
||||
|
||||
let parenDepth = 0;
|
||||
let signatureEnded = false;
|
||||
let braceStart = -1;
|
||||
for (let index = start; index < source.length; index += 1) {
|
||||
const char = source[index];
|
||||
if (char === '(') {
|
||||
parenDepth += 1;
|
||||
} else if (char === ')') {
|
||||
parenDepth -= 1;
|
||||
if (parenDepth === 0) {
|
||||
signatureEnded = true;
|
||||
}
|
||||
} else if (char === '{' && signatureEnded) {
|
||||
braceStart = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (braceStart < 0) {
|
||||
throw new Error(`missing body for function ${name}`);
|
||||
}
|
||||
|
||||
let depth = 0;
|
||||
let end = braceStart;
|
||||
for (; end < source.length; end += 1) {
|
||||
const char = source[end];
|
||||
if (char === '{') depth += 1;
|
||||
if (char === '}') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
end += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
test('password submit treats direct OAuth consent as a login-code skip', async () => {
|
||||
const api = new Function(`
|
||||
const location = { href: 'https://auth.openai.com/authorize' };
|
||||
|
||||
function inspectLoginAuthState() {
|
||||
return {
|
||||
state: 'oauth_consent_page',
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
|
||||
function throwIfStopped() {}
|
||||
async function sleep() {
|
||||
throw new Error('should not wait once oauth consent is detected');
|
||||
}
|
||||
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6RecoverableResult')}
|
||||
${extractFunction('normalizeStep6Snapshot')}
|
||||
${extractFunction('getStep6OptionMessage')}
|
||||
${extractFunction('resolveStep6PostSubmitSnapshot')}
|
||||
${extractFunction('waitForStep6PostSubmitTransition')}
|
||||
${extractFunction('waitForStep6PasswordSubmitTransition')}
|
||||
|
||||
return {
|
||||
run() {
|
||||
return waitForStep6PasswordSubmitTransition(123, 1000);
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const transition = await api.run();
|
||||
|
||||
assert.equal(transition.action, 'done');
|
||||
assert.equal(transition.result.state, 'oauth_consent_page');
|
||||
assert.equal(transition.result.skipLoginVerificationStep, true);
|
||||
assert.equal(transition.result.directOAuthConsentPage, true);
|
||||
assert.equal(transition.result.loginVerificationRequestedAt, null);
|
||||
});
|
||||
|
||||
test('step 7 entry succeeds when the auth page is already on OAuth consent', async () => {
|
||||
const logs = [];
|
||||
const api = new Function(`
|
||||
const location = { href: 'https://auth.openai.com/authorize' };
|
||||
const logs = arguments[0];
|
||||
|
||||
function inspectLoginAuthState() {
|
||||
return {
|
||||
state: 'oauth_consent_page',
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
|
||||
function throwIfStopped() {}
|
||||
async function sleep() {}
|
||||
function log(message, level = 'info') {
|
||||
logs.push({ message, level });
|
||||
}
|
||||
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('normalizeStep6Snapshot')}
|
||||
${extractFunction('waitForKnownLoginAuthState')}
|
||||
${extractFunction('step6_login')}
|
||||
|
||||
return {
|
||||
run() {
|
||||
return step6_login({ email: 'user@example.com' });
|
||||
},
|
||||
};
|
||||
`)(logs);
|
||||
|
||||
const result = await api.run();
|
||||
|
||||
assert.equal(result.step6Outcome, 'success');
|
||||
assert.equal(result.state, 'oauth_consent_page');
|
||||
assert.equal(result.skipLoginVerificationStep, true);
|
||||
assert.equal(result.directOAuthConsentPage, true);
|
||||
assert.equal(logs.some(({ level }) => level === 'ok'), true);
|
||||
});
|
||||
@@ -833,6 +833,69 @@ test('verification flow uses configured login resend count for step 8', async ()
|
||||
assert.equal(pollCalls, 3);
|
||||
});
|
||||
|
||||
test('verification flow can complete Plus visible login-code step with shared step 8 semantics', async () => {
|
||||
const completed = [];
|
||||
const fillMessages = [];
|
||||
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (step, payload) => {
|
||||
completed.push({ step, payload });
|
||||
},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isStopError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||
pollHotmailVerificationCode: async () => ({}),
|
||||
pollLuckmailVerificationCode: async () => ({}),
|
||||
sendToContentScript: async (_source, message) => {
|
||||
if (message.type === 'FILL_CODE') {
|
||||
fillMessages.push(message);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
sendToMailContentScriptResilient: async () => ({ code: '654321', emailTimestamp: 456 }),
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||
});
|
||||
|
||||
await helpers.resolveVerificationStep(
|
||||
8,
|
||||
{ email: 'user@example.com', lastLoginCode: null },
|
||||
{ provider: 'qq', label: 'QQ 邮箱' },
|
||||
{
|
||||
completionStep: 11,
|
||||
requestFreshCodeFirst: false,
|
||||
maxResendRequests: 0,
|
||||
resendIntervalMs: 0,
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(fillMessages.map((message) => message.step), [8]);
|
||||
assert.deepStrictEqual(completed, [
|
||||
{
|
||||
step: 11,
|
||||
payload: {
|
||||
emailTimestamp: 456,
|
||||
code: '654321',
|
||||
phoneVerificationRequired: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('verification flow waits during resend cooldown instead of tight-looping', async () => {
|
||||
const sleepCalls = [];
|
||||
let pollCalls = 0;
|
||||
|
||||
Reference in New Issue
Block a user