Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ec61b2e
feat(openai-reauth): 新增独立 OAuth 重新授权 flow
ranger1112 May 28, 2026
fe3f96b
chore: gitignore 加入 .omc/ 和 /tmp/
ranger1112 May 28, 2026
b2908c4
feat(openai-reauth): sidepanel UI 集成
ranger1112 May 28, 2026
0145c58
fix(openai-reauth): review 发现的 sidepanel bug 修复
ranger1112 May 28, 2026
8c87fa8
feat(openai-reauth): 直接吃 sub2api 整文件 + UI 选 mailProvider
ranger1112 May 28, 2026
61f51af
fix(openai-reauth): AUTO_RUN 启动时也注入 reauthInputAccount
ranger1112 May 28, 2026
45885f3
feat(openai-reauth): 文件选择器替换 textarea + 隐藏无关 UI
ranger1112 May 28, 2026
c8594da
fix(openai-reauth): 步4 主动点击 OAuth 同意页继续按钮
ranger1112 May 28, 2026
c2f2e32
fix(sidepanel): reauth UI 收尾 + 修复 SAVE_SETTING 死循环
ranger1112 May 28, 2026
c4b1050
fix(openai-reauth): 步3 增加 RESEND_VERIFICATION_CODE 循环 + 缩短 2925 单轮 poll
ranger1112 May 28, 2026
2f133da
feat(openai-reauth): 新增批量重新授权协调器 + START_REAUTH_BATCH 消息
ranger1112 May 28, 2026
043f10b
feat(sidepanel): reauth 批量模式 UI + chrome.downloads 整文件下载
ranger1112 May 28, 2026
1756cb4
test(openai-reauth): batch-runner 15 个用例 + 修 stale sidepanel updatePa…
ranger1112 May 28, 2026
15e2257
feat(reauth-ui): reauth 流程 UI 结构重组 + 视觉美化
ranger1112 May 28, 2026
9bfcd5c
fix(openai-reauth): 修批量授权完成 UI 不更新 + 配套 sidepanel 调整
ranger1112 May 28, 2026
f22b520
fix(openai-reauth): review 修复 — 邮箱选项去硬编码 + 注释/行尾/公告补全
ranger1112 May 29, 2026
85a9bc8
fix(openai-reauth): review 四项优化 — extractAccountEmail 去重 / STATE_PATC…
ranger1112 May 29, 2026
e4a0173
fix(openai-reauth): 封禁账号检测从 ~3 分钟优化到 ~1 秒
ranger1112 May 29, 2026
4cf5079
fix(openai-reauth): 修复 PR review 反馈的健壮性问题
ranger1112 May 29, 2026
c13e132
fix(openai-reauth): 手机验证页触发账号级跳过
ranger1112 May 29, 2026
11496b9
fix(openai-reauth): 步骤4启动即预检手机验证
ranger1112 May 29, 2026
41b714b
feat(openai-reauth): 让批量结果可安全导出成功账号子集
ranger1112 May 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 强制所有文本文件使用 LF,避免 Windows 下 CRLF 重写整文件造成 git diff stat 虚高。
* text=auto eol=lf

