From cbe9b1fabf4bbe131c90098da12f7aa4c9665f30 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Fri, 17 Apr 2026 15:56:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E5=8F=96cookie=E6=B8=85=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=B8=BA=E6=96=B0=E7=9A=84=E7=AC=AC=E5=85=AD?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 61 ++-- background.js | 195 ++++++----- background/logging-status.js | 6 +- background/message-router.js | 14 +- background/navigation-utils.js | 2 +- background/steps/clear-login-cookies.js | 19 ++ background/steps/confirm-oauth.js | 54 +-- background/steps/fetch-login-code.js | 72 ++-- background/steps/oauth-login.js | 38 +-- background/steps/platform-verify.js | 54 +-- background/verification-flow.js | 6 +- content/signup-page.js | 70 ++-- content/sub2api-panel.js | 12 +- content/vps-panel.js | 30 +- data/step-definitions.js | 9 +- sidepanel/sidepanel.html | 8 +- sidepanel/sidepanel.js | 19 +- tests/auto-run-fresh-attempt-reset.test.js | 3 + tests/auto-run-step6-restart.test.js | 42 +-- ...ackground-account-history-settings.test.js | 5 +- tests/background-luckmail.test.js | 4 +- tests/background-step-modules.test.js | 3 +- tests/background-step6-retry-limit.test.js | 87 ++--- tests/background-step7-recovery.test.js | 28 +- tests/step-definitions-module.test.js | 3 +- tests/step8-stop-cleanup.test.js | 6 +- tests/step9-localhost-cleanup-scope.test.js | 2 +- tests/verification-flow-polling.test.js | 6 +- 项目完整链路说明.md | 58 +++- 项目文件结构说明.md | 317 ++++++++---------- 30 files changed, 630 insertions(+), 603 deletions(-) create mode 100644 background/steps/clear-login-cookies.js diff --git a/README.md b/README.md index 83a5d1d..487eb31 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ 2. 填好 `SUB2API` 地址、登录邮箱、登录密码、分组名 3. `Mail` 与 `邮箱生成` 的配置方式同方案 A 4. Step 1 会直接在 SUB2API 后台生成 OAuth 链接 -5. Step 9 会把 localhost 回调提交回 SUB2API,并直接创建 OpenAI 账号 +5. Step 10 会把 localhost 回调提交回 SUB2API,并直接创建 OpenAI 账号 ### 方案 C:`Hotmail 账号池` @@ -146,7 +146,7 @@ http(s):///management.html#/oauth ``` -Step 1 和 Step 9 都依赖这个地址。 +Step 1 和 Step 10 都依赖这个地址。 ### `Mail` @@ -416,17 +416,18 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 ### 单步模式 -侧边栏共有 9 个步骤按钮,可逐步执行: +侧边栏共有 10 个步骤按钮,可逐步执行: 1. `Open ChatGPT` 2. `Signup + Email` 3. `Fill Password` 4. `Get Signup Code` 5. `Fill Name / Birthday` -6. `Login via OAuth` -7. `Get Login Code` -8. `Manual OAuth Confirm` -9. `CPA Verify` +6. `Clear Login Cookies` +7. `Login via OAuth` +8. `Get Login Code` +9. `Manual OAuth Confirm` +10. `CPA Verify` ### Auto 模式 @@ -440,7 +441,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 4. 如果不是 Hotmail,则按当前“邮箱生成”配置尝试自动获取邮箱(Duck / Cloudflare / iCloud 等) 5. Step 2 点击注册、填写邮箱,并按真实落地页进入密码页或直接进入邮箱验证码页 6. 如果自动获取失败,暂停并等待你在侧边栏填写邮箱后点击 `Continue` -7. 继续执行 Step 3 ~ Step 9 +7. 继续执行 Step 3 ~ Step 10 也就是说: @@ -511,18 +512,26 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 如果页面是生日模式,会填写年月日;如果页面上存在 `input[name='age']`,则直接填写年龄。 点击 `完成帐户创建` 后,Step 5 会立刻记为完成,不再等待页面跳转结果;自动运行在进入 Step 6 前只会等待当前页面加载完成,不再接管 ChatGPT 跳转或 onboarding 跳过逻辑。 -### Step 6: Login via OAuth +### Step 6: Clear Login Cookies -在登录前会先重新获取一遍最新的 CPA OAuth 链接,再使用刚注册的账号登录。 - -当前 Step 6 的完成标准不是“邮箱/密码已提交”,而是: +这一步只负责登录前清理环境: - 开始前先等待 10 秒 - 直接删除 `chatgpt.com / openai.com` 相关 cookies -- 再去 CPA / SUB2API 刷新最新 OAuth 链接 +- 必要时再用 `browsingData` 补扫一次 + +把 cookies 清理独立成单独步骤后,后续登录链路的重开锚点就不再落在这里。 + +### Step 7: Login via OAuth + +这一步会重新获取一遍最新的 CPA OAuth 链接,再使用刚注册的账号登录。 + +当前 Step 7 的完成标准不是“邮箱/密码已提交”,而是: + +- 已刷新到最新 OAuth 链接 - 认证页已经真正进入登录验证码页面 -- 如遇登录超时报错,会先尝试自动点击认证页上的 `重试` 恢复当前页面,再由后台按既有逻辑重跑整个 Step 6 -- 如遇登录页长时间停滞,会由后台刷新 OAuth 后重跑整个 Step 6 +- 如遇登录超时报错,会先尝试自动点击认证页上的 `重试` 恢复当前页面,再由后台按既有逻辑重跑整个 Step 7 +- 如遇登录页长时间停滞,会由后台刷新 OAuth 后重跑整个 Step 7 支持: @@ -530,23 +539,23 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 - 必要时切换到一次性验证码登录 - 直到登录验证码页就绪才算步骤完成 -### Step 7: Get Login Code +### Step 8: Get Login Code -Step 7 默认要求当前认证页已经处于登录验证码页。 +Step 8 默认要求当前认证页已经处于登录验证码页。 它只负责: - 打开邮箱并轮询登录验证码 - 填写并提交登录验证码 -- 验证码链路失败后按有限次数回退到 Step 6 +- 验证码链路失败后按有限次数回退到 Step 7 与 Step 4 类似,但会使用稍微不同的关键词组合去找登录验证码邮件。 -### Step 8: Manual OAuth Confirm +### Step 9: Manual OAuth Confirm 严格回调捕获规则: -- 步骤 8 现在只接受 `http(s)://localhost:/auth/callback?code=...&state=...` 或 `http(s)://127.0.0.1:/auth/callback?code=...&state=...` +- 步骤 9 现在只接受 `http(s)://localhost:/auth/callback?code=...&state=...` 或 `http(s)://127.0.0.1:/auth/callback?code=...&state=...` - 监听范围只限于当前 OAuth 认证标签页的主 frame 跳转 - 普通 `localhost` 页面,包括本地部署的 CPA 面板,不会再被误判为回调地址 @@ -566,15 +575,15 @@ Step 7 默认要求当前认证页已经处于登录验证码页。 - 如果 120 秒内没有捕获到 localhost 回调,会报错超时 - README 中的按钮名称沿用了旧文案,但代码行为是“自动尝试点击” -### Step 9: CPA Verify +### Step 10: CPA Verify 校验规则: -- 步骤 9 会拒绝任何不是真实 `/auth/callback`,或缺少 `code` / `state` 的本地回调地址 +- 步骤 10 会拒绝任何不是真实 `/auth/callback`,或缺少 `code` / `state` 的本地回调地址 - 成功后的清理只会针对 `/auth` 这一类真实回调标签页,不会再泛化清理任意 localhost 路径 - 侧边栏可切换“本地 CPA”策略,默认是 `全部回调` -- 选择 `全部回调` 时,即使 CPA 部署在本地,也会执行步骤 9 -- 选择 `跳过第9步` 时,仅当本地 CPA 且步骤 8 已拿到回调地址时,才会直接跳过步骤 9 +- 选择 `全部回调` 时,即使 CPA 部署在本地,也会执行步骤 10 +- 选择 `跳过第10步` 时,仅当本地 CPA 且步骤 9 已拿到回调地址时,才会直接跳过步骤 10 回到 CPA 面板: @@ -653,8 +662,8 @@ background.js 后台主控,编排 1~9 步、Tab 复用、状态 manifest.json 扩展清单 data/names.js 随机姓名、生日数据 content/utils.js 通用工具:等待元素、点击、日志、停止控制 -content/vps-panel.js CPA 面板步骤:内部 OAuth 刷新 / Step 9 -content/signup-page.js ChatGPT 官网 + OpenAI 注册/登录页步骤:Step 1 / 2 / 3 / 5 / 6 / 8 +content/vps-panel.js CPA 面板步骤:内部 OAuth 刷新 / Step 10 +content/signup-page.js ChatGPT 官网 + OpenAI 注册/登录页步骤:Step 1 / 2 / 3 / 5 / 7 / 9 hotmail-utils.js Hotmail 收信相关通用辅助 content/duck-mail.js Duck 邮箱自动获取 content/qq-mail.js QQ 邮箱验证码轮询 diff --git a/background.js b/background.js index b7372a4..e83791d 100644 --- a/background.js +++ b/background.js @@ -19,6 +19,7 @@ importScripts( 'background/steps/fill-password.js', 'background/steps/fetch-signup-code.js', 'background/steps/fill-profile.js', + 'background/steps/clear-login-cookies.js', 'background/steps/oauth-login.js', 'background/steps/fetch-login-code.js', 'background/steps/confirm-oauth.js', @@ -32,6 +33,14 @@ importScripts( 'content/activation-utils.js' ); +const SHARED_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.() || []; +const STEP_IDS = SHARED_STEP_DEFINITIONS + .map((definition) => Number(definition?.id)) + .filter(Number.isFinite) + .sort((left, right) => left - right); +const LAST_STEP_ID = STEP_IDS[STEP_IDS.length - 1] || 10; +const FINAL_OAUTH_CHAIN_START_STEP = 7; + const { extractVerificationCodeFromMessage, filterHotmailAccountsByUsage, @@ -144,7 +153,7 @@ const AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0; const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600; const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds']; const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit'; -const DEFAULT_CPA_CALLBACK_MODE = 'step8'; +const DEFAULT_CPA_CALLBACK_MODE = 'step9'; const MAIL_2925_MODE_PROVIDE = 'provide'; const MAIL_2925_MODE_RECEIVE = 'receive'; const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE; @@ -258,10 +267,7 @@ const PRE_LOGIN_COOKIE_CLEAR_ORIGINS = [ const DEFAULT_STATE = { currentStep: 0, // 当前流程执行到的步骤编号。 - stepStatuses: { - 1: 'pending', 2: 'pending', 3: 'pending', 4: 'pending', 5: 'pending', // 运行时步骤状态映射,不要手动预填。 - 6: 'pending', 7: 'pending', 8: 'pending', 9: 'pending', - }, + stepStatuses: Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending'])), oauthUrl: null, // 运行时抓取到的 OAuth 地址,不要手动预填。 email: null, // 运行时邮箱,由程序自动获取并写入,不能手动预填。 password: null, // 运行时实际密码,由 customPassword 或程序自动生成后写入。 @@ -610,9 +616,14 @@ function normalizeLocalCpaStep9Mode(value = '') { } function normalizeCpaCallbackMode(value = '') { - return String(value || '').trim().toLowerCase() === 'step6' - ? 'step6' - : DEFAULT_CPA_CALLBACK_MODE; + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'step7' || normalized === 'step6') { + return 'step7'; + } + if (normalized === 'step9' || normalized === 'step8') { + return 'step9'; + } + return DEFAULT_CPA_CALLBACK_MODE; } function normalizeCloudflareDomain(rawValue = '') { @@ -3491,7 +3502,7 @@ function shouldSkipLoginVerificationForCpaCallback(state) { return navigationUtils.shouldSkipLoginVerificationForCpaCallback(state); } return getPanelMode(state) === 'cpa' - && normalizeCpaCallbackMode(state?.cpaCallbackMode) === 'step6'; + && normalizeCpaCallbackMode(state?.cpaCallbackMode) === 'step7'; } function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) { @@ -3806,7 +3817,7 @@ function getFirstUnfinishedStep(statuses = {}) { if (typeof loggingStatus !== 'undefined' && loggingStatus?.getFirstUnfinishedStep) { return loggingStatus.getFirstUnfinishedStep(statuses); } - for (let step = 1; step <= 9; step++) { + for (const step of STEP_IDS) { if (!isStepDoneStatus(statuses[step] || 'pending')) return step; } return null; @@ -3858,14 +3869,14 @@ function getDownstreamStateResets(step) { localhostUrl: null, }; } - if (step === 5 || step === 6 || step === 7) { + if (step === 5 || step === 6 || step === 7 || step === 8) { return { lastLoginCode: null, loginVerificationRequestedAt: null, localhostUrl: null, }; } - if (step === 8) { + if (step === 9) { return { localhostUrl: null, }; @@ -3879,7 +3890,7 @@ async function invalidateDownstreamAfterStepRestart(step, options = {}) { const statuses = { ...(state.stepStatuses || {}) }; const changedSteps = []; - for (let downstream = step + 1; downstream <= 9; downstream++) { + for (let downstream = step + 1; downstream <= LAST_STEP_ID; downstream++) { if (statuses[downstream] !== 'pending') { statuses[downstream] = 'pending'; changedSteps.push(downstream); @@ -4307,7 +4318,7 @@ async function ensureManualInteractionAllowed(actionLabel) { async function skipStep(step) { const state = await ensureManualInteractionAllowed('跳过步骤'); - if (!Number.isInteger(step) || step < 1 || step > 9) { + if (!Number.isInteger(step) || !STEP_IDS.includes(step)) { throw new Error(`无效步骤:${step}`); } @@ -4367,7 +4378,7 @@ async function clickWithDebugger(tabId, rect) { throw new Error('未找到用于调试点击的认证页面标签页。'); } if (!rect || !Number.isFinite(rect.centerX) || !Number.isFinite(rect.centerY)) { - throw new Error('步骤 8 的调试器兜底点击需要有效的按钮坐标。'); + throw new Error('步骤 9 的调试器兜底点击需要有效的按钮坐标。'); } const target = { tabId }; @@ -4375,7 +4386,7 @@ async function clickWithDebugger(tabId, rect) { await chrome.debugger.attach(target, '1.3'); } catch (err) { throw new Error( - `步骤 8 的调试器兜底点击附加失败:${err.message}。` + + `步骤 9 的调试器兜底点击附加失败:${err.message}。` + '如果认证页标签已打开 DevTools,请先关闭后重试。' ); } @@ -4500,7 +4511,7 @@ async function handleStepData(step, payload) { await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt }); } break; - case 6: + case 7: if (payload.loginVerificationRequestedAt) { await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt }); } @@ -4511,22 +4522,22 @@ async function handleStepData(step, payload) { signupVerificationRequestedAt: null, }); break; - case 7: + case 8: await setState({ lastEmailTimestamp: payload.emailTimestamp || null, loginVerificationRequestedAt: null, }); break; - case 8: + case 9: if (payload.localhostUrl) { if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) { - throw new Error('步骤 8 返回了无效的 localhost OAuth 回调地址。'); + throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。'); } await setState({ localhostUrl: payload.localhostUrl }); broadcastDataUpdate({ localhostUrl: payload.localhostUrl }); } break; - case 9: { + case 10: { if (payload.localhostUrl) { await closeLocalhostCallbackTabs(payload.localhostUrl); } @@ -4571,8 +4582,8 @@ async function handleStepData(step, payload) { const stepWaiters = new Map(); 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]); -const STEP_COMPLETION_SIGNAL_STEPS = new Set([3, 5, 9]); +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]); function waitForStepComplete(step, timeoutMs = 120000) { return new Promise((resolve, reject) => { @@ -4618,11 +4629,11 @@ async function completeStepFromBackground(step, payload = {}) { return; } - const completionState = step === 9 ? await getState() : null; + const completionState = step === LAST_STEP_ID ? await getState() : null; await setStepStatus(step, 'completed'); await addLog(`步骤 ${step} 已完成`, 'ok'); await handleStepData(step, payload); - if (step === 9) { + if (step === LAST_STEP_ID) { await appendAndBroadcastAccountRunRecord('success', completionState); } notifyStepComplete(step, payload); @@ -5274,7 +5285,7 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { async function runAutoSequenceFromStep(startStep, context = {}) { const { targetRun, totalRuns, attemptRuns, continued = false } = context; - let postStep6RestartCount = 0; + let postStep7RestartCount = 0; if (continued) { await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续当前进度,从步骤 ${startStep} 开始(第 ${attemptRuns} 次尝试)===`, 'info'); @@ -5315,12 +5326,12 @@ async function runAutoSequenceFromStep(startStep, context = {}) { } let step = Math.max(startStep, 4); - while (step <= 9) { + while (step <= LAST_STEP_ID) { try { await executeStepAndWait(step, AUTO_STEP_DELAYS[step]); const latestState = await getState(); - if (step === 6 && shouldSkipLoginVerificationForCpaCallback(latestState)) { - step = 8; + if (step === FINAL_OAUTH_CHAIN_START_STEP && shouldSkipLoginVerificationForCpaCallback(latestState)) { + step = 9; continue; } step += 1; @@ -5331,7 +5342,7 @@ async function runAutoSequenceFromStep(startStep, context = {}) { const restartDecision = await getPostStep6AutoRestartDecision(step, err); if (restartDecision.shouldRestart) { - postStep6RestartCount += 1; + postStep7RestartCount += 1; const authState = restartDecision.authState; const authStateLabel = authState?.state ? getLoginAuthStateLabel(authState.state) : '未知页面'; const authStateSuffix = authState?.url @@ -5340,19 +5351,19 @@ async function runAutoSequenceFromStep(startStep, context = {}) { ? `当前认证页:${authStateLabel}` : '未获取到认证页状态'; await addLog( - `步骤 ${step}:检测到报错且当前未进入 add-phone,正在回到步骤 6 重新开始授权流程(第 ${postStep6RestartCount} 次重开)。${authStateSuffix};原因:${restartDecision.errorMessage || '未知错误'}`, + `步骤 ${step}:检测到报错且当前未进入 add-phone,正在回到步骤 7 重新开始授权流程(第 ${postStep7RestartCount} 次重开)。${authStateSuffix};原因:${restartDecision.errorMessage || '未知错误'}`, 'warn' ); - await invalidateDownstreamAfterStepRestart(5, { - logLabel: `步骤 ${step} 报错后准备回到步骤 6 重试(第 ${postStep6RestartCount} 次重开)`, + await invalidateDownstreamAfterStepRestart(6, { + logLabel: `步骤 ${step} 报错后准备回到步骤 7 重试(第 ${postStep7RestartCount} 次重开)`, }); - step = 6; + step = 7; continue; } if (restartDecision.blockedByAddPhone) { const addPhoneUrl = restartDecision.authState?.url || 'https://auth.openai.com/add-phone'; - await addLog(`步骤 ${step}:检测到认证流程进入 add-phone(${addPhoneUrl}),停止自动回到步骤 6 重开。`, 'warn'); + await addLog(`步骤 ${step}:检测到认证流程进入 add-phone(${addPhoneUrl}),停止自动回到步骤 7 重开。`, 'warn'); } throw err; } @@ -5586,6 +5597,10 @@ const step5Executor = self.MultiPageBackgroundStep5?.createStep5Executor({ sendToContentScript, }); const step6Executor = self.MultiPageBackgroundStep6?.createStep6Executor({ + completeStepFromBackground, + runPreStep6CookieCleanup, +}); +const step7Executor = self.MultiPageBackgroundStep7?.createStep7Executor({ addLog, completeStepFromBackground, getErrorMessage, @@ -5595,20 +5610,19 @@ const step6Executor = self.MultiPageBackgroundStep6?.createStep6Executor({ isStep6SuccessResult, refreshOAuthUrlBeforeStep6, reuseOrCreateTab, - runPreStep6CookieCleanup, sendToContentScriptResilient, shouldSkipLoginVerificationForCpaCallback, skipLoginVerificationStepsForCpaCallback, STEP6_MAX_ATTEMPTS, throwIfStopped, }); -const step7Executor = self.MultiPageBackgroundStep7?.createStep7Executor({ +const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({ addLog, chrome, CLOUDFLARE_TEMP_EMAIL_PROVIDER, confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass, - ensureStep7VerificationPageReady, - executeStep6: (...args) => executeStep6(...args), + ensureStep8VerificationPageReady, + executeStep7: (...args) => executeStep7(...args), getPanelMode, getMailConfig, getState, @@ -5628,7 +5642,7 @@ const step7Executor = self.MultiPageBackgroundStep7?.createStep7Executor({ STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS, throwIfStopped, }); -const step9Executor = self.MultiPageBackgroundStep9?.createStep9Executor({ +const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({ addLog, chrome, closeConflictingTabsForSource, @@ -5646,17 +5660,18 @@ const step9Executor = self.MultiPageBackgroundStep9?.createStep9Executor({ shouldBypassStep9ForLocalCpa, SUB2API_STEP9_RESPONSE_TIMEOUT_MS, }); -const stepDefinitions = self.MultiPageStepDefinitions?.getSteps?.() || []; +const stepDefinitions = SHARED_STEP_DEFINITIONS; const stepExecutorsByKey = { 'open-chatgpt': () => step1Executor.executeStep1(), 'submit-signup-email': (state) => step2Executor.executeStep2(state), 'fill-password': (state) => step3Executor.executeStep3(state), 'fetch-signup-code': (state) => step4Executor.executeStep4(state), 'fill-profile': (state) => step5Executor.executeStep5(state), - 'oauth-login': (state) => step6Executor.executeStep6(state), - 'fetch-login-code': (state) => step7Executor.executeStep7(state), - 'confirm-oauth': (state) => step8Executor.executeStep8(state), - 'platform-verify': (state) => step9Executor.executeStep9(state), + 'clear-login-cookies': () => step6Executor.executeStep6(), + 'oauth-login': (state) => step7Executor.executeStep7(state), + 'fetch-login-code': (state) => step8Executor.executeStep8(state), + 'confirm-oauth': (state) => step9Executor.executeStep9(state), + 'platform-verify': (state) => step10Executor.executeStep10(state), }; const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter({ addLog, @@ -6064,13 +6079,13 @@ async function runPreStep6CookieCleanup() { } // ============================================================ -// Step 6: Login and ensure the auth page reaches the login verification page +// Step 7: Login and ensure the auth page reaches the login verification page // ============================================================ async function refreshOAuthUrlBeforeStep6(state) { - await addLog(`步骤 6:正在刷新登录用的 ${getPanelModeLabel(state)} OAuth 链接...`); + await addLog(`步骤 7:正在刷新登录用的 ${getPanelModeLabel(state)} OAuth 链接...`); console.log(LOG_PREFIX, '[refreshOAuthUrlBeforeStep6] requesting fresh OAuth directly from panel'); - const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: '步骤 6' }); + const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: '步骤 7' }); await handleStepData(1, refreshResult); if (!refreshResult?.oauthUrl) { @@ -6101,7 +6116,7 @@ function isAddPhoneAuthState(authState = {}) { async function getPostStep6AutoRestartDecision(step, error) { const normalizedStep = Number(step); const errorMessage = getErrorMessage(error); - if (!Number.isFinite(normalizedStep) || normalizedStep < 6 || normalizedStep > 9) { + if (!Number.isFinite(normalizedStep) || normalizedStep < 7 || normalizedStep > LAST_STEP_ID) { return { shouldRestart: false, blockedByAddPhone: false, @@ -6122,7 +6137,7 @@ async function getPostStep6AutoRestartDecision(step, error) { let authState = null; try { authState = await getLoginAuthStateFromContent({ - logMessage: `步骤 ${normalizedStep}:正在确认当前认证页状态,以决定是否回到步骤 6 重开...`, + logMessage: `步骤 ${normalizedStep}:正在确认当前认证页状态,以决定是否回到步骤 7 重开...`, }); } catch (inspectError) { console.warn(LOG_PREFIX, '[AutoRun] failed to inspect login auth state after post-step6 error', { @@ -6150,7 +6165,7 @@ async function getPostStep6AutoRestartDecision(step, error) { } async function getLoginAuthStateFromContent(options = {}) { - const { logMessage = '步骤 7:认证页正在切换,等待页面重新就绪后继续确认验证码页状态...' } = options; + const { logMessage = '步骤 8:认证页正在切换,等待页面重新就绪后继续确认验证码页状态...' } = options; const result = await sendToContentScriptResilient( 'signup-page', { @@ -6172,7 +6187,7 @@ async function getLoginAuthStateFromContent(options = {}) { return result || {}; } -async function ensureStep7VerificationPageReady() { +async function ensureStep8VerificationPageReady() { const pageState = await getLoginAuthStateFromContent(); if (pageState.state === 'verification_page') { return pageState; @@ -6180,7 +6195,7 @@ async function ensureStep7VerificationPageReady() { const stateLabel = getLoginAuthStateLabel(pageState.state); const urlPart = pageState.url ? ` URL: ${pageState.url}` : ''; - throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 6。当前状态:${stateLabel}.${urlPart}`.trim()); + throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:${stateLabel}.${urlPart}`.trim()); } async function skipLoginVerificationStepsForCpaCallback() { @@ -6188,21 +6203,21 @@ async function skipLoginVerificationStepsForCpaCallback() { lastLoginCode: null, loginVerificationRequestedAt: null, }); - await setStepStatus(6, 'skipped'); - await addLog('步骤 6:当前已选择“第六步回调”,直接跳过步骤 6、7。', 'warn'); + await setStepStatus(7, 'skipped'); + await addLog('步骤 7:当前已选择“第七步回调”,直接跳过步骤 7、8。', 'warn'); const latestState = await getState(); - if (!isStepDoneStatus(latestState.stepStatuses?.[7])) { - await setStepStatus(7, 'skipped'); - await addLog('步骤 7:当前已选择“第六步回调”,本轮无需获取登录验证码。', 'warn'); + if (!isStepDoneStatus(latestState.stepStatuses?.[8])) { + await setStepStatus(8, 'skipped'); + await addLog('步骤 8:当前已选择“第七步回调”,本轮无需获取登录验证码。', 'warn'); } } -async function executeStep6(state, options = {}) { - return step6Executor.executeStep6(state, options); +async function executeStep6() { + return step6Executor.executeStep6(); } // ============================================================ -// Step 7: Poll login verification mail and submit the login code +// Step 7: Refresh OAuth and log in // ============================================================ async function executeStep7(state) { @@ -6210,7 +6225,15 @@ async function executeStep7(state) { } // ============================================================ -// Step 8: 完成 OAuth(自动点击 + localhost 回调监听) +// Step 8: Poll login verification mail and submit the login code +// ============================================================ + +async function executeStep8(state) { + return step8Executor.executeStep8(state); +} + +// ============================================================ +// Step 9: 完成 OAuth(自动点击 + localhost 回调监听) // ============================================================ let webNavListener = null; @@ -6323,12 +6346,12 @@ async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS) throwIfStopped(); const pageState = await getStep8PageState(tabId); if (pageState?.addPhonePage) { - throw new Error('步骤 8:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。'); + throw new Error('步骤 9:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。'); } if (pageState?.retryPage) { await recoverAuthRetryPageOnTab(tabId, { flow: 'auth', - logLabel: '步骤 8:检测到认证页重试页,正在点击“重试”恢复', + logLabel: '步骤 9:检测到认证页重试页,正在点击“重试”恢复', step: 8, timeoutMs: Math.max(5000, Math.min(12000, timeoutMs)), }); @@ -6338,7 +6361,7 @@ async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS) } if (pageState?.consentReady) { if (retryRecovered) { - await addLog('步骤 8:认证页重试页已恢复,准备重新定位“继续”按钮...', 'info'); + await addLog('步骤 9:认证页重试页已恢复,准备重新定位“继续”按钮...', 'info'); } return pageState; } @@ -6346,7 +6369,7 @@ async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS) recovered = true; await ensureStep8SignupPageReady(tabId, { timeoutMs: Math.min(10000, timeoutMs), - logMessage: '步骤 8:认证页内容脚本已失联,正在等待页面重新就绪...', + logMessage: '步骤 9:认证页内容脚本已失联,正在等待页面重新就绪...', }); continue; } @@ -6354,13 +6377,13 @@ async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS) await sleepWithStop(250); } - throw new Error('步骤 8:长时间未进入 OAuth 同意页,无法定位“继续”按钮。'); + throw new Error('步骤 9:长时间未进入 OAuth 同意页,无法定位“继续”按钮。'); } async function prepareStep8DebuggerClick(tabId) { await ensureStep8SignupPageReady(tabId, { timeoutMs: 15000, - logMessage: '步骤 8:认证页内容脚本已失联,正在恢复后继续定位按钮...', + logMessage: '步骤 9:认证页内容脚本已失联,正在恢复后继续定位按钮...', }); const result = await sendToContentScriptResilient('signup-page', { type: 'STEP8_FIND_AND_CLICK', @@ -6369,7 +6392,7 @@ async function prepareStep8DebuggerClick(tabId) { }, { timeoutMs: 15000, retryDelayMs: 600, - logMessage: '步骤 8:认证页正在切换,等待 OAuth 同意页按钮重新就绪...', + logMessage: '步骤 9:认证页正在切换,等待 OAuth 同意页按钮重新就绪...', }); if (result?.error) { @@ -6382,7 +6405,7 @@ async function prepareStep8DebuggerClick(tabId) { async function triggerStep8ContentStrategy(tabId, strategy) { await ensureStep8SignupPageReady(tabId, { timeoutMs: 15000, - logMessage: '步骤 8:认证页内容脚本已失联,正在恢复后继续点击“继续”按钮...', + logMessage: '步骤 9:认证页内容脚本已失联,正在恢复后继续点击“继续”按钮...', }); const result = await sendToContentScriptResilient('signup-page', { type: 'STEP8_TRIGGER_CONTINUE', @@ -6395,7 +6418,7 @@ async function triggerStep8ContentStrategy(tabId, strategy) { }, { timeoutMs: 15000, retryDelayMs: 600, - logMessage: '步骤 8:认证页正在切换,等待“继续”按钮重新就绪...', + logMessage: '步骤 9:认证页正在切换,等待“继续”按钮重新就绪...', }); if (result?.error) { @@ -6409,7 +6432,7 @@ async function recoverAuthRetryPageOnTab(tabId, payload = {}, options = {}) { await ensureStep8SignupPageReady(tabId, { timeoutMs: options.readyTimeoutMs ?? 15000, retryDelayMs: options.retryDelayMs ?? 600, - logMessage: options.readyLogMessage || '步骤 8:认证页内容脚本已失联,正在恢复后继续处理重试页...', + logMessage: options.readyLogMessage || '步骤 9:认证页内容脚本已失联,正在恢复后继续处理重试页...', }); const result = await sendToContentScriptResilient('signup-page', { type: 'RECOVER_AUTH_RETRY_PAGE', @@ -6418,7 +6441,7 @@ async function recoverAuthRetryPageOnTab(tabId, payload = {}, options = {}) { }, { timeoutMs: options.timeoutMs ?? 15000, retryDelayMs: options.retryDelayMs ?? 600, - logMessage: options.logMessage || '步骤 8:认证页正在切换,等待“重试”按钮重新就绪...', + logMessage: options.logMessage || '步骤 9:认证页正在切换,等待“重试”按钮重新就绪...', }); if (result?.error) { @@ -6430,7 +6453,7 @@ async function recoverAuthRetryPageOnTab(tabId, payload = {}, options = {}) { async function reloadStep8ConsentPage(tabId, timeoutMs = 30000) { if (!Number.isInteger(tabId)) { - throw new Error('步骤 8:缺少有效的认证页标签页,无法刷新后重试。'); + throw new Error('步骤 9:缺少有效的认证页标签页,无法刷新后重试。'); } await chrome.tabs.update(tabId, { active: true }).catch(() => { }); @@ -6441,7 +6464,7 @@ async function reloadStep8ConsentPage(tabId, timeoutMs = 30000) { if (settled) return; settled = true; chrome.tabs.onUpdated.removeListener(listener); - reject(new Error('步骤 8:刷新认证页后等待页面完成加载超时。')); + reject(new Error('步骤 9:刷新认证页后等待页面完成加载超时。')); }, timeoutMs); const listener = (updatedTabId, changeInfo) => { @@ -6466,7 +6489,7 @@ async function reloadStep8ConsentPage(tabId, timeoutMs = 30000) { await ensureStep8SignupPageReady(tabId, { timeoutMs: Math.min(15000, timeoutMs), - logMessage: '步骤 8:认证页刷新后内容脚本尚未就绪,正在等待页面恢复...', + logMessage: '步骤 9:认证页刷新后内容脚本尚未就绪,正在等待页面恢复...', }); } @@ -6479,7 +6502,7 @@ async function waitForStep8ClickEffect(tabId, baselineUrl, timeoutMs = STEP8_CLI const tab = await chrome.tabs.get(tabId).catch(() => null); if (!tab) { - throw new Error('步骤 8:认证页面标签页已关闭,无法继续自动授权。'); + throw new Error('步骤 9:认证页面标签页已关闭,无法继续自动授权。'); } if (baselineUrl && typeof tab.url === 'string' && tab.url !== baselineUrl) { @@ -6488,12 +6511,12 @@ async function waitForStep8ClickEffect(tabId, baselineUrl, timeoutMs = STEP8_CLI const pageState = await getStep8PageState(tabId); if (pageState?.addPhonePage) { - throw new Error('步骤 8:点击“继续”后页面跳到了手机号页面,当前流程无法继续自动授权。'); + throw new Error('步骤 9:点击“继续”后页面跳到了手机号页面,当前流程无法继续自动授权。'); } if (pageState?.retryPage) { await recoverAuthRetryPageOnTab(tabId, { flow: 'auth', - logLabel: '步骤 8:点击“继续”后进入重试页,正在点击“重试”恢复', + logLabel: '步骤 9:点击“继续”后进入重试页,正在点击“重试”恢复', step: 8, timeoutMs: Math.max(5000, Math.min(12000, timeoutMs)), }); @@ -6509,7 +6532,7 @@ async function waitForStep8ClickEffect(tabId, baselineUrl, timeoutMs = STEP8_CLI recovered = true; await ensureStep8SignupPageReady(tabId, { timeoutMs: Math.max(3000, Math.min(8000, timeoutMs)), - logMessage: '步骤 8:点击后认证页正在重载,正在等待内容脚本重新就绪...', + logMessage: '步骤 9:点击后认证页正在重载,正在等待内容脚本重新就绪...', }).catch(() => null); continue; } @@ -6547,7 +6570,7 @@ function getStep8EffectLabel(effect) { } } -const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({ +const step9Executor = self.MultiPageBackgroundStep9?.createStep9Executor({ addLog, chrome, cleanupStep8NavigationListeners, @@ -6580,16 +6603,16 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({ waitForStep8Ready, }); -async function executeStep8(state) { - return step8Executor.executeStep8(state); +async function executeStep9(state) { + return step9Executor.executeStep9(state); } // ============================================================ -// Step 9: 平台回调验证 +// Step 10: 平台回调验证 // ============================================================ -async function executeStep9(state) { - return step9Executor.executeStep9(state); +async function executeStep10(state) { + return step10Executor.executeStep10(state); } // ============================================================ diff --git a/background/logging-status.js b/background/logging-status.js index 51e98d8..9b98972 100644 --- a/background/logging-status.js +++ b/background/logging-status.js @@ -102,7 +102,11 @@ } function getFirstUnfinishedStep(statuses = {}) { - for (let step = 1; step <= 9; step++) { + const stepIds = Object.keys(DEFAULT_STATE.stepStatuses || {}) + .map((step) => Number(step)) + .filter(Number.isFinite) + .sort((left, right) => left - right); + for (const step of stepIds) { if (!isStepDoneStatus(statuses[step] || 'pending')) { return step; } diff --git a/background/message-router.js b/background/message-router.js index a340e05..14a8e6f 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -132,7 +132,7 @@ await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt }); } break; - case 6: + case 7: if (payload.loginVerificationRequestedAt) { await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt }); } @@ -143,22 +143,22 @@ signupVerificationRequestedAt: null, }); break; - case 7: + case 8: await setState({ lastEmailTimestamp: payload.emailTimestamp || null, loginVerificationRequestedAt: null, }); break; - case 8: + case 9: if (payload.localhostUrl) { if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) { - throw new Error('步骤 8 返回了无效的 localhost OAuth 回调地址。'); + throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。'); } await setState({ localhostUrl: payload.localhostUrl }); broadcastDataUpdate({ localhostUrl: payload.localhostUrl }); } break; - case 9: { + case 10: { if (payload.localhostUrl) { await closeLocalhostCallbackTabs(payload.localhostUrl); } @@ -232,11 +232,11 @@ return { ok: true, error: errorMessage }; } - const completionState = message.step === 9 ? await getState() : null; + const completionState = message.step === 10 ? await getState() : null; await setStepStatus(message.step, 'completed'); await addLog(`步骤 ${message.step} 已完成`, 'ok'); await handleStepData(message.step, message.payload); - if (message.step === 9 && typeof appendAccountRunRecord === 'function') { + if (message.step === 10 && typeof appendAccountRunRecord === 'function') { await appendAccountRunRecord('success', completionState); } notifyStepComplete(message.step, message.payload); diff --git a/background/navigation-utils.js b/background/navigation-utils.js index 4926e63..067e08c 100644 --- a/background/navigation-utils.js +++ b/background/navigation-utils.js @@ -92,7 +92,7 @@ function shouldSkipLoginVerificationForCpaCallback(state) { return getPanelMode(state) === 'cpa' - && normalizeCpaCallbackMode(state?.cpaCallbackMode) === 'step6'; + && normalizeCpaCallbackMode(state?.cpaCallbackMode) === 'step7'; } function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) { diff --git a/background/steps/clear-login-cookies.js b/background/steps/clear-login-cookies.js new file mode 100644 index 0000000..10b87ea --- /dev/null +++ b/background/steps/clear-login-cookies.js @@ -0,0 +1,19 @@ +(function attachBackgroundStep6(root, factory) { + root.MultiPageBackgroundStep6 = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep6Module() { + function createStep6Executor(deps = {}) { + const { + completeStepFromBackground, + runPreStep6CookieCleanup, + } = deps; + + async function executeStep6() { + await runPreStep6CookieCleanup(); + await completeStepFromBackground(6); + } + + return { executeStep6 }; + } + + return { createStep6Executor }; +}); diff --git a/background/steps/confirm-oauth.js b/background/steps/confirm-oauth.js index 12a0b90..b43a937 100644 --- a/background/steps/confirm-oauth.js +++ b/background/steps/confirm-oauth.js @@ -1,7 +1,7 @@ -(function attachBackgroundStep8(root, factory) { - root.MultiPageBackgroundStep8 = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() { - function createStep8Executor(deps = {}) { +(function attachBackgroundStep9(root, factory) { + root.MultiPageBackgroundStep9 = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep9Module() { + function createStep9Executor(deps = {}) { const { addLog, chrome, @@ -32,12 +32,12 @@ setStep8TabUpdatedListener, } = deps; - async function executeStep8(state) { + async function executeStep9(state) { if (!state.oauthUrl) { - throw new Error('缺少登录用 OAuth 链接,请先完成步骤 6。'); + throw new Error('缺少登录用 OAuth 链接,请先完成步骤 7。'); } - await addLog('步骤 8:正在监听 localhost 回调地址...'); + await addLog('步骤 9:正在监听 localhost 回调地址...'); return new Promise((resolve, reject) => { let resolved = false; @@ -48,7 +48,7 @@ setStep8PendingReject(null); }; - const rejectStep8 = (error) => { + const rejectStep9 = (error) => { if (resolved) return; resolved = true; clearTimeout(timeout); @@ -56,15 +56,15 @@ reject(error); }; - const finalizeStep8Callback = (callbackUrl) => { + const finalizeStep9Callback = (callbackUrl) => { if (resolved || !callbackUrl) return; resolved = true; cleanupListener(); clearTimeout(timeout); - addLog(`步骤 8:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => { - return completeStepFromBackground(8, { localhostUrl: callbackUrl }); + addLog(`步骤 9:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => { + return completeStepFromBackground(9, { localhostUrl: callbackUrl }); }).then(() => { resolve(); }).catch((err) => { @@ -73,26 +73,26 @@ }; const timeout = setTimeout(() => { - rejectStep8(new Error('120 秒内未捕获到 localhost 回调跳转,步骤 8 的点击可能被拦截了。')); + rejectStep9(new Error('120 秒内未捕获到 localhost 回调跳转,步骤 9 的点击可能被拦截了。')); }, 120000); setStep8PendingReject((error) => { - rejectStep8(error); + rejectStep9(error); }); setWebNavListener((details) => { const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId); - finalizeStep8Callback(callbackUrl); + finalizeStep9Callback(callbackUrl); }); setWebNavCommittedListener((details) => { const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId); - finalizeStep8Callback(callbackUrl); + finalizeStep9Callback(callbackUrl); }); setStep8TabUpdatedListener((tabId, changeInfo, tab) => { const callbackUrl = getStep8CallbackUrlFromTabUpdate(tabId, changeInfo, tab, signupTabId); - finalizeStep8Callback(callbackUrl); + finalizeStep9Callback(callbackUrl); }); (async () => { @@ -103,10 +103,10 @@ if (signupTabId && await isTabAlive('signup-page')) { await chrome.tabs.update(signupTabId, { active: true }); - await addLog('步骤 8:已切回认证页,正在准备调试器点击...'); + await addLog('步骤 9:已切回认证页,正在准备调试器点击...'); } else { signupTabId = await reuseOrCreateTab('signup-page', state.oauthUrl); - await addLog('步骤 8:已重新打开认证页,正在准备调试器点击...'); + await addLog('步骤 9:已重新打开认证页,正在准备调试器点击...'); } throwIfStep8SettledOrStopped(resolved); @@ -115,7 +115,7 @@ chrome.tabs.onUpdated.addListener(deps.getStep8TabUpdatedListener()); await ensureStep8SignupPageReady(signupTabId, { timeoutMs: 15000, - logMessage: '步骤 8:认证页内容脚本尚未就绪,正在等待页面恢复...', + logMessage: '步骤 9:认证页内容脚本尚未就绪,正在等待页面恢复...', }); for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) { @@ -128,7 +128,7 @@ const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)]; - await addLog(`步骤 8:第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label})...`); + await addLog(`步骤 9:第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label})...`); if (strategy.mode === 'debugger') { const clickTarget = await prepareStep8DebuggerClick(signupTabId); @@ -148,33 +148,33 @@ } if (effect.progressed) { - await addLog(`步骤 8:检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info'); + await addLog(`步骤 9:检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info'); break; } if (effect.restartCurrentStep) { - await addLog(`步骤 8:${getStep8EffectLabel(effect)},准备重新定位“继续”按钮并重试...`, 'warn'); + await addLog(`步骤 9:${getStep8EffectLabel(effect)},准备重新定位“继续”按钮并重试...`, 'warn'); await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS); continue; } if (round >= STEP8_MAX_ROUNDS) { - throw new Error(`步骤 8:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`); + throw new Error(`步骤 9:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`); } - await addLog(`步骤 8:${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS})...`, 'warn'); + await addLog(`步骤 9:${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS})...`, 'warn'); await reloadStep8ConsentPage(signupTabId); await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS); } } catch (err) { - rejectStep8(err); + rejectStep9(err); } })(); }); } - return { executeStep8 }; + return { executeStep9 }; } - return { createStep8Executor }; + return { createStep9Executor }; }); diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index 89d2d77..beb4f60 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -1,14 +1,14 @@ -(function attachBackgroundStep7(root, factory) { - root.MultiPageBackgroundStep7 = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep7Module() { - function createStep7Executor(deps = {}) { +(function attachBackgroundStep8(root, factory) { + root.MultiPageBackgroundStep8 = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() { + function createStep8Executor(deps = {}) { const { addLog, chrome, CLOUDFLARE_TEMP_EMAIL_PROVIDER, confirmCustomVerificationStepBypass, - ensureStep7VerificationPageReady, - executeStep6, + ensureStep8VerificationPageReady, + executeStep7, getPanelMode, getMailConfig, getState, @@ -29,7 +29,7 @@ throwIfStopped, } = deps; - async function runStep7Attempt(state) { + async function runStep8Attempt(state) { const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); const stepStartedAt = Date.now(); @@ -39,25 +39,25 @@ await chrome.tabs.update(authTabId, { active: true }); } else { if (!state.oauthUrl) { - throw new Error('缺少登录用 OAuth 链接,请先完成步骤 6。'); + throw new Error('缺少登录用 OAuth 链接,请先完成步骤 7。'); } await reuseOrCreateTab('signup-page', state.oauthUrl); } throwIfStopped(); - await ensureStep7VerificationPageReady(); - await addLog('步骤 7:登录验证码页面已就绪,开始获取验证码。', 'info'); + await ensureStep8VerificationPageReady(); + await addLog('步骤 8:登录验证码页面已就绪,开始获取验证码。', 'info'); if (shouldUseCustomRegistrationEmail(state)) { - await confirmCustomVerificationStepBypass(7); + await confirmCustomVerificationStepBypass(8); return; } throwIfStopped(); if (mail.provider === HOTMAIL_PROVIDER || mail.provider === LUCKMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) { - await addLog(`步骤 7:正在通过 ${mail.label} 轮询验证码...`); + await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`); } else { - await addLog(`步骤 7:正在打开${mail.label}...`); + await addLog(`步骤 8:正在打开${mail.label}...`); const alive = await isTabAlive(mail.source); if (alive) { @@ -79,54 +79,52 @@ } const shouldRefreshOAuthBeforeSubmit = getPanelMode(state) === 'cpa'; - let step6ReplayCompleted = false; + let step7ReplayCompleted = false; - await resolveVerificationStep(7, state, mail, { + await resolveVerificationStep(8, state, mail, { filterAfterTimestamp: mail.provider === HOTMAIL_PROVIDER ? undefined : Math.max(0, stepStartedAt - 60000), requestFreshCodeFirst: false, resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925') ? 0 : STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS, beforeSubmit: shouldRefreshOAuthBeforeSubmit ? async (result) => { - if (step6ReplayCompleted) { + if (step7ReplayCompleted) { return; } - step6ReplayCompleted = true; - await addLog(`步骤 7:已拿到登录验证码 ${result.code},先刷新 CPA OAuth 链接并重走步骤 6,再回填验证码。`, 'warn'); - await rerunStep6ForStep7Recovery({ - logMessage: '步骤 7:正在重新获取最新 CPA OAuth 链接,并快速重走步骤 6...', - skipPreLoginCleanup: true, + step7ReplayCompleted = true; + await addLog(`步骤 8:已拿到登录验证码 ${result.code},先刷新 CPA OAuth 链接并重走步骤 7,再回填验证码。`, 'warn'); + await rerunStep7ForStep8Recovery({ + logMessage: '步骤 8:正在重新获取最新 CPA OAuth 链接,并快速重走步骤 7...', postStepDelayMs: 1200, }); - await ensureStep7VerificationPageReady(); - await addLog('步骤 7:登录验证码页面已重新就绪,开始回填刚才获取到的验证码。', 'info'); + await ensureStep8VerificationPageReady(); + await addLog('步骤 8:登录验证码页面已重新就绪,开始回填刚才获取到的验证码。', 'info'); } : undefined, }); } - async function rerunStep6ForStep7Recovery(options = {}) { + async function rerunStep7ForStep8Recovery(options = {}) { const { - logMessage = '步骤 7:正在回到步骤 6,重新发起登录验证码流程...', - skipPreLoginCleanup = false, + logMessage = '步骤 8:正在回到步骤 7,重新发起登录验证码流程...', postStepDelayMs = 3000, } = options; const currentState = await getState(); await addLog(logMessage, 'warn'); - await executeStep6(currentState, { skipPreLoginCleanup }); + await executeStep7(currentState); if (postStepDelayMs > 0) { await sleepWithStop(postStepDelayMs); } } - async function executeStep7(state) { + async function executeStep8(state) { if (shouldSkipLoginVerificationForCpaCallback(state)) { await setState({ lastLoginCode: null, loginVerificationRequestedAt: null, }); - await setStepStatus(7, 'skipped'); - await addLog('步骤 7:当前已选择“第六步回调”,本轮无需获取登录验证码。', 'warn'); + await setStepStatus(8, 'skipped'); + await addLog('步骤 8:当前已选择“第七步回调”,本轮无需获取登录验证码。', 'warn'); return; } @@ -136,7 +134,7 @@ while (true) { try { - await runStep7Attempt(currentState); + await runStep8Attempt(currentState); return; } catch (err) { if (!isVerificationMailPollingError(err)) { @@ -150,25 +148,25 @@ mailPollingAttempt += 1; await addLog( - `步骤 7:检测到邮箱轮询类失败,准备从步骤 6 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`, + `步骤 8:检测到邮箱轮询类失败,准备从步骤 7 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`, 'warn' ); - await rerunStep6ForStep7Recovery(); + await rerunStep7ForStep8Recovery(); currentState = await getState(); } } if (lastMailPollingError) { throw new Error( - `步骤 7:登录验证码流程在 ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} 轮邮箱轮询恢复后仍未成功。最后一次原因:${lastMailPollingError.message}` + `步骤 8:登录验证码流程在 ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} 轮邮箱轮询恢复后仍未成功。最后一次原因:${lastMailPollingError.message}` ); } - throw new Error('步骤 7:登录验证码流程未成功完成。'); + throw new Error('步骤 8:登录验证码流程未成功完成。'); } - return { executeStep7 }; + return { executeStep8 }; } - return { createStep7Executor }; + return { createStep8Executor }; }); diff --git a/background/steps/oauth-login.js b/background/steps/oauth-login.js index 845bc3f..6e49e92 100644 --- a/background/steps/oauth-login.js +++ b/background/steps/oauth-login.js @@ -1,7 +1,7 @@ -(function attachBackgroundStep6(root, factory) { - root.MultiPageBackgroundStep6 = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep6Module() { - function createStep6Executor(deps = {}) { +(function attachBackgroundStep7(root, factory) { + root.MultiPageBackgroundStep7 = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep7Module() { + function createStep7Executor(deps = {}) { const { addLog, completeStepFromBackground, @@ -12,7 +12,6 @@ isStep6SuccessResult, refreshOAuthUrlBeforeStep6, reuseOrCreateTab, - runPreStep6CookieCleanup, sendToContentScriptResilient, shouldSkipLoginVerificationForCpaCallback, skipLoginVerificationStepsForCpaCallback, @@ -20,8 +19,7 @@ throwIfStopped, } = deps; - async function executeStep6(state, options = {}) { - const { skipPreLoginCleanup = false } = options; + async function executeStep7(state) { if (shouldSkipLoginVerificationForCpaCallback(state)) { await skipLoginVerificationStepsForCpaCallback(); return; @@ -30,10 +28,6 @@ throw new Error('缺少邮箱地址,请先完成步骤 3。'); } - if (!skipPreLoginCleanup) { - await runPreStep6CookieCleanup(); - } - let attempt = 0; let lastError = null; @@ -46,9 +40,9 @@ const oauthUrl = await refreshOAuthUrlBeforeStep6(currentState); if (attempt === 1) { - await addLog('步骤 6:正在打开最新 OAuth 链接并登录...'); + await addLog('步骤 7:正在打开最新 OAuth 链接并登录...'); } else { - await addLog(`步骤 6:上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn'); + await addLog(`步骤 7:上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn'); } await reuseOrCreateTab('signup-page', oauthUrl); @@ -57,7 +51,7 @@ 'signup-page', { type: 'EXECUTE_STEP', - step: 6, + step: 7, source: 'background', payload: { email: currentState.email, @@ -67,7 +61,7 @@ { timeoutMs: 180000, retryDelayMs: 700, - logMessage: '步骤 6:认证页正在切换,等待页面重新就绪后继续登录...', + logMessage: '步骤 7:认证页正在切换,等待页面重新就绪后继续登录...', } ); @@ -76,7 +70,7 @@ } if (isStep6SuccessResult(result)) { - await completeStepFromBackground(6, { + await completeStepFromBackground(7, { loginVerificationRequestedAt: result.loginVerificationRequestedAt || null, }); return; @@ -84,11 +78,11 @@ if (isStep6RecoverableResult(result)) { const reasonMessage = result.message - || `当前停留在${getLoginAuthStateLabel(result.state)},准备重新执行步骤 6。`; + || `当前停留在${getLoginAuthStateLabel(result.state)},准备重新执行步骤 7。`; throw new Error(reasonMessage); } - throw new Error('步骤 6:认证页未返回可识别的登录结果。'); + throw new Error('步骤 7:认证页未返回可识别的登录结果。'); } catch (err) { throwIfStopped(err); lastError = err; @@ -96,15 +90,15 @@ break; } - await addLog(`步骤 6:第 ${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn'); + await addLog(`步骤 7:第 ${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn'); } } - throw new Error(`步骤 6:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`); + throw new Error(`步骤 7:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`); } - return { executeStep6 }; + return { executeStep7 }; } - return { createStep6Executor }; + return { createStep7Executor }; }); diff --git a/background/steps/platform-verify.js b/background/steps/platform-verify.js index 7e46b05..17d0cdc 100644 --- a/background/steps/platform-verify.js +++ b/background/steps/platform-verify.js @@ -1,7 +1,7 @@ -(function attachBackgroundStep9(root, factory) { - root.MultiPageBackgroundStep9 = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep9Module() { - function createStep9Executor(deps = {}) { +(function attachBackgroundStep10(root, factory) { + root.MultiPageBackgroundStep10 = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep10Module() { + function createStep10Executor(deps = {}) { const { addLog, chrome, @@ -21,34 +21,34 @@ SUB2API_STEP9_RESPONSE_TIMEOUT_MS, } = deps; - async function executeStep9(state) { + async function executeStep10(state) { if (getPanelMode(state) === 'sub2api') { - return executeSub2ApiStep9(state); + return executeSub2ApiStep10(state); } - return executeCpaStep9(state); + return executeCpaStep10(state); } - async function executeCpaStep9(state) { + async function executeCpaStep10(state) { if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { - throw new Error('步骤 8 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 8。'); + throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。'); } if (!state.localhostUrl) { - throw new Error('缺少 localhost 回调地址,请先完成步骤 8。'); + throw new Error('缺少 localhost 回调地址,请先完成步骤 9。'); } if (!state.vpsUrl) { throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。'); } if (shouldBypassStep9ForLocalCpa(state)) { - await addLog('步骤 9:检测到本地 CPA,且当前策略为“跳过第9步”,本轮不再重复提交回调地址。', 'info'); - await completeStepFromBackground(9, { + await addLog('步骤 10:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info'); + await completeStepFromBackground(10, { localhostUrl: state.localhostUrl, verifiedStatus: 'local-auto', }); return; } - await addLog('步骤 9:正在打开 CPA 面板...'); + await addLog('步骤 10:正在打开 CPA 面板...'); const injectFiles = ['content/activation-utils.js', 'content/utils.js', 'content/vps-panel.js']; let tabId = await getTabId('vps-panel'); @@ -69,19 +69,19 @@ inject: injectFiles, timeoutMs: 45000, retryDelayMs: 900, - logMessage: '步骤 9:CPA 面板仍在加载,正在重试连接...', + logMessage: '步骤 10:CPA 面板仍在加载,正在重试连接...', }); - await addLog('步骤 9:正在填写回调地址...'); + await addLog('步骤 10:正在填写回调地址...'); const result = await sendToContentScriptResilient('vps-panel', { type: 'EXECUTE_STEP', - step: 9, + step: 10, source: 'background', payload: { localhostUrl: state.localhostUrl, vpsPassword: state.vpsPassword }, }, { timeoutMs: 30000, retryDelayMs: 700, - logMessage: '步骤 9:CPA 面板通信未就绪,正在等待页面恢复...', + logMessage: '步骤 10:CPA 面板通信未就绪,正在等待页面恢复...', }); if (result?.error) { @@ -89,12 +89,12 @@ } } - async function executeSub2ApiStep9(state) { + async function executeSub2ApiStep10(state) { if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { - throw new Error('步骤 8 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 8。'); + throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。'); } if (!state.localhostUrl) { - throw new Error('缺少 localhost 回调地址,请先完成步骤 8。'); + throw new Error('缺少 localhost 回调地址,请先完成步骤 9。'); } if (!state.sub2apiSessionId) { throw new Error('缺少 SUB2API 会话信息,请重新执行步骤 1。'); @@ -109,7 +109,7 @@ const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl); const injectFiles = ['content/utils.js', 'content/sub2api-panel.js']; - await addLog('步骤 9:正在打开 SUB2API 后台...'); + await addLog('步骤 10:正在打开 SUB2API 后台...'); let tabId = await getTabId('sub2api-panel'); const alive = tabId && await isTabAlive('sub2api-panel'); @@ -131,10 +131,10 @@ injectSource: 'sub2api-panel', }); - await addLog('步骤 9:正在向 SUB2API 提交回调并创建账号...'); + await addLog('步骤 10:正在向 SUB2API 提交回调并创建账号...'); const result = await sendToContentScript('sub2api-panel', { type: 'EXECUTE_STEP', - step: 9, + step: 10, source: 'background', payload: { localhostUrl: state.localhostUrl, @@ -157,11 +157,11 @@ } return { - executeCpaStep9, - executeStep9, - executeSub2ApiStep9, + executeCpaStep10, + executeStep10, + executeSub2ApiStep10, }; } - return { createStep9Executor }; + return { createStep10Executor }; }); diff --git a/background/verification-flow.js b/background/verification-flow.js index 6ad59ee..47bb39c 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -78,7 +78,7 @@ } return { - filterAfterTimestamp: getHotmailVerificationRequestTimestamp(7, state), + filterAfterTimestamp: getHotmailVerificationRequestTimestamp(8, state), senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'], subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'], targetEmail: state.email, @@ -116,7 +116,7 @@ if (step === 4) { await setState({ signupVerificationRequestedAt: requestedAt }); } - if (step === 7) { + if (step === 8) { await setState({ loginVerificationRequestedAt: requestedAt }); } @@ -391,7 +391,7 @@ let lastResendAt = Number(options.lastResendAt) || 0; const updateFilterAfterTimestampForVerificationStep = async (requestedAt) => { - if ((step !== 4 && step !== 7) || !requestedAt) { + if ((step !== 4 && step !== 8) || !requestedAt) { return nextFilterAfterTimestamp; } diff --git a/content/signup-page.js b/content/signup-page.js index bc84a6b..d10617a 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -37,7 +37,7 @@ if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1' } if (message.type === 'STEP8_FIND_AND_CLICK') { - log(`步骤 8:${err.message}`, 'error'); + log(`步骤 9:${err.message}`, 'error'); sendResponse({ error: err.message }); return; } @@ -61,8 +61,8 @@ async function handleCommand(message) { case 2: return await step2_clickRegister(message.payload); case 3: return await step3_fillEmailPassword(message.payload); case 5: return await step5_fillNameBirthday(message.payload); - case 6: return await step6_login(message.payload); - case 8: return await step8_findAndClick(); + case 7: return await step6_login(message.payload); + case 9: return await step8_findAndClick(); default: throw new Error(`signup-page.js 不处理步骤 ${message.step}`); } case 'FILL_CODE': @@ -199,7 +199,7 @@ function isEmailVerificationPage() { } async function resendVerificationCode(step, timeout = 45000) { - if (step === 7) { + if (step === 8) { await waitForLoginVerificationPageReady(); } @@ -1185,7 +1185,7 @@ async function waitForLoginVerificationPageReady(timeout = 10000) { } throw new Error( - `当前未进入登录验证码页面,请先重新完成步骤 6。当前状态:${getLoginAuthStateLabel(snapshot)}。URL: ${snapshot?.url || location.href}` + `当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:${getLoginAuthStateLabel(snapshot)}。URL: ${snapshot?.url || location.href}` ); } @@ -1216,15 +1216,15 @@ async function createStep6LoginTimeoutRecoverableResult(reason, snapshot, messag try { const recoveryResult = await recoverCurrentAuthRetryPage({ flow: 'login', - logLabel: '步骤 6:检测到登录超时报错,正在点击“重试”恢复当前页面', + logLabel: '步骤 7:检测到登录超时报错,正在点击“重试”恢复当前页面', step: 6, timeoutMs: 12000, }); if (recoveryResult?.recovered) { - log('步骤 6:登录超时报错页已点击“重试”,准备重新执行当前步骤。', 'warn'); + log('步骤 7:登录超时报错页已点击“重试”,准备重新执行当前步骤。', 'warn'); } } catch (error) { - log(`步骤 6:登录超时报错页自动点击“重试”失败:${error.message}`, 'warn'); + log(`步骤 7:登录超时报错页自动点击“重试”失败:${error.message}`, 'warn'); } } @@ -1248,9 +1248,9 @@ function throwForStep6FatalState(snapshot) { snapshot = normalizeStep6Snapshot(snapshot); switch (snapshot?.state) { case 'oauth_consent_page': - throw new Error(`当前页面已进入 OAuth 授权页,未经过登录验证码页,无法完成步骤 6。URL: ${snapshot.url}`); + throw new Error(`当前页面已进入 OAuth 授权页,未经过登录验证码页,无法完成步骤 7。URL: ${snapshot.url}`); case 'add_phone_page': - throw new Error(`当前页面已进入手机号页面,未经过登录验证码页,无法完成步骤 6。URL: ${snapshot.url}`); + throw new Error(`当前页面已进入手机号页面,未经过登录验证码页,无法完成步骤 7。URL: ${snapshot.url}`); case 'unknown': throw new Error(`无法识别当前登录页面状态。URL: ${snapshot?.url || location.href}`); default: @@ -1412,7 +1412,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { async function waitForVerificationSubmitOutcome(step, timeout) { - const resolvedTimeout = timeout ?? (step === 7 ? 30000 : 12000); + const resolvedTimeout = timeout ?? (step === 8 ? 30000 : 12000); const start = Date.now(); while (Date.now() - start < resolvedTimeout) { @@ -1427,11 +1427,11 @@ async function waitForVerificationSubmitOutcome(step, timeout) { return { success: true }; } - if (step === 7 && isStep8Ready()) { + if (step === 8 && isStep8Ready()) { return { success: true }; } - if (step === 7 && isAddPhonePageReady()) { + if (step === 8 && isAddPhonePageReady()) { return { success: true, addPhonePage: true }; } @@ -1454,7 +1454,7 @@ async function fillVerificationCode(step, payload) { log(`步骤 ${step}:正在填写验证码:${code}`); - if (step === 7) { + if (step === 8) { await waitForLoginVerificationPageReady(); } @@ -1771,11 +1771,11 @@ async function step6SwitchToOneTimeCodeLogin(snapshot) { }); } - log('步骤 6:已检测到一次性验证码登录入口,准备切换...'); + log('步骤 7:已检测到一次性验证码登录入口,准备切换...'); const loginVerificationRequestedAt = Date.now(); await humanPause(350, 900); simulateClick(switchTrigger); - log('步骤 6:已点击一次性验证码登录'); + log('步骤 7:已点击一次性验证码登录'); await sleep(1200); return waitForStep6SwitchTransition(loginVerificationRequestedAt); } @@ -1785,26 +1785,26 @@ async function step6LoginFromPasswordPage(payload, snapshot) { if (currentSnapshot.passwordInput) { if (!payload.password) { - throw new Error('登录时缺少密码,步骤 6 无法继续。'); + throw new Error('登录时缺少密码,步骤 7 无法继续。'); } - log('步骤 6:已进入密码页,准备填写密码...'); + log('步骤 7:已进入密码页,准备填写密码...'); await humanPause(550, 1450); fillInput(currentSnapshot.passwordInput, payload.password); - log('步骤 6:已填写密码'); + log('步骤 7:已填写密码'); await sleep(500); const passwordSubmittedAt = Date.now(); await triggerLoginSubmitAction(currentSnapshot.submitButton, currentSnapshot.passwordInput); - log('步骤 6:已提交密码'); + log('步骤 7:已提交密码'); const transition = await waitForStep6PasswordSubmitTransition(passwordSubmittedAt); if (transition.action === 'done') { - log('步骤 6:已进入登录验证码页面。', 'ok'); + log('步骤 7:已进入登录验证码页面。', 'ok'); return transition.result; } if (transition.action === 'recoverable') { - log(`步骤 6:${transition.result.message || '提交密码后仍未进入登录验证码页面,准备重新执行步骤 6。'}`, 'warn'); + log(`步骤 7:${transition.result.message || '提交密码后仍未进入登录验证码页面,准备重新执行步骤 7。'}`, 'warn'); return transition.result; } if (transition.action === 'switch') { @@ -1835,23 +1835,23 @@ async function step6LoginFromEmailPage(payload, snapshot) { if ((emailInput.value || '').trim() !== payload.email) { await humanPause(500, 1400); fillInput(emailInput, payload.email); - log('步骤 6:已填写邮箱'); + log('步骤 7:已填写邮箱'); } else { - log('步骤 6:邮箱已在输入框中,准备提交...'); + log('步骤 7:邮箱已在输入框中,准备提交...'); } await sleep(500); const emailSubmittedAt = Date.now(); await triggerLoginSubmitAction(currentSnapshot.submitButton, emailInput); - log('步骤 6:已提交邮箱'); + log('步骤 7:已提交邮箱'); const transition = await waitForStep6EmailSubmitTransition(emailSubmittedAt); if (transition.action === 'done') { - log('步骤 6:已进入登录验证码页面。', 'ok'); + log('步骤 7:已进入登录验证码页面。', 'ok'); return transition.result; } if (transition.action === 'recoverable') { - log(`步骤 6:${transition.result.message || '提交邮箱后仍未进入目标页面,准备重新执行步骤 6。'}`, 'warn'); + log(`步骤 7:${transition.result.message || '提交邮箱后仍未进入目标页面,准备重新执行步骤 7。'}`, 'warn'); return transition.result; } if (transition.action === 'password') { @@ -1867,17 +1867,17 @@ async function step6_login(payload) { const { email } = payload; if (!email) throw new Error('登录时缺少邮箱地址。'); - log(`步骤 6:正在使用 ${email} 登录...`); + log(`步骤 7:正在使用 ${email} 登录...`); const snapshot = normalizeStep6Snapshot(await waitForKnownLoginAuthState(15000)); if (snapshot.state === 'verification_page') { - log('步骤 6:登录验证码页面已就绪。', 'ok'); + log('步骤 7:登录验证码页面已就绪。', 'ok'); return createStep6SuccessResult(snapshot, { via: 'already_on_verification_page' }); } if (snapshot.state === 'login_timeout_error_page') { - log('步骤 6:检测到登录超时报错,准备重新执行步骤 6。', 'warn'); + log('步骤 7:检测到登录超时报错,准备重新执行步骤 7。', 'warn'); return await createStep6LoginTimeoutRecoverableResult( 'login_timeout_error_page', snapshot, @@ -1898,19 +1898,19 @@ async function step6_login(payload) { } // ============================================================ -// Step 8: Find "继续" on OAuth consent page for debugger click +// Step 9: Find "继续" on OAuth consent page for debugger click // ============================================================ // After login + verification, page shows: // "使用 ChatGPT 登录到 Codex" with a "继续" submit button. // Background performs the actual click through the debugger Input API. async function step8_findAndClick() { - log('步骤 8:正在查找 OAuth 同意页的“继续”按钮...'); + log('步骤 9:正在查找 OAuth 同意页的“继续”按钮...'); const continueBtn = await prepareStep8ContinueButton(); const rect = getSerializableRect(continueBtn); - log('步骤 8:已找到“继续”按钮并准备好调试器点击坐标。'); + log('步骤 9:已找到“继续”按钮并准备好调试器点击坐标。'); return { rect, buttonText: (continueBtn.textContent || '').trim(), @@ -1969,10 +1969,10 @@ async function step8_triggerContinue(payload = {}) { simulateClick(continueBtn); break; default: - throw new Error(`未知的 Step 8 触发策略:${strategy}`); + throw new Error(`未知的 Step 9 触发策略:${strategy}`); } - log(`Step 8: continue button triggered via ${strategy}.`); + log(`Step 9: continue button triggered via ${strategy}.`); return { strategy, ...getStep8State(), diff --git a/content/sub2api-panel.js b/content/sub2api-panel.js index eecf348..8be143b 100644 --- a/content/sub2api-panel.js +++ b/content/sub2api-panel.js @@ -66,7 +66,7 @@ async function handleStep(step, payload = {}) { switch (step) { case 1: return step1_generateOpenAiAuthUrl(payload); - case 9: + case 10: return step9_submitOpenAiCallback(payload); default: throw new Error(`sub2api-panel.js 不处理步骤 ${step}`); @@ -218,7 +218,7 @@ function parseLocalhostCallback(rawUrl) { throw new Error('回调 URL 协议不正确。'); } if (!['localhost', '127.0.0.1'].includes(parsed.hostname)) { - throw new Error('步骤 9 只接受 localhost / 127.0.0.1 回调地址。'); + throw new Error('步骤 10 只接受 localhost / 127.0.0.1 回调地址。'); } if (parsed.pathname !== '/auth/callback') { throw new Error('回调 URL 路径必须是 /auth/callback。'); @@ -365,7 +365,7 @@ async function step9_submitOpenAiCallback(payload = {}) { throw new Error('本次 localhost 回调中的 state 与步骤 1 生成的 state 不一致,请重新执行步骤 1。'); } - log('步骤 9:正在向 SUB2API 交换 OpenAI 授权码...'); + log('步骤 10:正在向 SUB2API 交换 OpenAI 授权码...'); const exchangeData = await requestJson(origin, '/api/v1/admin/openai/exchange-code', { method: 'POST', token, @@ -399,7 +399,7 @@ async function step9_submitOpenAiCallback(payload = {}) { createPayload.extra = extra; } - log(`步骤 9:授权码交换成功,正在创建 SUB2API 账号(名称:${accountName})...`); + log(`步骤 10:授权码交换成功,正在创建 SUB2API 账号(名称:${accountName})...`); const createdAccount = await requestJson(origin, '/api/v1/admin/accounts', { method: 'POST', token, @@ -407,8 +407,8 @@ async function step9_submitOpenAiCallback(payload = {}) { }); const verifiedStatus = `SUB2API 已创建账号 #${createdAccount?.id || 'unknown'}`; - log(`步骤 9:${verifiedStatus}`, 'ok'); - reportComplete(9, { + log(`步骤 10:${verifiedStatus}`, 'ok'); + reportComplete(10, { localhostUrl: callback.url, verifiedStatus, }); diff --git a/content/vps-panel.js b/content/vps-panel.js index 688dde8..28b3c4f 100644 --- a/content/vps-panel.js +++ b/content/vps-panel.js @@ -83,7 +83,7 @@ if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1') async function handleStep(step, payload) { switch (step) { case 1: return await step1_getOAuthLink(payload); - case 9: return await step9_vpsVerify(payload); + case 10: return await step9_vpsVerify(payload); default: throw new Error(`vps-panel.js 不处理步骤 ${step}`); } @@ -467,11 +467,11 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS if (diagnostics.signature !== lastDiagnosticsSignature) { lastDiagnosticsSignature = diagnostics.signature; lastHeartbeatLoggedAt = elapsed; - log(`步骤 9:认证状态检测中,${diagnostics.summary}`); + log(`步骤 10:认证状态检测中,${diagnostics.summary}`); console.log(LOG_PREFIX, '[Step 9] status badge diagnostics changed', diagnostics); } else if (elapsed - lastHeartbeatLoggedAt >= 10000) { lastHeartbeatLoggedAt = elapsed; - log(`步骤 9:仍在等待认证成功,${diagnostics.summary}`); + log(`步骤 10:仍在等待认证成功,${diagnostics.summary}`); console.log(LOG_PREFIX, '[Step 9] still waiting for success badge', diagnostics); } @@ -488,7 +488,7 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS ? `;错误样式徽标:${diagnostics.errorStyledSummary}` : ''; log( - `步骤 9:检测到“认证成功”相关徽标,但未命中精确成功条件。当前聚焦="${getInlineTextSnippet(diagnostics.selectedText || '(空)', 80)}";成功相关徽标:${diagnostics.successLikeSummary}${errorStyledSuffix}`, + `步骤 10:检测到“认证成功”相关徽标,但未命中精确成功条件。当前聚焦="${getInlineTextSnippet(diagnostics.selectedText || '(空)', 80)}";成功相关徽标:${diagnostics.successLikeSummary}${errorStyledSuffix}`, 'warn' ); console.warn(LOG_PREFIX, '[Step 9] success-like badge detected without exact match', diagnostics); @@ -507,7 +507,7 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS ? diagnostics.pageErrorSummary : diagnostics.failureSummary; log( - `步骤 9:同时检测到成功徽标和失败提示,本轮不判定成功。成功徽标:${diagnostics.exactSuccessSummary};失败提示:${failureSummary}`, + `步骤 10:同时检测到成功徽标和失败提示,本轮不判定成功。成功徽标:${diagnostics.exactSuccessSummary};失败提示:${failureSummary}`, 'warn' ); console.warn(LOG_PREFIX, '[Step 9] success badge is blocked by visible failure', diagnostics); @@ -777,7 +777,7 @@ async function step1_getOAuthLink(payload, options = {}) { } // ============================================================ -// 步骤 9:CPA 回调验证——填写 localhost 回调地址并提交 +// 步骤 10:CPA 回调验证——填写 localhost 回调地址并提交 // ============================================================ async function step9_vpsVerify(payload) { @@ -786,22 +786,22 @@ async function step9_vpsVerify(payload) { // 优先从 payload 读取 localhostUrl;没有时再回退到全局状态 let localhostUrl = payload?.localhostUrl; if (localhostUrl && !isLocalhostOAuthCallbackUrl(localhostUrl)) { - throw new Error('步骤 9 只接受真实的 localhost OAuth 回调地址,请重新执行步骤 8。'); + throw new Error('步骤 10 只接受真实的 localhost OAuth 回调地址,请重新执行步骤 9。'); } if (!localhostUrl) { - log('步骤 9:payload 中没有 localhostUrl,正在从状态中读取...'); + log('步骤 10:payload 中没有 localhostUrl,正在从状态中读取...'); const state = await chrome.runtime.sendMessage({ type: 'GET_STATE' }); localhostUrl = state.localhostUrl; if (localhostUrl && !isLocalhostOAuthCallbackUrl(localhostUrl)) { - throw new Error('步骤 9 只接受真实的 localhost OAuth 回调地址,请重新执行步骤 8。'); + throw new Error('步骤 10 只接受真实的 localhost OAuth 回调地址,请重新执行步骤 9。'); } } if (!localhostUrl) { throw new Error('未找到 localhost 回调地址,请先完成步骤 8。'); } - log(`步骤 9:已获取 localhostUrl:${localhostUrl.slice(0, 60)}...`); + log(`步骤 10:已获取 localhostUrl:${localhostUrl.slice(0, 60)}...`); - log('步骤 9:正在查找回调地址输入框...'); + log('步骤 10:正在查找回调地址输入框...'); // Find the callback URL input // Actual DOM: @@ -818,7 +818,7 @@ async function step9_vpsVerify(payload) { await humanPause(600, 1500); fillInput(urlInput, localhostUrl); - log(`步骤 9:已填写回调地址:${localhostUrl.slice(0, 80)}...`); + log(`步骤 10:已填写回调地址:${localhostUrl.slice(0, 80)}...`); // Find and click the callback submit button in supported UI languages. const callbackSubmitPattern = /提交回调\s*URL|Submit\s+Callback\s+URL|Отправить\s+Callback\s+URL/i; @@ -839,9 +839,9 @@ async function step9_vpsVerify(payload) { await humanPause(450, 1200); simulateClick(submitBtn); - log('步骤 9:已点击回调提交按钮,正在等待认证结果...'); + log('步骤 10:已点击回调提交按钮,正在等待认证结果...'); const verifiedStatus = await waitForExactSuccessBadge(); - log(`步骤 9:${verifiedStatus}`, 'ok'); - reportComplete(9, { localhostUrl, verifiedStatus }); + log(`步骤 10:${verifiedStatus}`, 'ok'); + reportComplete(10, { localhostUrl, verifiedStatus }); } diff --git a/data/step-definitions.js b/data/step-definitions.js index 3c65947..5dd6b3a 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -7,10 +7,11 @@ { id: 3, order: 30, key: 'fill-password', title: '填写密码并继续' }, { id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码' }, { id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日' }, - { id: 6, order: 60, key: 'oauth-login', title: '刷新 OAuth 并登录' }, - { id: 7, order: 70, key: 'fetch-login-code', title: '获取登录验证码' }, - { id: 8, order: 80, key: 'confirm-oauth', title: '自动确认 OAuth' }, - { id: 9, order: 90, key: 'platform-verify', title: '平台回调验证' }, + { id: 6, order: 60, key: 'clear-login-cookies', title: '清理登录 Cookies' }, + { id: 7, order: 70, key: 'oauth-login', title: '刷新 OAuth 并登录' }, + { id: 8, order: 80, key: 'fetch-login-code', title: '获取登录验证码' }, + { id: 9, order: 90, key: 'confirm-oauth', title: '自动确认 OAuth' }, + { id: 10, order: 100, key: 'platform-verify', title: '平台回调验证' }, ]; function getSteps() { diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 32bd058..0fa14c4 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -118,16 +118,16 @@
本地 CPA -
+
- +
CPA回调
- - + +