# 多注册流程来源与驱动注册设计 本文解决的是另一个会在第二个 flow 接入时立刻爆炸的问题:当前项目虽然有 `tab-runtime`,但“来源 source 是谁、该注入什么脚本、URL family 怎么判定、localhost callback 应该清谁”仍然带着明显的 OpenAI 单流程假设。 ## 1. 当前源码里的真实耦合 ### 1.1 `signup-page` 其实不是抽象 source,而是 OpenAI Auth 标签页别名 目前这些地方都把 OpenAI Auth / Consent / Add-phone 页面统称为 `signup-page`: - `content/utils.js` 的 `detectScriptSource()` - `background/navigation-utils.js` 的 `matchesSourceUrlFamily()` - `core/flow-kernel/tab-runtime.js` 的注册表、冲突清理、callback 清理 这说明当前的 source 体系不是“可扩展的来源注册表”,而是“OpenAI 时代遗留的 source 命名”。 ### 1.2 `content/utils.js` 的默认回退不安全 当前 `detectScriptSource()` 在无法识别页面时会回退成 `vps-panel`。这对单流程时期问题不大,但多 flow 下会出现两个风险: 1. 新 flow 页面被误识别成 `vps-panel` 2. `PING / READY / COMPLETE` 日志和标签注册打到错误 source ### 1.3 `tab-runtime` 的 localhost cleanup 直接写死 `registry['signup-page']` `closeLocalhostCallbackTabs()` 现在默认把匹配 callback 的残留 tab 从 `signup-page` 名下清掉。将来只要第二个 flow 也有 callback,或者 callback 不是来自 OpenAI Auth 页,这里就会清错或漏清。 ### 1.4 manifest 的静态注入列表目前基本围绕 OpenAI `manifest.json` 里的静态内容脚本匹配域名,目前只有 OpenAI Auth 页是明确的 flow 页面入口。第二个 flow 如果沿用这套做法,后续只会不断在 manifest 里继续加分散规则,缺少统一 source 定义中心。 ## 2. 设计目标 后续必须拆成两层注册表: 1. `sourceRegistry`:管理“页面来源与标签页生命周期” 2. `driverRegistry`:管理“这个来源页面由哪个 content driver 负责,支持哪些命令” 这两层不要再混成一个 `signup-page` 字符串。 ## 3. `sourceRegistry` 设计 建议 source 用稳定 ID,而不是模糊业务词。 ```js sourceRegistry = { 'openai-auth': { flowId: 'openai', kind: 'flow-page', label: 'OpenAI 认证页', hostPatterns: [ 'https://auth.openai.com/*', 'https://auth0.openai.com/*', 'https://accounts.openai.com/*', ], familyMatcher: 'openai-auth-family', injectFiles: [ 'content/activation-utils.js', 'content/utils.js', 'content/operation-delay.js', 'flows/openai/content/auth-page-recovery.js', 'flows/openai/content/phone-country-utils.js', 'flows/openai/content/phone-auth.js', 'flows/openai/content/openai-auth.js', ], cleanupScopes: ['oauth-localhost-callback'], }, 'mail-gmail': { flowId: null, kind: 'mail-provider', label: 'Gmail', hostPatterns: ['https://mail.google.com/*'], familyMatcher: 'gmail-family', injectFiles: ['content/activation-utils.js', 'content/utils.js', 'content/gmail-mail.js'], }, 'panel-sub2api': { flowId: 'openai', kind: 'panel-page', label: 'SUB2API 后台', dynamicOnly: true, familyMatcher: 'sub2api-panel-family', injectFiles: ['content/utils.js', 'flows/openai/content/sub2api-panel.js'], }, }; ``` ### 3.1 source 里至少要有这些字段 - `id` - `flowId` - `kind` - `label` - `hostPatterns` - `familyMatcher` - `injectFiles` - `readyPolicy` - `cleanupScopes` - `tabReusePolicy` ### 3.2 source 不等于 flow 一个 flow 会有多个 source: - `openai-entry` - `openai-auth` - `openai-plus-checkout` - `openai-paypal` - `openai-gopay` - `panel-sub2api` 同样,一个 source 也可能不属于具体 flow,例如: - `mail-gmail` - `mail-163` - `mail-2925` - `mail-inbucket` ## 4. `driverRegistry` 设计 `driverRegistry` 解决的是“页面上能做什么”,不是“标签页如何复用”。 ```js driverRegistry = { 'openai-auth': { sourceId: 'openai-auth', driverId: 'flows/openai/content/openai-auth', commands: [ 'OPEN_SIGNUP', 'SUBMIT_SIGNUP_IDENTIFIER', 'SUBMIT_PASSWORD', 'SUBMIT_PROFILE', 'SUBMIT_LOGIN_CODE', 'SUBMIT_PHONE_CODE', 'DETECT_AUTH_STATE', ], }, 'mail-gmail': { sourceId: 'mail-gmail', driverId: 'content/gmail-mail', commands: ['POLL_MAILBOX', 'OPEN_MESSAGE', 'DELETE_MESSAGE'], }, }; ``` 这样做的好处是: - `tab-runtime` 只关心 source - workflow / mail service 只关心 driver capability - 后续换内容脚本实现时,不需要改 tab 生命周期代码 ## 5. `signup-page` 的迁移策略 `signup-page` 不能直接继续作为未来通用 source 名字。 建议迁移方式: 1. 新增正式 source:`openai-auth` 2. 在 `sourceRegistry.aliases` 里临时保留: - `signup-page -> openai-auth` 3. `getSourceLabel()`、`matchesSourceUrlFamily()`、`ensureContentScriptReadyOnTab()` 全部优先读注册表 4. 等 OpenAI 全链路迁完后,再删除 `signup-page` 别名 ## 6. localhost callback cleanup 怎么改 当前 callback 清理逻辑最大的问题是“只知道 callback URL,不知道 callback 属于哪个 flow source”。 建议改成: ```js callbackRegistry = { 'oauth-localhost-callback': { ownerSourceId: 'openai-auth', matcher: isLocalhostOAuthCallbackUrl, clearOwnerTabOnClose: true, }, }; ``` `closeLocalhostCallbackTabs()` 的输入要升级为: - `cleanupScope` - `callbackUrl` - `ownerSourceId` 而不是默认拿 `signup-page`。 ## 7. `content/utils.js` 的改法 ### 7.1 禁止默认回退成 `vps-panel` 新的规则应该是: - 有 `window.__MULTIPAGE_SOURCE` 时,以注入 source 为准 - 没有显式 source 时,只在静态 host map 里做白名单匹配 - 仍无法识别时返回 `unknown-source` `unknown-source` 不参与 tab 注册,不自动 `reportReady()`,只记录诊断日志。 ### 7.2 child frame ready 规则也要转到注册表 当前 `shouldReportReadyForFrame()` 里写死了多个 source 名称。后续应改成 source metadata: - `readyPolicy: 'top-frame-only'` - `readyPolicy: 'allow-child-frame'` ## 8. manifest / 动态注入策略 原则上以后由 `sourceRegistry` 作为单一事实来源,manifest 只是实现载体。 建议约定: - 稳定公共页面可继续走静态 `content_scripts` - 变动大、域名多、只在运行期打开的页面改走 `chrome.scripting.executeScript` - 每个 source 明确自己的 `hostPatterns` 和 `injectFiles` - 新增 flow 时,不允许直接在业务代码里散写 `injectFiles = [...]` ## 9. 与网络策略的关系 `sourceRegistry` 只负责页面来源,不负责代理策略;但每个 flow 应同时提供自己的 `networkProfile`,至少包含: - `guardDomains` - `probeEndpoints` - `failCloseDomains` 否则 `ip-proxy-core` 仍会继续写死 `chatgpt.com / openai.com`。 ## 10. 第一阶段迁移顺序 1. 引入 `sourceRegistry / driverRegistry` 与旧 source alias。 2. 把 `getSourceLabel()`、`matchesSourceUrlFamily()`、`ensureContentScriptReadyOnTab()` 改成读注册表。 3. 去掉 `detectScriptSource()` 的 `vps-panel` 默认回退。 4. 改 `closeLocalhostCallbackTabs()`,不再写死 `signup-page`。 5. 第二个 flow 接入时,严格要求先注册 source 和 driver,再写步骤逻辑。 ## 11. 本文对应解决的缺口 本文主要补齐以下缺口: - `signup-page` 实际是 OpenAI source,不是通用注册页 - source URL family / content ready / localhost callback cleanup 没有统一注册表 - manifest 与动态注入没有统一入口 - `content/utils.js` 的默认 source 回退会污染未来 flow