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
|
// background.js — Service Worker: orchestration, state, tab management, message routing
|
||||||
|
|
||||||
importScripts(
|
importScripts(
|
||||||
|
'mail-provider-utils.js',
|
||||||
'managed-alias-utils.js',
|
'managed-alias-utils.js',
|
||||||
'mail2925-utils.js',
|
'mail2925-utils.js',
|
||||||
'background/phone-verification-flow.js',
|
'background/phone-verification-flow.js',
|
||||||
@@ -17,12 +18,17 @@ importScripts(
|
|||||||
'background/navigation-utils.js',
|
'background/navigation-utils.js',
|
||||||
'background/logging-status.js',
|
'background/logging-status.js',
|
||||||
'background/steps/registry.js',
|
'background/steps/registry.js',
|
||||||
|
'data/address-sources.js',
|
||||||
'data/step-definitions.js',
|
'data/step-definitions.js',
|
||||||
'background/steps/open-chatgpt.js',
|
'background/steps/open-chatgpt.js',
|
||||||
'background/steps/submit-signup-email.js',
|
'background/steps/submit-signup-email.js',
|
||||||
'background/steps/fill-password.js',
|
'background/steps/fill-password.js',
|
||||||
'background/steps/fetch-signup-code.js',
|
'background/steps/fetch-signup-code.js',
|
||||||
'background/steps/fill-profile.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/clear-login-cookies.js',
|
||||||
'background/steps/oauth-login.js',
|
'background/steps/oauth-login.js',
|
||||||
'background/steps/fetch-login-code.js',
|
'background/steps/fetch-login-code.js',
|
||||||
@@ -37,12 +43,15 @@ importScripts(
|
|||||||
'content/activation-utils.js'
|
'content/activation-utils.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
const SHARED_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.() || [];
|
const SHARED_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getAllSteps?.()
|
||||||
const STEP_IDS = SHARED_STEP_DEFINITIONS
|
|| self.MultiPageStepDefinitions?.getSteps?.()
|
||||||
|
|| [];
|
||||||
|
const DEFAULT_STEP_IDS = (self.MultiPageStepDefinitions?.getStepIds?.() || SHARED_STEP_DEFINITIONS
|
||||||
.map((definition) => Number(definition?.id))
|
.map((definition) => Number(definition?.id))
|
||||||
.filter(Number.isFinite)
|
.filter(Number.isFinite)
|
||||||
.sort((left, right) => left - right);
|
.sort((left, right) => left - right));
|
||||||
const LAST_STEP_ID = STEP_IDS[STEP_IDS.length - 1] || 10;
|
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 FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -127,6 +136,14 @@ const {
|
|||||||
const {
|
const {
|
||||||
isRecoverableStep9AuthFailure,
|
isRecoverableStep9AuthFailure,
|
||||||
} = self.MultiPageActivationUtils;
|
} = self.MultiPageActivationUtils;
|
||||||
|
const {
|
||||||
|
getIcloudForwardMailConfig,
|
||||||
|
normalizeIcloudForwardMailProvider,
|
||||||
|
normalizeIcloudTargetMailboxType,
|
||||||
|
} = self.MailProviderUtils || {};
|
||||||
|
const {
|
||||||
|
getAddressSeedForCountry,
|
||||||
|
} = self.MultiPageAddressSources || {};
|
||||||
|
|
||||||
const LOG_PREFIX = '[MultiPage:bg]';
|
const LOG_PREFIX = '[MultiPage:bg]';
|
||||||
const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill';
|
const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill';
|
||||||
@@ -271,6 +288,9 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
autoStepDelaySeconds: null,
|
autoStepDelaySeconds: null,
|
||||||
phoneVerificationEnabled: false,
|
phoneVerificationEnabled: false,
|
||||||
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
||||||
|
plusModeEnabled: false,
|
||||||
|
paypalEmail: '',
|
||||||
|
paypalPassword: '',
|
||||||
mailProvider: '163',
|
mailProvider: '163',
|
||||||
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
||||||
mail2925UseAccountPool: false,
|
mail2925UseAccountPool: false,
|
||||||
@@ -279,6 +299,8 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
customEmailPool: [],
|
customEmailPool: [],
|
||||||
autoDeleteUsedIcloudAlias: false,
|
autoDeleteUsedIcloudAlias: false,
|
||||||
icloudHostPreference: 'auto',
|
icloudHostPreference: 'auto',
|
||||||
|
icloudTargetMailboxType: 'icloud-inbox',
|
||||||
|
icloudForwardMailProvider: 'qq',
|
||||||
icloudFetchMode: 'reuse_existing',
|
icloudFetchMode: 'reuse_existing',
|
||||||
accountRunHistoryTextEnabled: true,
|
accountRunHistoryTextEnabled: true,
|
||||||
accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
|
accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
|
||||||
@@ -745,6 +767,39 @@ function normalizePanelMode(value = '') {
|
|||||||
return 'cpa';
|
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 = '') {
|
function normalizeMailProvider(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
switch (normalized) {
|
switch (normalized) {
|
||||||
@@ -975,9 +1030,14 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
case 'customPassword':
|
case 'customPassword':
|
||||||
return String(value || '');
|
return String(value || '');
|
||||||
|
case 'paypalEmail':
|
||||||
|
return String(value || '').trim();
|
||||||
|
case 'paypalPassword':
|
||||||
|
return String(value || '');
|
||||||
case 'autoRunSkipFailures':
|
case 'autoRunSkipFailures':
|
||||||
case 'autoRunDelayEnabled':
|
case 'autoRunDelayEnabled':
|
||||||
case 'phoneVerificationEnabled':
|
case 'phoneVerificationEnabled':
|
||||||
|
case 'plusModeEnabled':
|
||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
case 'autoRunFallbackThreadIntervalMinutes':
|
case 'autoRunFallbackThreadIntervalMinutes':
|
||||||
return normalizeAutoRunFallbackThreadIntervalMinutes(value);
|
return normalizeAutoRunFallbackThreadIntervalMinutes(value);
|
||||||
@@ -1004,6 +1064,18 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
case 'icloudHostPreference':
|
case 'icloudHostPreference':
|
||||||
return normalizeIcloudHost(value) || 'auto';
|
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':
|
case 'icloudFetchMode':
|
||||||
return normalizeIcloudFetchMode(value);
|
return normalizeIcloudFetchMode(value);
|
||||||
case 'accountRunHistoryHelperBaseUrl':
|
case 'accountRunHistoryHelperBaseUrl':
|
||||||
@@ -1274,19 +1346,57 @@ async function setPasswordState(password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildContributionModeState(enabled, persistedSettings = {}, currentState = {}) {
|
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 = {};
|
const currentContributionState = {};
|
||||||
for (const key of CONTRIBUTION_RUNTIME_KEYS) {
|
for (const key of CONTRIBUTION_RUNTIME_KEYS) {
|
||||||
currentContributionState[key] = currentState[key] !== undefined
|
currentContributionState[key] = currentState[key] !== undefined
|
||||||
? currentState[key]
|
? currentState[key]
|
||||||
: CONTRIBUTION_RUNTIME_DEFAULTS[key];
|
: CONTRIBUTION_RUNTIME_DEFAULTS[key];
|
||||||
}
|
}
|
||||||
|
const routingState = resolveContributionModeRoutingState({
|
||||||
|
...persistedSettings,
|
||||||
|
...currentState,
|
||||||
|
...currentContributionState,
|
||||||
|
});
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
return {
|
return {
|
||||||
...currentContributionState,
|
...currentContributionState,
|
||||||
contributionMode: true,
|
contributionMode: true,
|
||||||
contributionModeExpected: true,
|
contributionModeExpected: true,
|
||||||
panelMode: 'cpa',
|
contributionSource: routingState.source,
|
||||||
|
contributionTargetGroupName: routingState.targetGroupName,
|
||||||
|
panelMode: routingState.source,
|
||||||
customPassword: '',
|
customPassword: '',
|
||||||
accountRunHistoryTextEnabled: false,
|
accountRunHistoryTextEnabled: false,
|
||||||
};
|
};
|
||||||
@@ -1296,6 +1406,8 @@ function buildContributionModeState(enabled, persistedSettings = {}, currentStat
|
|||||||
...CONTRIBUTION_RUNTIME_DEFAULTS,
|
...CONTRIBUTION_RUNTIME_DEFAULTS,
|
||||||
contributionMode: false,
|
contributionMode: false,
|
||||||
contributionModeExpected: false,
|
contributionModeExpected: false,
|
||||||
|
contributionSource: routingState.source,
|
||||||
|
contributionTargetGroupName: routingState.targetGroupName,
|
||||||
panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode,
|
panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode,
|
||||||
customPassword: persistedSettings.customPassword || '',
|
customPassword: persistedSettings.customPassword || '',
|
||||||
accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled),
|
accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled),
|
||||||
@@ -1309,13 +1421,18 @@ async function setContributionMode(enabled) {
|
|||||||
getState(),
|
getState(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const contributionRoutingSource = normalizedEnabled
|
||||||
|
? (String(currentState?.contributionSource || persistedSettings?.panelMode || '').trim().toLowerCase() === 'sub2api'
|
||||||
|
? 'sub2api'
|
||||||
|
: 'sub2api')
|
||||||
|
: (persistedSettings.panelMode || DEFAULT_STATE.panelMode);
|
||||||
if (normalizedEnabled) {
|
if (normalizedEnabled) {
|
||||||
await setPersistentSettings({ panelMode: 'cpa' });
|
await setPersistentSettings({ panelMode: contributionRoutingSource });
|
||||||
}
|
}
|
||||||
|
|
||||||
const updates = buildContributionModeState(normalizedEnabled, {
|
const updates = buildContributionModeState(normalizedEnabled, {
|
||||||
...persistedSettings,
|
...persistedSettings,
|
||||||
...(normalizedEnabled ? { panelMode: 'cpa' } : {}),
|
...(normalizedEnabled ? { panelMode: contributionRoutingSource } : {}),
|
||||||
}, currentState);
|
}, currentState);
|
||||||
|
|
||||||
await setState(updates);
|
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 = {}) {
|
async function icloudRequest(method, url, options = {}) {
|
||||||
const { data } = options;
|
const { data } = options;
|
||||||
let response;
|
const maxAttempts = Math.max(1, Math.floor(Number(options.maxAttempts) || 1));
|
||||||
try {
|
const logRetries = Boolean(options.logRetries);
|
||||||
response = await fetch(url, {
|
const retryLabel = String(options.retryLabel || getIcloudRequestTargetLabel(url) || 'iCloud 请求');
|
||||||
method,
|
|
||||||
credentials: 'include',
|
let lastError = null;
|
||||||
headers: data !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
body: data !== undefined ? JSON.stringify(data) : undefined,
|
let response;
|
||||||
});
|
try {
|
||||||
} catch (err) {
|
response = await fetch(url, {
|
||||||
throw new Error(`iCloud 请求失败:${method} ${url},${err.message}`);
|
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 lastError || new Error(`iCloud 请求失败:${method} ${url}`);
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateIcloudSession(setupUrl) {
|
async function validateIcloudSession(setupUrl) {
|
||||||
@@ -3641,12 +3823,7 @@ async function fetchIcloudHideMyEmail(options = {}) {
|
|||||||
const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService();
|
const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService();
|
||||||
await addLog(`iCloud:已通过 ${new URL(setupUrl).host} 验证会话`, 'ok');
|
await addLog(`iCloud:已通过 ${new URL(setupUrl).host} 验证会话`, 'ok');
|
||||||
|
|
||||||
const existingAliasesResponse = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`);
|
const existingAliases = await listIcloudAliases().catch(() => []);
|
||||||
const state = await getState();
|
|
||||||
const existingAliases = normalizeIcloudAliasList(existingAliasesResponse, {
|
|
||||||
usedEmails: getEffectiveUsedEmails(state),
|
|
||||||
preservedEmails: getPreservedAliasMap(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!generateNew) {
|
if (!generateNew) {
|
||||||
const reusableAlias = pickReusableIcloudAlias(existingAliases);
|
const reusableAlias = pickReusableIcloudAlias(existingAliases);
|
||||||
@@ -3667,19 +3844,35 @@ async function fetchIcloudHideMyEmail(options = {}) {
|
|||||||
throw new Error(generated?.error?.errorMessage || 'iCloud 隐私邮箱生成失败。');
|
throw new Error(generated?.error?.errorMessage || 'iCloud 隐私邮箱生成失败。');
|
||||||
}
|
}
|
||||||
|
|
||||||
const reserved = await icloudRequest('POST', `${serviceUrl}/v1/hme/reserve`, {
|
let alias = '';
|
||||||
data: {
|
try {
|
||||||
hme: generated.result.hme,
|
const reserved = await icloudRequest('POST', `${serviceUrl}/v1/hme/reserve`, {
|
||||||
label: getIcloudAliasLabel(),
|
data: {
|
||||||
note: 'Generated through Multi-Page Automation',
|
hme: generated.result.hme,
|
||||||
},
|
label: getIcloudAliasLabel(),
|
||||||
});
|
note: 'Generated through Multi-Page Automation',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!reserved?.success || !reserved?.result?.hme?.hme) {
|
if (!reserved?.success || !reserved?.result?.hme?.hme) {
|
||||||
throw new Error(reserved?.error?.errorMessage || 'iCloud 隐私邮箱保留失败。');
|
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 setEmailState(alias);
|
||||||
await addLog(`iCloud:已创建并保留新别名 ${alias}`, 'ok');
|
await addLog(`iCloud:已创建并保留新别名 ${alias}`, 'ok');
|
||||||
broadcastIcloudAliasesChanged({ reason: 'created', email: alias });
|
broadcastIcloudAliasesChanged({ reason: 'created', email: alias });
|
||||||
@@ -4040,6 +4233,28 @@ async function sendToMailContentScriptResilient(mail, message, options = {}) {
|
|||||||
return tabRuntime.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
|
// Logging
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -4306,7 +4521,11 @@ function getFirstUnfinishedStep(statuses = {}) {
|
|||||||
if (typeof loggingStatus !== 'undefined' && loggingStatus?.getFirstUnfinishedStep) {
|
if (typeof loggingStatus !== 'undefined' && loggingStatus?.getFirstUnfinishedStep) {
|
||||||
return loggingStatus.getFirstUnfinishedStep(statuses);
|
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;
|
if (!isStepDoneStatus(statuses[step] || 'pending')) return step;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -4389,9 +4608,10 @@ async function invalidateDownstreamAfterStepRestart(step, options = {}) {
|
|||||||
const { logLabel = `步骤 ${step} 重新执行` } = options;
|
const { logLabel = `步骤 ${step} 重新执行` } = options;
|
||||||
const state = await getState();
|
const state = await getState();
|
||||||
const statuses = { ...(state.stepStatuses || {}) };
|
const statuses = { ...(state.stepStatuses || {}) };
|
||||||
|
const lastStepId = getLastStepIdForState(state);
|
||||||
const changedSteps = [];
|
const changedSteps = [];
|
||||||
|
|
||||||
for (let downstream = step + 1; downstream <= LAST_STEP_ID; downstream++) {
|
for (let downstream = step + 1; downstream <= lastStepId; downstream++) {
|
||||||
if (statuses[downstream] !== 'pending') {
|
if (statuses[downstream] !== 'pending') {
|
||||||
statuses[downstream] = 'pending';
|
statuses[downstream] = 'pending';
|
||||||
changedSteps.push(downstream);
|
changedSteps.push(downstream);
|
||||||
@@ -4936,8 +5156,9 @@ async function ensureManualInteractionAllowed(actionLabel) {
|
|||||||
|
|
||||||
async function skipStep(step) {
|
async function skipStep(step) {
|
||||||
const state = await ensureManualInteractionAllowed('跳过步骤');
|
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}`);
|
throw new Error(`无效步骤:${step}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5272,11 +5493,13 @@ async function completeStepFromBackground(step, payload = {}) {
|
|||||||
return;
|
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 setStepStatus(step, 'completed');
|
||||||
await addLog(`步骤 ${step} 已完成`, 'ok');
|
await addLog(`步骤 ${step} 已完成`, 'ok');
|
||||||
await handleStepData(step, payload);
|
await handleStepData(step, payload);
|
||||||
if (step === LAST_STEP_ID) {
|
if (step === completionLastStepId) {
|
||||||
await appendAndBroadcastAccountRunRecord('success', completionState);
|
await appendAndBroadcastAccountRunRecord('success', completionState);
|
||||||
}
|
}
|
||||||
notifyStepComplete(step, payload);
|
notifyStepComplete(step, payload);
|
||||||
@@ -5373,7 +5596,7 @@ async function waitForRunningStepsToFinish(payload = {}) {
|
|||||||
return currentState;
|
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;
|
let activeTopLevelAuthChainExecution = null;
|
||||||
|
|
||||||
function isAuthChainStep(step) {
|
function isAuthChainStep(step) {
|
||||||
@@ -5541,7 +5764,8 @@ async function executeStep(step, options = {}) {
|
|||||||
await setState({ flowStartTime: Date.now() });
|
await setState({ flowStartTime: Date.now() });
|
||||||
}
|
}
|
||||||
|
|
||||||
await stepRegistry.executeStep(step, state);
|
const activeStepRegistry = getStepRegistryForState(state);
|
||||||
|
await activeStepRegistry.executeStep(step, { ...state, visibleStep: step });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
executionError = err;
|
executionError = err;
|
||||||
const state = await getState();
|
const state = await getState();
|
||||||
@@ -6278,8 +6502,12 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
|
|
||||||
let restartFromStep1WithCurrentEmail = false;
|
let restartFromStep1WithCurrentEmail = false;
|
||||||
let step = Math.max(currentStartStep, 4);
|
let step = Math.max(currentStartStep, 4);
|
||||||
while (step <= LAST_STEP_ID) {
|
while (true) {
|
||||||
const latestState = await getState();
|
const latestState = await getState();
|
||||||
|
const flowLastStepId = getLastStepIdForState(latestState);
|
||||||
|
if (step > flowLastStepId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
const currentStatus = latestState.stepStatuses?.[step] || 'pending';
|
const currentStatus = latestState.stepStatuses?.[step] || 'pending';
|
||||||
if (isStepDoneStatus(currentStatus)) {
|
if (isStepDoneStatus(currentStatus)) {
|
||||||
await addLog(`自动运行:步骤 ${step} 当前状态为 ${currentStatus},将直接继续后续流程。`, 'info');
|
await addLog(`自动运行:步骤 ${step} 当前状态为 ${currentStatus},将直接继续后续流程。`, 'info');
|
||||||
@@ -6631,6 +6859,54 @@ const step6Executor = self.MultiPageBackgroundStep6?.createStep6Executor({
|
|||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
runPreStep6CookieCleanup,
|
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({
|
const step7Executor = self.MultiPageBackgroundStep7?.createStep7Executor({
|
||||||
addLog,
|
addLog,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
@@ -6695,7 +6971,6 @@ const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({
|
|||||||
shouldBypassStep9ForLocalCpa,
|
shouldBypassStep9ForLocalCpa,
|
||||||
SUB2API_STEP9_RESPONSE_TIMEOUT_MS,
|
SUB2API_STEP9_RESPONSE_TIMEOUT_MS,
|
||||||
});
|
});
|
||||||
const stepDefinitions = SHARED_STEP_DEFINITIONS;
|
|
||||||
const stepExecutorsByKey = {
|
const stepExecutorsByKey = {
|
||||||
'open-chatgpt': () => step1Executor.executeStep1(),
|
'open-chatgpt': () => step1Executor.executeStep1(),
|
||||||
'submit-signup-email': (state) => step2Executor.executeStep2(state),
|
'submit-signup-email': (state) => step2Executor.executeStep2(state),
|
||||||
@@ -6703,6 +6978,10 @@ const stepExecutorsByKey = {
|
|||||||
'fetch-signup-code': (state) => step4Executor.executeStep4(state),
|
'fetch-signup-code': (state) => step4Executor.executeStep4(state),
|
||||||
'fill-profile': (state) => step5Executor.executeStep5(state),
|
'fill-profile': (state) => step5Executor.executeStep5(state),
|
||||||
'clear-login-cookies': () => step6Executor.executeStep6(),
|
'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),
|
'oauth-login': (state) => step7Executor.executeStep7(state),
|
||||||
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
||||||
'confirm-oauth': (state) => step9Executor.executeStep9(state),
|
'confirm-oauth': (state) => step9Executor.executeStep9(state),
|
||||||
@@ -6753,6 +7032,9 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
getPendingAutoRunTimerPlan,
|
getPendingAutoRunTimerPlan,
|
||||||
getSourceLabel,
|
getSourceLabel,
|
||||||
getState,
|
getState,
|
||||||
|
getStepDefinitionForState,
|
||||||
|
getStepIdsForState,
|
||||||
|
getLastStepIdForState,
|
||||||
getTabId,
|
getTabId,
|
||||||
getStopRequested: () => stopRequested,
|
getStopRequested: () => stopRequested,
|
||||||
handleCloudflareSecurityBlocked,
|
handleCloudflareSecurityBlocked,
|
||||||
@@ -6810,12 +7092,15 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
upsertHotmailAccount,
|
upsertHotmailAccount,
|
||||||
verifyHotmailAccount,
|
verifyHotmailAccount,
|
||||||
});
|
});
|
||||||
const stepRegistry = self.MultiPageBackgroundStepRegistry?.createStepRegistry(
|
function getStepRegistryForState(state = {}) {
|
||||||
stepDefinitions.map((definition) => ({
|
const stepDefinitions = getStepDefinitionsForState(state);
|
||||||
...definition,
|
return self.MultiPageBackgroundStepRegistry?.createStepRegistry(
|
||||||
execute: stepExecutorsByKey[definition.key],
|
stepDefinitions.map((definition) => ({
|
||||||
}))
|
...definition,
|
||||||
);
|
execute: stepExecutorsByKey[definition.key],
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function requestOAuthUrlFromPanel(state, options = {}) {
|
async function requestOAuthUrlFromPanel(state, options = {}) {
|
||||||
return panelBridge.requestOAuthUrlFromPanel(state, options);
|
return panelBridge.requestOAuthUrlFromPanel(state, options);
|
||||||
@@ -6890,9 +7175,30 @@ function getMailConfig(state) {
|
|||||||
return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(API对接/本地助手)' };
|
return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(API对接/本地助手)' };
|
||||||
}
|
}
|
||||||
if (provider === ICLOUD_PROVIDER) {
|
if (provider === ICLOUD_PROVIDER) {
|
||||||
|
const resolveForwardMailConfig = typeof getIcloudForwardMailConfig === 'function'
|
||||||
|
? getIcloudForwardMailConfig
|
||||||
|
: (typeof getSharedIcloudForwardMailConfig === 'function' ? getSharedIcloudForwardMailConfig : null);
|
||||||
const configuredHost = getConfiguredIcloudHostPreference(state)
|
const configuredHost = getConfiguredIcloudHostPreference(state)
|
||||||
|| normalizeIcloudHost(state?.preferredIcloudHost)
|
|| normalizeIcloudHost(state?.preferredIcloudHost)
|
||||||
|| 'icloud.com';
|
|| '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 loginUrl = getIcloudLoginUrlForHost(configuredHost) || 'https://www.icloud.com/';
|
||||||
const mailUrl = getIcloudMailUrlForHost(configuredHost) || loginUrl;
|
const mailUrl = getIcloudMailUrlForHost(configuredHost) || loginUrl;
|
||||||
return {
|
return {
|
||||||
@@ -7271,10 +7577,11 @@ function isAddPhoneAuthState(authState = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getPostStep6AutoRestartDecision(step, error) {
|
async function getPostStep6AutoRestartDecision(step, error) {
|
||||||
|
const currentState = await getState().catch(() => ({}));
|
||||||
const normalizedStep = Number(step);
|
const normalizedStep = Number(step);
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
const shouldForceRestartFromStep7 = /restart step 7 with a new number/i.test(errorMessage);
|
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 {
|
return {
|
||||||
shouldRestart: false,
|
shouldRestart: false,
|
||||||
blockedByAddPhone: false,
|
blockedByAddPhone: false,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
const RUNTIME_DEFAULTS = {
|
const RUNTIME_DEFAULTS = {
|
||||||
contributionMode: false,
|
contributionMode: false,
|
||||||
contributionModeExpected: false,
|
contributionModeExpected: false,
|
||||||
|
contributionSource: 'sub2api',
|
||||||
|
contributionTargetGroupName: 'codex号池',
|
||||||
contributionNickname: '',
|
contributionNickname: '',
|
||||||
contributionQq: '',
|
contributionQq: '',
|
||||||
contributionSessionId: '',
|
contributionSessionId: '',
|
||||||
@@ -38,6 +40,8 @@
|
|||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
let listenersBound = false;
|
let listenersBound = false;
|
||||||
|
const pendingCallbackSubmissions = new Map();
|
||||||
|
const pendingCapturedCallbacks = new Map();
|
||||||
|
|
||||||
function normalizeString(value = '') {
|
function normalizeString(value = '') {
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
@@ -256,6 +260,41 @@
|
|||||||
return qq;
|
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 = {}) {
|
function buildStatusMessage(status, payload = {}) {
|
||||||
const label = getStatusLabel(status);
|
const label = getStatusLabel(status);
|
||||||
const details = [
|
const details = [
|
||||||
@@ -435,46 +474,58 @@
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
await applyRuntimeUpdates({
|
const dedupeKey = `${sessionId}::${normalizedUrl}`;
|
||||||
contributionCallbackUrl: normalizedUrl,
|
if (pendingCallbackSubmissions.has(dedupeKey)) {
|
||||||
contributionCallbackStatus: 'submitting',
|
return pendingCallbackSubmissions.get(dedupeKey);
|
||||||
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 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 = {}) {
|
async function handleCapturedCallback(rawUrl, metadata = {}) {
|
||||||
@@ -487,6 +538,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizedUrl = normalizeString(rawUrl);
|
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);
|
const currentCallbackStatus = normalizeContributionCallbackStatus(currentState.contributionCallbackStatus);
|
||||||
if (
|
if (
|
||||||
normalizedUrl
|
normalizedUrl
|
||||||
@@ -496,24 +554,31 @@
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
await applyRuntimeUpdates({
|
const task = (async () => {
|
||||||
contributionCallbackUrl: normalizedUrl,
|
await applyRuntimeUpdates({
|
||||||
contributionCallbackStatus: 'captured',
|
contributionCallbackUrl: normalizedUrl,
|
||||||
contributionCallbackMessage: buildCallbackMessage('captured'),
|
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(),
|
|
||||||
});
|
});
|
||||||
} 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 = {}) {
|
async function pollContributionStatus(options = {}) {
|
||||||
@@ -594,13 +659,15 @@
|
|||||||
return pollContributionStatus({ reason: 'resume_existing' });
|
return pollContributionStatus({ reason: 'resume_existing' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const routingState = resolveContributionModeRoutingState(currentState);
|
||||||
const payload = await fetchContributionJson('/start', {
|
const payload = await fetchContributionJson('/start', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
nickname: buildNickname(currentState, options.nickname),
|
nickname: buildNickname(currentState, options.nickname),
|
||||||
qq: buildContributionQq(currentState, options.qq),
|
qq: buildContributionQq(currentState, options.qq),
|
||||||
email: normalizeString(currentState.email),
|
email: normalizeString(currentState.email),
|
||||||
source: 'cpa',
|
source: routingState.source,
|
||||||
|
target_group_name: routingState.targetGroupName,
|
||||||
channel: 'codex-extension',
|
channel: 'codex-extension',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,6 +40,9 @@
|
|||||||
getPendingAutoRunTimerPlan,
|
getPendingAutoRunTimerPlan,
|
||||||
getSourceLabel,
|
getSourceLabel,
|
||||||
getState,
|
getState,
|
||||||
|
getStepDefinitionForState,
|
||||||
|
getStepIdsForState,
|
||||||
|
getLastStepIdForState,
|
||||||
getTabId,
|
getTabId,
|
||||||
getStopRequested,
|
getStopRequested,
|
||||||
handleAutoRunLoopUnhandledError,
|
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) {
|
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) {
|
switch (step) {
|
||||||
case 1: {
|
case 1: {
|
||||||
const updates = {};
|
const updates = {};
|
||||||
@@ -217,7 +260,8 @@
|
|||||||
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
|
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 10: {
|
case 10:
|
||||||
|
case 13: {
|
||||||
if (payload.localhostUrl) {
|
if (payload.localhostUrl) {
|
||||||
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||||
}
|
}
|
||||||
@@ -305,11 +349,13 @@
|
|||||||
return { ok: true, error: errorMessage };
|
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 setStepStatus(message.step, 'completed');
|
||||||
await addLog(`步骤 ${message.step} 已完成`, 'ok');
|
await addLog(`步骤 ${message.step} 已完成`, 'ok');
|
||||||
await handleStepData(message.step, message.payload);
|
await handleStepData(message.step, message.payload);
|
||||||
if (message.step === 10 && typeof appendAccountRunRecord === 'function') {
|
if (message.step === lastStepId && typeof appendAccountRunRecord === 'function') {
|
||||||
await appendAccountRunRecord('success', completionState);
|
await appendAccountRunRecord('success', completionState);
|
||||||
}
|
}
|
||||||
notifyStepComplete(message.step, message.payload);
|
notifyStepComplete(message.step, message.payload);
|
||||||
|
|||||||
@@ -26,6 +26,11 @@
|
|||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolvePlatformVerifyStep(state = {}) {
|
||||||
|
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||||
|
return visibleStep >= 10 ? visibleStep : 10;
|
||||||
|
}
|
||||||
|
|
||||||
function parseLocalhostCallback(rawUrl) {
|
function parseLocalhostCallback(rawUrl) {
|
||||||
let parsed;
|
let parsed;
|
||||||
try {
|
try {
|
||||||
@@ -180,6 +185,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function executeCpaStep10(state) {
|
async function executeCpaStep10(state) {
|
||||||
|
const platformVerifyStep = resolvePlatformVerifyStep(state);
|
||||||
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
||||||
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
||||||
}
|
}
|
||||||
@@ -192,7 +198,7 @@
|
|||||||
|
|
||||||
if (shouldBypassStep9ForLocalCpa(state)) {
|
if (shouldBypassStep9ForLocalCpa(state)) {
|
||||||
await addLog('步骤 10:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info');
|
await addLog('步骤 10:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info');
|
||||||
await completeStepFromBackground(10, {
|
await completeStepFromBackground(platformVerifyStep, {
|
||||||
localhostUrl: state.localhostUrl,
|
localhostUrl: state.localhostUrl,
|
||||||
verifiedStatus: 'local-auto',
|
verifiedStatus: 'local-auto',
|
||||||
});
|
});
|
||||||
@@ -225,7 +231,7 @@
|
|||||||
|| normalizeString(result?.status_message)
|
|| normalizeString(result?.status_message)
|
||||||
|| 'CPA 已通过接口提交回调';
|
|| 'CPA 已通过接口提交回调';
|
||||||
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
||||||
await completeStepFromBackground(10, {
|
await completeStepFromBackground(platformVerifyStep, {
|
||||||
localhostUrl: callback.url,
|
localhostUrl: callback.url,
|
||||||
verifiedStatus,
|
verifiedStatus,
|
||||||
});
|
});
|
||||||
@@ -237,6 +243,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function executeCodex2ApiStep10(state) {
|
async function executeCodex2ApiStep10(state) {
|
||||||
|
const platformVerifyStep = resolvePlatformVerifyStep(state);
|
||||||
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
|
||||||
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
|
||||||
}
|
}
|
||||||
@@ -272,7 +279,7 @@
|
|||||||
|
|
||||||
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
|
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
|
||||||
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
await addLog(`步骤 10:${verifiedStatus}`, 'ok');
|
||||||
await completeStepFromBackground(10, {
|
await completeStepFromBackground(platformVerifyStep, {
|
||||||
localhostUrl: callback.url,
|
localhostUrl: callback.url,
|
||||||
verifiedStatus,
|
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' } },
|
getState: async () => overrides.state || { stepStatuses: { 3: 'pending' } },
|
||||||
getStepDefinitionForState: overrides.getStepDefinitionForState,
|
getStepDefinitionForState: overrides.getStepDefinitionForState,
|
||||||
getStepIdsForState: overrides.getStepIdsForState,
|
getStepIdsForState: overrides.getStepIdsForState,
|
||||||
|
getLastStepIdForState: overrides.getLastStepIdForState,
|
||||||
getTabId: overrides.getTabId || (async () => null),
|
getTabId: overrides.getTabId || (async () => null),
|
||||||
getStopRequested: () => false,
|
getStopRequested: () => false,
|
||||||
handleAutoRunLoopUnhandledError: async () => {},
|
handleAutoRunLoopUnhandledError: async () => {},
|
||||||
|
|||||||
Reference in New Issue
Block a user