fix(pro7.5): stabilize icloud/session flow and logged-in step skipping
This commit is contained in:
+70
-17
@@ -272,6 +272,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
customEmailPool: [],
|
customEmailPool: [],
|
||||||
autoDeleteUsedIcloudAlias: false,
|
autoDeleteUsedIcloudAlias: false,
|
||||||
icloudHostPreference: 'auto',
|
icloudHostPreference: 'auto',
|
||||||
|
icloudFetchMode: 'reuse_existing',
|
||||||
accountRunHistoryTextEnabled: false,
|
accountRunHistoryTextEnabled: false,
|
||||||
accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
|
accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
|
||||||
gmailBaseEmail: '',
|
gmailBaseEmail: '',
|
||||||
@@ -674,6 +675,11 @@ function normalizeEmailGenerator(value = '') {
|
|||||||
return 'duck';
|
return 'duck';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeIcloudFetchMode(value = '') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized === 'always_new' ? 'always_new' : 'reuse_existing';
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeCustomEmailPool(value = []) {
|
function normalizeCustomEmailPool(value = []) {
|
||||||
const source = Array.isArray(value)
|
const source = Array.isArray(value)
|
||||||
? value
|
? value
|
||||||
@@ -978,6 +984,8 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
case 'icloudHostPreference':
|
case 'icloudHostPreference':
|
||||||
return normalizeIcloudHost(value) || 'auto';
|
return normalizeIcloudHost(value) || 'auto';
|
||||||
|
case 'icloudFetchMode':
|
||||||
|
return normalizeIcloudFetchMode(value);
|
||||||
case 'accountRunHistoryHelperBaseUrl':
|
case 'accountRunHistoryHelperBaseUrl':
|
||||||
return normalizeAccountRunHistoryHelperBaseUrl(value);
|
return normalizeAccountRunHistoryHelperBaseUrl(value);
|
||||||
case 'gmailBaseEmail':
|
case 'gmailBaseEmail':
|
||||||
@@ -3448,10 +3456,16 @@ async function validateIcloudSession(setupUrl) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveIcloudPremiumMailService() {
|
async function resolveIcloudPremiumMailService(options = {}) {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
const state = await getState();
|
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) {
|
for (const setupUrl of setupUrls) {
|
||||||
try {
|
try {
|
||||||
@@ -3480,17 +3494,19 @@ function getIcloudAliasLabel() {
|
|||||||
return `MultiPage ${dateStr}`;
|
return `MultiPage ${dateStr}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkIcloudSession() {
|
async function checkIcloudSession(options = {}) {
|
||||||
return withIcloudLoginHelp('检查 iCloud 会话', async () => {
|
const actionLabel = String(options?.actionLabel || '检查 iCloud 会话').trim() || '检查 iCloud 会话';
|
||||||
const { setupUrl } = await resolveIcloudPremiumMailService();
|
const { actionLabel: _ignoredActionLabel, ...resolveOptions } = options || {};
|
||||||
|
return withIcloudLoginHelp(actionLabel, async () => {
|
||||||
|
const { setupUrl } = await resolveIcloudPremiumMailService(resolveOptions);
|
||||||
await addLog(`iCloud:会话校验通过(${new URL(setupUrl).host})`, 'ok');
|
await addLog(`iCloud:会话校验通过(${new URL(setupUrl).host})`, 'ok');
|
||||||
return { ok: true, setupUrl };
|
return { ok: true, setupUrl };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listIcloudAliases() {
|
async function listIcloudAliases(options = {}) {
|
||||||
return withIcloudLoginHelp('加载 iCloud 隐私邮箱列表', async () => {
|
return withIcloudLoginHelp('加载 iCloud 隐私邮箱列表', async () => {
|
||||||
const { serviceUrl } = await resolveIcloudPremiumMailService();
|
const { serviceUrl } = await resolveIcloudPremiumMailService(options);
|
||||||
const response = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`);
|
const response = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`);
|
||||||
const state = await getState();
|
const state = await getState();
|
||||||
return normalizeIcloudAliasList(response, {
|
return normalizeIcloudAliasList(response, {
|
||||||
@@ -3580,9 +3596,10 @@ async function deleteUsedIcloudAliases() {
|
|||||||
return { deleted, skipped };
|
return { deleted, skipped };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchIcloudHideMyEmail() {
|
async function fetchIcloudHideMyEmail(options = {}) {
|
||||||
return withIcloudLoginHelp('获取 iCloud 隐私邮箱', async () => {
|
return withIcloudLoginHelp('获取 iCloud 隐私邮箱', async () => {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
|
const generateNew = Boolean(options?.generateNew);
|
||||||
await addLog('iCloud:正在校验当前浏览器登录状态...', 'info');
|
await addLog('iCloud:正在校验当前浏览器登录状态...', 'info');
|
||||||
|
|
||||||
const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService();
|
const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService();
|
||||||
@@ -3595,12 +3612,16 @@ async function fetchIcloudHideMyEmail() {
|
|||||||
preservedEmails: getPreservedAliasMap(state),
|
preservedEmails: getPreservedAliasMap(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const reusableAlias = pickReusableIcloudAlias(existingAliases);
|
if (!generateNew) {
|
||||||
if (reusableAlias) {
|
const reusableAlias = pickReusableIcloudAlias(existingAliases);
|
||||||
await setEmailState(reusableAlias.email);
|
if (reusableAlias) {
|
||||||
await addLog(`iCloud:复用未使用别名 ${reusableAlias.email}`, 'ok');
|
await setEmailState(reusableAlias.email);
|
||||||
broadcastIcloudAliasesChanged({ reason: 'selected', email: reusableAlias.email });
|
await addLog(`iCloud:复用未使用别名 ${reusableAlias.email}`, 'ok');
|
||||||
return reusableAlias.email;
|
broadcastIcloudAliasesChanged({ reason: 'selected', email: reusableAlias.email });
|
||||||
|
return reusableAlias.email;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await addLog('iCloud:已启用“始终创建新别名”,本次将跳过复用。', 'info');
|
||||||
}
|
}
|
||||||
|
|
||||||
await addLog('iCloud:没有可复用别名,开始生成新的 Hide My Email 地址...', 'warn');
|
await addLog('iCloud:没有可复用别名,开始生成新的 Hide My Email 地址...', 'warn');
|
||||||
@@ -5970,7 +5991,10 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
|
|||||||
if (attempt > 1) {
|
if (attempt > 1) {
|
||||||
await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn');
|
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',
|
||||||
|
generator,
|
||||||
|
});
|
||||||
await addLog(
|
await addLog(
|
||||||
`=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
|
`=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
|
||||||
'ok'
|
'ok'
|
||||||
@@ -6116,7 +6140,10 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
|
|||||||
if (attempt > 1) {
|
if (attempt > 1) {
|
||||||
await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn');
|
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',
|
||||||
|
generator,
|
||||||
|
});
|
||||||
await addLog(
|
await addLog(
|
||||||
`=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
|
`=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
|
||||||
'ok'
|
'ok'
|
||||||
@@ -6201,9 +6228,15 @@ 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 (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 {
|
try {
|
||||||
await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
|
await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
|
||||||
const latestState = await getState();
|
|
||||||
step += 1;
|
step += 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
@@ -6442,6 +6475,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create
|
|||||||
getTabId,
|
getTabId,
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
isMail2925LimitReachedError,
|
isMail2925LimitReachedError,
|
||||||
|
isRetryableContentScriptTransportError,
|
||||||
isStopError,
|
isStopError,
|
||||||
LUCKMAIL_PROVIDER,
|
LUCKMAIL_PROVIDER,
|
||||||
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
||||||
@@ -6450,6 +6484,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create
|
|||||||
pollHotmailVerificationCode,
|
pollHotmailVerificationCode,
|
||||||
pollLuckmailVerificationCode,
|
pollLuckmailVerificationCode,
|
||||||
sendToContentScript,
|
sendToContentScript,
|
||||||
|
sendToContentScriptResilient,
|
||||||
sendToMailContentScriptResilient,
|
sendToMailContentScriptResilient,
|
||||||
setState,
|
setState,
|
||||||
setStepStatus,
|
setStepStatus,
|
||||||
@@ -6467,6 +6502,7 @@ const step2Executor = self.MultiPageBackgroundStep2?.createStep2Executor({
|
|||||||
chrome,
|
chrome,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
ensureContentScriptReadyOnTab,
|
ensureContentScriptReadyOnTab,
|
||||||
|
ensureSignupAuthEntryPageReady,
|
||||||
ensureSignupEntryPageReady,
|
ensureSignupEntryPageReady,
|
||||||
ensureSignupPostEmailPageReadyInTab,
|
ensureSignupPostEmailPageReadyInTab,
|
||||||
getTabId,
|
getTabId,
|
||||||
@@ -6487,12 +6523,24 @@ const step3Executor = self.MultiPageBackgroundStep3?.createStep3Executor({
|
|||||||
setState,
|
setState,
|
||||||
SIGNUP_PAGE_INJECT_FILES,
|
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({
|
const step4Executor = self.MultiPageBackgroundStep4?.createStep4Executor({
|
||||||
addLog,
|
addLog,
|
||||||
chrome,
|
chrome,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
||||||
ensureMail2925MailboxSession,
|
ensureMail2925MailboxSession,
|
||||||
|
ensureIcloudMailSession: ensureIcloudMailSessionForVerification,
|
||||||
getMailConfig,
|
getMailConfig,
|
||||||
getTabId,
|
getTabId,
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
@@ -6539,6 +6587,7 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
|
|||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||||||
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
||||||
ensureMail2925MailboxSession,
|
ensureMail2925MailboxSession,
|
||||||
|
ensureIcloudMailSession: ensureIcloudMailSessionForVerification,
|
||||||
ensureStep8VerificationPageReady,
|
ensureStep8VerificationPageReady,
|
||||||
getOAuthFlowRemainingMs,
|
getOAuthFlowRemainingMs,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
@@ -6721,6 +6770,10 @@ async function ensureSignupEntryPageReady(step = 1) {
|
|||||||
return signupFlowHelpers.ensureSignupEntryPageReady(step);
|
return signupFlowHelpers.ensureSignupEntryPageReady(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureSignupAuthEntryPageReady(step = 1) {
|
||||||
|
return signupFlowHelpers.ensureSignupEntryPageReady(step);
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
|
async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
|
||||||
return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options);
|
return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,7 +283,9 @@
|
|||||||
return fetchManagedAliasEmail(mergedState, options);
|
return fetchManagedAliasEmail(mergedState, options);
|
||||||
}
|
}
|
||||||
if (generator === 'icloud') {
|
if (generator === 'icloud') {
|
||||||
return fetchIcloudHideMyEmail();
|
return fetchIcloudHideMyEmail({
|
||||||
|
generateNew: Boolean(options.generateNew),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (generator === 'cloudflare') {
|
if (generator === 'cloudflare') {
|
||||||
return fetchCloudflareEmail(mergedState, options);
|
return fetchCloudflareEmail(mergedState, options);
|
||||||
|
|||||||
@@ -191,6 +191,14 @@
|
|||||||
lastEmailTimestamp: payload.emailTimestamp || null,
|
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||||
signupVerificationRequestedAt: null,
|
signupVerificationRequestedAt: null,
|
||||||
});
|
});
|
||||||
|
if (payload.skipProfileStep) {
|
||||||
|
const latestState = await getState();
|
||||||
|
const step5Status = latestState.stepStatuses?.[5];
|
||||||
|
if (step5Status !== 'running' && step5Status !== 'completed' && step5Status !== 'manual_completed') {
|
||||||
|
await setStepStatus(5, 'skipped');
|
||||||
|
await addLog('步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。', 'warn');
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
await setState({
|
await setState({
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||||||
confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass,
|
||||||
ensureMail2925MailboxSession,
|
ensureMail2925MailboxSession,
|
||||||
|
ensureIcloudMailSession,
|
||||||
ensureStep8VerificationPageReady,
|
ensureStep8VerificationPageReady,
|
||||||
getOAuthFlowRemainingMs,
|
getOAuthFlowRemainingMs,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
@@ -140,6 +141,15 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
|
||||||
|
await addLog('步骤 8:正在确认 iCloud 邮箱登录态...', 'info');
|
||||||
|
await ensureIcloudMailSession({
|
||||||
|
state,
|
||||||
|
step: 8,
|
||||||
|
actionLabel: '步骤 8:确认 iCloud 邮箱登录态',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (
|
if (
|
||||||
mail.provider === HOTMAIL_PROVIDER
|
mail.provider === HOTMAIL_PROVIDER
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass,
|
||||||
ensureMail2925MailboxSession,
|
ensureMail2925MailboxSession,
|
||||||
|
ensureIcloudMailSession,
|
||||||
getMailConfig,
|
getMailConfig,
|
||||||
getTabId,
|
getTabId,
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
@@ -111,6 +112,15 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
|
||||||
|
await addLog('步骤 4:正在确认 iCloud 邮箱登录态...', 'info');
|
||||||
|
await ensureIcloudMailSession({
|
||||||
|
state,
|
||||||
|
step: 4,
|
||||||
|
actionLabel: '步骤 4:确认 iCloud 邮箱登录态',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (
|
if (
|
||||||
mail.provider === HOTMAIL_PROVIDER
|
mail.provider === HOTMAIL_PROVIDER
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
chrome,
|
chrome,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
ensureContentScriptReadyOnTab,
|
ensureContentScriptReadyOnTab,
|
||||||
|
ensureSignupAuthEntryPageReady,
|
||||||
ensureSignupEntryPageReady,
|
ensureSignupEntryPageReady,
|
||||||
ensureSignupPostEmailPageReadyInTab,
|
ensureSignupPostEmailPageReadyInTab,
|
||||||
getTabId,
|
getTabId,
|
||||||
@@ -16,6 +17,107 @@
|
|||||||
SIGNUP_PAGE_INJECT_FILES,
|
SIGNUP_PAGE_INJECT_FILES,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
|
function getErrorMessage(error) {
|
||||||
|
return String(typeof error === 'string' ? error : error?.message || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupEntryUnavailableErrorMessage(errorLike) {
|
||||||
|
const message = getErrorMessage(errorLike);
|
||||||
|
return /未找到可用的邮箱输入入口|当前页面没有可用的注册入口,也不在邮箱\/密码页/.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRetryableStep2TransportErrorMessage(errorLike) {
|
||||||
|
const message = getErrorMessage(errorLike);
|
||||||
|
return /Content script on signup-page did not respond in \d+s|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
|
||||||
|
const url = String(rawUrl || '').trim();
|
||||||
|
if (!url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const host = String(parsed.hostname || '').toLowerCase();
|
||||||
|
if (!['chatgpt.com', 'www.chatgpt.com'].includes(host)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const path = String(parsed.pathname || '');
|
||||||
|
if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shouldForceAuthEntryRetry(tabId) {
|
||||||
|
if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const tab = await chrome.tabs.get(tabId);
|
||||||
|
const currentUrl = String(tab?.url || '');
|
||||||
|
return isLikelyLoggedInChatgptHomeUrl(currentUrl);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTabUrl(tabId) {
|
||||||
|
if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const tab = await chrome.tabs.get(tabId);
|
||||||
|
return String(tab?.url || '');
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completeStep2AsLoggedInSession(tabId, resolvedEmail, reasonMessage = '') {
|
||||||
|
const currentUrl = await getTabUrl(tabId);
|
||||||
|
if (!isLikelyLoggedInChatgptHomeUrl(currentUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const reasonText = getErrorMessage(reasonMessage);
|
||||||
|
const reasonSuffix = reasonText ? `(触发原因:${reasonText})` : '';
|
||||||
|
await addLog(`步骤 2:检测到当前会话已登录 ChatGPT,已跳过注册链路(步骤 3/4/5),将直接进入步骤 6。${reasonSuffix}`, 'warn');
|
||||||
|
await completeStepFromBackground(2, {
|
||||||
|
email: resolvedEmail,
|
||||||
|
nextSignupState: 'already_logged_in_home',
|
||||||
|
nextSignupUrl: currentUrl || 'https://chatgpt.com/',
|
||||||
|
skippedPasswordStep: true,
|
||||||
|
skipRegistrationFlow: true,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitSignupEmail(resolvedEmail, options = {}) {
|
||||||
|
const {
|
||||||
|
timeoutMs = 35000,
|
||||||
|
retryDelayMs = 700,
|
||||||
|
logMessage = '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...',
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await sendToContentScriptResilient('signup-page', {
|
||||||
|
type: 'EXECUTE_STEP',
|
||||||
|
step: 2,
|
||||||
|
source: 'background',
|
||||||
|
payload: { email: resolvedEmail },
|
||||||
|
}, {
|
||||||
|
timeoutMs,
|
||||||
|
retryDelayMs,
|
||||||
|
logMessage,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return { error: getErrorMessage(error) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function executeStep2(state) {
|
async function executeStep2(state) {
|
||||||
const resolvedEmail = await resolveSignupEmailForFlow(state);
|
const resolvedEmail = await resolveSignupEmailForFlow(state);
|
||||||
|
|
||||||
@@ -34,19 +136,73 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const step2Result = await sendToContentScriptResilient('signup-page', {
|
if (await shouldForceAuthEntryRetry(signupTabId)) {
|
||||||
type: 'EXECUTE_STEP',
|
await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交邮箱。', 'warn');
|
||||||
step: 2,
|
try {
|
||||||
source: 'background',
|
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
|
||||||
payload: { email: resolvedEmail },
|
} catch (entryError) {
|
||||||
}, {
|
const entryErrorMessage = getErrorMessage(entryError);
|
||||||
timeoutMs: 20000,
|
if (await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, entryErrorMessage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交邮箱...', 'warn');
|
||||||
|
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let step2Result = await submitSignupEmail(resolvedEmail, {
|
||||||
|
timeoutMs: 35000,
|
||||||
retryDelayMs: 700,
|
retryDelayMs: 700,
|
||||||
logMessage: '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...',
|
logMessage: '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (step2Result?.error) {
|
if (step2Result?.error) {
|
||||||
throw new Error(step2Result.error);
|
const errorMessage = getErrorMessage(step2Result.error);
|
||||||
|
if (isSignupEntryUnavailableErrorMessage(errorMessage)) {
|
||||||
|
await addLog('步骤 2:未找到邮箱输入入口,正在切换认证入口页后重试一次...', 'warn');
|
||||||
|
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
|
||||||
|
step2Result = await submitSignupEmail(resolvedEmail, {
|
||||||
|
timeoutMs: 35000,
|
||||||
|
retryDelayMs: 700,
|
||||||
|
logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (step2Result?.error) {
|
||||||
|
const retryErrorMessage = getErrorMessage(step2Result.error);
|
||||||
|
if (isSignupEntryUnavailableErrorMessage(retryErrorMessage)) {
|
||||||
|
if (await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, retryErrorMessage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await addLog('步骤 2:认证入口仍不可用,正在重新进入官网注册入口再重试一次...', 'warn');
|
||||||
|
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
|
||||||
|
step2Result = await submitSignupEmail(resolvedEmail, {
|
||||||
|
timeoutMs: 35000,
|
||||||
|
retryDelayMs: 700,
|
||||||
|
logMessage: '步骤 2:重试官网注册入口后正在重新提交邮箱...',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isRetryableStep2TransportErrorMessage(errorMessage)) {
|
||||||
|
await addLog('步骤 2:注册入口页通信超时,正在切换认证入口页并重试提交邮箱...', 'warn');
|
||||||
|
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
|
||||||
|
step2Result = await submitSignupEmail(resolvedEmail, {
|
||||||
|
timeoutMs: 45000,
|
||||||
|
retryDelayMs: 700,
|
||||||
|
logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step2Result?.error) {
|
||||||
|
const finalErrorMessage = getErrorMessage(step2Result.error);
|
||||||
|
if (
|
||||||
|
(isSignupEntryUnavailableErrorMessage(finalErrorMessage)
|
||||||
|
|| isRetryableStep2TransportErrorMessage(finalErrorMessage))
|
||||||
|
&& await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, finalErrorMessage)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(finalErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!step2Result?.alreadyOnPasswordPage) {
|
if (!step2Result?.alreadyOnPasswordPage) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
pollHotmailVerificationCode,
|
pollHotmailVerificationCode,
|
||||||
pollLuckmailVerificationCode,
|
pollLuckmailVerificationCode,
|
||||||
sendToContentScript,
|
sendToContentScript,
|
||||||
|
sendToContentScriptResilient,
|
||||||
sendToMailContentScriptResilient,
|
sendToMailContentScriptResilient,
|
||||||
setState,
|
setState,
|
||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
@@ -30,6 +31,12 @@
|
|||||||
VERIFICATION_POLL_MAX_ROUNDS,
|
VERIFICATION_POLL_MAX_ROUNDS,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
|
const isRetryableVerificationTransportError = typeof deps.isRetryableContentScriptTransportError === 'function'
|
||||||
|
? deps.isRetryableContentScriptTransportError
|
||||||
|
: ((error) => /back\/forward cache|message channel is closed|Receiving end does not exist|port closed before a response was received|A listener indicated an asynchronous response|did not respond in \d+s/i.test(
|
||||||
|
String(typeof error === 'string' ? error : error?.message || '')
|
||||||
|
));
|
||||||
|
|
||||||
function getVerificationCodeStateKey(step) {
|
function getVerificationCodeStateKey(step) {
|
||||||
return step === 4 ? 'lastSignupCode' : 'lastLoginCode';
|
return step === 4 ? 'lastSignupCode' : 'lastLoginCode';
|
||||||
}
|
}
|
||||||
@@ -38,6 +45,89 @@
|
|||||||
return step === 4 ? '注册' : '登录';
|
return step === 4 ? '注册' : '登录';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
|
||||||
|
const url = String(rawUrl || '').trim();
|
||||||
|
if (!url) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const host = String(parsed.hostname || '').toLowerCase();
|
||||||
|
if (!['chatgpt.com', 'www.chatgpt.com'].includes(host)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const path = String(parsed.pathname || '');
|
||||||
|
if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupProfilePageUrl(rawUrl) {
|
||||||
|
const url = String(rawUrl || '').trim();
|
||||||
|
if (!url) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const host = String(parsed.hostname || '').toLowerCase();
|
||||||
|
if (!['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(host)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /\/create-account\/profile(?:[/?#]|$)/i.test(String(parsed.pathname || ''));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function detectStep4PostSubmitFallback(tabId, options = {}) {
|
||||||
|
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 8000);
|
||||||
|
const pollIntervalMs = Math.max(100, Number(options.pollIntervalMs) || 250);
|
||||||
|
const startedAt = Date.now();
|
||||||
|
let lastUrl = '';
|
||||||
|
|
||||||
|
while (Date.now() - startedAt < timeoutMs) {
|
||||||
|
throwIfStopped();
|
||||||
|
try {
|
||||||
|
const tab = await chrome.tabs.get(tabId);
|
||||||
|
const currentUrl = String(tab?.url || '').trim();
|
||||||
|
if (currentUrl) {
|
||||||
|
lastUrl = currentUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLikelyLoggedInChatgptHomeUrl(currentUrl)) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
reason: 'chatgpt_home',
|
||||||
|
skipProfileStep: true,
|
||||||
|
url: currentUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSignupProfilePageUrl(currentUrl)) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
reason: 'signup_profile',
|
||||||
|
skipProfileStep: false,
|
||||||
|
url: currentUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep polling until timeout; tab may be mid-navigation.
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleepWithStop(pollIntervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
reason: 'unknown',
|
||||||
|
skipProfileStep: false,
|
||||||
|
url: lastUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getVerificationResendStateKey() {
|
function getVerificationResendStateKey() {
|
||||||
return 'verificationResendCount';
|
return 'verificationResendCount';
|
||||||
}
|
}
|
||||||
@@ -448,6 +538,17 @@
|
|||||||
`步骤 ${step}:距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,继续刷新邮箱(第 ${round}/${maxRounds} 轮)...`,
|
`步骤 ${step}:距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,继续刷新邮箱(第 ${round}/${maxRounds} 轮)...`,
|
||||||
'info'
|
'info'
|
||||||
);
|
);
|
||||||
|
const configuredIntervalMs = Math.max(
|
||||||
|
1,
|
||||||
|
Number(payloadOverrides.intervalMs)
|
||||||
|
|| Number(pollOverrides.intervalMs)
|
||||||
|
|| 3000
|
||||||
|
);
|
||||||
|
const cooldownSleepMs = Math.min(
|
||||||
|
remainingBeforeResendMs,
|
||||||
|
Math.max(1000, Math.min(configuredIntervalMs, 3000))
|
||||||
|
);
|
||||||
|
await sleepWithStop(cooldownSleepMs);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,19 +698,54 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await chrome.tabs.update(signupTabId, { active: true });
|
await chrome.tabs.update(signupTabId, { active: true });
|
||||||
const result = await sendToContentScript('signup-page', {
|
const baseResponseTimeoutMs = await getResponseTimeoutMsForStep(
|
||||||
|
step,
|
||||||
|
options,
|
||||||
|
step === 7 ? 45000 : 30000,
|
||||||
|
`填写${getVerificationCodeLabel(step)}验证码`
|
||||||
|
);
|
||||||
|
const message = {
|
||||||
type: 'FILL_CODE',
|
type: 'FILL_CODE',
|
||||||
step,
|
step,
|
||||||
source: 'background',
|
source: 'background',
|
||||||
payload: { code },
|
payload: { code },
|
||||||
}, {
|
};
|
||||||
responseTimeoutMs: await getResponseTimeoutMsForStep(
|
let result;
|
||||||
step,
|
if (typeof sendToContentScriptResilient === 'function') {
|
||||||
options,
|
try {
|
||||||
step === 7 ? 45000 : 30000,
|
result = await sendToContentScriptResilient('signup-page', message, {
|
||||||
`填写${getVerificationCodeLabel(step)}验证码`
|
timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000),
|
||||||
),
|
retryDelayMs: 700,
|
||||||
});
|
responseTimeoutMs: baseResponseTimeoutMs,
|
||||||
|
logMessage: `步骤 ${step}:认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...`,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (step === 4 && isRetryableVerificationTransportError(err)) {
|
||||||
|
const fallback = await detectStep4PostSubmitFallback(signupTabId, {
|
||||||
|
timeoutMs: 9000,
|
||||||
|
pollIntervalMs: 300,
|
||||||
|
});
|
||||||
|
if (fallback.success) {
|
||||||
|
const fallbackLabel = fallback.reason === 'chatgpt_home'
|
||||||
|
? 'ChatGPT 已登录首页'
|
||||||
|
: '注册资料页';
|
||||||
|
await addLog(`步骤 4:验证码提交后页面已切换到${fallbackLabel},按提交成功继续。`, 'warn');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
assumed: true,
|
||||||
|
transportRecovered: true,
|
||||||
|
skipProfileStep: Boolean(fallback.skipProfileStep),
|
||||||
|
url: fallback.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = await sendToContentScript('signup-page', message, {
|
||||||
|
responseTimeoutMs: baseResponseTimeoutMs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (result && result.error) {
|
if (result && result.error) {
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
@@ -735,6 +871,7 @@
|
|||||||
`步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts})...`,
|
`步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts})...`,
|
||||||
'warn'
|
'warn'
|
||||||
);
|
);
|
||||||
|
await sleepWithStop(Math.min(remainingBeforeResendMs, 2000));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,6 +900,7 @@
|
|||||||
await completeStepFromBackground(step, {
|
await completeStepFromBackground(step, {
|
||||||
emailTimestamp: result.emailTimestamp,
|
emailTimestamp: result.emailTimestamp,
|
||||||
code: result.code,
|
code: result.code,
|
||||||
|
...(step === 4 && submitResult?.skipProfileStep ? { skipProfileStep: true } : {}),
|
||||||
});
|
});
|
||||||
triggerPostSuccessMailboxCleanup(step, mail);
|
triggerPostSuccessMailboxCleanup(step, mail);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2122,6 +2122,20 @@ async function fillVerificationCode(step, payload) {
|
|||||||
const { code } = payload;
|
const { code } = payload;
|
||||||
if (!code) throw new Error('未提供验证码。');
|
if (!code) throw new Error('未提供验证码。');
|
||||||
|
|
||||||
|
if (step === 4 && isStep5Ready()) {
|
||||||
|
log(`步骤 ${step}:检测到页面已进入下一阶段,本次验证码提交按成功处理。`, 'ok');
|
||||||
|
return { success: true, assumed: true, alreadyAdvanced: true };
|
||||||
|
}
|
||||||
|
if (step === 8) {
|
||||||
|
if (isStep8Ready()) {
|
||||||
|
log(`步骤 ${step}:检测到页面已进入 OAuth 同意页,本次验证码提交按成功处理。`, 'ok');
|
||||||
|
return { success: true, assumed: true, alreadyAdvanced: true };
|
||||||
|
}
|
||||||
|
if (isAddPhonePageReady()) {
|
||||||
|
return { success: true, addPhonePage: true, url: location.href };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log(`步骤 ${step}:正在填写验证码:${code}`);
|
log(`步骤 ${step}:正在填写验证码:${code}`);
|
||||||
|
|
||||||
if (step === 8) {
|
if (step === 8) {
|
||||||
|
|||||||
@@ -631,6 +631,13 @@
|
|||||||
<option value="icloud.com.cn">iCloud.com.cn</option>
|
<option value="icloud.com.cn">iCloud.com.cn</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">获取策略</span>
|
||||||
|
<select id="select-icloud-fetch-mode" class="data-select">
|
||||||
|
<option value="reuse_existing">优先复用已有未用别名</option>
|
||||||
|
<option value="always_new">始终创建新别名</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">自动删除</span>
|
<span class="data-label">自动删除</span>
|
||||||
<label class="option-toggle" for="checkbox-auto-delete-icloud">
|
<label class="option-toggle" for="checkbox-auto-delete-icloud">
|
||||||
|
|||||||
@@ -562,6 +562,10 @@ const normalizeIcloudHost = window.IcloudUtils?.normalizeIcloudHost
|
|||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
return normalized === 'icloud.com' || normalized === 'icloud.com.cn' ? normalized : '';
|
return normalized === 'icloud.com' || normalized === 'icloud.com.cn' ? normalized : '';
|
||||||
});
|
});
|
||||||
|
const normalizeIcloudFetchMode = (value) => {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized === 'always_new' ? 'always_new' : 'reuse_existing';
|
||||||
|
};
|
||||||
const getIcloudLoginUrlForHost = window.IcloudUtils?.getIcloudLoginUrlForHost
|
const getIcloudLoginUrlForHost = window.IcloudUtils?.getIcloudLoginUrlForHost
|
||||||
|| ((host) => host === 'icloud.com.cn' ? 'https://www.icloud.com.cn/' : (host === 'icloud.com' ? 'https://www.icloud.com/' : ''));
|
|| ((host) => host === 'icloud.com.cn' ? 'https://www.icloud.com.cn/' : (host === 'icloud.com' ? 'https://www.icloud.com/' : ''));
|
||||||
|
|
||||||
@@ -1644,6 +1648,9 @@ function collectSettingsPayload() {
|
|||||||
!cloudflareTempEmailDomainEditMode ? selectTempEmailDomain.value : tempEmailActiveDomain
|
!cloudflareTempEmailDomainEditMode ? selectTempEmailDomain.value : tempEmailActiveDomain
|
||||||
) || tempEmailActiveDomain;
|
) || tempEmailActiveDomain;
|
||||||
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
||||||
|
const icloudFetchModeRawValue = typeof selectIcloudFetchMode !== 'undefined'
|
||||||
|
? String(selectIcloudFetchMode?.value || '')
|
||||||
|
: '';
|
||||||
const mail2925UseAccountPool = typeof inputMail2925UseAccountPool !== 'undefined'
|
const mail2925UseAccountPool = typeof inputMail2925UseAccountPool !== 'undefined'
|
||||||
? Boolean(inputMail2925UseAccountPool?.checked)
|
? Boolean(inputMail2925UseAccountPool?.checked)
|
||||||
: Boolean(latestState?.mail2925UseAccountPool);
|
: Boolean(latestState?.mail2925UseAccountPool);
|
||||||
@@ -1675,6 +1682,9 @@ function collectSettingsPayload() {
|
|||||||
: [],
|
: [],
|
||||||
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
|
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
|
||||||
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
|
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
|
||||||
|
icloudFetchMode: (icloudFetchModeRawValue.trim().toLowerCase() === 'always_new'
|
||||||
|
? 'always_new'
|
||||||
|
: 'reuse_existing'),
|
||||||
...(contributionModeEnabled ? {} : {
|
...(contributionModeEnabled ? {} : {
|
||||||
accountRunHistoryTextEnabled: Boolean(inputAccountRunHistoryTextEnabled?.checked),
|
accountRunHistoryTextEnabled: Boolean(inputAccountRunHistoryTextEnabled?.checked),
|
||||||
accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value),
|
accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value),
|
||||||
@@ -2081,6 +2091,9 @@ function applySettingsState(state) {
|
|||||||
? 'icloud.com'
|
? 'icloud.com'
|
||||||
: (String(state?.icloudHostPreference || '').trim().toLowerCase() === 'icloud.com.cn' ? 'icloud.com.cn' : 'auto');
|
: (String(state?.icloudHostPreference || '').trim().toLowerCase() === 'icloud.com.cn' ? 'icloud.com.cn' : 'auto');
|
||||||
}
|
}
|
||||||
|
if (selectIcloudFetchMode) {
|
||||||
|
selectIcloudFetchMode.value = normalizeIcloudFetchMode(state?.icloudFetchMode);
|
||||||
|
}
|
||||||
if (checkboxAutoDeleteIcloud) {
|
if (checkboxAutoDeleteIcloud) {
|
||||||
checkboxAutoDeleteIcloud.checked = Boolean(state?.autoDeleteUsedIcloudAlias);
|
checkboxAutoDeleteIcloud.checked = Boolean(state?.autoDeleteUsedIcloudAlias);
|
||||||
}
|
}
|
||||||
@@ -3396,6 +3409,12 @@ function updateButtonStates() {
|
|||||||
if (btnIcloudRefresh) btnIcloudRefresh.disabled = disableIcloudControls;
|
if (btnIcloudRefresh) btnIcloudRefresh.disabled = disableIcloudControls;
|
||||||
if (btnIcloudDeleteUsed) btnIcloudDeleteUsed.disabled = disableIcloudControls || !hasDeletableUsedIcloudAliases();
|
if (btnIcloudDeleteUsed) btnIcloudDeleteUsed.disabled = disableIcloudControls || !hasDeletableUsedIcloudAliases();
|
||||||
if (selectIcloudHostPreference) selectIcloudHostPreference.disabled = disableIcloudControls;
|
if (selectIcloudHostPreference) selectIcloudHostPreference.disabled = disableIcloudControls;
|
||||||
|
if (selectIcloudFetchMode) {
|
||||||
|
const allowIcloudFetchMode = getSelectedEmailGenerator() === ICLOUD_PROVIDER
|
||||||
|
&& !isCustomMailProvider()
|
||||||
|
&& !isManagedAliasProvider();
|
||||||
|
selectIcloudFetchMode.disabled = disableIcloudControls || !allowIcloudFetchMode;
|
||||||
|
}
|
||||||
if (checkboxAutoDeleteIcloud) checkboxAutoDeleteIcloud.disabled = disableIcloudControls;
|
if (checkboxAutoDeleteIcloud) checkboxAutoDeleteIcloud.disabled = disableIcloudControls;
|
||||||
if (btnContributionMode) btnContributionMode.disabled = isContributionButtonLocked();
|
if (btnContributionMode) btnContributionMode.disabled = isContributionButtonLocked();
|
||||||
updateStopButtonState(anyRunning || autoScheduled || isAutoRunPausedPhase() || autoLocked);
|
updateStopButtonState(anyRunning || autoScheduled || isAutoRunPausedPhase() || autoLocked);
|
||||||
@@ -4614,6 +4633,11 @@ selectIcloudHostPreference?.addEventListener('change', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
selectIcloudFetchMode?.addEventListener('change', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
checkboxAutoDeleteIcloud?.addEventListener('change', () => {
|
checkboxAutoDeleteIcloud?.addEventListener('change', () => {
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
@@ -5183,6 +5207,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
? 'icloud.com'
|
? 'icloud.com'
|
||||||
: (hostPreference === 'icloud.com.cn' ? 'icloud.com.cn' : 'auto');
|
: (hostPreference === 'icloud.com.cn' ? 'icloud.com.cn' : 'auto');
|
||||||
}
|
}
|
||||||
|
if (message.payload.icloudFetchMode !== undefined && selectIcloudFetchMode) {
|
||||||
|
selectIcloudFetchMode.value = normalizeIcloudFetchMode(message.payload.icloudFetchMode);
|
||||||
|
}
|
||||||
if (message.payload.autoRunSkipFailures !== undefined) {
|
if (message.payload.autoRunSkipFailures !== undefined) {
|
||||||
inputAutoSkipFailures.checked = Boolean(message.payload.autoRunSkipFailures);
|
inputAutoSkipFailures.checked = Boolean(message.payload.autoRunSkipFailures);
|
||||||
updateFallbackThreadIntervalInputState();
|
updateFallbackThreadIntervalInputState();
|
||||||
|
|||||||
@@ -330,3 +330,124 @@ return {
|
|||||||
assert.deepStrictEqual(result.events.steps, [1, 2, 3, 4]);
|
assert.deepStrictEqual(result.events.steps, [1, 2, 3, 4]);
|
||||||
assert.equal(result.events.logs.some(({ message }) => /沿用当前邮箱回到步骤 1 重新开始/.test(message)), false);
|
assert.equal(result.events.logs.some(({ message }) => /沿用当前邮箱回到步骤 1 重新开始/.test(message)), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auto-run skips steps 4/5 when step 2 has already marked registration chain as skipped', async () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 };
|
||||||
|
const LAST_STEP_ID = 10;
|
||||||
|
const FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||||
|
const chrome = {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
sendMessage: async () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentState = {
|
||||||
|
email: 'already@login.example',
|
||||||
|
password: 'Secret123!',
|
||||||
|
mailProvider: 'icloud',
|
||||||
|
stepStatuses: {
|
||||||
|
1: 'pending',
|
||||||
|
2: 'pending',
|
||||||
|
3: 'pending',
|
||||||
|
4: 'pending',
|
||||||
|
5: 'pending',
|
||||||
|
6: 'pending',
|
||||||
|
7: 'pending',
|
||||||
|
8: 'pending',
|
||||||
|
9: 'pending',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const events = {
|
||||||
|
steps: [],
|
||||||
|
logs: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function addLog(message, level = 'info') {
|
||||||
|
events.logs.push({ message, level });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureAutoEmailReady() {
|
||||||
|
return currentState.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function broadcastAutoRunStatus() {}
|
||||||
|
|
||||||
|
async function getState() {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setState(updates) {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...updates,
|
||||||
|
stepStatuses: updates.stepStatuses ? { ...updates.stepStatuses } : currentState.stepStatuses,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStopError(error) {
|
||||||
|
return (error?.message || String(error || '')) === '流程已被用户停止。';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStepDoneStatus(status) {
|
||||||
|
return status === 'completed' || status === 'manual_completed' || status === 'skipped';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeStepAndWait(step) {
|
||||||
|
events.steps.push(step);
|
||||||
|
if (step === 2) {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
stepStatuses: {
|
||||||
|
...currentState.stepStatuses,
|
||||||
|
3: 'skipped',
|
||||||
|
4: 'skipped',
|
||||||
|
5: 'skipped',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTabId() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invalidateDownstreamAfterStepRestart() {}
|
||||||
|
|
||||||
|
function getLoginAuthStateLabel(state) {
|
||||||
|
return state || 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error) {
|
||||||
|
return error?.message || String(error || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLoginAuthStateFromContent() {
|
||||||
|
return { state: 'password_page', url: 'https://auth.openai.com/log-in' };
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await runAutoSequenceFromStep(1, {
|
||||||
|
targetRun: 1,
|
||||||
|
totalRuns: 1,
|
||||||
|
attemptRuns: 1,
|
||||||
|
continued: false,
|
||||||
|
});
|
||||||
|
return { events, currentState };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const { events } = await api.run();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.steps, [1, 2, 6, 7, 8, 9, 10]);
|
||||||
|
assert.equal(events.logs.some(({ message }) => /步骤 4 当前状态为 skipped/.test(message)), true);
|
||||||
|
assert.equal(events.logs.some(({ message }) => /步骤 5 当前状态为 skipped/.test(message)), true);
|
||||||
|
});
|
||||||
|
|||||||
@@ -170,6 +170,20 @@ test('message router skips steps 3/4/5 when step 2 detects already logged-in ses
|
|||||||
assert.equal(events.logs[0]?.message, '步骤 2:检测到当前已登录会话,已自动跳过步骤 3/4/5,流程将直接进入步骤 6。');
|
assert.equal(events.logs[0]?.message, '步骤 2:检测到当前已登录会话,已自动跳过步骤 3/4/5,流程将直接进入步骤 6。');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('message router skips step 5 when step 4 reports already logged-in transition', async () => {
|
||||||
|
const { router, events } = createRouter({
|
||||||
|
state: { stepStatuses: { 5: 'pending' } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.handleStepData(4, {
|
||||||
|
emailTimestamp: 123,
|
||||||
|
skipProfileStep: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.stepStatuses, [{ step: 5, status: 'skipped' }]);
|
||||||
|
assert.equal(events.logs[0]?.message, '步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。');
|
||||||
|
});
|
||||||
|
|
||||||
test('message router finalizes step 3 before marking it completed', async () => {
|
test('message router finalizes step 3 before marking it completed', async () => {
|
||||||
const { router, events } = createRouter();
|
const { router, events } = createRouter();
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,55 @@ test('step 2 keeps password flow when landing on password page', async () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('step 2 falls back to already-logged-in branch when auth entry recovery fails on chatgpt home', async () => {
|
||||||
|
const completedPayloads = [];
|
||||||
|
const logs = [];
|
||||||
|
|
||||||
|
const executor = step2Api.createStep2Executor({
|
||||||
|
addLog: async (message, level = 'info') => {
|
||||||
|
logs.push({ message, level });
|
||||||
|
},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
get: async () => ({ url: 'https://chatgpt.com/' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeStepFromBackground: async (step, payload) => {
|
||||||
|
completedPayloads.push({ step, payload });
|
||||||
|
},
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
ensureSignupAuthEntryPageReady: async () => {
|
||||||
|
throw new Error('当前页面没有可用的注册入口,也不在邮箱/密码页。URL: https://chatgpt.com/');
|
||||||
|
},
|
||||||
|
ensureSignupEntryPageReady: async () => ({ tabId: 13 }),
|
||||||
|
ensureSignupPostEmailPageReadyInTab: async () => ({
|
||||||
|
state: 'password_page',
|
||||||
|
url: 'https://auth.openai.com/create-account/password',
|
||||||
|
}),
|
||||||
|
getTabId: async () => 13,
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
resolveSignupEmailForFlow: async () => 'user@example.com',
|
||||||
|
sendToContentScriptResilient: async () => ({ submitted: true }),
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep2({ email: 'user@example.com' });
|
||||||
|
|
||||||
|
assert.equal(completedPayloads.length, 1);
|
||||||
|
assert.deepStrictEqual(completedPayloads[0], {
|
||||||
|
step: 2,
|
||||||
|
payload: {
|
||||||
|
email: 'user@example.com',
|
||||||
|
nextSignupState: 'already_logged_in_home',
|
||||||
|
nextSignupUrl: 'https://chatgpt.com/',
|
||||||
|
skippedPasswordStep: true,
|
||||||
|
skipRegistrationFlow: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.ok(logs.some((item) => /已跳过注册链路/.test(item.message)));
|
||||||
|
});
|
||||||
|
|
||||||
test('signup flow helper recognizes email verification page as post-email landing page', async () => {
|
test('signup flow helper recognizes email verification page as post-email landing page', async () => {
|
||||||
let ensureCalls = 0;
|
let ensureCalls = 0;
|
||||||
let passwordReadyChecks = 0;
|
let passwordReadyChecks = 0;
|
||||||
|
|||||||
@@ -119,3 +119,49 @@ test('step 4 does not request a fresh code first for Cloudflare temp mail', asyn
|
|||||||
assert.equal(capturedOptions.requestFreshCodeFirst, false);
|
assert.equal(capturedOptions.requestFreshCodeFirst, false);
|
||||||
assert.equal(capturedOptions.resendIntervalMs, 25000);
|
assert.equal(capturedOptions.resendIntervalMs, 25000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('step 4 checks iCloud session before polling iCloud mailbox', async () => {
|
||||||
|
let icloudChecks = 0;
|
||||||
|
let resolved = false;
|
||||||
|
|
||||||
|
const executor = api.createStep4Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
|
ensureIcloudMailSession: async () => {
|
||||||
|
icloudChecks += 1;
|
||||||
|
},
|
||||||
|
ensureMail2925MailboxSession: async () => {},
|
||||||
|
getMailConfig: () => ({
|
||||||
|
source: 'icloud-mail',
|
||||||
|
url: 'https://www.icloud.com/mail/',
|
||||||
|
label: 'iCloud 邮箱',
|
||||||
|
}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
resolveVerificationStep: async () => {
|
||||||
|
resolved = true;
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
sendToContentScriptResilient: async () => ({}),
|
||||||
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep4({
|
||||||
|
email: 'user@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(icloudChecks, 1);
|
||||||
|
assert.equal(resolved, true);
|
||||||
|
});
|
||||||
|
|||||||
@@ -296,3 +296,57 @@ test('step 8 does not rerun step 7 when verification submit lands on add-phone',
|
|||||||
assert.equal(calls.rerunStep7, 0);
|
assert.equal(calls.rerunStep7, 0);
|
||||||
assert.ok(!calls.logs.some(({ message }) => /准备从步骤 7 重新开始/.test(message)));
|
assert.ok(!calls.logs.some(({ message }) => /准备从步骤 7 重新开始/.test(message)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('step 8 checks iCloud session before polling iCloud mailbox', async () => {
|
||||||
|
let icloudChecks = 0;
|
||||||
|
let resolved = false;
|
||||||
|
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
|
ensureIcloudMailSession: async () => {
|
||||||
|
icloudChecks += 1;
|
||||||
|
},
|
||||||
|
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page', displayedEmail: '' }),
|
||||||
|
rerunStep7ForStep8Recovery: async () => {},
|
||||||
|
getOAuthFlowRemainingMs: async () => 8000,
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => Math.min(defaultTimeoutMs, 8000),
|
||||||
|
getMailConfig: () => ({
|
||||||
|
source: 'icloud-mail',
|
||||||
|
url: 'https://www.icloud.com/mail/',
|
||||||
|
label: 'iCloud 邮箱',
|
||||||
|
navigateOnReuse: true,
|
||||||
|
}),
|
||||||
|
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||||
|
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
isVerificationMailPollingError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
resolveVerificationStep: async () => {
|
||||||
|
resolved = true;
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||||
|
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep8({
|
||||||
|
email: 'user@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(icloudChecks, 1);
|
||||||
|
assert.equal(resolved, true);
|
||||||
|
});
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ function fillInput(el, value) {
|
|||||||
filledValues.push(value);
|
filledValues.push(value);
|
||||||
}
|
}
|
||||||
async function sleep() {}
|
async function sleep() {}
|
||||||
|
function isStep5Ready() { return false; }
|
||||||
|
function isStep8Ready() { return false; }
|
||||||
|
function isAddPhonePageReady() { return false; }
|
||||||
function isVisibleElement() { return true; }
|
function isVisibleElement() { return true; }
|
||||||
function isActionEnabled(el) { return Boolean(el) && !el.disabled; }
|
function isActionEnabled(el) { return Boolean(el) && !el.disabled; }
|
||||||
function getActionText(el) { return el.textContent || ''; }
|
function getActionText(el) { return el.textContent || ''; }
|
||||||
|
|||||||
@@ -758,3 +758,165 @@ test('verification flow uses configured login resend count for step 8', async ()
|
|||||||
assert.deepStrictEqual(resendSteps, [8, 8]);
|
assert.deepStrictEqual(resendSteps, [8, 8]);
|
||||||
assert.equal(pollCalls, 3);
|
assert.equal(pollCalls, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('verification flow waits during resend cooldown instead of tight-looping', async () => {
|
||||||
|
const sleepCalls = [];
|
||||||
|
let pollCalls = 0;
|
||||||
|
|
||||||
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||||
|
getHotmailVerificationPollConfig: () => ({}),
|
||||||
|
getHotmailVerificationRequestTimestamp: () => 0,
|
||||||
|
getState: async () => ({}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isStopError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||||
|
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||||
|
pollHotmailVerificationCode: async () => ({}),
|
||||||
|
pollLuckmailVerificationCode: async () => ({}),
|
||||||
|
sendToContentScript: async () => ({}),
|
||||||
|
sendToMailContentScriptResilient: async (_mail, message) => {
|
||||||
|
if (message.type !== 'POLL_EMAIL') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
pollCalls += 1;
|
||||||
|
return pollCalls === 1
|
||||||
|
? {}
|
||||||
|
: { code: '654321', emailTimestamp: 123 };
|
||||||
|
},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
sleepWithStop: async (ms) => {
|
||||||
|
sleepCalls.push(ms);
|
||||||
|
},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await helpers.pollFreshVerificationCodeWithResendInterval(
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
email: 'user@example.com',
|
||||||
|
lastSignupCode: null,
|
||||||
|
},
|
||||||
|
{ provider: 'qq', label: 'QQ 邮箱' },
|
||||||
|
{
|
||||||
|
maxResendRequests: 0,
|
||||||
|
resendIntervalMs: 25000,
|
||||||
|
lastResendAt: Date.now(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(result.code, '654321');
|
||||||
|
assert.equal(pollCalls, 2);
|
||||||
|
assert.ok(sleepCalls.length >= 1);
|
||||||
|
assert.ok(sleepCalls[0] >= 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verification flow uses resilient signup-page transport when submitting verification code', async () => {
|
||||||
|
const resilientCalls = [];
|
||||||
|
|
||||||
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||||
|
getHotmailVerificationPollConfig: () => ({}),
|
||||||
|
getHotmailVerificationRequestTimestamp: () => 0,
|
||||||
|
getState: async () => ({}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isStopError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||||
|
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||||
|
pollHotmailVerificationCode: async () => ({}),
|
||||||
|
pollLuckmailVerificationCode: async () => ({}),
|
||||||
|
sendToContentScript: async () => {
|
||||||
|
throw new Error('should not use non-resilient channel');
|
||||||
|
},
|
||||||
|
sendToContentScriptResilient: async (_source, message, options) => {
|
||||||
|
resilientCalls.push({ message, options });
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
sendToMailContentScriptResilient: async () => ({}),
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await helpers.submitVerificationCode(4, '654321');
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { success: true });
|
||||||
|
assert.equal(resilientCalls.length, 1);
|
||||||
|
assert.equal(resilientCalls[0].message.type, 'FILL_CODE');
|
||||||
|
assert.equal(resilientCalls[0].message.payload.code, '654321');
|
||||||
|
assert.ok(resilientCalls[0].options.timeoutMs >= 30000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verification flow treats retryable submit transport failure as success when step 4 already redirected to logged-in home', async () => {
|
||||||
|
const logs = [];
|
||||||
|
|
||||||
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
|
addLog: async (message, level = 'info') => {
|
||||||
|
logs.push({ message, level });
|
||||||
|
},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
get: async () => ({ url: 'https://chatgpt.com/' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||||
|
getHotmailVerificationPollConfig: () => ({}),
|
||||||
|
getHotmailVerificationRequestTimestamp: () => 0,
|
||||||
|
getState: async () => ({}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isRetryableContentScriptTransportError: (error) => /message channel is closed/i.test(String(error?.message || error || '')),
|
||||||
|
isStopError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||||
|
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||||
|
pollHotmailVerificationCode: async () => ({}),
|
||||||
|
pollLuckmailVerificationCode: async () => ({}),
|
||||||
|
sendToContentScript: async () => {
|
||||||
|
throw new Error('should not use non-resilient channel');
|
||||||
|
},
|
||||||
|
sendToContentScriptResilient: async () => {
|
||||||
|
throw new Error('The page keeping the extension port is moved into back/forward cache, so the message channel is closed.');
|
||||||
|
},
|
||||||
|
sendToMailContentScriptResilient: async () => ({}),
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await helpers.submitVerificationCode(4, '654321');
|
||||||
|
|
||||||
|
assert.equal(result.success, true);
|
||||||
|
assert.equal(result.skipProfileStep, true);
|
||||||
|
assert.equal(result.assumed, true);
|
||||||
|
assert.equal(result.transportRecovered, true);
|
||||||
|
assert.equal(logs.some(({ message }) => /验证码提交后页面已切换到ChatGPT 已登录首页/.test(message)), true);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user