Просмотр исходного кода

feat: 添加手机号页面错误处理逻辑,更新步骤7重试机制及文档说明

祁连海 4 месяцев назад
Родитель
Сommit
14a3e9d6b2

+ 8 - 0
background/steps/oauth-login.js

@@ -19,6 +19,11 @@
       throwIfStopped,
       throwIfStopped,
     } = deps;
     } = deps;
 
 
+    function isAddPhoneAuthFailure(error) {
+      const message = String(typeof error === 'string' ? error : error?.message || '');
+      return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)|add-phone|手机号页面|手机号码|手机号|phone\s+number|telephone/i.test(message);
+    }
+
     async function executeStep7(state) {
     async function executeStep7(state) {
       if (!state.email) {
       if (!state.email) {
         throw new Error('缺少邮箱地址,请先完成步骤 3。');
         throw new Error('缺少邮箱地址,请先完成步骤 3。');
@@ -91,6 +96,9 @@
           throw new Error('步骤 7:认证页未返回可识别的登录结果。');
           throw new Error('步骤 7:认证页未返回可识别的登录结果。');
         } catch (err) {
         } catch (err) {
           throwIfStopped(err);
           throwIfStopped(err);
+          if (isAddPhoneAuthFailure(err)) {
+            throw err;
+          }
           lastError = err;
           lastError = err;
           if (attempt >= STEP6_MAX_ATTEMPTS) {
           if (attempt >= STEP6_MAX_ATTEMPTS) {
             break;
             break;

+ 51 - 0
tests/background-step6-retry-limit.test.js

@@ -75,6 +75,57 @@ test('step 7 retries up to configured limit and then fails', async () => {
   assert.equal(events.completed, 0);
   assert.equal(events.completed, 0);
 });
 });
 
 
+test('step 7 exits internal retry loop immediately when add-phone is detected', async () => {
+  const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
+  const globalScope = {};
+  const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
+
+  const events = {
+    refreshCalls: 0,
+    sendCalls: 0,
+    completed: 0,
+    logs: [],
+  };
+
+  const executor = api.createStep7Executor({
+    addLog: async (message, level = 'info') => {
+      events.logs.push({ message, level });
+    },
+    completeStepFromBackground: async () => {
+      events.completed += 1;
+    },
+    getErrorMessage: (error) => error?.message || String(error || ''),
+    getLoginAuthStateLabel: (state) => state || 'unknown',
+    getState: async () => ({ email: 'user@example.com', password: 'secret' }),
+    isStep6RecoverableResult: (result) => result?.step6Outcome === 'recoverable',
+    isStep6SuccessResult: (result) => result?.step6Outcome === 'success',
+    refreshOAuthUrlBeforeStep6: async () => {
+      events.refreshCalls += 1;
+      return `https://oauth.example/${events.refreshCalls}`;
+    },
+    reuseOrCreateTab: async () => {},
+    sendToContentScriptResilient: async () => {
+      events.sendCalls += 1;
+      throw new Error('提交密码后页面直接进入手机号页面,未经过登录验证码页。URL: https://auth.openai.com/add-phone');
+    },
+    STEP6_MAX_ATTEMPTS: 3,
+    throwIfStopped: () => {},
+  });
+
+  await assert.rejects(
+    () => executor.executeStep7({ email: 'user@example.com', password: 'secret' }),
+    /add-phone/
+  );
+
+  assert.equal(events.refreshCalls, 1, 'add-phone should stop further OAuth refresh attempts');
+  assert.equal(events.sendCalls, 1, 'add-phone should stop after the first failed login attempt');
+  assert.equal(events.completed, 0);
+  assert.ok(
+    !events.logs.some(({ message }) => /准备重试/.test(message)),
+    'add-phone failure should not be logged as an internal retryable attempt'
+  );
+});
+
 test('step 7 starts a new oauth timeout window for each refreshed oauth url', async () => {
 test('step 7 starts a new oauth timeout window for each refreshed oauth url', async () => {
   const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
   const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
   const globalScope = {};
   const globalScope = {};

+ 2 - 2
项目完整链路说明.md

@@ -298,7 +298,7 @@
 2. 打开最新 OAuth 链接
 2. 打开最新 OAuth 链接
 3. 登录
 3. 登录
 4. 确保真正进入验证码页
 4. 确保真正进入验证码页
-5. 如果未进入验证码页,则按可恢复逻辑最多重试 3 次
+5. 如果未进入验证码页,则按可恢复逻辑最多重试 3 次;但一旦当前失败已经明确进入 `https://auth.openai.com/add-phone`,则立即退出步骤 7 内部重试,不再继续第 2 / 3 次尝试
 6. 自动运行一旦进入步骤 7 之后的链路,若后续步骤报错且认证页未进入 `https://auth.openai.com/add-phone`,则统一回到步骤 7 重新开始授权流程
 6. 自动运行一旦进入步骤 7 之后的链路,若后续步骤报错且认证页未进入 `https://auth.openai.com/add-phone`,则统一回到步骤 7 重新开始授权流程
 7. CPA 回调阶段固定为步骤 9,不再支持“第七步回调”跳过步骤 7/8
 7. CPA 回调阶段固定为步骤 9,不再支持“第七步回调”跳过步骤 7/8
 
 
@@ -448,7 +448,7 @@
 3. 计算是否从中断点继续
 3. 计算是否从中断点继续
 4. 每轮执行前重置必要运行态
 4. 每轮执行前重置必要运行态
 5. 执行 `runAutoSequenceFromStep`
 5. 执行 `runAutoSequenceFromStep`
-   - 步骤 7 内部仍保留登录态恢复的有限重试
+   - 步骤 7 内部仍保留登录态恢复的有限重试,但 `add-phone / 手机号页` 属于立即跳出的不可重试错误
    - 一旦进入步骤 7~10,遇到普通报错且认证流程未进入 `add-phone`,则自动回到步骤 7 无限重开
    - 一旦进入步骤 7~10,遇到普通报错且认证流程未进入 `add-phone`,则自动回到步骤 7 无限重开
    - 如果是手动停止,则立即退出自动流程,不会再触发“回到步骤 7 重开”
    - 如果是手动停止,则立即退出自动流程,不会再触发“回到步骤 7 重开”
 6. 如果失败,根据设置决定:
 6. 如果失败,根据设置决定:

+ 2 - 2
项目文件结构说明.md

@@ -59,7 +59,7 @@
 - `background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口。
 - `background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口。
 - `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交。
 - `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交。
 - `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。
 - `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。
-- `background/steps/oauth-login.js`:步骤 7 实现,负责刷新 OAuth 链接、登录和确保进入验证码页。
+- `background/steps/oauth-login.js`:步骤 7 实现,负责刷新 OAuth 链接、登录和确保进入验证码页;普通可恢复登录态失败会按上限重试,但一旦识别到认证流程进入 `add-phone / 手机号页`,会立即退出步骤 7 内部重试
 - `background/steps/open-chatgpt.js`:步骤 1 实现,负责打开 ChatGPT 官网并确认入口就绪。
 - `background/steps/open-chatgpt.js`:步骤 1 实现,负责打开 ChatGPT 官网并确认入口就绪。
 - `background/steps/platform-verify.js`:步骤 10 实现,负责 CPA / SUB2API 回调验证。
 - `background/steps/platform-verify.js`:步骤 10 实现,负责 CPA / SUB2API 回调验证。
 - `background/steps/registry.js`:步骤注册表工厂,负责用稳定的步骤元数据映射到执行器。
 - `background/steps/registry.js`:步骤注册表工厂,负责用稳定的步骤元数据映射到执行器。
@@ -143,7 +143,7 @@
 - `tests/background-signup-step2-branching.test.js`:测试在 Gmail / 2925 模式下,已有兼容别名邮箱时应直接复用,不应再次重生成。
 - `tests/background-signup-step2-branching.test.js`:测试在 Gmail / 2925 模式下,已有兼容别名邮箱时应直接复用,不应再次重生成。
 - `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。
 - `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。
 - `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。
 - `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。
-- `tests/background-step6-retry-limit.test.js`:测试步骤 6 的 Cookie 清理,以及步骤 7 的有限重试上限。
+- `tests/background-step6-retry-limit.test.js`:测试步骤 6 的 Cookie 清理,以及步骤 7 的有限重试上限与 `add-phone` 命中后的立即跳出行为
 - `tests/background-step7-recovery.test.js`:测试步骤 8 获取登录验证码后直接提交(不再回放步骤 7),并为 2925 关闭重发间隔。
 - `tests/background-step7-recovery.test.js`:测试步骤 8 获取登录验证码后直接提交(不再回放步骤 7),并为 2925 关闭重发间隔。
 - `tests/background-step-registry.test.js`:测试后台步骤注册表和共享步骤定义已接入。
 - `tests/background-step-registry.test.js`:测试后台步骤注册表和共享步骤定义已接入。
 - `tests/background-tab-runtime-module.test.js`:测试标签运行时模块已接入且导出工厂,并覆盖等待标签完成时的 Stop 中断行为。
 - `tests/background-tab-runtime-module.test.js`:测试标签运行时模块已接入且导出工厂,并覆盖等待标签完成时的 Stop 中断行为。