feat: 更新 OAuth 流程,调整步骤 6 和步骤 7 的逻辑,添加相关测试

This commit is contained in:
QLHazyCoder
2026-04-19 18:59:32 +08:00
parent 17e1c672f1
commit 7cb1bda672
12 changed files with 372 additions and 68 deletions
+143 -14
View File
@@ -321,6 +321,7 @@ const DEFAULT_STATE = {
signupVerificationRequestedAt: null,
loginVerificationRequestedAt: null,
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
currentHotmailAccountId: null,
preferredIcloudHost: '',
};
@@ -3975,6 +3976,7 @@ function getDownstreamStateResets(step) {
signupVerificationRequestedAt: null,
loginVerificationRequestedAt: null,
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
lastSignupCode: null,
lastLoginCode: null,
localhostUrl: null,
@@ -3987,6 +3989,7 @@ function getDownstreamStateResets(step) {
signupVerificationRequestedAt: null,
loginVerificationRequestedAt: null,
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
lastSignupCode: null,
lastLoginCode: null,
localhostUrl: null,
@@ -3998,6 +4001,7 @@ function getDownstreamStateResets(step) {
signupVerificationRequestedAt: null,
loginVerificationRequestedAt: null,
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
lastSignupCode: null,
lastLoginCode: null,
localhostUrl: null,
@@ -4008,6 +4012,7 @@ function getDownstreamStateResets(step) {
lastLoginCode: null,
loginVerificationRequestedAt: null,
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
localhostUrl: null,
};
}
@@ -4612,7 +4617,11 @@ async function skipStep(step) {
return { ok: true, step, status: 'skipped' };
}
function throwIfStopped() {
function throwIfStopped(error = null) {
const errorMessage = typeof error === 'string' ? error : error?.message;
if (errorMessage === STOP_ERROR_MESSAGE) {
throw error instanceof Error ? error : new Error(STOP_ERROR_MESSAGE);
}
if (stopRequested) {
throw new Error(STOP_ERROR_MESSAGE);
}
@@ -4796,6 +4805,7 @@ async function handleStepData(step, payload) {
await setState({
localhostUrl: payload.localhostUrl,
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
});
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
}
@@ -4993,6 +5003,59 @@ async function waitForRunningStepsToFinish(payload = {}) {
return currentState;
}
const AUTH_CHAIN_STEP_IDS = new Set([7, 8, 9, 10]);
let activeTopLevelAuthChainExecution = null;
function isAuthChainStep(step) {
return AUTH_CHAIN_STEP_IDS.has(Number(step));
}
async function acquireTopLevelAuthChainExecution(step) {
const normalizedStep = Number(step);
if (!isAuthChainStep(normalizedStep)) {
return {
joined: false,
release() {},
};
}
if (activeTopLevelAuthChainExecution) {
const activeExecution = activeTopLevelAuthChainExecution;
await addLog(
`步骤 ${normalizedStep}:检测到步骤 ${activeExecution.step} 正在运行,本次请求将复用当前授权链,不再重复启动。`,
'warn'
);
const result = await activeExecution.promise;
if (result?.error) {
throw result.error;
}
return {
joined: true,
release() {},
};
}
let settleExecution = () => {};
const promise = new Promise((resolve) => {
settleExecution = (error = null) => resolve({ error });
});
const execution = {
step: normalizedStep,
promise,
};
activeTopLevelAuthChainExecution = execution;
return {
joined: false,
release(error = null) {
if (activeTopLevelAuthChainExecution === execution) {
activeTopLevelAuthChainExecution = null;
}
settleExecution(error);
},
};
}
async function markRunningStepsStopped() {
const state = await getState();
const runningSteps = getRunningSteps(state.stepStatuses);
@@ -5089,21 +5152,29 @@ async function requestStop(options = {}) {
async function executeStep(step, options = {}) {
const { deferRetryableTransportError = false } = options;
console.log(LOG_PREFIX, `Executing step ${step}`);
throwIfStopped();
await setStepStatus(step, 'running');
await addLog(`步骤 ${step} 开始执行`);
await humanStepDelay();
const state = await getState();
// Set flow start time on first step
if (step === 1 && !state.flowStartTime) {
await setState({ flowStartTime: Date.now() });
const authChainClaim = await acquireTopLevelAuthChainExecution(step);
if (authChainClaim.joined) {
return;
}
let executionError = null;
throwIfStopped();
try {
await setStepStatus(step, 'running');
await addLog(`步骤 ${step} 开始执行`);
await humanStepDelay();
const state = await getState();
// Set flow start time on first step
if (step === 1 && !state.flowStartTime) {
await setState({ flowStartTime: Date.now() });
}
await stepRegistry.executeStep(step, state);
} catch (err) {
executionError = err;
const state = await getState();
if (isStopError(err)) {
await setStepStatus(step, 'stopped');
await addLog(`步骤 ${step} 已被用户停止`, 'warn');
@@ -5125,6 +5196,8 @@ async function executeStep(step, options = {}) {
);
}
throw err;
} finally {
authChainClaim.release(executionError);
}
}
@@ -5963,7 +6036,6 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
ensureStep8VerificationPageReady,
executeStep7: (...args) => executeStep7(...args),
getOAuthFlowRemainingMs,
getOAuthFlowStepTimeoutMs,
getPanelMode,
@@ -5975,9 +6047,9 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
isVerificationMailPollingError,
LUCKMAIL_PROVIDER,
resolveVerificationStep: verificationFlowHelpers.resolveVerificationStep,
rerunStep7ForStep8Recovery: (...args) => rerunStep7ForStep8Recovery(...args),
reuseOrCreateTab,
setState,
setStepStatus,
shouldUseCustomRegistrationEmail,
sleepWithStop,
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
@@ -6455,10 +6527,18 @@ function normalizeOAuthFlowDeadlineAt(value) {
return Math.floor(numeric);
}
function normalizeOAuthFlowSourceUrl(value) {
const normalized = String(value || '').trim();
return normalized || null;
}
async function startOAuthFlowTimeoutWindow(options = {}) {
const step = Number(options.step) || 7;
const deadlineAt = Date.now() + OAUTH_FLOW_TIMEOUT_MS;
await setState({ oauthFlowDeadlineAt: deadlineAt });
await setState({
oauthFlowDeadlineAt: deadlineAt,
oauthFlowDeadlineSourceUrl: normalizeOAuthFlowSourceUrl(options.oauthUrl),
});
await addLog(`步骤 ${step}:已拿到新的 OAuth 登录地址,开始 6 分钟倒计时。`, 'info');
return deadlineAt;
}
@@ -6468,10 +6548,22 @@ async function getOAuthFlowRemainingMs(options = {}) {
const actionLabel = String(options.actionLabel || '后续授权流程').trim() || '后续授权流程';
const state = options.state || await getState();
const deadlineAt = normalizeOAuthFlowDeadlineAt(state?.oauthFlowDeadlineAt);
const deadlineSourceUrl = normalizeOAuthFlowSourceUrl(state?.oauthFlowDeadlineSourceUrl);
const currentOauthUrl = normalizeOAuthFlowSourceUrl(options.oauthUrl !== undefined ? options.oauthUrl : state?.oauthUrl);
if (!deadlineAt) {
return null;
}
if (deadlineSourceUrl && currentOauthUrl && deadlineSourceUrl !== currentOauthUrl) {
console.warn(LOG_PREFIX, '[oauth-flow] ignoring stale deadline due to oauth url mismatch', {
step,
actionLabel,
deadlineSourceUrl,
currentOauthUrl,
});
return null;
}
const remainingMs = deadlineAt - Date.now();
if (remainingMs <= 0) {
throw buildOAuthFlowTimeoutError(step, actionLabel);
@@ -6617,6 +6709,43 @@ async function ensureStep8VerificationPageReady(options = {}) {
throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:${stateLabel}.${urlPart}`.trim());
}
async function rerunStep7ForStep8Recovery(options = {}) {
const {
logMessage = '步骤 8:正在回到步骤 7,重新发起登录验证码流程...',
postStepDelayMs = 3000,
} = options;
throwIfStopped();
const initialState = await getState();
await addLog(logMessage, 'warn');
await setStepStatus(7, 'running');
await addLog('步骤 7 开始执行');
try {
await step7Executor.executeStep7(initialState);
} catch (err) {
const latestState = await getState();
if (isStopError(err)) {
await setStepStatus(7, 'stopped');
await addLog('步骤 7 已被用户停止', 'warn');
await appendManualAccountRunRecordIfNeeded('step7_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));
throw err;
}
if (postStepDelayMs > 0) {
await sleepWithStop(postStepDelayMs);
}
}
async function executeStep6() {
return step6Executor.executeStep6();
}