feat: continue OAuth flow through HeroSMS phone verification

- 合并 dev 最新主线改动:同步当前认证页恢复、来源配置和侧栏能力的现有基线

- 本地补充修复:补齐 HeroSMS 手机验证与 sidepanel 初始化冲突,修正 Step 9 等待逻辑里的未声明变量,并同步结构/链路文档与回归测试

- 影响范围:OAuth 步骤 8/9、HeroSMS 配置、认证页内容脚本、侧栏初始化、项目文档与测试
This commit is contained in:
QLHazyCoder
2026-04-25 14:04:11 +08:00
73 changed files with 8442 additions and 462 deletions
+330 -54
View File
@@ -140,22 +140,26 @@ const ICLOUD_LOGIN_URLS = [
];
const ICLOUD_PROVIDER = 'icloud';
const GMAIL_PROVIDER = 'gmail';
const GMAIL_ALIAS_GENERATOR = 'gmail-alias';
const HOTMAIL_PROVIDER = 'hotmail-api';
const LUCKMAIL_PROVIDER = 'luckmail-api';
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool';
const HOTMAIL_MAILBOXES = ['INBOX', 'Junk'];
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
const CLOUDFLARE_SECURITY_BLOCK_ERROR_PREFIX = 'CF_SECURITY_BLOCKED::';
const CLOUDFLARE_SECURITY_BLOCK_USER_MESSAGE = '您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器';
const BROWSER_SWITCH_REQUIRED_ERROR_PREFIX = 'BROWSER_SWITCH_REQUIRED::';
const HUMAN_STEP_DELAY_MIN = 700;
const HUMAN_STEP_DELAY_MAX = 2200;
const STEP6_MAX_ATTEMPTS = 3;
const STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS = 8;
const OAUTH_FLOW_TIMEOUT_MS = 6 * 60 * 1000;
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
const SUB2API_STEP1_RESPONSE_TIMEOUT_MS = 90000;
const SUB2API_STEP9_RESPONSE_TIMEOUT_MS = 120000;
const DEFAULT_SUB2API_URL = 'https://sub2api.hisence.fun/admin/accounts';
const DEFAULT_CODEX2API_URL = 'http://localhost:8080/admin/accounts';
const DEFAULT_SUB2API_GROUP_NAME = 'codex';
const DEFAULT_SUB2API_PROXY_NAME = '';
const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
@@ -257,6 +261,8 @@ const PERSISTED_SETTING_DEFAULTS = {
sub2apiPassword: '',
sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME,
sub2apiDefaultProxyName: DEFAULT_SUB2API_PROXY_NAME,
codex2apiUrl: DEFAULT_CODEX2API_URL,
codex2apiAdminKey: '',
customPassword: '',
autoRunSkipFailures: false,
autoRunFallbackThreadIntervalMinutes: 0,
@@ -268,12 +274,16 @@ const PERSISTED_SETTING_DEFAULTS = {
mail2925Mode: DEFAULT_MAIL_2925_MODE,
mail2925UseAccountPool: false,
emailGenerator: 'duck',
customMailProviderPool: [],
customEmailPool: [],
autoDeleteUsedIcloudAlias: false,
icloudHostPreference: 'auto',
icloudFetchMode: 'reuse_existing',
accountRunHistoryTextEnabled: false,
accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
gmailBaseEmail: '',
mail2925BaseEmail: '',
currentMail2925AccountId: '',
emailPrefix: '',
inbucketHost: '',
inbucketMailbox: '',
@@ -286,6 +296,7 @@ const PERSISTED_SETTING_DEFAULTS = {
cloudflareTempEmailAdminAuth: '',
cloudflareTempEmailCustomAuth: '',
cloudflareTempEmailReceiveMailbox: '',
cloudflareTempEmailUseRandomSubdomain: false,
cloudflareTempEmailDomain: '',
cloudflareTempEmailDomains: [],
hotmailAccounts: [],
@@ -336,6 +347,8 @@ const DEFAULT_STATE = {
sub2apiGroupId: null, // SUB2API 目标分组 ID。
sub2apiDraftName: null, // SUB2API 本轮预生成的账号名称。
sub2apiProxyId: null, // SUB2API 本轮使用的代理 ID。
codex2apiSessionId: null, // Codex2API OAuth 会话 ID。
codex2apiOAuthState: null, // Codex2API OAuth state。
flowStartTime: null, // 当前流程开始时间。
tabRegistry: {}, // 程序维护的标签页注册表。
sourceLastUrls: {}, // 各来源页面最近一次打开的地址记录。
@@ -650,9 +663,21 @@ function getAutoRunTimerStatusPayload(plan) {
function normalizeEmailGenerator(value = '') {
const normalized = String(value || '').trim().toLowerCase();
const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
: 'custom-pool';
const gmailAliasGenerator = typeof GMAIL_ALIAS_GENERATOR === 'string'
? GMAIL_ALIAS_GENERATOR
: 'gmail-alias';
if (normalized === 'custom' || normalized === 'manual') {
return 'custom';
}
if (normalized === gmailAliasGenerator) {
return gmailAliasGenerator;
}
if (normalized === customEmailPoolGenerator) {
return customEmailPoolGenerator;
}
if (normalized === 'icloud') {
return 'icloud';
}
@@ -661,8 +686,60 @@ function normalizeEmailGenerator(value = '') {
return 'duck';
}
function normalizeIcloudFetchMode(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'always_new' ? 'always_new' : 'reuse_existing';
}
function normalizeCustomEmailPool(value = []) {
const source = Array.isArray(value)
? value
: String(value || '').split(/[\r\n,;]+/);
return source
.map((item) => String(item || '').trim().toLowerCase())
.filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
}
function isCustomEmailPoolGenerator(stateOrValue = {}) {
const generator = typeof stateOrValue === 'string'
? stateOrValue
: stateOrValue?.emailGenerator;
const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
: 'custom-pool';
return normalizeEmailGenerator(generator) === customEmailPoolGenerator;
}
function getCustomEmailPool(state = {}) {
return normalizeCustomEmailPool(state?.customEmailPool);
}
function getCustomEmailPoolEmailForRun(state = {}, targetRun = 1) {
const entries = getCustomEmailPool(state);
const numericRun = Math.max(1, Math.floor(Number(targetRun) || 1));
return entries[numericRun - 1] || '';
}
function getCustomMailProviderPool(state = {}) {
return normalizeCustomEmailPool(state?.customMailProviderPool);
}
function getCustomMailProviderPoolEmailForRun(state = {}, targetRun = 1) {
const entries = getCustomMailProviderPool(state);
const numericRun = Math.max(1, Math.floor(Number(targetRun) || 1));
return entries[numericRun - 1] || '';
}
function normalizePanelMode(value = '') {
return String(value || '').trim().toLowerCase() === 'sub2api' ? 'sub2api' : 'cpa';
const normalized = String(value || '').trim().toLowerCase();
if (normalized === 'sub2api') {
return 'sub2api';
}
if (normalized === 'codex2api') {
return 'codex2api';
}
return 'cpa';
}
function normalizeMailProvider(value = '') {
@@ -838,6 +915,7 @@ function getCloudflareTempEmailConfig(state = {}) {
adminAuth: String(state.cloudflareTempEmailAdminAuth || ''),
customAuth: String(state.cloudflareTempEmailCustomAuth || ''),
receiveMailbox: normalizeCloudflareTempEmailReceiveMailbox(state.cloudflareTempEmailReceiveMailbox),
useRandomSubdomain: Boolean(state.cloudflareTempEmailUseRandomSubdomain),
domain: normalizeCloudflareTempEmailDomain(state.cloudflareTempEmailDomain),
domains: normalizeCloudflareTempEmailDomains(state.cloudflareTempEmailDomains),
};
@@ -883,6 +961,10 @@ function normalizePersistentSettingValue(key, value) {
return String(value || '').trim();
case 'sub2apiDefaultProxyName':
return String(value || '').trim();
case 'codex2apiUrl':
return normalizeCodex2ApiUrl(value);
case 'codex2apiAdminKey':
return String(value || '').trim();
case 'customPassword':
return String(value || '');
case 'autoRunSkipFailures':
@@ -904,15 +986,22 @@ function normalizePersistentSettingValue(key, value) {
return Boolean(value);
case 'emailGenerator':
return normalizeEmailGenerator(value);
case 'customMailProviderPool':
case 'customEmailPool':
return normalizeCustomEmailPool(value);
case 'autoDeleteUsedIcloudAlias':
case 'accountRunHistoryTextEnabled':
case 'cloudflareTempEmailUseRandomSubdomain':
return Boolean(value);
case 'icloudHostPreference':
return normalizeIcloudHost(value) || 'auto';
case 'icloudFetchMode':
return normalizeIcloudFetchMode(value);
case 'accountRunHistoryHelperBaseUrl':
return normalizeAccountRunHistoryHelperBaseUrl(value);
case 'gmailBaseEmail':
case 'mail2925BaseEmail':
case 'currentMail2925AccountId':
case 'emailPrefix':
return String(value || '').trim();
case 'inbucketHost':
@@ -2146,6 +2235,18 @@ function parseGmailBaseEmail(rawValue) {
}
function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
if (
stateOrProvider
&& typeof stateOrProvider === 'object'
&& !Array.isArray(stateOrProvider)
&& normalizeEmailGenerator(stateOrProvider.emailGenerator) === (
typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
: 'custom-pool'
)
) {
return false;
}
const provider = typeof stateOrProvider === 'string'
? stateOrProvider
: stateOrProvider?.mailProvider;
@@ -2303,6 +2404,18 @@ function getManagedAliasBaseEmail(state = {}, provider = state?.mailProvider) {
}
function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
if (
stateOrProvider
&& typeof stateOrProvider === 'object'
&& !Array.isArray(stateOrProvider)
&& normalizeEmailGenerator(stateOrProvider.emailGenerator) === (
typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
: 'custom-pool'
)
) {
return false;
}
const provider = typeof stateOrProvider === 'string'
? stateOrProvider
: stateOrProvider?.mailProvider;
@@ -3017,6 +3130,16 @@ async function pollLuckmailVerificationCode(step, state, pollPayload = {}) {
excludeCodes: pollPayload.excludeCodes || [],
};
const initialCursor = normalizeLuckmailMailCursor((await getState()).currentLuckmailMailCursor);
if (!initialCursor.messageId && !initialCursor.receivedAt) {
const mailList = await client.user.getTokenMails(purchase.token);
const baselineCursor = buildLuckmailBaselineCursor(mailList?.mails || []);
await setLuckmailMailCursorState(baselineCursor);
if (baselineCursor?.messageId || baselineCursor?.receivedAt) {
await addLog(`步骤 ${step}:LuckMail 已保存当前邮箱旧邮件快照,后续仅使用新收到的验证码。`, 'info');
}
}
let lastError = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
throwIfStopped();
@@ -3360,10 +3483,16 @@ async function validateIcloudSession(setupUrl) {
return data;
}
async function resolveIcloudPremiumMailService() {
async function resolveIcloudPremiumMailService(options = {}) {
const errors = [];
const state = await getState();
const setupUrls = await getPreferredIcloudSetupUrls(state);
const explicitHost = normalizeIcloudHost(options?.hostPreference || options?.preferredHost || '');
const setupUrls = explicitHost
? (() => {
const forcedSetupUrl = getIcloudSetupUrlForHost(explicitHost);
return forcedSetupUrl ? [forcedSetupUrl] : [];
})()
: await getPreferredIcloudSetupUrls(state);
for (const setupUrl of setupUrls) {
try {
@@ -3392,17 +3521,19 @@ function getIcloudAliasLabel() {
return `MultiPage ${dateStr}`;
}
async function checkIcloudSession() {
return withIcloudLoginHelp('检查 iCloud 会话', async () => {
const { setupUrl } = await resolveIcloudPremiumMailService();
async function checkIcloudSession(options = {}) {
const actionLabel = String(options?.actionLabel || '检查 iCloud 会话').trim() || '检查 iCloud 会话';
const { actionLabel: _ignoredActionLabel, ...resolveOptions } = options || {};
return withIcloudLoginHelp(actionLabel, async () => {
const { setupUrl } = await resolveIcloudPremiumMailService(resolveOptions);
await addLog(`iCloud:会话校验通过(${new URL(setupUrl).host}`, 'ok');
return { ok: true, setupUrl };
});
}
async function listIcloudAliases() {
async function listIcloudAliases(options = {}) {
return withIcloudLoginHelp('加载 iCloud 隐私邮箱列表', async () => {
const { serviceUrl } = await resolveIcloudPremiumMailService();
const { serviceUrl } = await resolveIcloudPremiumMailService(options);
const response = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`);
const state = await getState();
return normalizeIcloudAliasList(response, {
@@ -3492,9 +3623,10 @@ async function deleteUsedIcloudAliases() {
return { deleted, skipped };
}
async function fetchIcloudHideMyEmail() {
async function fetchIcloudHideMyEmail(options = {}) {
return withIcloudLoginHelp('获取 iCloud 隐私邮箱', async () => {
throwIfStopped();
const generateNew = Boolean(options?.generateNew);
await addLog('iCloud:正在校验当前浏览器登录状态...', 'info');
const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService();
@@ -3507,12 +3639,16 @@ async function fetchIcloudHideMyEmail() {
preservedEmails: getPreservedAliasMap(state),
});
const reusableAlias = pickReusableIcloudAlias(existingAliases);
if (reusableAlias) {
await setEmailState(reusableAlias.email);
await addLog(`iCloud:复用未使用别名 ${reusableAlias.email}`, 'ok');
broadcastIcloudAliasesChanged({ reason: 'selected', email: reusableAlias.email });
return reusableAlias.email;
if (!generateNew) {
const reusableAlias = pickReusableIcloudAlias(existingAliases);
if (reusableAlias) {
await setEmailState(reusableAlias.email);
await addLog(`iCloud:复用未使用别名 ${reusableAlias.email}`, 'ok');
broadcastIcloudAliasesChanged({ reason: 'selected', email: reusableAlias.email });
return reusableAlias.email;
}
} else {
await addLog('iCloud:已启用“始终创建新别名”,本次将跳过复用。', 'info');
}
await addLog('iCloud:没有可复用别名,开始生成新的 Hide My Email 地址...', 'warn');
@@ -3637,11 +3773,31 @@ function normalizeSub2ApiUrl(rawUrl) {
return parsed.toString();
}
function normalizeCodex2ApiUrl(rawUrl) {
if (typeof navigationUtils !== 'undefined' && navigationUtils?.normalizeCodex2ApiUrl) {
return navigationUtils.normalizeCodex2ApiUrl(rawUrl);
}
const input = (rawUrl || '').trim() || DEFAULT_CODEX2API_URL;
const withProtocol = /^https?:\/\//i.test(input) ? input : `http://${input}`;
const parsed = new URL(withProtocol);
if (!parsed.pathname || parsed.pathname === '/' || parsed.pathname === '/admin') {
parsed.pathname = '/admin/accounts';
}
parsed.hash = '';
return parsed.toString();
}
function getPanelMode(state = {}) {
if (typeof navigationUtils !== 'undefined' && navigationUtils?.getPanelMode) {
return navigationUtils.getPanelMode(state);
}
return state.panelMode === 'sub2api' ? 'sub2api' : 'cpa';
if (state.panelMode === 'sub2api') {
return 'sub2api';
}
if (state.panelMode === 'codex2api') {
return 'codex2api';
}
return 'cpa';
}
function getPanelModeLabel(modeOrState) {
@@ -3649,7 +3805,13 @@ function getPanelModeLabel(modeOrState) {
return navigationUtils.getPanelModeLabel(modeOrState);
}
const mode = typeof modeOrState === 'string' ? modeOrState : getPanelMode(modeOrState);
return mode === 'sub2api' ? 'SUB2API' : 'CPA';
if (mode === 'sub2api') {
return 'SUB2API';
}
if (mode === 'codex2api') {
return 'Codex2API';
}
return 'CPA';
}
function isSignupPageHost(hostname = '') {
@@ -3953,6 +4115,7 @@ function isRetryableContentScriptTransportError(error) {
}
const navigationUtils = self.MultiPageBackgroundNavigationUtils?.createNavigationUtils({
DEFAULT_CODEX2API_URL,
DEFAULT_SUB2API_URL,
normalizeLocalCpaStep9Mode,
});
@@ -4017,6 +4180,17 @@ function getTerminalSecurityBlockedTitle(error) {
return 'Cloudflare 风控拦截';
}
function isBrowserSwitchRequiredError(error) {
return getErrorMessage(error).startsWith(BROWSER_SWITCH_REQUIRED_ERROR_PREFIX);
}
function getBrowserSwitchRequiredMessage(error) {
const message = getErrorMessage(error);
return message.startsWith(BROWSER_SWITCH_REQUIRED_ERROR_PREFIX)
? message.slice(BROWSER_SWITCH_REQUIRED_ERROR_PREFIX.length).trim()
: message;
}
function broadcastSecurityBlockedAlert(title = '流程已完全停止', message = CLOUDFLARE_SECURITY_BLOCK_USER_MESSAGE, alertText = '检测到 Cloudflare 风控,请暂停当前操作。') {
chrome.runtime.sendMessage({
type: 'SECURITY_BLOCKED_ALERT',
@@ -4040,6 +4214,13 @@ async function handleCloudflareSecurityBlocked(error) {
return message;
}
async function handleBrowserSwitchRequired(error) {
const message = getBrowserSwitchRequiredMessage(error)
|| '检测到第 10 步的特殊冲突状态,请更换浏览器后重新进行注册登录。';
await requestStop({ logMessage: message });
return message;
}
function isVerificationMailPollingError(error) {
if (typeof loggingStatus !== 'undefined' && loggingStatus?.isVerificationMailPollingError) {
return loggingStatus.isVerificationMailPollingError(error);
@@ -4128,6 +4309,8 @@ function getDownstreamStateResets(step) {
sub2apiOAuthState: null,
sub2apiGroupId: null,
sub2apiDraftName: null,
codex2apiSessionId: null,
codex2apiOAuthState: null,
flowStartTime: null,
password: null,
lastEmailTimestamp: null,
@@ -4913,6 +5096,8 @@ async function handleStepData(step, payload) {
if (payload.sub2apiGroupId !== undefined) updates.sub2apiGroupId = payload.sub2apiGroupId || null;
if (payload.sub2apiDraftName !== undefined) updates.sub2apiDraftName = payload.sub2apiDraftName || null;
if (payload.sub2apiProxyId !== undefined) updates.sub2apiProxyId = payload.sub2apiProxyId || null;
if (payload.codex2apiSessionId !== undefined) updates.codex2apiSessionId = payload.codex2apiSessionId || null;
if (payload.codex2apiOAuthState !== undefined) updates.codex2apiOAuthState = payload.codex2apiOAuthState || null;
if (Object.keys(updates).length) {
await setState(updates);
}
@@ -4997,7 +5182,12 @@ async function handleStepData(step, payload) {
});
}
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
if (shouldUseCustomRegistrationEmail(latestState) && latestState.email) {
const shouldClearCustomPoolEmail = String(latestState?.emailGenerator || '').trim().toLowerCase() === (
typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
: 'custom-pool'
);
if ((shouldUseCustomRegistrationEmail(latestState) || shouldClearCustomPoolEmail) && latestState.email) {
await setEmailStateSilently(null);
}
break;
@@ -5343,6 +5533,10 @@ async function executeStep(step, options = {}) {
await handleCloudflareSecurityBlocked(err);
throw new Error(STOP_ERROR_MESSAGE);
}
if (isBrowserSwitchRequiredError(err)) {
await handleBrowserSwitchRequired(err);
throw new Error(STOP_ERROR_MESSAGE);
}
if (!(deferRetryableTransportError && doesStepUseCompletionSignal(step) && isRetryableContentScriptTransportError(err))) {
await setStepStatus(step, 'failed');
await addLog(`步骤 ${step} 失败:${err.message}`, 'error');
@@ -5407,9 +5601,21 @@ async function executeStepAndWait(step, delayAfter = 2000) {
}
function getEmailGeneratorLabel(generator) {
const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
: 'custom-pool';
const gmailAliasGenerator = typeof GMAIL_ALIAS_GENERATOR === 'string'
? GMAIL_ALIAS_GENERATOR
: 'gmail-alias';
if (generator === 'custom') {
return '自定义邮箱';
}
if (generator === gmailAliasGenerator) {
return 'Gmail +tag 邮箱';
}
if (generator === customEmailPoolGenerator) {
return '自定义邮箱池';
}
if (generator === 'icloud') {
return 'iCloud 隐私邮箱';
}
@@ -5440,6 +5646,7 @@ const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMa
sleepWithStop,
throwIfStopped,
upsertMail2925AccountInList,
waitForTabComplete,
waitForTabUrlMatch,
});
@@ -5509,11 +5716,13 @@ const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGenerat
buildGeneratedAliasEmail,
buildCloudflareTempEmailHeaders,
CLOUDFLARE_TEMP_EMAIL_GENERATOR,
CUSTOM_EMAIL_POOL_GENERATOR,
DUCK_AUTOFILL_URL,
fetch,
fetchIcloudHideMyEmail,
getCloudflareTempEmailAddressFromResponse,
getCloudflareTempEmailConfig,
getCustomEmailPoolEmail: getCustomEmailPoolEmailForRun,
getState,
ensureMail2925AccountForFlow,
joinCloudflareTempEmailUrl,
@@ -5756,6 +5965,34 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
return currentState.email;
}
if (isCustomMailProvider(currentState)) {
const poolSize = getCustomMailProviderPool(currentState).length;
if (poolSize > 0) {
const queuedEmail = getCustomMailProviderPoolEmailForRun(currentState, targetRun);
if (!queuedEmail) {
throw new Error(`自定义邮箱号池第 ${targetRun} 个邮箱不存在,请检查号池数量是否与自动轮数一致。`);
}
await setEmailState(queuedEmail);
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:自定义邮箱号池已就绪:${queuedEmail}(第 ${attemptRuns} 次尝试;第 4/8 步仍需手动输入验证码)===`, 'ok');
return queuedEmail;
}
}
if (isCustomEmailPoolGenerator(currentState)) {
const queuedEmail = getCustomEmailPoolEmailForRun(currentState, targetRun);
if (!queuedEmail) {
const poolSize = getCustomEmailPool(currentState).length;
throw new Error(
poolSize > 0
? `自定义邮箱池第 ${targetRun} 个邮箱不存在,请检查邮箱池数量是否与自动轮数一致。`
: '自定义邮箱池为空,请先至少填写 1 个邮箱。'
);
}
await setEmailState(queuedEmail);
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:自定义邮箱池已就绪:${queuedEmail}(第 ${attemptRuns} 次尝试)===`, 'ok');
return queuedEmail;
}
if (shouldUseCustomRegistrationEmail(currentState)) {
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn');
await broadcastAutoRunStatus('waiting_email', {
@@ -5781,7 +6018,10 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
if (attempt > 1) {
await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn');
}
const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator });
const generatedEmail = await fetchGeneratedEmail(currentState, {
generateNew: generator !== 'icloud' || normalizeIcloudFetchMode(currentState.icloudFetchMode) === 'always_new',
generator,
});
await addLog(
`=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
'ok'
@@ -5874,6 +6114,34 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
return currentState.email;
}
if (isCustomMailProvider(currentState)) {
const poolSize = getCustomMailProviderPool(currentState).length;
if (poolSize > 0) {
const queuedEmail = getCustomMailProviderPoolEmailForRun(currentState, targetRun);
if (!queuedEmail) {
throw new Error(`自定义邮箱号池第 ${targetRun} 个邮箱不存在,请检查号池数量是否与自动轮数一致。`);
}
await setEmailState(queuedEmail);
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:自定义邮箱号池已就绪:${queuedEmail}(第 ${attemptRuns} 次尝试;第 4/8 步仍需手动输入验证码)===`, 'ok');
return queuedEmail;
}
}
if (isCustomEmailPoolGenerator(currentState)) {
const queuedEmail = getCustomEmailPoolEmailForRun(currentState, targetRun);
if (!queuedEmail) {
const poolSize = getCustomEmailPool(currentState).length;
throw new Error(
poolSize > 0
? `自定义邮箱池第 ${targetRun} 个邮箱不存在,请检查邮箱池数量是否与自动轮数一致。`
: '自定义邮箱池为空,请先至少填写 1 个邮箱。'
);
}
await setEmailState(queuedEmail);
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:自定义邮箱池已就绪:${queuedEmail}(第 ${attemptRuns} 次尝试)===`, 'ok');
return queuedEmail;
}
if (shouldUseCustomRegistrationEmail(currentState)) {
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn');
await broadcastAutoRunStatus('waiting_email', {
@@ -5899,7 +6167,10 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
if (attempt > 1) {
await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn');
}
const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator });
const generatedEmail = await fetchGeneratedEmail(currentState, {
generateNew: generator !== 'icloud' || normalizeIcloudFetchMode(currentState.icloudFetchMode) === 'always_new',
generator,
});
await addLog(
`=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
'ok'
@@ -5984,9 +6255,15 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
let restartFromStep1WithCurrentEmail = false;
let step = Math.max(currentStartStep, 4);
while (step <= LAST_STEP_ID) {
const latestState = await getState();
const currentStatus = latestState.stepStatuses?.[step] || 'pending';
if (isStepDoneStatus(currentStatus)) {
await addLog(`自动运行:步骤 ${step} 当前状态为 ${currentStatus},将直接继续后续流程。`, 'info');
step += 1;
continue;
}
try {
await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
const latestState = await getState();
step += 1;
} catch (err) {
if (isStopError(err)) {
@@ -6176,6 +6453,7 @@ const panelBridge = self.MultiPageBackgroundPanelBridge?.createPanelBridge({
closeConflictingTabsForSource,
ensureContentScriptReadyOnTab,
getPanelMode,
normalizeCodex2ApiUrl,
normalizeSub2ApiUrl,
rememberSourceLastUrl,
sendToContentScript,
@@ -6196,6 +6474,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
isGeneratedAliasProvider,
isReusableGeneratedAliasEmail,
isSignupEmailVerificationPageUrl,
isRetryableContentScriptTransportError,
isHotmailProvider,
isLuckmailProvider,
isSignupPasswordPageUrl,
@@ -6223,6 +6502,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create
getTabId,
HOTMAIL_PROVIDER,
isMail2925LimitReachedError,
isRetryableContentScriptTransportError,
isStopError,
LUCKMAIL_PROVIDER,
MAIL_2925_VERIFICATION_INTERVAL_MS,
@@ -6231,6 +6511,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create
pollHotmailVerificationCode,
pollLuckmailVerificationCode,
sendToContentScript,
sendToContentScriptResilient,
sendToMailContentScriptResilient,
setState,
setStepStatus,
@@ -6263,6 +6544,7 @@ const step2Executor = self.MultiPageBackgroundStep2?.createStep2Executor({
chrome,
completeStepFromBackground,
ensureContentScriptReadyOnTab,
ensureSignupAuthEntryPageReady,
ensureSignupEntryPageReady,
ensureSignupPostEmailPageReadyInTab,
getTabId,
@@ -6283,12 +6565,24 @@ const step3Executor = self.MultiPageBackgroundStep3?.createStep3Executor({
setState,
SIGNUP_PAGE_INJECT_FILES,
});
async function ensureIcloudMailSessionForVerification(options = {}) {
const flowState = options?.state || await getState().catch(() => ({}));
const hostPreference = getConfiguredIcloudHostPreference(flowState)
|| normalizeIcloudHost(flowState?.preferredIcloudHost);
return checkIcloudSession({
...(hostPreference ? { hostPreference } : {}),
actionLabel: options?.actionLabel || '检查 iCloud 会话',
});
}
const step4Executor = self.MultiPageBackgroundStep4?.createStep4Executor({
addLog,
chrome,
completeStepFromBackground,
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
ensureMail2925MailboxSession,
ensureIcloudMailSession: ensureIcloudMailSessionForVerification,
getMailConfig,
getTabId,
HOTMAIL_PROVIDER,
@@ -6335,6 +6629,7 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
ensureMail2925MailboxSession,
ensureIcloudMailSession: ensureIcloudMailSessionForVerification,
ensureStep8VerificationPageReady,
getOAuthFlowRemainingMs,
getOAuthFlowStepTimeoutMs,
@@ -6366,6 +6661,7 @@ const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({
getTabId,
isLocalhostOAuthCallbackUrl,
isTabAlive,
normalizeCodex2ApiUrl,
normalizeSub2ApiUrl,
rememberSourceLastUrl,
reuseOrCreateTab,
@@ -6432,6 +6728,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
getPendingAutoRunTimerPlan,
getSourceLabel,
getState,
getTabId,
getStopRequested: () => stopRequested,
handleCloudflareSecurityBlocked,
handleAutoRunLoopUnhandledError,
@@ -6443,6 +6740,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
isLocalhostOAuthCallbackUrl,
isLuckmailProvider,
isStopError,
isTabAlive,
launchAutoRunTimerPlan,
listIcloudAliases,
listLuckmailPurchasesForManagement,
@@ -6514,6 +6812,10 @@ async function ensureSignupEntryPageReady(step = 1) {
return signupFlowHelpers.ensureSignupEntryPageReady(step);
}
async function ensureSignupAuthEntryPageReady(step = 1) {
return signupFlowHelpers.ensureSignupEntryPageReady(step);
}
async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options);
}
@@ -6814,7 +7116,7 @@ async function runPreStep6CookieCleanup() {
async function refreshOAuthUrlBeforeStep6(state) {
if (state?.contributionModeExpected && !state?.contributionMode) {
throw new Error('步骤 7:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API 链路。请重新进入贡献模式后再点击自动。');
throw new Error('步骤 7:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 链路。请重新进入贡献模式后再点击自动。');
}
if (state?.contributionMode && contributionOAuthManager?.startContributionFlow) {
await addLog('步骤 7contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...', 'info');
@@ -6830,7 +7132,7 @@ async function refreshOAuthUrlBeforeStep6(state) {
await handleStepData(1, { oauthUrl });
return oauthUrl;
}
await addLog(`步骤 7contributionMode=false,走普通 CPA / SUB2API 链路(当前面板:${getPanelModeLabel(state)}),正在刷新 OAuth 登录地址...`, 'info');
await addLog(`步骤 7contributionMode=false,走普通 CPA / SUB2API / Codex2API 链路(当前面板:${getPanelModeLabel(state)}),正在刷新 OAuth 登录地址...`, 'info');
console.log(LOG_PREFIX, '[refreshOAuthUrlBeforeStep6] requesting fresh OAuth directly from panel');
const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: '步骤 7' });
await handleStepData(1, refreshResult);
@@ -6868,7 +7170,7 @@ async function startOAuthFlowTimeoutWindow(options = {}) {
oauthFlowDeadlineAt: deadlineAt,
oauthFlowDeadlineSourceUrl: normalizeOAuthFlowSourceUrl(options.oauthUrl),
});
await addLog(`步骤 ${step}:已拿到新的 OAuth 登录地址,开始 6 分钟倒计时。`, 'info');
await addLog(`步骤 ${step}:已拿到新的 OAuth 登录地址,开始 ${Math.round(OAUTH_FLOW_TIMEOUT_MS / 60000)} 分钟倒计时。`, 'info');
return deadlineAt;
}
@@ -7218,7 +7520,6 @@ async function getStep8PageState(tabId, responseTimeoutMs = 1500) {
async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS) {
const start = Date.now();
let recovered = false;
let retryRecovered = false;
while (Date.now() - start < timeoutMs) {
throwIfStopped();
@@ -7229,7 +7530,6 @@ async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS)
if (pageState?.addPhonePage || pageState?.phoneVerificationPage) {
await phoneVerificationHelpers.completePhoneVerificationFlow(tabId, pageState);
recovered = false;
retryRecovered = false;
await sleepWithStop(250);
continue;
}
@@ -7237,20 +7537,9 @@ async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS)
throw new Error('步骤 9:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。');
}
if (pageState?.retryPage) {
await recoverAuthRetryPageOnTab(tabId, {
flow: 'auth',
logLabel: '步骤 9:检测到认证页重试页,正在点击“重试”恢复',
step: 8,
timeoutMs: Math.max(1000, Math.min(12000, timeoutMs)),
});
retryRecovered = true;
await sleepWithStop(250);
continue;
throw new Error(`步骤 9:当前认证页已进入重试页,当前流程将直接报错。URL: ${pageState.url || 'unknown'}`);
}
if (pageState?.consentReady) {
if (retryRecovered) {
await addLog('步骤 9:认证页重试页已恢复,准备重新定位“继续”按钮...', 'info');
}
return pageState;
}
if (pageState === null && !recovered) {
@@ -7415,18 +7704,7 @@ async function waitForStep8ClickEffect(tabId, baselineUrl, timeoutMs = STEP8_CLI
throw new Error('步骤 9:点击“继续”后页面跳到了手机号页面,当前流程无法继续自动授权。');
}
if (pageState?.retryPage) {
await recoverAuthRetryPageOnTab(tabId, {
flow: 'auth',
logLabel: '步骤 9:点击“继续”后进入重试页,正在点击“重试”恢复',
step: 8,
timeoutMs: Math.max(1000, Math.min(12000, timeoutMs)),
});
return {
progressed: false,
reason: 'retry_page_recovered',
restartCurrentStep: true,
url: pageState.url || baselineUrl || '',
};
throw new Error(`步骤 9:点击“继续”后页面进入认证页重试页,当前流程将直接报错。URL: ${pageState.url || baselineUrl || 'unknown'}`);
}
if (pageState === null) {
if (!recovered) {
@@ -7460,8 +7738,6 @@ function getStep8EffectLabel(effect) {
switch (effect?.reason) {
case 'url_changed':
return `URL 已变化:${effect.url}`;
case 'retry_page_recovered':
return '页面进入重试页并已恢复,需要重新执行当前步骤';
case 'page_reloading':
return '页面正在跳转或重载';
case 'left_consent_page':
@@ -7578,7 +7854,7 @@ async function executeContributionStep10(state) {
async function executeStep10(state) {
if (state?.contributionModeExpected && !state?.contributionMode) {
throw new Error('步骤 10:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API 提交。请重新进入贡献模式后再点击自动。');
throw new Error('步骤 10:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API / Codex2API 提交。请重新进入贡献模式后再点击自动。');
}
if (state?.contributionMode) {
return executeContributionStep10(state);