# 二进制资源不动。
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.webp binary
*.zip binary
*.crx binary
*.pdf binary
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
/data/account-run-history.json
.npm-test.log
.omx/
.omc/
/tmp/
/node_modules
/.runtime
/docs/新步骤顺序
Expand Down
109 changes: 109 additions & 0 deletions background.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
importScripts(
'flows/openai/index.js',
'flows/openai/workflow.js',
'flows/openai-reauth/index.js',
'flows/openai-reauth/workflow.js',
'flows/kiro/index.js',
'flows/kiro/workflow.js',
'flows/grok/index.js',
Expand Down Expand Up @@ -52,6 +54,7 @@ importScripts(
'background/signup-flow-helpers.js',
'background/mail-rule-registry.js',
'flows/openai/mail-rules.js',
'flows/openai-reauth/mail-rules.js',
'flows/kiro/mail-rules.js',
'flows/grok/mail-rules.js',
'background/flow-mail-polling.js',
Expand Down Expand Up @@ -82,6 +85,14 @@ importScripts(
'flows/openai/background/steps/fetch-login-code.js',
'flows/openai/background/steps/confirm-oauth.js',
'flows/openai/background/steps/platform-verify.js',
'flows/openai-reauth/reauth-account-validator.js',
'flows/openai-reauth/background/oauth-client.js',
'flows/openai-reauth/background/cookie-cleanup.js',
'flows/openai-reauth/background/batch-runner.js',
'flows/openai-reauth/background/steps/prepare-reauth.js',
'flows/openai-reauth/background/steps/submit-reauth-email.js',
'flows/openai-reauth/background/steps/fetch-reauth-code.js',
'flows/openai-reauth/background/steps/capture-reauth-callback.js',
'data/names.js',
'hotmail-utils.js',
'microsoft-email.js',
Expand Down Expand Up @@ -4108,6 +4119,9 @@ function buildFreshAutoRunKeepState(prevState = {}) {
if (typeof grokStateHelpers?.buildFreshKeepState === 'function') {
Object.assign(keepState, grokStateHelpers.buildFreshKeepState(sourceState));
}
if (activeFlowId === 'openai-reauth' && isPlainObjectValue(sourceState.reauthInputAccount)) {
keepState.reauthInputAccount = sourceState.reauthInputAccount;
}
if (Object.prototype.hasOwnProperty.call(sourceState, 'settingsSchemaVersion')) {
keepState.settingsSchemaVersion = Number(sourceState.settingsSchemaVersion) || 0;
}
Expand Down Expand Up @@ -4228,6 +4242,19 @@ async function setState(updates) {
...currentSessionState,
}, updates);
await chrome.storage.session.set(sessionUpdates);

// 广播 STATE_PATCH:让 sidepanel 等监听方即时同步增量。
// payload 用 sessionUpdates(真实生效的最终值,而非 raw updates)。
// 无接收方时 sendMessage 会 reject,只吞「无接收方」错误,其余 warn 输出方便排查。
try {
chrome.runtime.sendMessage({ type: 'STATE_PATCH', payload: sessionUpdates }).catch((err) => {
const msg = err?.message || '';
if (!msg.includes('Could not establish connection') && !msg.includes('receiving end does not exist')) {
console.warn('[setState] STATE_PATCH broadcast failed:', msg);
}
});
} catch (_) { }

const persistentAliasUpdates = {};
if (Object.prototype.hasOwnProperty.call(sessionUpdates, 'manualAliasUsage')) {
persistentAliasUpdates.manualAliasUsage = normalizeBooleanMap(sessionUpdates.manualAliasUsage);
Expand Down Expand Up @@ -13743,6 +13770,11 @@ const openAiMailRules = self.MultiPageOpenAiMailRules?.createOpenAiMailRules({
MAIL_2925_VERIFICATION_INTERVAL_MS,
MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
});
const openAiReauthMailRules = self.MultiPageOpenAiReauthMailRules?.createOpenAiReauthMailRules({
getHotmailVerificationRequestTimestamp,
MAIL_2925_VERIFICATION_INTERVAL_MS,
MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
});
const kiroMailRules = self.MultiPageKiroMailRules?.createKiroMailRules({
LUCKMAIL_PROVIDER,
MAIL_2925_VERIFICATION_INTERVAL_MS,
Expand All @@ -13757,6 +13789,7 @@ const mailRuleRegistry = self.MultiPageBackgroundMailRuleRegistry?.createMailRul
defaultFlowId: DEFAULT_ACTIVE_FLOW_ID,
flowBuilders: {
openai: openAiMailRules,
'openai-reauth': openAiReauthMailRules,
kiro: kiroMailRules,
grok: grokMailRules,
},
Expand Down Expand Up @@ -14351,6 +14384,10 @@ const stepExecutorsByKey = {
'post-bound-email-phone-verification': (state) => step8Executor.executeBoundEmailPostLoginPhoneVerification(state),
'confirm-oauth': (state) => step9Executor.executeStep9(state),
'platform-verify': (state) => executeStep10(state),
'prepare-reauth': (state) => prepareReauthExecutor.executePrepareReauth(state),
'submit-reauth-email': (state) => submitReauthEmailExecutor.executeSubmitReauthEmail(state),
'fetch-reauth-code': (state) => fetchReauthCodeExecutor.executeFetchReauthCode(state),
'capture-reauth-callback': (state) => captureReauthCallbackExecutor.executeCaptureReauthCallback(state),
'kiro-open-register-page': (state) => kiroRegisterRunner.executeKiroOpenRegisterPage(state),
'kiro-submit-email': (state) => kiroRegisterRunner.executeKiroSubmitEmail(state),
'kiro-submit-name': (state) => kiroRegisterRunner.executeKiroSubmitName(state),
Expand Down Expand Up @@ -14404,6 +14441,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
fetchGeneratedEmail,
refreshGpcCardBalance,
finalizePhoneActivationAfterSuccessfulFlow,
getReauthBatchRunner: () => reauthBatchRunner,
testKiroRsConnection: async (baseUrl, apiKey) => {
if (typeof self.MultiPageBackgroundKiroPublisherKiroRs?.checkKiroRsConnection !== 'function') {
throw new Error('kiro.rs 连接测试能力尚未接入。');
Expand Down Expand Up @@ -16294,6 +16332,77 @@ const step9Executor = self.MultiPageBackgroundStep9?.createStep9Executor({
waitForStep8Ready,
});

const openAiReauthOAuthClient = self.MultiPageOpenAiReauthOAuthClient || null;
const openAiReauthCookieCleanup = self.MultiPageOpenAiReauthCookieCleanup || null;

const prepareReauthExecutor = self.MultiPageOpenAiReauthPrepareStep?.createPrepareReauthExecutor({
addLog,
chrome,
clearOpenAiAuthCookies: openAiReauthCookieCleanup?.clearOpenAiAuthCookies,
completeNodeFromBackground,
generatePkcePair: openAiReauthOAuthClient?.generatePkcePair,
generateState: openAiReauthOAuthClient?.generateState,
buildAuthorizeUrl: openAiReauthOAuthClient?.buildAuthorizeUrl,
registerTab,
reuseOrCreateTab,
setState,
});

const submitReauthEmailExecutor = self.MultiPageOpenAiReauthSubmitEmailStep?.createSubmitReauthEmailExecutor({
addLog,
completeNodeFromBackground,
reuseOrCreateTab,
sendToContentScriptResilient,
throwIfStopped,
});

const fetchReauthCodeExecutor = self.MultiPageOpenAiReauthFetchCodeStep?.createFetchReauthCodeExecutor({
addLog,
completeNodeFromBackground,
pollFlowVerificationCode: flowMailPollingService?.pollFlowVerificationCode,
sendToContentScriptResilient,
sleepWithStop,
throwIfStopped,
});

const captureReauthCallbackExecutor = self.MultiPageOpenAiReauthCaptureCallbackStep?.createCaptureReauthCallbackExecutor({
addLog,
chrome,
completeNodeFromBackground,
exchangeAuthorizationCode: openAiReauthOAuthClient?.exchangeAuthorizationCode,
parseCallbackUrl: openAiReauthOAuthClient?.parseCallbackUrl,
buildUpdatedAccount: openAiReauthOAuthClient?.buildUpdatedAccount,
setState,
// 复用注册流程 step9 的 OAuth 同意页点击编排
getTabId,
isTabAlive,
ensureStep8SignupPageReady,
waitForStep8Ready,
prepareStep8DebuggerClick,
clickWithDebugger,
triggerStep8ContentStrategy,
waitForStep8ClickEffect,
getStep8EffectLabel,
reloadStep8ConsentPage,
sleepWithStop,
throwIfStopped,
STEP8_STRATEGIES,
STEP8_MAX_ROUNDS,
STEP8_CLICK_RETRY_DELAY_MS,
STEP8_READY_WAIT_TIMEOUT_MS,
});

const reauthBatchRunner = self.MultiPageOpenAiReauthBatchRunner?.createReauthBatchRunner({
addLog,
executeNode,
getNodeIdsForState,
getState,
setState,
sleepWithStop,
throwIfStopped,
extractAccountEmail: self.MultiPageOpenAiReauthAccountValidator?.extractAccountEmail,
});

async function executeStep9(state) {
return step9Executor.executeStep9(state);
}
Expand Down
50 changes: 50 additions & 0 deletions background/message-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
getCurrentPayPalAccount,
getCurrentMail2925Account,
getPendingAutoRunTimerPlan,
getReauthBatchRunner = () => null,
getSourceLabel,
getState,
getNodeDefinitionForState,
Expand Down Expand Up @@ -1375,6 +1376,9 @@
await setPersistentSettings({ emailPrefix: message.payload.emailPrefix });
await setState({ emailPrefix: message.payload.emailPrefix });
}
if (message.payload.reauthInputAccount !== undefined) {
await setState({ reauthInputAccount: message.payload.reauthInputAccount });
}
const executionState = await getState();
if (doesNodeUseCompletionSignal(nodeId, executionState)) {
const completionPayload = await executeNodeViaCompletionSignal(nodeId);
Expand Down Expand Up @@ -1410,6 +1414,9 @@
if (Object.keys(autoRunFlowStateUpdates).length > 0 && typeof setState === 'function') {
await setState(autoRunFlowStateUpdates);
}
if (message.payload?.reauthInputAccount !== undefined && typeof setState === 'function') {
await setState({ reauthInputAccount: message.payload.reauthInputAccount });
}
const state = await getState();
const autoRunStartValidation = validateAutoRunStart(state, {
activeFlowId: autoRunFlowStateUpdates.activeFlowId ?? state?.activeFlowId,
Expand All @@ -1430,6 +1437,49 @@
return { ok: true };
}

case 'START_REAUTH_BATCH': {
clearStopRequest();
if (message.source === 'sidepanel') {
await lockAutomationWindowFromMessage(message, sender);
}
const runner = getReauthBatchRunner();
if (!runner || typeof runner.executeReauthBatch !== 'function') {
throw new Error('reauth 批量处理器尚未初始化。');
}
const payload = message.payload || {};
const accounts = Array.isArray(payload.accounts) ? payload.accounts : [];
if (accounts.length === 0) {
throw new Error('未选择任何待批量处理的账号。');
}
const mailProvider = String(payload.mailProvider || '').trim();
if (!mailProvider) {
throw new Error('未指定 mailProvider,请先在 sidepanel 选择邮箱来源。');
}
if (typeof setState === 'function') {
await setState({
activeFlowId: 'openai-reauth',
flowId: 'openai-reauth',
mailProvider,
});
}
// fire-and-forget:后台执行,进度通过 setState 广播
runner.executeReauthBatch({
accounts,
mailProvider,
originalFileText: String(payload.originalFileText || ''),
skipOnFailure: payload.skipOnFailure !== false,
}).catch(async (error) => {
const message = error instanceof Error ? error.message : String(error || 'reauth 批量处理失败');
console.warn('[MessageRouter] reauth batch failed:', message);
if (typeof addLog === 'function') {
try {
await addLog(`reauth 批量处理终止:${message}`, 'error', { stepKey: 'reauth-batch' });
} catch (_) {}
}
});
return { ok: true };
}

case 'SKIP_AUTO_RUN_COUNTDOWN': {
clearStopRequest();
if (message.source === 'sidepanel') {
Expand Down
29 changes: 29 additions & 0 deletions docs/openai-reauth-release-公告草稿.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 新增 OpenAI 重新授权流程(含新增浏览器权限)

本次更新新增独立的 OpenAI Reauth flow,并因「批量结果一键下载整文件」能力新增 `downloads` 权限。**升级扩展时浏览器会弹出权限授予提示,属正常现象,授权后即可正常使用全部功能。**

## 本次调整

- 新增独立流程 **「OpenAI 重新授权」**:针对 `refresh_token` 路径已被服务端 revoke、必须重新走完整 OAuth 才能拿到新 token 的 sub2api 账号
- 支持单账号 / sub2api 整文件 / accounts 数组三种 JSON 输入
- 支持「批量模式」:一次性对整文件内所有账号执行重新授权,自动累计成功 / 失败结果
- 批量结果可一键下载为整文件 JSON(保留原文件结构,失败账号原样保留)
- 提供 2925 / Hotmail / iCloud / LuckMail / Cloud Mail / YYDS Mail / Cloudflare Temp Email 七种邮箱来源

## 影响范围

- **新增浏览器权限**:`downloads`(仅用于批量结果整文件下载)
- 升级后首次启用时,Chrome 会显示「此扩展需要新权限」提示,需要点击「启用扩展」/「授予」才会继续运行
- 不影响现有 OpenAI 注册流程 / Kiro / Grok flow 的使用

## 用户需要做什么

- 升级后在 `chrome://extensions` 页面**确认授予新权限**(仅一次)
- 如果没有重新授权需求,可继续使用原 flow,无需任何操作
- 需要重新授权时:sidepanel 切到「OpenAI 重新授权」→ 选 sub2api JSON 文件 → 选邮箱来源 → 单账号点「自动」/ 多账号开启「批量模式」→ 完成后下载更新后的整文件 JSON

## 补充说明

- 本次新增的 `downloads` 权限仅在用户主动点击「下载完整 JSON 文件」时使用,不会主动写入任何本地文件
- 所有 token / refresh_token 全程在本机处理,不外发任何第三方
- 升级后如未弹权限提示但流程报错,请手动 disable / re-enable 扩展触发权限授予
34 changes: 24 additions & 10 deletions flows/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
id: 'openai',
path: 'flows/openai/',
},
'openai-reauth': {
id: 'openai-reauth',
path: 'flows/openai-reauth/',
},
kiro: {
id: 'kiro',
path: 'flows/kiro/',
Expand All @@ -32,18 +36,28 @@
if (!baseEntry) {
return null;
}
function pickDefinition() {
switch (normalized) {
case 'openai': return rootScope.MultiPageOpenAiFlowDefinition || null;
case 'openai-reauth': return rootScope.MultiPageOpenAiReauthFlowDefinition || null;
case 'kiro': return rootScope.MultiPageKiroFlowDefinition || null;
case 'grok': return rootScope.MultiPageGrokFlowDefinition || null;
default: return null;
}
}
function pickWorkflow() {
switch (normalized) {
case 'openai': return rootScope.MultiPageOpenAiWorkflow || null;
case 'openai-reauth': return rootScope.MultiPageOpenAiReauthWorkflow || null;
case 'kiro': return rootScope.MultiPageKiroWorkflow || null;
case 'grok': return rootScope.MultiPageGrokWorkflow || null;
default: return null;
}
}
return {
...baseEntry,
definition: normalized === 'openai'
? (rootScope.MultiPageOpenAiFlowDefinition || null)
: (normalized === 'kiro'
? (rootScope.MultiPageKiroFlowDefinition || null)
: (rootScope.MultiPageGrokFlowDefinition || null)),
workflow: normalized === 'openai'
? (rootScope.MultiPageOpenAiWorkflow || null)
: (normalized === 'kiro'
? (rootScope.MultiPageKiroWorkflow || null)
: (rootScope.MultiPageGrokWorkflow || null)),
definition: pickDefinition(),
workflow: pickWorkflow(),
};
}

Expand Down
Loading