fix: restore dev flow compatibility in CPA auth hardening
This commit is contained in:
+365
-58
@@ -1,6 +1,7 @@
|
||||
// background.js — Service Worker: orchestration, state, tab management, message routing
|
||||
|
||||
importScripts(
|
||||
'mail-provider-utils.js',
|
||||
'managed-alias-utils.js',
|
||||
'mail2925-utils.js',
|
||||
'background/phone-verification-flow.js',
|
||||
@@ -17,12 +18,17 @@ importScripts(
|
||||
'background/navigation-utils.js',
|
||||
'background/logging-status.js',
|
||||
'background/steps/registry.js',
|
||||
'data/address-sources.js',
|
||||
'data/step-definitions.js',
|
||||
'background/steps/open-chatgpt.js',
|
||||
'background/steps/submit-signup-email.js',
|
||||
'background/steps/fill-password.js',
|
||||
'background/steps/fetch-signup-code.js',
|
||||
'background/steps/fill-profile.js',
|
||||
'background/steps/create-plus-checkout.js',
|
||||
'background/steps/fill-plus-checkout.js',
|
||||
'background/steps/paypal-approve.js',
|
||||
'background/steps/plus-return-confirm.js',
|
||||
'background/steps/clear-login-cookies.js',
|
||||
'background/steps/oauth-login.js',
|
||||
'background/steps/fetch-login-code.js',
|
||||
@@ -37,12 +43,15 @@ importScripts(
|
||||
'content/activation-utils.js'
|
||||
);
|
||||
|
||||
const SHARED_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.() || [];
|
||||
const STEP_IDS = SHARED_STEP_DEFINITIONS
|
||||
const SHARED_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getAllSteps?.()
|
||||
|| self.MultiPageStepDefinitions?.getSteps?.()
|
||||
|| [];
|
||||
const DEFAULT_STEP_IDS = (self.MultiPageStepDefinitions?.getStepIds?.() || 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;
|
||||
.sort((left, right) => left - right));
|
||||
const STEP_IDS = DEFAULT_STEP_IDS;
|
||||
const LAST_STEP_ID = DEFAULT_STEP_IDS[DEFAULT_STEP_IDS.length - 1] || 10;
|
||||
const FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||
|
||||
const {
|
||||
@@ -127,6 +136,14 @@ const {
|
||||
const {
|
||||
isRecoverableStep9AuthFailure,
|
||||
} = self.MultiPageActivationUtils;
|
||||
const {
|
||||
getIcloudForwardMailConfig,
|
||||
normalizeIcloudForwardMailProvider,
|
||||
normalizeIcloudTargetMailboxType,
|
||||
} = self.MailProviderUtils || {};
|
||||
const {
|
||||
getAddressSeedForCountry,
|
||||
} = self.MultiPageAddressSources || {};
|
||||
|
||||
const LOG_PREFIX = '[MultiPage:bg]';
|
||||
const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill';
|
||||
@@ -271,6 +288,9 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
autoStepDelaySeconds: null,
|
||||
phoneVerificationEnabled: false,
|
||||
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
||||
plusModeEnabled: false,
|
||||
paypalEmail: '',
|
||||
paypalPassword: '',
|
||||
mailProvider: '163',
|
||||
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
||||
mail2925UseAccountPool: false,
|
||||
@@ -279,6 +299,8 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
customEmailPool: [],
|
||||
autoDeleteUsedIcloudAlias: false,
|
||||
icloudHostPreference: 'auto',
|
||||
icloudTargetMailboxType: 'icloud-inbox',
|
||||
icloudForwardMailProvider: 'qq',
|
||||
icloudFetchMode: 'reuse_existing',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
|
||||
@@ -745,6 +767,39 @@ function normalizePanelMode(value = '') {
|
||||
return 'cpa';
|
||||
}
|
||||
|
||||
function isPlusModeState(state = {}) {
|
||||
return Boolean(state?.plusModeEnabled);
|
||||
}
|
||||
|
||||
function getStepDefinitionsForState(state = {}) {
|
||||
return self.MultiPageStepDefinitions?.getSteps?.({ plusModeEnabled: isPlusModeState(state) })
|
||||
|| SHARED_STEP_DEFINITIONS;
|
||||
}
|
||||
|
||||
function getStepIdsForState(state = {}) {
|
||||
return self.MultiPageStepDefinitions?.getStepIds?.({ plusModeEnabled: isPlusModeState(state) })
|
||||
|| getStepDefinitionsForState(state)
|
||||
.map((definition) => Number(definition?.id))
|
||||
.filter(Number.isFinite)
|
||||
.sort((left, right) => left - right);
|
||||
}
|
||||
|
||||
function getLastStepIdForState(state = {}) {
|
||||
const fromModule = self.MultiPageStepDefinitions?.getLastStepId?.({ plusModeEnabled: isPlusModeState(state) });
|
||||
if (Number.isFinite(fromModule) && fromModule > 0) {
|
||||
return Number(fromModule);
|
||||
}
|
||||
const stepIds = getStepIdsForState(state);
|
||||
return stepIds[stepIds.length - 1] || LAST_STEP_ID;
|
||||
}
|
||||
|
||||
function getStepDefinitionForState(step, state = {}) {
|
||||
const numericStep = Number(step);
|
||||
const definition = getStepDefinitionsForState(state)
|
||||
.find((item) => Number(item?.id) === numericStep);
|
||||
return definition || null;
|
||||
}
|
||||
|
||||
function normalizeMailProvider(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
switch (normalized) {
|
||||
@@ -975,9 +1030,14 @@ function normalizePersistentSettingValue(key, value) {
|
||||
return String(value || '').trim();
|
||||
case 'customPassword':
|
||||
return String(value || '');
|
||||
case 'paypalEmail':
|
||||
return String(value || '').trim();
|
||||
case 'paypalPassword':
|
||||
return String(value || '');
|
||||
case 'autoRunSkipFailures':
|
||||
case 'autoRunDelayEnabled':
|
||||
case 'phoneVerificationEnabled':
|
||||
case 'plusModeEnabled':
|
||||
return Boolean(value);
|
||||
case 'autoRunFallbackThreadIntervalMinutes':
|
||||
return normalizeAutoRunFallbackThreadIntervalMinutes(value);
|
||||
@@ -1004,6 +1064,18 @@ function normalizePersistentSettingValue(key, value) {
|
||||
return Boolean(value);
|
||||
case 'icloudHostPreference':
|
||||
return normalizeIcloudHost(value) || 'auto';
|
||||
case 'icloudTargetMailboxType':
|
||||
return typeof normalizeIcloudTargetMailboxType === 'function'
|
||||
? normalizeIcloudTargetMailboxType(value)
|
||||
: String(value || '').trim().toLowerCase() === 'forward-mailbox'
|
||||
? 'forward-mailbox'
|
||||
: 'icloud-inbox';
|
||||
case 'icloudForwardMailProvider':
|
||||
return typeof normalizeIcloudForwardMailProvider === 'function'
|
||||
? normalizeIcloudForwardMailProvider(value)
|
||||
: String(value || '').trim().toLowerCase() === 'gmail'
|
||||
? 'gmail'
|
||||
: 'qq';
|
||||
case 'icloudFetchMode':
|
||||
return normalizeIcloudFetchMode(value);
|
||||
case 'accountRunHistoryHelperBaseUrl':
|
||||
@@ -1274,19 +1346,57 @@ async function setPasswordState(password) {
|
||||
}
|
||||
|
||||
function buildContributionModeState(enabled, persistedSettings = {}, currentState = {}) {
|
||||
function normalizeContributionModeSource(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === 'sub2api' ? 'sub2api' : 'cpa';
|
||||
}
|
||||
|
||||
function resolveContributionModeRoutingState(state = {}) {
|
||||
const currentStatus = String(state?.contributionStatus || '').trim().toLowerCase();
|
||||
const currentSource = normalizeContributionModeSource(state?.contributionSource || state?.panelMode);
|
||||
const hasActiveSession = Boolean(
|
||||
String(state?.contributionSessionId || '').trim()
|
||||
&& currentStatus
|
||||
&& !['auto_approved', 'auto_rejected', 'expired', 'error'].includes(currentStatus)
|
||||
);
|
||||
if (hasActiveSession) {
|
||||
return {
|
||||
source: currentSource,
|
||||
targetGroupName: currentSource === 'sub2api'
|
||||
? (String(state?.contributionTargetGroupName || '').trim() || 'codex号池')
|
||||
: '',
|
||||
};
|
||||
}
|
||||
|
||||
const source = 'sub2api';
|
||||
return {
|
||||
source,
|
||||
targetGroupName: isPlusModeState(state)
|
||||
? 'openai-plus'
|
||||
: (String(state?.contributionTargetGroupName || '').trim() || 'codex号池'),
|
||||
};
|
||||
}
|
||||
|
||||
const currentContributionState = {};
|
||||
for (const key of CONTRIBUTION_RUNTIME_KEYS) {
|
||||
currentContributionState[key] = currentState[key] !== undefined
|
||||
? currentState[key]
|
||||
: CONTRIBUTION_RUNTIME_DEFAULTS[key];
|
||||
}
|
||||
const routingState = resolveContributionModeRoutingState({
|
||||
...persistedSettings,
|
||||
...currentState,
|
||||
...currentContributionState,
|
||||
});
|
||||
|
||||
if (enabled) {
|
||||
return {
|
||||
...currentContributionState,
|
||||
contributionMode: true,
|
||||
contributionModeExpected: true,
|
||||
panelMode: 'cpa',
|
||||
contributionSource: routingState.source,
|
||||
contributionTargetGroupName: routingState.targetGroupName,
|
||||
panelMode: routingState.source,
|
||||
customPassword: '',
|
||||
accountRunHistoryTextEnabled: false,
|
||||
};
|
||||
@@ -1296,6 +1406,8 @@ function buildContributionModeState(enabled, persistedSettings = {}, currentStat
|
||||
...CONTRIBUTION_RUNTIME_DEFAULTS,
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
contributionSource: routingState.source,
|
||||
contributionTargetGroupName: routingState.targetGroupName,
|
||||
panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode,
|
||||
customPassword: persistedSettings.customPassword || '',
|
||||
accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled),
|
||||
@@ -1309,13 +1421,18 @@ async function setContributionMode(enabled) {
|
||||
getState(),
|
||||
]);
|
||||
|
||||
const contributionRoutingSource = normalizedEnabled
|
||||
? (String(currentState?.contributionSource || persistedSettings?.panelMode || '').trim().toLowerCase() === 'sub2api'
|
||||
? 'sub2api'
|
||||
: 'sub2api')
|
||||
: (persistedSettings.panelMode || DEFAULT_STATE.panelMode);
|
||||
if (normalizedEnabled) {
|
||||
await setPersistentSettings({ panelMode: 'cpa' });
|
||||
await setPersistentSettings({ panelMode: contributionRoutingSource });
|
||||
}
|
||||
|
||||
const updates = buildContributionModeState(normalizedEnabled, {
|
||||
...persistedSettings,
|
||||
...(normalizedEnabled ? { panelMode: 'cpa' } : {}),
|
||||
...(normalizedEnabled ? { panelMode: contributionRoutingSource } : {}),
|
||||
}, currentState);
|
||||
|
||||
await setState(updates);
|
||||
@@ -3459,29 +3576,94 @@ async function withIcloudLoginHelp(actionLabel, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function getIcloudRequestTargetLabel(url = '') {
|
||||
try {
|
||||
const parsed = new URL(String(url || ''));
|
||||
if (/\/v2\/hme\/list(?:[/?#]|$)/i.test(parsed.pathname)) return '别名列表';
|
||||
if (/\/v1\/hme\/generate(?:[/?#]|$)/i.test(parsed.pathname)) return '生成别名';
|
||||
if (/\/v1\/hme\/reserve(?:[/?#]|$)/i.test(parsed.pathname)) return '预留别名';
|
||||
if (/\/v1\/hme\/delete(?:[/?#]|$)/i.test(parsed.pathname)) return '删除别名';
|
||||
} catch {}
|
||||
return 'iCloud 请求';
|
||||
}
|
||||
|
||||
function getIcloudRetryDelay(attemptIndex = 0) {
|
||||
const retryDelays = Array.isArray(globalThis.ICLOUD_RETRY_DELAYS_MS)
|
||||
? globalThis.ICLOUD_RETRY_DELAYS_MS
|
||||
: [500, 1200, 2000];
|
||||
const index = Math.max(0, Math.floor(Number(attemptIndex) || 0));
|
||||
const delay = Number(retryDelays[Math.min(index, retryDelays.length - 1)]);
|
||||
return Number.isFinite(delay) ? Math.max(0, delay) : 0;
|
||||
}
|
||||
|
||||
function isIcloudRetryableStatus(status) {
|
||||
const code = Number(status);
|
||||
return code === 408 || code === 409 || code === 421 || code === 425 || code === 429 || code === 500 || code === 502 || code === 503 || code === 504;
|
||||
}
|
||||
|
||||
function isIcloudRetryableError(error) {
|
||||
const message = String(error?.message || error || '').toLowerCase();
|
||||
return /failed to fetch|networkerror|network request failed|timeout|aborted|econnreset|eai_again|temporarily unavailable|503|502|504/.test(message)
|
||||
|| Boolean(error?.networkFailure);
|
||||
}
|
||||
|
||||
async function icloudRequest(method, url, options = {}) {
|
||||
const { data } = options;
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
credentials: 'include',
|
||||
headers: data !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: data !== undefined ? JSON.stringify(data) : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`iCloud 请求失败:${method} ${url},${err.message}`);
|
||||
const maxAttempts = Math.max(1, Math.floor(Number(options.maxAttempts) || 1));
|
||||
const logRetries = Boolean(options.logRetries);
|
||||
const retryLabel = String(options.retryLabel || getIcloudRequestTargetLabel(url) || 'iCloud 请求');
|
||||
|
||||
let lastError = null;
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
credentials: 'include',
|
||||
headers: data !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: data !== undefined ? JSON.stringify(data) : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
lastError = new Error(`iCloud 请求失败:${method} ${url},${err.message}`);
|
||||
const canRetry = attempt < maxAttempts && isIcloudRetryableError(err);
|
||||
if (!canRetry) break;
|
||||
if (logRetries) {
|
||||
addLog(`iCloud:${retryLabel}请求异常,准备重试(${attempt}/${maxAttempts}):${err.message}`, 'warn').catch(() => {});
|
||||
}
|
||||
const delayMs = getIcloudRetryDelay(attempt - 1);
|
||||
if (delayMs > 0) {
|
||||
await sleepWithStop(delayMs);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error(`iCloud 请求失败:${method} ${url},status ${response.status}`);
|
||||
lastError = error;
|
||||
const canRetry = attempt < maxAttempts && isIcloudRetryableStatus(response.status);
|
||||
if (!canRetry) break;
|
||||
if (logRetries) {
|
||||
addLog(`iCloud:${retryLabel}返回 status ${response.status},准备重试(${attempt}/${maxAttempts})。`, 'warn').catch(() => {});
|
||||
}
|
||||
const delayMs = getIcloudRetryDelay(attempt - 1);
|
||||
if (delayMs > 0) {
|
||||
await sleepWithStop(delayMs);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof response.json === 'function') {
|
||||
return await response.json();
|
||||
}
|
||||
const text = await response.text();
|
||||
return text ? JSON.parse(text) : {};
|
||||
} catch (err) {
|
||||
throw new Error(`iCloud 返回的 JSON 无法解析:${method} ${url},${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`iCloud 请求失败:${method} ${url},status ${response.status}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return await response.json();
|
||||
} catch (err) {
|
||||
throw new Error(`iCloud 返回的 JSON 无法解析:${method} ${url},${err.message}`);
|
||||
}
|
||||
throw lastError || new Error(`iCloud 请求失败:${method} ${url}`);
|
||||
}
|
||||
|
||||
async function validateIcloudSession(setupUrl) {
|
||||
@@ -3641,12 +3823,7 @@ async function fetchIcloudHideMyEmail(options = {}) {
|
||||
const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService();
|
||||
await addLog(`iCloud:已通过 ${new URL(setupUrl).host} 验证会话`, 'ok');
|
||||
|
||||
const existingAliasesResponse = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`);
|
||||
const state = await getState();
|
||||
const existingAliases = normalizeIcloudAliasList(existingAliasesResponse, {
|
||||
usedEmails: getEffectiveUsedEmails(state),
|
||||
preservedEmails: getPreservedAliasMap(state),
|
||||
});
|
||||
const existingAliases = await listIcloudAliases().catch(() => []);
|
||||
|
||||
if (!generateNew) {
|
||||
const reusableAlias = pickReusableIcloudAlias(existingAliases);
|
||||
@@ -3667,19 +3844,35 @@ async function fetchIcloudHideMyEmail(options = {}) {
|
||||
throw new Error(generated?.error?.errorMessage || 'iCloud 隐私邮箱生成失败。');
|
||||
}
|
||||
|
||||
const reserved = await icloudRequest('POST', `${serviceUrl}/v1/hme/reserve`, {
|
||||
data: {
|
||||
hme: generated.result.hme,
|
||||
label: getIcloudAliasLabel(),
|
||||
note: 'Generated through Multi-Page Automation',
|
||||
},
|
||||
});
|
||||
let alias = '';
|
||||
try {
|
||||
const reserved = await icloudRequest('POST', `${serviceUrl}/v1/hme/reserve`, {
|
||||
data: {
|
||||
hme: generated.result.hme,
|
||||
label: getIcloudAliasLabel(),
|
||||
note: 'Generated through Multi-Page Automation',
|
||||
},
|
||||
});
|
||||
|
||||
if (!reserved?.success || !reserved?.result?.hme?.hme) {
|
||||
throw new Error(reserved?.error?.errorMessage || 'iCloud 隐私邮箱保留失败。');
|
||||
if (!reserved?.success || !reserved?.result?.hme?.hme) {
|
||||
throw new Error(reserved?.error?.errorMessage || 'iCloud 隐私邮箱保留失败。');
|
||||
}
|
||||
|
||||
alias = String(reserved.result.hme.hme || '').trim().toLowerCase();
|
||||
} catch (reserveError) {
|
||||
const reserveFailedAlias = String(generated?.result?.hme || '').trim().toLowerCase();
|
||||
if (!reserveFailedAlias) {
|
||||
throw reserveError;
|
||||
}
|
||||
const aliases = await listIcloudAliases().catch(() => []);
|
||||
const aliasFromList = findIcloudAliasByEmail(aliases, reserveFailedAlias);
|
||||
if (!aliasFromList?.email) {
|
||||
throw reserveError;
|
||||
}
|
||||
alias = String(aliasFromList.email || '').trim().toLowerCase();
|
||||
await addLog(`iCloud:保留接口报错,但已在列表确认别名 ${alias},继续使用该地址。`, 'warn');
|
||||
}
|
||||
|
||||
const alias = String(reserved.result.hme.hme || '').trim().toLowerCase();
|
||||
await setEmailState(alias);
|
||||
await addLog(`iCloud:已创建并保留新别名 ${alias}`, 'ok');
|
||||
broadcastIcloudAliasesChanged({ reason: 'created', email: alias });
|
||||
@@ -4040,6 +4233,28 @@ async function sendToMailContentScriptResilient(mail, message, options = {}) {
|
||||
return tabRuntime.sendToMailContentScriptResilient(mail, message, options);
|
||||
}
|
||||
|
||||
function throwIfStepExecutionStopped(error = null) {
|
||||
throwIfStopped(error);
|
||||
}
|
||||
|
||||
async function waitForTabCompleteUntilStopped(tabId, options = {}) {
|
||||
return waitForTabComplete(tabId, options);
|
||||
}
|
||||
|
||||
async function waitForTabUrlMatchUntilStopped(tabId, matcher, options = {}) {
|
||||
return waitForTabUrlMatch(tabId, matcher, options);
|
||||
}
|
||||
|
||||
async function ensureContentScriptReadyOnTabUntilStopped(source, tabId, options = {}) {
|
||||
throwIfStepExecutionStopped();
|
||||
return ensureContentScriptReadyOnTab(source, tabId, options);
|
||||
}
|
||||
|
||||
async function sendTabMessageUntilStopped(tabId, source, message, options = {}) {
|
||||
throwIfStepExecutionStopped();
|
||||
return tabRuntime.sendTabMessage(tabId, source, message, options);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Logging
|
||||
// ============================================================
|
||||
@@ -4306,7 +4521,11 @@ function getFirstUnfinishedStep(statuses = {}) {
|
||||
if (typeof loggingStatus !== 'undefined' && loggingStatus?.getFirstUnfinishedStep) {
|
||||
return loggingStatus.getFirstUnfinishedStep(statuses);
|
||||
}
|
||||
for (const step of STEP_IDS) {
|
||||
const stepIds = Object.keys({ ...DEFAULT_STATE.stepStatuses, ...statuses })
|
||||
.map((step) => Number(step))
|
||||
.filter(Number.isFinite)
|
||||
.sort((left, right) => left - right);
|
||||
for (const step of stepIds) {
|
||||
if (!isStepDoneStatus(statuses[step] || 'pending')) return step;
|
||||
}
|
||||
return null;
|
||||
@@ -4389,9 +4608,10 @@ async function invalidateDownstreamAfterStepRestart(step, options = {}) {
|
||||
const { logLabel = `步骤 ${step} 重新执行` } = options;
|
||||
const state = await getState();
|
||||
const statuses = { ...(state.stepStatuses || {}) };
|
||||
const lastStepId = getLastStepIdForState(state);
|
||||
const changedSteps = [];
|
||||
|
||||
for (let downstream = step + 1; downstream <= LAST_STEP_ID; downstream++) {
|
||||
for (let downstream = step + 1; downstream <= lastStepId; downstream++) {
|
||||
if (statuses[downstream] !== 'pending') {
|
||||
statuses[downstream] = 'pending';
|
||||
changedSteps.push(downstream);
|
||||
@@ -4936,8 +5156,9 @@ async function ensureManualInteractionAllowed(actionLabel) {
|
||||
|
||||
async function skipStep(step) {
|
||||
const state = await ensureManualInteractionAllowed('跳过步骤');
|
||||
const stepIds = getStepIdsForState(state);
|
||||
|
||||
if (!Number.isInteger(step) || !STEP_IDS.includes(step)) {
|
||||
if (!Number.isInteger(step) || !stepIds.includes(step)) {
|
||||
throw new Error(`无效步骤:${step}`);
|
||||
}
|
||||
|
||||
@@ -5272,11 +5493,13 @@ async function completeStepFromBackground(step, payload = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
const completionState = step === LAST_STEP_ID ? await getState() : null;
|
||||
const latestState = await getState();
|
||||
const completionLastStepId = getLastStepIdForState(latestState);
|
||||
const completionState = step === completionLastStepId ? latestState : null;
|
||||
await setStepStatus(step, 'completed');
|
||||
await addLog(`步骤 ${step} 已完成`, 'ok');
|
||||
await handleStepData(step, payload);
|
||||
if (step === LAST_STEP_ID) {
|
||||
if (step === completionLastStepId) {
|
||||
await appendAndBroadcastAccountRunRecord('success', completionState);
|
||||
}
|
||||
notifyStepComplete(step, payload);
|
||||
@@ -5373,7 +5596,7 @@ async function waitForRunningStepsToFinish(payload = {}) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
const AUTH_CHAIN_STEP_IDS = new Set([7, 8, 9, 10]);
|
||||
const AUTH_CHAIN_STEP_IDS = new Set([7, 8, 9, 10, 11, 12, 13]);
|
||||
let activeTopLevelAuthChainExecution = null;
|
||||
|
||||
function isAuthChainStep(step) {
|
||||
@@ -5541,7 +5764,8 @@ async function executeStep(step, options = {}) {
|
||||
await setState({ flowStartTime: Date.now() });
|
||||
}
|
||||
|
||||
await stepRegistry.executeStep(step, state);
|
||||
const activeStepRegistry = getStepRegistryForState(state);
|
||||
await activeStepRegistry.executeStep(step, { ...state, visibleStep: step });
|
||||
} catch (err) {
|
||||
executionError = err;
|
||||
const state = await getState();
|
||||
@@ -6278,8 +6502,12 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
||||
|
||||
let restartFromStep1WithCurrentEmail = false;
|
||||
let step = Math.max(currentStartStep, 4);
|
||||
while (step <= LAST_STEP_ID) {
|
||||
while (true) {
|
||||
const latestState = await getState();
|
||||
const flowLastStepId = getLastStepIdForState(latestState);
|
||||
if (step > flowLastStepId) {
|
||||
break;
|
||||
}
|
||||
const currentStatus = latestState.stepStatuses?.[step] || 'pending';
|
||||
if (isStepDoneStatus(currentStatus)) {
|
||||
await addLog(`自动运行:步骤 ${step} 当前状态为 ${currentStatus},将直接继续后续流程。`, 'info');
|
||||
@@ -6631,6 +6859,54 @@ const step6Executor = self.MultiPageBackgroundStep6?.createStep6Executor({
|
||||
completeStepFromBackground,
|
||||
runPreStep6CookieCleanup,
|
||||
});
|
||||
const plusCheckoutCreateExecutor = self.MultiPageBackgroundPlusCheckoutCreate?.createPlusCheckoutCreateExecutor({
|
||||
addLog,
|
||||
chrome,
|
||||
completeStepFromBackground,
|
||||
ensureContentScriptReadyOnTabUntilStopped,
|
||||
registerTab,
|
||||
sendTabMessageUntilStopped,
|
||||
setState,
|
||||
sleepWithStop,
|
||||
waitForTabCompleteUntilStopped,
|
||||
});
|
||||
const plusCheckoutBillingExecutor = self.MultiPageBackgroundPlusCheckoutBilling?.createPlusCheckoutBillingExecutor({
|
||||
addLog,
|
||||
chrome,
|
||||
completeStepFromBackground,
|
||||
ensureContentScriptReadyOnTabUntilStopped,
|
||||
fetch,
|
||||
generateRandomName,
|
||||
getAddressSeedForCountry,
|
||||
getTabId,
|
||||
isTabAlive,
|
||||
setState,
|
||||
sleepWithStop,
|
||||
waitForTabCompleteUntilStopped,
|
||||
waitForTabUrlMatchUntilStopped,
|
||||
});
|
||||
const payPalApproveExecutor = self.MultiPageBackgroundPayPalApprove?.createPayPalApproveExecutor({
|
||||
addLog,
|
||||
chrome,
|
||||
completeStepFromBackground,
|
||||
ensureContentScriptReadyOnTabUntilStopped,
|
||||
getTabId,
|
||||
isTabAlive,
|
||||
sendTabMessageUntilStopped,
|
||||
setState,
|
||||
sleepWithStop,
|
||||
waitForTabCompleteUntilStopped,
|
||||
waitForTabUrlMatchUntilStopped,
|
||||
});
|
||||
const plusReturnConfirmExecutor = self.MultiPageBackgroundPlusReturnConfirm?.createPlusReturnConfirmExecutor({
|
||||
addLog,
|
||||
completeStepFromBackground,
|
||||
getTabId,
|
||||
isTabAlive,
|
||||
setState,
|
||||
sleepWithStop,
|
||||
waitForTabUrlMatchUntilStopped,
|
||||
});
|
||||
const step7Executor = self.MultiPageBackgroundStep7?.createStep7Executor({
|
||||
addLog,
|
||||
completeStepFromBackground,
|
||||
@@ -6695,7 +6971,6 @@ const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({
|
||||
shouldBypassStep9ForLocalCpa,
|
||||
SUB2API_STEP9_RESPONSE_TIMEOUT_MS,
|
||||
});
|
||||
const stepDefinitions = SHARED_STEP_DEFINITIONS;
|
||||
const stepExecutorsByKey = {
|
||||
'open-chatgpt': () => step1Executor.executeStep1(),
|
||||
'submit-signup-email': (state) => step2Executor.executeStep2(state),
|
||||
@@ -6703,6 +6978,10 @@ const stepExecutorsByKey = {
|
||||
'fetch-signup-code': (state) => step4Executor.executeStep4(state),
|
||||
'fill-profile': (state) => step5Executor.executeStep5(state),
|
||||
'clear-login-cookies': () => step6Executor.executeStep6(),
|
||||
'plus-checkout-create': () => plusCheckoutCreateExecutor.executePlusCheckoutCreate(),
|
||||
'plus-checkout-billing': () => plusCheckoutBillingExecutor.executePlusCheckoutBilling(),
|
||||
'paypal-approve': (state) => payPalApproveExecutor.executePayPalApprove(state),
|
||||
'plus-checkout-return': (state) => plusReturnConfirmExecutor.executePlusReturnConfirm(state),
|
||||
'oauth-login': (state) => step7Executor.executeStep7(state),
|
||||
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
||||
'confirm-oauth': (state) => step9Executor.executeStep9(state),
|
||||
@@ -6753,6 +7032,9 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
||||
getPendingAutoRunTimerPlan,
|
||||
getSourceLabel,
|
||||
getState,
|
||||
getStepDefinitionForState,
|
||||
getStepIdsForState,
|
||||
getLastStepIdForState,
|
||||
getTabId,
|
||||
getStopRequested: () => stopRequested,
|
||||
handleCloudflareSecurityBlocked,
|
||||
@@ -6810,12 +7092,15 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
||||
upsertHotmailAccount,
|
||||
verifyHotmailAccount,
|
||||
});
|
||||
const stepRegistry = self.MultiPageBackgroundStepRegistry?.createStepRegistry(
|
||||
stepDefinitions.map((definition) => ({
|
||||
...definition,
|
||||
execute: stepExecutorsByKey[definition.key],
|
||||
}))
|
||||
);
|
||||
function getStepRegistryForState(state = {}) {
|
||||
const stepDefinitions = getStepDefinitionsForState(state);
|
||||
return self.MultiPageBackgroundStepRegistry?.createStepRegistry(
|
||||
stepDefinitions.map((definition) => ({
|
||||
...definition,
|
||||
execute: stepExecutorsByKey[definition.key],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
async function requestOAuthUrlFromPanel(state, options = {}) {
|
||||
return panelBridge.requestOAuthUrlFromPanel(state, options);
|
||||
@@ -6890,9 +7175,30 @@ function getMailConfig(state) {
|
||||
return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(API对接/本地助手)' };
|
||||
}
|
||||
if (provider === ICLOUD_PROVIDER) {
|
||||
const resolveForwardMailConfig = typeof getIcloudForwardMailConfig === 'function'
|
||||
? getIcloudForwardMailConfig
|
||||
: (typeof getSharedIcloudForwardMailConfig === 'function' ? getSharedIcloudForwardMailConfig : null);
|
||||
const configuredHost = getConfiguredIcloudHostPreference(state)
|
||||
|| normalizeIcloudHost(state?.preferredIcloudHost)
|
||||
|| 'icloud.com';
|
||||
const targetMailboxType = typeof normalizeIcloudTargetMailboxType === 'function'
|
||||
? normalizeIcloudTargetMailboxType(state?.icloudTargetMailboxType)
|
||||
: 'icloud-inbox';
|
||||
if (targetMailboxType === 'forward-mailbox' && typeof resolveForwardMailConfig === 'function') {
|
||||
const normalizeForwardProvider = typeof normalizeIcloudForwardMailProvider === 'function'
|
||||
? normalizeIcloudForwardMailProvider
|
||||
: (value = '') => String(value || '').trim().toLowerCase() === 'gmail' ? 'gmail' : 'qq';
|
||||
const forwardProvider = normalizeForwardProvider(state?.icloudForwardMailProvider);
|
||||
const forwardConfig = resolveForwardMailConfig(forwardProvider);
|
||||
if (forwardConfig && !forwardConfig.error) {
|
||||
return {
|
||||
...forwardConfig,
|
||||
label: `iCloud 转发(${forwardConfig.label})`,
|
||||
icloudForwarding: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const loginUrl = getIcloudLoginUrlForHost(configuredHost) || 'https://www.icloud.com/';
|
||||
const mailUrl = getIcloudMailUrlForHost(configuredHost) || loginUrl;
|
||||
return {
|
||||
@@ -7271,10 +7577,11 @@ function isAddPhoneAuthState(authState = {}) {
|
||||
}
|
||||
|
||||
async function getPostStep6AutoRestartDecision(step, error) {
|
||||
const currentState = await getState().catch(() => ({}));
|
||||
const normalizedStep = Number(step);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
const shouldForceRestartFromStep7 = /restart step 7 with a new number/i.test(errorMessage);
|
||||
if (!Number.isFinite(normalizedStep) || normalizedStep < 7 || normalizedStep > LAST_STEP_ID) {
|
||||
if (!Number.isFinite(normalizedStep) || normalizedStep < 7 || normalizedStep > getLastStepIdForState(currentState)) {
|
||||
return {
|
||||
shouldRestart: false,
|
||||
blockedByAddPhone: false,
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
const RUNTIME_DEFAULTS = {
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
contributionQq: '',
|
||||
contributionSessionId: '',
|
||||
@@ -38,6 +40,8 @@
|
||||
} = deps;
|
||||
|
||||
let listenersBound = false;
|
||||
const pendingCallbackSubmissions = new Map();
|
||||
const pendingCapturedCallbacks = new Map();
|
||||
|
||||
function normalizeString(value = '') {
|
||||
return String(value || '').trim();
|
||||
@@ -256,6 +260,41 @@
|
||||
return qq;
|
||||
}
|
||||
|
||||
function isPlusModeState(state = {}) {
|
||||
return Boolean(state?.plusModeEnabled);
|
||||
}
|
||||
|
||||
function normalizeContributionModeSource(value = '') {
|
||||
const normalized = normalizeString(value).toLowerCase();
|
||||
return normalized === 'sub2api' ? 'sub2api' : 'cpa';
|
||||
}
|
||||
|
||||
function resolveContributionModeRoutingState(state = {}) {
|
||||
const currentStatus = normalizeString(state?.contributionStatus).toLowerCase();
|
||||
const currentSource = normalizeContributionModeSource(state?.contributionSource);
|
||||
const hasActiveSession = Boolean(
|
||||
normalizeString(state?.contributionSessionId)
|
||||
&& currentStatus
|
||||
&& !FINAL_STATUSES.has(currentStatus)
|
||||
);
|
||||
|
||||
if (hasActiveSession) {
|
||||
return {
|
||||
source: currentSource,
|
||||
targetGroupName: currentSource === 'sub2api'
|
||||
? (normalizeString(state?.contributionTargetGroupName) || 'codex号池')
|
||||
: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
source: 'sub2api',
|
||||
targetGroupName: isPlusModeState(state)
|
||||
? 'openai-plus'
|
||||
: (normalizeString(state?.contributionTargetGroupName) || 'codex号池'),
|
||||
};
|
||||
}
|
||||
|
||||
function buildStatusMessage(status, payload = {}) {
|
||||
const label = getStatusLabel(status);
|
||||
const details = [
|
||||
@@ -435,46 +474,58 @@
|
||||
return currentState;
|
||||
}
|
||||
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'submitting',
|
||||
contributionCallbackMessage: buildCallbackMessage('submitting'),
|
||||
});
|
||||
|
||||
try {
|
||||
const payload = await fetchContributionJson('/submit-callback', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
session_id: sessionId,
|
||||
callback_url: normalizedUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const nextStatus = 'submitted';
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: nextStatus,
|
||||
contributionCallbackMessage: buildCallbackMessage(nextStatus, payload),
|
||||
});
|
||||
|
||||
if (typeof closeLocalhostCallbackTabs === 'function') {
|
||||
await closeLocalhostCallbackTabs(normalizedUrl).catch(() => {});
|
||||
}
|
||||
|
||||
return await pollContributionStatus({ reason: options.reason || 'submit_callback' });
|
||||
} catch (error) {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'failed',
|
||||
contributionCallbackMessage: `回调提交失败:${error.message}`,
|
||||
});
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:回调提交失败:${error.message}`, 'warn');
|
||||
}
|
||||
|
||||
throw error;
|
||||
const dedupeKey = `${sessionId}::${normalizedUrl}`;
|
||||
if (pendingCallbackSubmissions.has(dedupeKey)) {
|
||||
return pendingCallbackSubmissions.get(dedupeKey);
|
||||
}
|
||||
|
||||
const task = (async () => {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'submitting',
|
||||
contributionCallbackMessage: buildCallbackMessage('submitting'),
|
||||
});
|
||||
|
||||
try {
|
||||
const payload = await fetchContributionJson('/submit-callback', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
session_id: sessionId,
|
||||
callback_url: normalizedUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const nextStatus = 'submitted';
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: nextStatus,
|
||||
contributionCallbackMessage: buildCallbackMessage(nextStatus, payload),
|
||||
});
|
||||
|
||||
if (typeof closeLocalhostCallbackTabs === 'function') {
|
||||
await closeLocalhostCallbackTabs(normalizedUrl).catch(() => {});
|
||||
}
|
||||
|
||||
return await pollContributionStatus({ reason: options.reason || 'submit_callback' });
|
||||
} catch (error) {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'failed',
|
||||
contributionCallbackMessage: `回调提交失败:${error.message}`,
|
||||
});
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:回调提交失败:${error.message}`, 'warn');
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
pendingCallbackSubmissions.delete(dedupeKey);
|
||||
}
|
||||
})();
|
||||
|
||||
pendingCallbackSubmissions.set(dedupeKey, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
async function handleCapturedCallback(rawUrl, metadata = {}) {
|
||||
@@ -487,6 +538,13 @@
|
||||
}
|
||||
|
||||
const normalizedUrl = normalizeString(rawUrl);
|
||||
const callbackDedupeKey = `${normalizeString(currentState.contributionSessionId)}::${normalizedUrl}`;
|
||||
if (pendingCapturedCallbacks.has(callbackDedupeKey)) {
|
||||
return pendingCapturedCallbacks.get(callbackDedupeKey);
|
||||
}
|
||||
if (pendingCallbackSubmissions.has(callbackDedupeKey)) {
|
||||
return pendingCallbackSubmissions.get(callbackDedupeKey);
|
||||
}
|
||||
const currentCallbackStatus = normalizeContributionCallbackStatus(currentState.contributionCallbackStatus);
|
||||
if (
|
||||
normalizedUrl
|
||||
@@ -496,24 +554,31 @@
|
||||
return currentState;
|
||||
}
|
||||
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'captured',
|
||||
contributionCallbackMessage: buildCallbackMessage('captured'),
|
||||
});
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:已捕获回调地址(${metadata.source || 'unknown'})。`, 'info');
|
||||
}
|
||||
|
||||
try {
|
||||
return await submitContributionCallback(normalizedUrl, {
|
||||
reason: metadata.source || 'navigation',
|
||||
stateOverride: await getState(),
|
||||
const task = (async () => {
|
||||
await applyRuntimeUpdates({
|
||||
contributionCallbackUrl: normalizedUrl,
|
||||
contributionCallbackStatus: 'captured',
|
||||
contributionCallbackMessage: buildCallbackMessage('captured'),
|
||||
});
|
||||
} catch {
|
||||
return getState();
|
||||
}
|
||||
|
||||
if (typeof addLog === 'function') {
|
||||
await addLog(`贡献模式:已捕获回调地址(${metadata.source || 'unknown'})。`, 'info');
|
||||
}
|
||||
|
||||
try {
|
||||
return await submitContributionCallback(normalizedUrl, {
|
||||
reason: metadata.source || 'navigation',
|
||||
stateOverride: await getState(),
|
||||
});
|
||||
} catch {
|
||||
return getState();
|
||||
} finally {
|
||||
pendingCapturedCallbacks.delete(callbackDedupeKey);
|
||||
}
|
||||
})();
|
||||
|
||||
pendingCapturedCallbacks.set(callbackDedupeKey, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
async function pollContributionStatus(options = {}) {
|
||||
@@ -594,13 +659,15 @@
|
||||
return pollContributionStatus({ reason: 'resume_existing' });
|
||||
}
|
||||
|
||||
const routingState = resolveContributionModeRoutingState(currentState);
|
||||
const payload = await fetchContributionJson('/start', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
nickname: buildNickname(currentState, options.nickname),
|
||||
qq: buildContributionQq(currentState, options.qq),
|
||||
email: normalizeString(currentState.email),
|
||||
source: 'cpa',
|
||||
source: routingState.source,
|
||||
target_group_name: routingState.targetGroupName,
|
||||
channel: 'codex-extension',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
getPendingAutoRunTimerPlan,
|
||||
getSourceLabel,
|
||||
getState,
|
||||
getStepDefinitionForState,
|
||||
getStepIdsForState,
|
||||
getLastStepIdForState,
|
||||
getTabId,
|
||||
getStopRequested,
|
||||
handleAutoRunLoopUnhandledError,
|
||||
@@ -127,7 +130,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLastStepIdForState(state = {}) {
|
||||
if (typeof getLastStepIdForState === 'function') {
|
||||
const fromResolver = Number(getLastStepIdForState(state));
|
||||
if (Number.isFinite(fromResolver) && fromResolver > 0) {
|
||||
return fromResolver;
|
||||
}
|
||||
}
|
||||
const stepIds = typeof getStepIdsForState === 'function'
|
||||
? (getStepIdsForState(state) || [])
|
||||
: Object.keys(state?.stepStatuses || {}).map((step) => Number(step)).filter(Number.isFinite);
|
||||
const sorted = stepIds.slice().sort((left, right) => left - right);
|
||||
return Math.max(10, sorted[sorted.length - 1] || 0);
|
||||
}
|
||||
|
||||
async function handleStepData(step, payload) {
|
||||
if (payload.skipLoginVerificationStep || payload.directOAuthConsentPage) {
|
||||
const latestState = await getState();
|
||||
const currentDefinition = typeof getStepDefinitionForState === 'function'
|
||||
? getStepDefinitionForState(step, latestState)
|
||||
: null;
|
||||
const currentKey = String(currentDefinition?.key || '').trim();
|
||||
if (currentKey === 'oauth-login') {
|
||||
const stepIds = typeof getStepIdsForState === 'function'
|
||||
? (getStepIdsForState(latestState) || [])
|
||||
: Object.keys(latestState?.stepStatuses || {}).map((item) => Number(item)).filter(Number.isFinite);
|
||||
const ordered = stepIds.slice().sort((left, right) => left - right);
|
||||
const currentIndex = ordered.indexOf(Number(step));
|
||||
const nextStep = currentIndex >= 0 ? ordered[currentIndex + 1] : null;
|
||||
const nextDefinition = Number.isFinite(nextStep) && typeof getStepDefinitionForState === 'function'
|
||||
? getStepDefinitionForState(nextStep, latestState)
|
||||
: null;
|
||||
if (Number.isFinite(nextStep) && String(nextDefinition?.key || '').trim() === 'fetch-login-code') {
|
||||
const nextStatus = latestState.stepStatuses?.[nextStep];
|
||||
if (nextStatus !== 'running' && nextStatus !== 'completed' && nextStatus !== 'manual_completed') {
|
||||
await setStepStatus(nextStep, 'skipped');
|
||||
await addLog(`步骤 ${step}:检测到已直达 OAuth 授权页,已自动跳过步骤 ${nextStep} 登录验证码。`, 'warn');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 1: {
|
||||
const updates = {};
|
||||
@@ -217,7 +260,8 @@
|
||||
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
|
||||
}
|
||||
break;
|
||||
case 10: {
|
||||
case 10:
|
||||
case 13: {
|
||||
if (payload.localhostUrl) {
|
||||
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||
}
|
||||
@@ -305,11 +349,13 @@
|
||||
return { ok: true, error: errorMessage };
|
||||
}
|
||||
|
||||
const completionState = message.step === 10 ? await getState() : null;
|
||||
const latestState = await getState();
|
||||
const lastStepId = resolveLastStepIdForState(latestState);
|
||||
const completionState = message.step === lastStepId ? latestState : null;
|
||||
await setStepStatus(message.step, 'completed');
|
||||
await addLog(`步骤 ${message.step} 已完成`, 'ok');
|
||||
await handleStepData(message.step, message.payload);
|
||||
if (message.step === 10 && typeof appendAccountRunRecord === 'function') {
|
||||
if (message.step === lastStepId && typeof appendAccountRunRecord === 'function') {
|
||||
await appendAccountRunRecord('success', completionState);
|
||||
}
|
||||
notifyStepComplete(message.step, message.payload);
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function resolvePlatformVerifyStep(state = {}) {
|
||||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||
return visibleStep >= 10 ? visibleStep : 10;
|
||||
}
|
||||
|
||||
function parseLocalhostCallback(rawUrl) {
|
||||
let parsed;
|
||||
try {
|
||||
@@ -180,6 +185,7 @@
|
||||
}
|
||||
|
||||
async function executeCpaStep10(state) {
|
||||
const platformVerifyStep = resolvePlatformVerifyStep(state);
|
||||
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
||||
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
||||
}
|
||||
@@ -192,7 +198,7 @@
|
||||
|
||||
if (shouldBypassStep9ForLocalCpa(state)) {
|
||||
await addLog('步骤 10:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info');
|
||||
await completeStepFromBackground(10, {
|
||||
await completeStepFromBackground(platformVerifyStep, {
|
||||
localhostUrl: state.localhostUrl,
|
||||
verifiedStatus: 'local-auto',
|
||||
});
|
||||
@@ -225,7 +231,7 @@
|
||||
|| normalizeString(result?.status_message)
|
||||
|| 'CPA 已通过接口提交回调';
|
||||
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
||||
await completeStepFromBackground(10, {
|
||||
await completeStepFromBackground(platformVerifyStep, {
|
||||
localhostUrl: callback.url,
|
||||
verifiedStatus,
|
||||
});
|
||||
@@ -237,6 +243,7 @@
|
||||
}
|
||||
|
||||
async function executeCodex2ApiStep10(state) {
|
||||
const platformVerifyStep = resolvePlatformVerifyStep(state);
|
||||
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
||||
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
||||
}
|
||||
@@ -272,7 +279,7 @@
|
||||
|
||||
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
|
||||
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
||||
await completeStepFromBackground(10, {
|
||||
await completeStepFromBackground(platformVerifyStep, {
|
||||
localhostUrl: callback.url,
|
||||
verifiedStatus,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
|
||||
test('message router appends success record on Plus final step instead of hard-coded step 10', async () => {
|
||||
const appendCalls = [];
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
appendAccountRunRecord: async (...args) => {
|
||||
appendCalls.push(args);
|
||||
},
|
||||
batchUpdateLuckmailPurchases: async () => {},
|
||||
buildLocalhostCleanupPrefix: () => '',
|
||||
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||
buildPersistentSettingsPayload: () => ({}),
|
||||
broadcastDataUpdate: () => {},
|
||||
cancelScheduledAutoRun: async () => {},
|
||||
checkIcloudSession: async () => {},
|
||||
clearAutoRunTimerAlarm: async () => {},
|
||||
clearLuckmailRuntimeState: async () => {},
|
||||
clearStopRequest: () => {},
|
||||
closeLocalhostCallbackTabs: async () => {},
|
||||
closeTabsByUrlPrefix: async () => {},
|
||||
deleteHotmailAccount: async () => {},
|
||||
deleteHotmailAccounts: async () => {},
|
||||
deleteIcloudAlias: async () => {},
|
||||
deleteUsedIcloudAliases: async () => {},
|
||||
disableUsedLuckmailPurchases: async () => {},
|
||||
doesStepUseCompletionSignal: () => false,
|
||||
ensureManualInteractionAllowed: async () => ({}),
|
||||
executeStep: async () => {},
|
||||
executeStepViaCompletionSignal: async () => {},
|
||||
exportSettingsBundle: async () => ({}),
|
||||
fetchGeneratedEmail: async () => '',
|
||||
finalizeStep3Completion: async () => {},
|
||||
finalizeIcloudAliasAfterSuccessfulFlow: async () => {},
|
||||
findHotmailAccount: async () => null,
|
||||
flushCommand: async () => {},
|
||||
getCurrentLuckmailPurchase: () => null,
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getSourceLabel: () => '',
|
||||
getState: async () => ({ plusModeEnabled: true, stepStatuses: { 13: 'pending' } }),
|
||||
getLastStepIdForState: () => 13,
|
||||
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'oauth-login' : 'platform-verify' }),
|
||||
getStepIdsForState: () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
|
||||
getTabId: async () => null,
|
||||
getStopRequested: () => false,
|
||||
handleAutoRunLoopUnhandledError: async () => {},
|
||||
handleCloudflareSecurityBlocked: async () => '',
|
||||
importSettingsBundle: async () => {},
|
||||
invalidateDownstreamAfterStepRestart: async () => {},
|
||||
isCloudflareSecurityBlockedError: () => false,
|
||||
isAutoRunLockedState: () => false,
|
||||
isHotmailProvider: () => false,
|
||||
isLocalhostOAuthCallbackUrl: () => true,
|
||||
isLuckmailProvider: () => false,
|
||||
isStopError: () => false,
|
||||
isTabAlive: async () => false,
|
||||
launchAutoRunTimerPlan: async () => {},
|
||||
listIcloudAliases: async () => [],
|
||||
listLuckmailPurchasesForManagement: async () => [],
|
||||
normalizeHotmailAccounts: (items) => items,
|
||||
normalizeRunCount: (value) => value,
|
||||
AUTO_RUN_TIMER_KIND_SCHEDULED_START: 'scheduled',
|
||||
notifyStepComplete: () => {},
|
||||
notifyStepError: () => {},
|
||||
patchHotmailAccount: async () => {},
|
||||
patchMail2925Account: async () => {},
|
||||
registerTab: async () => {},
|
||||
requestStop: async () => {},
|
||||
resetState: async () => {},
|
||||
resumeAutoRun: async () => {},
|
||||
scheduleAutoRun: async () => {},
|
||||
selectLuckmailPurchase: async () => {},
|
||||
setCurrentHotmailAccount: async () => {},
|
||||
setCurrentMail2925Account: async () => {},
|
||||
setContributionMode: async () => {},
|
||||
setEmailState: async () => {},
|
||||
setEmailStateSilently: async () => {},
|
||||
setIcloudAliasPreservedState: async () => {},
|
||||
setIcloudAliasUsedState: async () => {},
|
||||
setLuckmailPurchaseDisabledState: async () => {},
|
||||
setLuckmailPurchasePreservedState: async () => {},
|
||||
setLuckmailPurchaseUsedState: async () => {},
|
||||
setPersistentSettings: async () => {},
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
skipAutoRunCountdown: async () => false,
|
||||
skipStep: async () => {},
|
||||
startAutoRunLoop: async () => {},
|
||||
syncHotmailAccounts: async () => {},
|
||||
testHotmailAccountMailAccess: async () => {},
|
||||
upsertHotmailAccount: async () => {},
|
||||
verifyHotmailAccount: async () => {},
|
||||
});
|
||||
|
||||
await router.handleMessage({ type: 'STEP_COMPLETE', step: 13, payload: {} }, {});
|
||||
|
||||
assert.equal(appendCalls.length, 1);
|
||||
assert.equal(appendCalls[0][0], 'success');
|
||||
});
|
||||
@@ -61,6 +61,7 @@ function createRouter(overrides = {}) {
|
||||
getState: async () => overrides.state || { stepStatuses: { 3: 'pending' } },
|
||||
getStepDefinitionForState: overrides.getStepDefinitionForState,
|
||||
getStepIdsForState: overrides.getStepIdsForState,
|
||||
getLastStepIdForState: overrides.getLastStepIdForState,
|
||||
getTabId: overrides.getTabId || (async () => null),
|
||||
getStopRequested: () => false,
|
||||
handleAutoRunLoopUnhandledError: async () => {},
|
||||
|
||||
Reference in New Issue
Block a user