(function attachBackgroundStep10(root, factory) { root.MultiPageBackgroundStep10 = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep10Module() { function createStep10Executor(deps = {}) { const { addLog, chrome, closeConflictingTabsForSource, completeStepFromBackground, ensureContentScriptReadyOnTab, getPanelMode, getTabId, isLocalhostOAuthCallbackUrl, isTabAlive, normalizeCodex2ApiUrl, normalizeSub2ApiUrl, rememberSourceLastUrl, reuseOrCreateTab, sendToContentScript, sendToContentScriptResilient, shouldBypassStep9ForLocalCpa, SUB2API_STEP9_RESPONSE_TIMEOUT_MS, } = deps; function normalizeString(value = '') { return String(value || '').trim(); } function resolvePlatformVerifyStep(state = {}) { const visibleStep = Math.floor(Number(state?.visibleStep) || 0); return visibleStep >= 10 ? visibleStep : 10; } function resolveConfirmOauthStep(platformVerifyStep = 10) { return Number(platformVerifyStep) >= 13 ? 12 : 9; } function resolveAuthLoginStep(platformVerifyStep = 10) { return Number(platformVerifyStep) >= 13 ? 10 : 7; } function addStepLog(step, message, level = 'info') { return addLog(message, level, { step, stepKey: 'platform-verify' }); } function parseLocalhostCallback(rawUrl, platformVerifyStep = 10) { const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep); let parsed; try { parsed = new URL(rawUrl); } catch { throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址格式无效,请重新执行步骤 ${confirmOauthStep}。`); } const code = normalizeString(parsed.searchParams.get('code')); const state = normalizeString(parsed.searchParams.get('state')); if (!code || !state) { throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmOauthStep}。`); } return { url: parsed.toString(), code, state, }; } function getCodex2ApiErrorMessage(payload, responseStatus = 500) { const details = [ payload?.error, payload?.message, payload?.detail, payload?.reason, ] .map((value) => normalizeString(value)) .find(Boolean); return details || `Codex2API 请求失败(HTTP ${responseStatus})。`; } function deriveCpaManagementOrigin(vpsUrl) { const normalizedUrl = normalizeString(vpsUrl); if (!normalizedUrl) { throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。'); } let parsed; try { parsed = new URL(normalizedUrl); } catch { throw new Error('CPA 地址格式无效,请先在侧边栏检查。'); } return parsed.origin; } function getCpaApiErrorMessage(payload, responseStatus = 500) { const details = [ payload?.error, payload?.message, payload?.detail, payload?.reason, ] .map((value) => normalizeString(value)) .find(Boolean); return details || `CPA 管理接口请求失败(HTTP ${responseStatus})。`; } async function fetchCpaManagementJson(origin, path, options = {}) { const controller = new AbortController(); const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 20000)); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const managementKey = normalizeString(options.managementKey); const headers = { Accept: 'application/json', 'Content-Type': 'application/json', }; if (managementKey) { headers.Authorization = `Bearer ${managementKey}`; headers['X-Management-Key'] = managementKey; } const response = await fetch(`${origin}${path}`, { method: options.method || 'POST', headers, body: options.body === undefined ? undefined : JSON.stringify(options.body), signal: controller.signal, }); let payload = {}; try { payload = await response.json(); } catch { payload = {}; } if (!response.ok) { throw new Error(getCpaApiErrorMessage(payload, response.status)); } return payload; } catch (error) { if (error?.name === 'AbortError') { throw new Error('CPA 管理接口请求超时,请稍后重试。'); } throw error; } finally { clearTimeout(timer); } } function isSub2ApiTransientExchangeError(error) { const message = normalizeString(error?.message || error); if (!message) { return false; } const tokenExchangeFailure = /auth\.openai\.com\/oauth\/token/i.test(message); const transientNetworkSignal = /unexpected\s+eof|eof|connection\s+refused|i\/o\s+timeout|context\s+deadline\s+exceeded|connection\s+reset|broken\s+pipe|failed\s+to\s+fetch|temporarily\s+unavailable|timeout/i.test(message); const transientExchangeUserSignal = /token_exchange_user_error|invalid\s+request\.\s+please\s+try\s+again\s+later/i.test(message); if (transientExchangeUserSignal) { return true; } return tokenExchangeFailure && transientNetworkSignal; } async function sleep(ms = 0) { const timeout = Math.max(0, Number(ms) || 0); if (!timeout) return; await new Promise((resolve) => setTimeout(resolve, timeout)); } async function fetchCodex2ApiJson(origin, path, options = {}) { const controller = new AbortController(); const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 30000)); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(`${origin}${path}`, { method: options.method || 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-Admin-Key': normalizeString(options.adminKey), }, body: options.body === undefined ? undefined : JSON.stringify(options.body), signal: controller.signal, }); let payload = {}; try { payload = await response.json(); } catch { payload = {}; } if (!response.ok) { throw new Error(getCodex2ApiErrorMessage(payload, response.status)); } return payload; } catch (error) { if (error?.name === 'AbortError') { throw new Error('Codex2API 请求超时,请稍后重试。'); } throw error; } finally { clearTimeout(timer); } } async function executeStep10(state) { if (getPanelMode(state) === 'codex2api') { return executeCodex2ApiStep10(state); } if (getPanelMode(state) === 'sub2api') { return executeSub2ApiStep10(state); } return executeCpaStep10(state); } async function executeCpaStep10(state) { const platformVerifyStep = resolvePlatformVerifyStep(state); const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep); const authLoginStep = resolveAuthLoginStep(platformVerifyStep); if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}。`); } if (!state.localhostUrl) { throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}。`); } if (!state.vpsUrl) { throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。'); } if (shouldBypassStep9ForLocalCpa(state)) { await addStepLog(platformVerifyStep, '检测到本地 CPA,且当前策略为“跳过平台回调验证”,本轮不再重复提交回调地址。', 'info'); await completeStepFromBackground(platformVerifyStep, { localhostUrl: state.localhostUrl, verifiedStatus: 'local-auto', }); return; } const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep); const expectedState = normalizeString(state.cpaOAuthState); if (expectedState && expectedState !== callback.state) { throw new Error(`CPA 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}。`); } const managementKey = normalizeString(state.vpsPassword); if (!managementKey) { throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。'); } await addStepLog(platformVerifyStep, '正在通过 CPA 管理接口提交回调地址...'); try { const origin = normalizeString(state.cpaManagementOrigin) || deriveCpaManagementOrigin(state.vpsUrl); const result = await fetchCpaManagementJson(origin, '/v0/management/oauth-callback', { method: 'POST', managementKey, body: { provider: 'codex', redirect_url: callback.url, }, }); const verifiedStatus = normalizeString(result?.message) || normalizeString(result?.status_message) || 'CPA 已通过接口提交回调'; await addStepLog(platformVerifyStep, verifiedStatus, 'ok'); await completeStepFromBackground(platformVerifyStep, { localhostUrl: callback.url, verifiedStatus, }); } catch (error) { const reason = normalizeString(error?.message) || 'unknown error'; await addStepLog(platformVerifyStep, `CPA 接口提交失败:${reason}`, 'error'); throw error; } } async function executeCodex2ApiStep10(state) { const platformVerifyStep = resolvePlatformVerifyStep(state); const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep); const authLoginStep = resolveAuthLoginStep(platformVerifyStep); if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}。`); } if (!state.localhostUrl) { throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}。`); } if (!state.codex2apiSessionId) { throw new Error(`缺少 Codex2API 会话信息,请重新执行步骤 ${authLoginStep}。`); } if (!normalizeString(state.codex2apiAdminKey)) { throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。'); } const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep); const expectedState = normalizeString(state.codex2apiOAuthState); if (expectedState && expectedState !== callback.state) { throw new Error(`Codex2API 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}。`); } const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl); const origin = new URL(codex2apiUrl).origin; await addStepLog(platformVerifyStep, '正在向 Codex2API 提交回调并创建账号...'); const result = await fetchCodex2ApiJson(origin, '/api/admin/oauth/exchange-code', { adminKey: state.codex2apiAdminKey, method: 'POST', body: { session_id: state.codex2apiSessionId, code: callback.code, state: callback.state, }, }); const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功'; await addStepLog(platformVerifyStep, verifiedStatus, 'ok'); await completeStepFromBackground(platformVerifyStep, { localhostUrl: callback.url, verifiedStatus, }); } async function executeSub2ApiStep10(state) { const platformVerifyStep = resolvePlatformVerifyStep(state); const visibleStep = platformVerifyStep; const confirmOauthStep = resolveConfirmOauthStep(visibleStep); if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) { throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}。`); } if (!state.localhostUrl) { throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}。`); } if (!state.sub2apiSessionId) { throw new Error('缺少 SUB2API 会话信息,请重新执行步骤 1。'); } if (!state.sub2apiEmail) { throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。'); } if (!state.sub2apiPassword) { throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。'); } const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl); const injectFiles = ['content/utils.js', 'content/sub2api-panel.js']; await addStepLog(visibleStep, '正在打开 SUB2API 后台...'); let tabId = await getTabId('sub2api-panel'); const alive = tabId && await isTabAlive('sub2api-panel'); if (!alive) { tabId = await reuseOrCreateTab('sub2api-panel', sub2apiUrl, { inject: injectFiles, injectSource: 'sub2api-panel', reloadIfSameUrl: true, }); } else { await closeConflictingTabsForSource('sub2api-panel', sub2apiUrl, { excludeTabIds: [tabId] }); await chrome.tabs.update(tabId, { active: true }); await rememberSourceLastUrl('sub2api-panel', sub2apiUrl); } await ensureContentScriptReadyOnTab('sub2api-panel', tabId, { inject: injectFiles, injectSource: 'sub2api-panel', }); await addStepLog(visibleStep, '正在向 SUB2API 提交回调并创建账号...'); const requestMessage = { type: 'EXECUTE_STEP', step: platformVerifyStep, source: 'background', payload: { localhostUrl: state.localhostUrl, visibleStep, sub2apiUrl, sub2apiEmail: state.sub2apiEmail, sub2apiPassword: state.sub2apiPassword, sub2apiGroupName: state.sub2apiGroupName, sub2apiDefaultProxyName: state.sub2apiDefaultProxyName, sub2apiAccountPriority: state.sub2apiAccountPriority, sub2apiProxyId: state.sub2apiProxyId, sub2apiSessionId: state.sub2apiSessionId, sub2apiOAuthState: state.sub2apiOAuthState, sub2apiGroupId: state.sub2apiGroupId, sub2apiGroupIds: state.sub2apiGroupIds, sub2apiDraftName: state.sub2apiDraftName, }, }; const maxExchangeAttempts = 3; let lastError = null; for (let attempt = 1; attempt <= maxExchangeAttempts; attempt += 1) { try { const result = await sendToContentScript('sub2api-panel', requestMessage, { responseTimeoutMs: SUB2API_STEP9_RESPONSE_TIMEOUT_MS, }); if (result?.error) { throw new Error(result.error); } return; } catch (error) { lastError = error; if (!isSub2ApiTransientExchangeError(error) || attempt >= maxExchangeAttempts) { throw error; } await addLog( `SUB2API 回调交换出现临时网络波动(${error.message}),正在重试 ${attempt + 1}/${maxExchangeAttempts}...`, 'warn', { step: visibleStep, stepKey: 'platform-verify' } ); await sleep(1200 * attempt); } } if (lastError) { throw lastError; } } return { executeCpaStep10, executeCodex2ApiStep10, executeStep10, executeSub2ApiStep10, }; } return { createStep10Executor }; });