chore: preserve step-aware logging after upstream sync

- 吸收 master 的核心改动:按可见步骤归类日志显示,覆盖 OAuth、手机号验证、平台验证和内容脚本日志链路。

- 本地补充修复:保留平台验证成功后的手机号接码确认收尾,避免 #193/#194 恢复的手机号复用链路被合并覆盖。

- 影响范围:background routing、phone verification、platform verify、content scripts、sidepanel 与相关测试/文档。
This commit is contained in:
QLHazyCoder
2026-05-04 04:41:49 +08:00
25 changed files with 690 additions and 290 deletions
+20 -2
View File
@@ -31,10 +31,28 @@
return labels[source] || source || '未知来源';
}
async function addLog(message, level = 'info') {
function normalizeLogStep(value) {
const step = Math.floor(Number(value) || 0);
return step > 0 ? step : null;
}
function buildLogEntry(message, level = 'info', options = {}) {
const normalizedOptions = options && typeof options === 'object' ? options : {};
const step = normalizeLogStep(normalizedOptions.step);
const stepKey = String(normalizedOptions.stepKey || '').trim();
return {
message: String(message || ''),
level,
timestamp: Date.now(),
step,
stepKey,
};
}
async function addLog(message, level = 'info', options = {}) {
const state = await getState();
const logs = state.logs || [];
const entry = { message, level, timestamp: Date.now() };
const entry = buildLogEntry(message, level, options);
logs.push(entry);
if (logs.length > 500) logs.splice(0, logs.length - 500);
await setState({ logs });
+24 -7
View File
@@ -222,6 +222,9 @@
if (typeof markCurrentRegistrationAccountUsed !== 'function') {
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
}
if (typeof finalizePhoneActivationAfterSuccessfulFlow === 'function') {
await finalizePhoneActivationAfterSuccessfulFlow(latestState);
}
}
async function handleStepData(step, payload) {
@@ -258,7 +261,10 @@
const currentStatus = latestState.stepStatuses?.[loginCodeStep];
if (!isStepProtectedFromAutoSkip(currentStatus)) {
await setStepStatus(loginCodeStep, 'skipped');
await addLog(`步骤 ${step}认证页已直接进入 OAuth 授权页,已自动跳过步骤 ${loginCodeStep} 的登录验证码。`, 'warn');
await addLog(`认证页已直接进入 OAuth 授权页,已自动跳过步骤 ${loginCodeStep} 的登录验证码。`, 'warn', {
step,
stepKey: 'oauth-login',
});
}
}
} else if (payload.loginVerificationRequestedAt) {
@@ -433,6 +439,9 @@
if (typeof markCurrentRegistrationAccountUsed !== 'function' && typeof markCurrentCustomEmailPoolEntryUsed === 'function') {
await markCurrentCustomEmailPoolEntryUsed(latestState);
}
if (typeof finalizePhoneActivationAfterSuccessfulFlow === 'function') {
await finalizePhoneActivationAfterSuccessfulFlow(latestState);
}
break;
}
default:
@@ -453,8 +462,16 @@
}
case 'LOG': {
const { message: msg, level } = message.payload;
await addLog(`[${getSourceLabel(message.source)}] ${msg}`, level);
const { message: msg, level, step: payloadStep, stepKey } = message.payload;
const logStep = Math.floor(Number(message.step || payloadStep) || 0);
await addLog(
`[${getSourceLabel(message.source)}] ${msg}`,
level,
{
step: logStep > 0 ? logStep : null,
stepKey,
}
);
return { ok: true };
}
@@ -479,7 +496,7 @@
}
const errorMessage = error?.message || String(error || '步骤 3 提交后确认失败');
await setStepStatus(message.step, 'failed');
await addLog(`步骤 ${message.step} 失败:${errorMessage}`, 'error');
await addLog(`失败:${errorMessage}`, 'error', { step: message.step });
await appendManualAccountRunRecordIfNeeded(`step${message.step}_failed`, null, errorMessage);
notifyStepError(message.step, errorMessage);
return { ok: true, error: errorMessage };
@@ -491,7 +508,7 @@
: 10;
const completionState = message.step === lastStepId ? completionStateCandidate : null;
await setStepStatus(message.step, 'completed');
await addLog(`步骤 ${message.step} 已完成`, 'ok');
await addLog('已完成', 'ok', { step: message.step });
await handleStepData(message.step, message.payload);
if (message.step === lastStepId && typeof appendAccountRunRecord === 'function') {
await appendAccountRunRecord('success', completionState);
@@ -510,12 +527,12 @@
}
if (isStopError(message.error)) {
await setStepStatus(message.step, 'stopped');
await addLog(`步骤 ${message.step} 已被用户停止`, 'warn');
await addLog('已被用户停止', 'warn', { step: message.step });
await appendManualAccountRunRecordIfNeeded(`step${message.step}_stopped`, null, message.error);
notifyStepError(message.step, message.error);
} else {
await setStepStatus(message.step, 'failed');
await addLog(`步骤 ${message.step} 失败:${message.error}`, 'error');
await addLog(`失败:${message.error}`, 'error', { step: message.step });
await appendManualAccountRunRecordIfNeeded(`step${message.step}_failed`, null, message.error);
notifyStepError(message.step, message.error);
}
+54 -7
View File
@@ -3,7 +3,7 @@
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPhoneVerificationModule() {
function createPhoneVerificationHelpers(deps = {}) {
const {
addLog,
addLog: rawAddLog = async () => {},
ensureStep8SignupPageReady,
fetchImpl = (...args) => fetch(...args),
getOAuthFlowStepTimeoutMs,
@@ -91,6 +91,35 @@
const PHONE_SMS_FAILURE_SKIP_THRESHOLD = 2;
const MAX_ACTIVATION_PRICE_HINTS = 256;
const activationPriceHintsByKey = new Map();
let activePhoneVerificationLogStep = null;
function normalizeLogStep(value) {
const step = Math.floor(Number(value) || 0);
return step > 0 ? step : null;
}
function normalizePhoneVerificationLogMessage(message) {
return String(message || '')
.replace(/^Step\s+9\s+diagnostics\s*:\s*/i, 'diagnostics: ')
.replace(/^Step\s+9\s*[:]\s*/i, '')
.replace(/^步骤\s*9\s*[:]\s*/, '')
.replace(/\bstep\s+9\b/gi, 'current step')
.trim();
}
async function addLog(message, level = 'info', options = {}) {
const normalizedOptions = options && typeof options === 'object' ? { ...options } : {};
const step = normalizeLogStep(normalizedOptions.step || normalizedOptions.visibleStep)
|| normalizeLogStep(activePhoneVerificationLogStep);
if (step) {
normalizedOptions.step = step;
if (!normalizedOptions.stepKey) {
normalizedOptions.stepKey = 'phone-verification';
}
}
delete normalizedOptions.visibleStep;
return rawAddLog(normalizePhoneVerificationLogMessage(message), level, normalizedOptions);
}
function normalizeUrl(value, fallback = DEFAULT_HERO_SMS_BASE_URL) {
const trimmed = String(value || '').trim();
@@ -3475,19 +3504,24 @@
}
async function readPhonePageState(tabId, timeoutMs = 10000) {
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
await ensureStep8SignupPageReady(tabId, {
timeoutMs,
logMessage: '步骤 9:等待认证页脚本恢复后继续手机号验证。',
visibleStep,
logStepKey: 'phone-verification',
});
const result = await sendToContentScriptResilient('signup-page', {
type: 'STEP8_GET_STATE',
source: 'background',
payload: {},
payload: { visibleStep },
}, {
timeoutMs,
responseTimeoutMs: timeoutMs,
retryDelayMs: 600,
logMessage: '步骤 9:认证页正在切换,等待后重新检查手机号验证状态...',
logStep: visibleStep,
logStepKey: 'phone-verification',
});
if (result?.error) {
@@ -3546,8 +3580,9 @@
async function submitPhoneNumber(tabId, phoneNumber, activation = null) {
const state = await getState();
const countryConfig = resolveCountryConfigFromActivation(activation, state);
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(30000, { step: 9, actionLabel: '提交添加手机号' })
? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: '提交添加手机号' })
: 30000;
const result = await sendToContentScriptResilient('signup-page', {
type: 'SUBMIT_PHONE_NUMBER',
@@ -3562,6 +3597,8 @@
responseTimeoutMs: timeoutMs,
retryDelayMs: 600,
logMessage: '步骤 9:等待添加手机号页面就绪...',
logStep: visibleStep,
logStepKey: 'phone-verification',
});
if (result?.error) {
@@ -3571,8 +3608,9 @@
}
async function submitPhoneVerificationCode(tabId, code) {
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(45000, { step: 9, actionLabel: '提交手机验证码' })
? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交手机验证码' })
: 45000;
const result = await sendToContentScriptResilient('signup-page', {
type: 'SUBMIT_PHONE_VERIFICATION_CODE',
@@ -3583,6 +3621,8 @@
responseTimeoutMs: timeoutMs,
retryDelayMs: 600,
logMessage: '步骤 9:等待手机验证码页面就绪后填写短信验证码...',
logStep: visibleStep,
logStepKey: 'phone-verification',
});
if (result?.error) {
@@ -3598,8 +3638,9 @@
}
async function resendPhoneVerificationCode(tabId) {
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(65000, { step: 9, actionLabel: 'resend phone verification code' })
? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: 'resend phone verification code' })
: 65000;
const result = await sendToContentScriptResilient('signup-page', {
type: 'RESEND_PHONE_VERIFICATION_CODE',
@@ -3610,6 +3651,8 @@
responseTimeoutMs: timeoutMs,
retryDelayMs: 600,
logMessage: '步骤 9:等待手机验证码重发按钮出现...',
logStep: visibleStep,
logStepKey: 'phone-verification',
});
if (result?.error) {
@@ -3619,8 +3662,9 @@
}
async function returnToAddPhone(tabId) {
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(30000, { step: 9, actionLabel: 'return to add-phone page' })
? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: 'return to add-phone page' })
: 30000;
const result = await sendToContentScriptResilient('signup-page', {
type: 'RETURN_TO_ADD_PHONE',
@@ -3631,6 +3675,8 @@
responseTimeoutMs: timeoutMs,
retryDelayMs: 600,
logMessage: '步骤 9:返回添加手机号页面以更换号码...',
logStep: visibleStep,
logStepKey: 'phone-verification',
});
if (result?.error) {
@@ -4105,7 +4151,8 @@
throw new Error('手机号验证未完成。');
}
async function completePhoneVerificationFlow(tabId, initialPageState = null) {
async function completePhoneVerificationFlow(tabId, initialPageState = null, options = {}) {
activePhoneVerificationLogStep = normalizeLogStep(options.visibleStep || options.step) || 9;
let state = await getState();
let activation = normalizeActivation(state[PHONE_ACTIVATION_STATE_KEY]);
let pageState = initialPageState || await readPhonePageState(tabId);
+23 -12
View File
@@ -47,6 +47,10 @@
return visibleStep >= 12 ? 10 : 7;
}
function addStepLog(step, message, level = 'info') {
return addLog(message, level, { step, stepKey: 'confirm-oauth' });
}
async function executeStep9(state) {
const visibleStep = getVisibleStep(state, 9);
let activeState = state;
@@ -56,7 +60,7 @@
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}`);
}
await addLog(`步骤 ${visibleStep}正在监听 localhost 回调地址...`);
await addStepLog(visibleStep, '正在监听 localhost 回调地址...');
let callbackTimeoutMs = LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS;
let timeoutRecoveryAttempted = false;
@@ -115,7 +119,7 @@
resolved = true;
cleanupListener();
addLog(`步骤 ${visibleStep}已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
addStepLog(visibleStep, `已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
return completeStepFromBackground(visibleStep, { localhostUrl: callbackUrl });
}).then(() => {
resolve();
@@ -185,10 +189,10 @@
if (signupTabId && await isTabAlive('signup-page')) {
await chrome.tabs.update(signupTabId, { active: true });
await addLog(`步骤 ${visibleStep}已切回认证页,正在准备调试器点击...`);
await addStepLog(visibleStep, '已切回认证页,正在准备调试器点击...');
} else {
signupTabId = await reuseOrCreateTab('signup-page', activeState.oauthUrl);
await addLog(`步骤 ${visibleStep}已重新打开认证页,正在准备调试器点击...`);
await addStepLog(visibleStep, '已重新打开认证页,正在准备调试器点击...');
}
throwIfStep8SettledOrStopped(resolved);
@@ -202,7 +206,9 @@
actionLabel: '等待 OAuth 同意页内容脚本就绪',
})
: 15000,
logMessage: `步骤 ${visibleStep}:认证页内容脚本尚未就绪,正在等待页面恢复...`,
visibleStep,
logStepKey: 'confirm-oauth',
logMessage: '认证页内容脚本尚未就绪,正在等待页面恢复...',
});
for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) {
@@ -214,7 +220,8 @@
step: visibleStep,
actionLabel: '等待 OAuth 同意页出现',
})
: STEP8_READY_WAIT_TIMEOUT_MS
: STEP8_READY_WAIT_TIMEOUT_MS,
{ visibleStep }
);
if (!pageState?.consentReady) {
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
@@ -223,7 +230,7 @@
const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)];
await addLog(`步骤 ${visibleStep}${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label}...`);
await addStepLog(visibleStep, `${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label}...`);
if (strategy.mode === 'debugger') {
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
@@ -235,9 +242,10 @@
const clickTarget = await prepareStep8DebuggerClick(signupTabId, {
timeoutMs: clickActionTimeoutMs,
responseTimeoutMs: clickActionTimeoutMs,
visibleStep,
});
throwIfStep8SettledOrStopped(resolved);
await clickWithDebugger(signupTabId, clickTarget?.rect);
await clickWithDebugger(signupTabId, clickTarget?.rect, { visibleStep });
} else {
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
@@ -248,6 +256,7 @@
await triggerStep8ContentStrategy(signupTabId, strategy.strategy, {
timeoutMs: clickActionTimeoutMs,
responseTimeoutMs: clickActionTimeoutMs,
visibleStep,
});
}
@@ -263,14 +272,15 @@
step: visibleStep,
actionLabel: '等待 OAuth 同意页点击生效',
})
: 15000
: 15000,
{ visibleStep }
);
if (resolved) {
return;
}
if (effect.progressed) {
await addLog(`步骤 ${visibleStep}检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
await addStepLog(visibleStep, `检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
break;
}
@@ -278,7 +288,7 @@
throw new Error(`步骤 ${visibleStep}:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`);
}
await addLog(`步骤 ${visibleStep}${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS}...`, 'warn');
await addStepLog(visibleStep, `${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS}...`, 'warn');
await reloadStep8ConsentPage(
signupTabId,
typeof getOAuthFlowStepTimeoutMs === 'function'
@@ -286,7 +296,8 @@
step: visibleStep,
actionLabel: '刷新 OAuth 同意页',
})
: 30000
: 30000,
{ visibleStep }
);
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
}
+36 -4
View File
@@ -5,7 +5,7 @@
function createStep8Executor(deps = {}) {
const {
addLog,
addLog: rawAddLog = async () => {},
chrome,
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
completeStepFromBackground,
@@ -32,6 +32,33 @@
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS,
throwIfStopped,
} = deps;
let activeFetchLoginCodeStep = null;
function normalizeLogStep(value) {
const step = Math.floor(Number(value) || 0);
return step > 0 ? step : null;
}
function normalizeStepLogMessage(message) {
return String(message || '')
.replace(/^步骤\s*\d+\s*[:]\s*/, '')
.replace(/^Step\s+\d+\s*[:]\s*/i, '')
.trim();
}
function addLog(message, level = 'info', options = {}) {
const normalizedOptions = options && typeof options === 'object' ? { ...options } : {};
const step = normalizeLogStep(normalizedOptions.step || normalizedOptions.visibleStep)
|| normalizeLogStep(activeFetchLoginCodeStep);
if (step) {
normalizedOptions.step = step;
if (!normalizedOptions.stepKey) {
normalizedOptions.stepKey = 'fetch-login-code';
}
}
delete normalizedOptions.visibleStep;
return rawAddLog(normalizeStepLogMessage(message), level, normalizedOptions);
}
function getVisibleStep(state, fallback = 8) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
@@ -181,6 +208,7 @@
async function runStep8Attempt(state, runtime = {}) {
const visibleStep = getVisibleStep(state, 8);
activeFetchLoginCodeStep = visibleStep;
const mail = getMailConfig(state);
if (mail.error) throw new Error(mail.error);
const stateLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
@@ -371,7 +399,9 @@
'warn'
);
await rerunStep7ForStep8Recovery({
logMessage: `步骤 ${visibleStep}邮箱通信异常持续未恢复,正在回到步骤 ${authLoginStep} 重新发起登录流程...`,
logMessage: `邮箱通信异常持续未恢复,正在回到步骤 ${authLoginStep} 重新发起登录流程...`,
logStep: visibleStep,
logStepKey: 'fetch-login-code',
});
currentState = await getState();
retryWithoutStep7Streak = 0;
@@ -415,8 +445,10 @@
);
await rerunStep7ForStep8Recovery({
logMessage: isStep8RestartStep7Error(currentError)
? `步骤 ${visibleStep}认证页进入重试/超时报错状态,正在回到步骤 ${authLoginStep} 重新发起登录流程...`
: `步骤 ${visibleStep}正在回到步骤 ${authLoginStep},重新发起登录验证码流程...`,
? `认证页进入重试/超时报错状态,正在回到步骤 ${authLoginStep} 重新发起登录流程...`
: `正在回到步骤 ${authLoginStep},重新发起登录验证码流程...`,
logStep: visibleStep,
logStepKey: 'fetch-login-code',
});
currentState = await getState();
}
+24 -11
View File
@@ -59,20 +59,26 @@
const password = currentState.password || currentState.customPassword || '';
const oauthUrl = await refreshOAuthUrlBeforeStep6(currentState);
if (typeof startOAuthFlowTimeoutWindow === 'function') {
await startOAuthFlowTimeoutWindow({ step: 7, oauthUrl });
await startOAuthFlowTimeoutWindow({ step: completionStep, oauthUrl });
}
const loginTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(180000, {
step: 7,
step: completionStep,
actionLabel: 'OAuth 登录并进入验证码页',
oauthUrl,
})
: 180000;
if (attempt === 1) {
await addLog('步骤 7正在打开最新 OAuth 链接并登录...');
await addLog('正在打开最新 OAuth 链接并登录...', 'info', {
step: completionStep,
stepKey: 'oauth-login',
});
} else {
await addLog(`步骤 7上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn');
await addLog(`上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn', {
step: completionStep,
stepKey: 'oauth-login',
});
}
await reuseOrCreateTab('signup-page', oauthUrl, { forceNew: true });
@@ -86,13 +92,16 @@
payload: {
email: currentState.email,
password,
visibleStep: completionStep,
},
},
{
timeoutMs: loginTimeoutMs,
responseTimeoutMs: loginTimeoutMs,
retryDelayMs: 700,
logMessage: '步骤 7认证页正在切换,等待页面重新就绪后继续登录...',
logMessage: '认证页正在切换,等待页面重新就绪后继续登录...',
logStep: completionStep,
logStepKey: 'oauth-login',
}
);
@@ -117,11 +126,11 @@
if (isStep6RecoverableResult(result)) {
const reasonMessage = result.message
|| `当前停留在${getLoginAuthStateLabel(result.state)},准备重新执行步骤 7`;
|| `当前停留在${getLoginAuthStateLabel(result.state)},准备重新执行步骤 ${completionStep}`;
throw new Error(reasonMessage);
}
throw new Error('步骤 7:认证页未返回可识别的登录结果。');
throw new Error(`步骤 ${completionStep}:认证页未返回可识别的登录结果。`);
} catch (err) {
throwIfStopped(err);
if (isAddPhoneAuthFailure(err)) {
@@ -129,8 +138,9 @@
}
if (isManagementSecretConfigError(err)) {
await addLog(
`步骤 7检测到来源后台管理密钥缺失或错误,不再重试,当前流程停止。原因:${getErrorMessage(err)}`,
'error'
`检测到来源后台管理密钥缺失或错误,不再重试,当前流程停止。原因:${getErrorMessage(err)}`,
'error',
{ step: completionStep, stepKey: 'oauth-login' }
);
throw err;
}
@@ -139,11 +149,14 @@
break;
}
await addLog(`步骤 7${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn');
await addLog(`${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn', {
step: completionStep,
stepKey: 'oauth-login',
});
}
}
throw new Error(`步骤 7:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`);
throw new Error(`步骤 ${completionStep}:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`);
}
return { executeStep7 };
+44 -24
View File
@@ -31,18 +31,31 @@
return visibleStep >= 10 ? visibleStep : 10;
}
function parseLocalhostCallback(rawUrl) {
function resolveConfirmOauthStep(platformVerifyStep = 10) {
return Number(platformVerifyStep) >= 13 ? 12 : 9;
}
function resolveAuthLoginStep(platformVerifyStep = 10) {
return Number(platformVerifyStep) >= 13 ? 10 : 7;
}
function addStepLog(step, message, level = 'info') {
return addLog(message, level, { step, stepKey: 'platform-verify' });
}
function parseLocalhostCallback(rawUrl, platformVerifyStep = 10) {
const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep);
let parsed;
try {
parsed = new URL(rawUrl);
} catch {
throw new Error('步骤 10 捕获到的 localhost OAuth 回调地址格式无效,请重新执行步骤 9。');
throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址格式无效,请重新执行步骤 ${confirmOauthStep}`);
}
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(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmOauthStep}`);
}
return {
@@ -206,18 +219,20 @@
async function executeCpaStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep);
const authLoginStep = resolveAuthLoginStep(platformVerifyStep);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}`);
}
if (!state.localhostUrl) {
throw new Error('缺少 localhost 回调地址,请先完成步骤 9。');
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}`);
}
if (!state.vpsUrl) {
throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。');
}
if (shouldBypassStep9ForLocalCpa(state)) {
await addLog('步骤 10检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info');
await addStepLog(platformVerifyStep, '检测到本地 CPA,且当前策略为“跳过平台回调验证”,本轮不再重复提交回调地址。', 'info');
await completeStepFromBackground(platformVerifyStep, {
localhostUrl: state.localhostUrl,
verifiedStatus: 'local-auto',
@@ -225,17 +240,17 @@
return;
}
const callback = parseLocalhostCallback(state.localhostUrl);
const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep);
const expectedState = normalizeString(state.cpaOAuthState);
if (expectedState && expectedState !== callback.state) {
throw new Error('CPA 回调 state 与当前授权会话不匹配,请重新执行步骤 7。');
throw new Error(`CPA 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}`);
}
const managementKey = normalizeString(state.vpsPassword);
if (!managementKey) {
throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。');
}
await addLog('步骤 10正在通过 CPA 管理接口提交回调地址...');
await addStepLog(platformVerifyStep, '正在通过 CPA 管理接口提交回调地址...');
try {
const origin = normalizeString(state.cpaManagementOrigin) || deriveCpaManagementOrigin(state.vpsUrl);
const result = await fetchCpaManagementJson(origin, '/v0/management/oauth-callback', {
@@ -250,43 +265,45 @@
const verifiedStatus = normalizeString(result?.message)
|| normalizeString(result?.status_message)
|| 'CPA 已通过接口提交回调';
await addLog(`步骤 10${verifiedStatus}`, 'ok');
await addStepLog(platformVerifyStep, verifiedStatus, 'ok');
await completeStepFromBackground(platformVerifyStep, {
localhostUrl: callback.url,
verifiedStatus,
});
} catch (error) {
const reason = normalizeString(error?.message) || 'unknown error';
await addLog(`步骤 10CPA 接口提交失败:${reason}`, 'error');
await addStepLog(platformVerifyStep, `CPA 接口提交失败:${reason}`, 'error');
throw error;
}
}
async function executeCodex2ApiStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep);
const authLoginStep = resolveAuthLoginStep(platformVerifyStep);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}`);
}
if (!state.localhostUrl) {
throw new Error('缺少 localhost 回调地址,请先完成步骤 9。');
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}`);
}
if (!state.codex2apiSessionId) {
throw new Error('缺少 Codex2API 会话信息,请重新执行步骤 7。');
throw new Error(`缺少 Codex2API 会话信息,请重新执行步骤 ${authLoginStep}`);
}
if (!normalizeString(state.codex2apiAdminKey)) {
throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。');
}
const callback = parseLocalhostCallback(state.localhostUrl);
const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep);
const expectedState = normalizeString(state.codex2apiOAuthState);
if (expectedState && expectedState !== callback.state) {
throw new Error('Codex2API 回调 state 与当前授权会话不匹配,请重新执行步骤 7。');
throw new Error(`Codex2API 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}`);
}
const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl);
const origin = new URL(codex2apiUrl).origin;
await addLog('步骤 10正在向 Codex2API 提交回调并创建账号...');
await addStepLog(platformVerifyStep, '正在向 Codex2API 提交回调并创建账号...');
const result = await fetchCodex2ApiJson(origin, '/api/admin/oauth/exchange-code', {
adminKey: state.codex2apiAdminKey,
method: 'POST',
@@ -298,7 +315,7 @@
});
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
await addLog(`步骤 10${verifiedStatus}`, 'ok');
await addStepLog(platformVerifyStep, verifiedStatus, 'ok');
await completeStepFromBackground(platformVerifyStep, {
localhostUrl: callback.url,
verifiedStatus,
@@ -308,11 +325,12 @@
async function executeSub2ApiStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
const visibleStep = platformVerifyStep;
const confirmOauthStep = resolveConfirmOauthStep(visibleStep);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}`);
}
if (!state.localhostUrl) {
throw new Error('缺少 localhost 回调地址,请先完成步骤 9。');
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}`);
}
if (!state.sub2apiSessionId) {
throw new Error('缺少 SUB2API 会话信息,请重新执行步骤 1。');
@@ -327,7 +345,7 @@
const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl);
const injectFiles = ['content/utils.js', 'content/sub2api-panel.js'];
await addLog('步骤 10正在打开 SUB2API 后台...');
await addStepLog(visibleStep, '正在打开 SUB2API 后台...');
let tabId = await getTabId('sub2api-panel');
const alive = tabId && await isTabAlive('sub2api-panel');
@@ -349,13 +367,14 @@
injectSource: 'sub2api-panel',
});
await addLog(`步骤 ${visibleStep}正在向 SUB2API 提交回调并创建账号...`);
await addStepLog(visibleStep, '正在向 SUB2API 提交回调并创建账号...');
const requestMessage = {
type: 'EXECUTE_STEP',
step: platformVerifyStep,
source: 'background',
payload: {
localhostUrl: state.localhostUrl,
visibleStep,
sub2apiUrl,
sub2apiEmail: state.sub2apiEmail,
sub2apiPassword: state.sub2apiPassword,
@@ -386,8 +405,9 @@
throw error;
}
await addLog(
`步骤 ${visibleStep}SUB2API 回调交换出现临时网络波动(${error.message}),正在重试 ${attempt + 1}/${maxExchangeAttempts}...`,
'warn'
`SUB2API 回调交换出现临时网络波动(${error.message}),正在重试 ${attempt + 1}/${maxExchangeAttempts}...`,
'warn',
{ step: visibleStep, stepKey: 'platform-verify' }
);
await sleep(1200 * attempt);
}
+18 -3
View File
@@ -339,6 +339,8 @@
timeoutMs = 30000,
retryDelayMs = 700,
logMessage = '',
logStep = null,
logStepKey = '',
} = options;
const start = Date.now();
@@ -399,7 +401,10 @@
if (logMessage && !logged) {
console.warn(LOG_PREFIX, `[ensureContentScriptReadyOnTab] ${source} tab=${tabId} still not ready after ${Date.now() - start}ms`);
await addLog(logMessage, 'warn');
await addLog(logMessage, 'warn', {
step: logStep,
stepKey: logStepKey,
});
logged = true;
}
@@ -670,6 +675,8 @@
timeoutMs = 30000,
retryDelayMs = 600,
logMessage = '',
logStep = null,
logStepKey = '',
responseTimeoutMs,
} = options;
const start = Date.now();
@@ -701,7 +708,10 @@
lastError = err;
if (logMessage && !logged) {
await addLog(logMessage, 'warn');
await addLog(logMessage, 'warn', {
step: logStep,
stepKey: logStepKey,
});
logged = true;
}
@@ -716,6 +726,8 @@
const {
timeoutMs = 45000,
maxRecoveryAttempts = 2,
logStep = null,
logStepKey = '',
responseTimeoutMs,
} = options;
const start = Date.now();
@@ -745,7 +757,10 @@
lastError = err;
if (!logged) {
await addLog(`步骤 ${message.step}${mail.label} 页面通信异常,正在尝试让邮箱页重新就绪...`, 'warn');
await addLog(`${mail.label} 页面通信异常,正在尝试让邮箱页重新就绪...`, 'warn', {
step: logStep,
stepKey: logStepKey,
});
logged = true;
}
+51 -5
View File
@@ -6,7 +6,7 @@
function createVerificationFlowHelpers(deps = {}) {
const {
addLog,
addLog: rawAddLog = async () => {},
chrome,
closeConflictingTabsForSource,
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
@@ -34,6 +34,33 @@
throwIfStopped,
VERIFICATION_POLL_MAX_ROUNDS,
} = deps;
let activeVerificationLogStep = null;
function normalizeLogStep(value) {
const step = Math.floor(Number(value) || 0);
return step > 0 ? step : null;
}
function normalizeVerificationLogMessage(message) {
return String(message || '')
.replace(/^步骤\s*\d+\s*[:]\s*/, '')
.replace(/^Step\s+\d+\s*[:]\s*/i, '')
.trim();
}
function addLog(message, level = 'info', options = {}) {
const normalizedOptions = options && typeof options === 'object' ? { ...options } : {};
const step = normalizeLogStep(normalizedOptions.step || normalizedOptions.visibleStep)
|| normalizeLogStep(activeVerificationLogStep);
if (step) {
normalizedOptions.step = step;
if (!normalizedOptions.stepKey) {
normalizedOptions.stepKey = step === 4 ? 'fetch-signup-code' : 'fetch-login-code';
}
}
delete normalizedOptions.visibleStep;
return rawAddLog(normalizeVerificationLogMessage(message), level, normalizedOptions);
}
const isRetryableVerificationTransportError = typeof deps.isRetryableContentScriptTransportError === 'function'
? deps.isRetryableContentScriptTransportError
@@ -518,6 +545,8 @@
timeoutMs: responseTimeoutMs,
responseTimeoutMs,
maxRecoveryAttempts: 2,
logStep: activeVerificationLogStep,
logStepKey: step === 4 ? 'fetch-signup-code' : 'fetch-login-code',
}
);
@@ -583,6 +612,8 @@
timeoutMs: 10000,
responseTimeoutMs: 5000,
maxRecoveryAttempts: 1,
logStep: activeVerificationLogStep,
logStepKey: step === 4 ? 'fetch-signup-code' : 'fetch-login-code',
}
);
} catch (_) {
@@ -693,6 +724,8 @@
timeoutMs: timeoutWindow.timeoutMs,
maxRecoveryAttempts: 2,
responseTimeoutMs: timeoutWindow.responseTimeoutMs,
logStep: activeVerificationLogStep,
logStepKey: step === 4 ? 'fetch-signup-code' : 'fetch-login-code',
}
);
@@ -959,6 +992,8 @@
timeoutMs: timeoutWindow.timeoutMs,
maxRecoveryAttempts: 2,
responseTimeoutMs: timeoutWindow.responseTimeoutMs,
logStep: activeVerificationLogStep,
logStepKey: step === 4 ? 'fetch-signup-code' : 'fetch-login-code',
}
);
@@ -1000,6 +1035,8 @@
}
async function submitVerificationCode(step, code, options = {}) {
const completionStep = getCompletionStep(step, options);
const authLoginStep = completionStep >= 11 ? 10 : 7;
const signupTabId = await getTabId('signup-page');
if (!signupTabId) {
throw new Error('认证页面标签页已关闭,无法填写验证码。');
@@ -1028,7 +1065,9 @@
timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000),
retryDelayMs: 700,
responseTimeoutMs: baseResponseTimeoutMs,
logMessage: `步骤 ${step}认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...`,
logMessage: '认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...',
logStep: completionStep,
logStepKey: step === 4 ? 'fetch-signup-code' : 'fetch-login-code',
});
} catch (err) {
if (step === 4 && isRetryableVerificationTransportError(err)) {
@@ -1058,9 +1097,15 @@
});
if (fallback.success) {
if (fallback.addPhonePage) {
await addLog('步骤 8验证码提交后通信中断,但页面已进入手机号验证页,按提交成功继续。', 'warn');
await addLog('验证码提交后通信中断,但页面已进入手机号验证页,按提交成功继续。', 'warn', {
step: completionStep,
stepKey: 'fetch-login-code',
});
} else {
await addLog('步骤 8验证码提交后通信中断,但页面已进入 OAuth 授权页,按提交成功继续。', 'warn');
await addLog('验证码提交后通信中断,但页面已进入 OAuth 授权页,按提交成功继续。', 'warn', {
step: completionStep,
stepKey: 'fetch-login-code',
});
}
return {
success: true,
@@ -1072,7 +1117,7 @@
}
if (fallback.restartStep7) {
const urlPart = fallback.url ? ` URL: ${fallback.url}` : '';
throw new Error(`STEP8_RESTART_STEP7::步骤 8:验证码提交后认证页进入登录超时报错页,请回到步骤 7 重新开始。${urlPart}`.trim());
throw new Error(`STEP8_RESTART_STEP7::步骤 ${completionStep}:验证码提交后认证页进入登录超时报错页,请回到步骤 ${authLoginStep} 重新开始。${urlPart}`.trim());
}
}
throw err;
@@ -1092,6 +1137,7 @@
async function resolveVerificationStep(step, state, mail, options = {}) {
const completionStep = getCompletionStep(step, options);
activeVerificationLogStep = completionStep;
const stateKey = getVerificationCodeStateKey(step);
const rejectedCodes = new Set();
const hotmailPollConfig = mail.provider === HOTMAIL_PROVIDER