963 lines
40 KiB
JavaScript
963 lines
40 KiB
JavaScript
(function attachBackgroundStep8(root, factory) {
|
||
root.MultiPageBackgroundStep8 = factory();
|
||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
|
||
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
|
||
|
||
function createStep8Executor(deps = {}) {
|
||
const {
|
||
addLog: rawAddLog = async () => {},
|
||
chrome,
|
||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||
CLOUD_MAIL_PROVIDER = 'cloudmail',
|
||
completeNodeFromBackground,
|
||
confirmCustomVerificationStepBypass,
|
||
ensureMail2925MailboxSession,
|
||
ensureIcloudMailSession,
|
||
ensureStep8VerificationPageReady,
|
||
getOAuthFlowRemainingMs,
|
||
getOAuthFlowStepTimeoutMs,
|
||
getMailConfig,
|
||
getState,
|
||
getTabId,
|
||
HOTMAIL_PROVIDER,
|
||
isTabAlive,
|
||
isVerificationMailPollingError,
|
||
LUCKMAIL_PROVIDER,
|
||
resolveSignupEmailForFlow,
|
||
resolveVerificationStep,
|
||
rerunStep7ForStep8Recovery,
|
||
reuseOrCreateTab,
|
||
sendToContentScriptResilient,
|
||
persistRegistrationEmailState = null,
|
||
phoneVerificationHelpers = null,
|
||
setState,
|
||
shouldUseCustomRegistrationEmail,
|
||
sleepWithStop,
|
||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS,
|
||
throwIfStopped,
|
||
} = deps;
|
||
let activeFetchLoginCodeStep = null;
|
||
let activeFetchLoginCodeStepKey = 'fetch-login-code';
|
||
|
||
function normalizeLogStep(value) {
|
||
const step = Math.floor(Number(value) || 0);
|
||
return step > 0 ? step : null;
|
||
}
|
||
|
||
function normalizeStepLogMessage(message) {
|
||
return String(message || '')
|
||
.replace(/^步骤\s*\d+\s*[::]\s*/, '')
|
||
.replace(/^Step\s+\d+\s*[::]\s*/i, '')
|
||
.trim();
|
||
}
|
||
|
||
function addLog(message, level = 'info', options = {}) {
|
||
const normalizedOptions = options && typeof options === 'object' ? { ...options } : {};
|
||
const step = normalizeLogStep(normalizedOptions.step || normalizedOptions.visibleStep)
|
||
|| normalizeLogStep(activeFetchLoginCodeStep);
|
||
if (step) {
|
||
normalizedOptions.step = step;
|
||
if (!normalizedOptions.stepKey) {
|
||
normalizedOptions.stepKey = activeFetchLoginCodeStepKey || 'fetch-login-code';
|
||
}
|
||
}
|
||
delete normalizedOptions.visibleStep;
|
||
return rawAddLog(normalizeStepLogMessage(message), level, normalizedOptions);
|
||
}
|
||
|
||
function getVisibleStep(state, fallback = 8) {
|
||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||
return visibleStep > 0 ? visibleStep : fallback;
|
||
}
|
||
|
||
function normalizeSignupMethod(value = '') {
|
||
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
|
||
}
|
||
|
||
function normalizeIdentifierType(value = '') {
|
||
const normalized = String(value || '').trim().toLowerCase();
|
||
return normalized === 'phone' || normalized === 'email' ? normalized : '';
|
||
}
|
||
|
||
function isPhoneLoginCodeMode(state = {}) {
|
||
if (normalizeIdentifierType(state?.accountIdentifierType) === 'phone') {
|
||
return true;
|
||
}
|
||
return normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod) === 'phone'
|
||
&& Boolean(
|
||
String(state?.signupPhoneNumber || '').trim()
|
||
|| String(state?.signupPhoneCompletedActivation?.phoneNumber || '').trim()
|
||
|| String(state?.signupPhoneActivation?.phoneNumber || '').trim()
|
||
);
|
||
}
|
||
|
||
function getAuthLoginStepForVisibleStep(visibleStep) {
|
||
return visibleStep >= 11 ? 10 : 7;
|
||
}
|
||
|
||
async function getStep8ReadyTimeoutMs(actionLabel, expectedOauthUrl = '', visibleStep = 8) {
|
||
if (typeof getOAuthFlowStepTimeoutMs !== 'function') {
|
||
return 15000;
|
||
}
|
||
|
||
return getOAuthFlowStepTimeoutMs(15000, {
|
||
step: visibleStep,
|
||
actionLabel,
|
||
oauthUrl: expectedOauthUrl,
|
||
});
|
||
}
|
||
|
||
function getStep8RemainingTimeResolver(expectedOauthUrl = '', visibleStep = 8) {
|
||
if (typeof getOAuthFlowRemainingMs !== 'function') {
|
||
return undefined;
|
||
}
|
||
|
||
return async (details = {}) => getOAuthFlowRemainingMs({
|
||
step: visibleStep,
|
||
actionLabel: details.actionLabel || '登录验证码流程',
|
||
oauthUrl: expectedOauthUrl,
|
||
});
|
||
}
|
||
|
||
function normalizeStep8VerificationTargetEmail(value) {
|
||
return String(value || '').trim().toLowerCase();
|
||
}
|
||
|
||
function resolveBoundEmailLoginTarget(state = {}, visibleStep = 0) {
|
||
const email = String(
|
||
state?.step8VerificationTargetEmail
|
||
|| state?.email
|
||
|| state?.registrationEmailState?.current
|
||
|| ''
|
||
).trim();
|
||
if (!email) {
|
||
throw new Error(`步骤 ${visibleStep || 0}:缺少绑定邮箱,无法使用邮箱模式重新发起 OAuth 登录。`);
|
||
}
|
||
return email;
|
||
}
|
||
|
||
function buildBoundEmailLoginState(state = {}, visibleStep = 0) {
|
||
const email = resolveBoundEmailLoginTarget(state, visibleStep);
|
||
return {
|
||
...state,
|
||
forceLoginIdentifierType: 'email',
|
||
forceEmailLogin: true,
|
||
signupMethod: 'email',
|
||
resolvedSignupMethod: 'email',
|
||
accountIdentifierType: 'email',
|
||
accountIdentifier: email,
|
||
email,
|
||
step8VerificationTargetEmail: normalizeStep8VerificationTargetEmail(email),
|
||
};
|
||
}
|
||
|
||
async function getLoginAuthStateFromContent(visibleStep, options = {}) {
|
||
if (typeof sendToContentScriptResilient !== 'function') {
|
||
return {};
|
||
}
|
||
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 15000);
|
||
const result = await sendToContentScriptResilient(
|
||
'openai-auth',
|
||
{
|
||
type: 'GET_LOGIN_AUTH_STATE',
|
||
source: 'background',
|
||
payload: {},
|
||
},
|
||
{
|
||
timeoutMs,
|
||
responseTimeoutMs: timeoutMs,
|
||
retryDelayMs: 600,
|
||
logMessage: options.logMessage || `步骤 ${visibleStep}:认证页正在切换,等待页面重新就绪...`,
|
||
logStep: visibleStep,
|
||
logStepKey: options.logStepKey || activeFetchLoginCodeStepKey || 'fetch-login-code',
|
||
}
|
||
);
|
||
if (result?.error) {
|
||
throw new Error(result.error);
|
||
}
|
||
return result || {};
|
||
}
|
||
|
||
async function submitAddEmailIfNeeded(state, visibleStep, initialPageState = null) {
|
||
if (typeof resolveSignupEmailForFlow !== 'function' || typeof sendToContentScriptResilient !== 'function') {
|
||
return { state, pageState: initialPageState };
|
||
}
|
||
|
||
const pageState = initialPageState?.state
|
||
? initialPageState
|
||
: await getLoginAuthStateFromContent(visibleStep, {
|
||
timeoutMs: 15000,
|
||
logMessage: `步骤 ${visibleStep}:正在确认是否已进入添加邮箱页...`,
|
||
});
|
||
if (pageState?.state !== 'add_email_page') {
|
||
return { state, pageState };
|
||
}
|
||
|
||
const latestState = typeof getState === 'function' ? await getState() : state;
|
||
const resolvedEmail = await resolveSignupEmailForFlow(latestState, {
|
||
preserveAccountIdentity: true,
|
||
});
|
||
await addLog(`步骤 ${visibleStep}:检测到添加邮箱页,正在添加邮箱 ${resolvedEmail} 并进入邮箱验证码页...`);
|
||
|
||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(60000, {
|
||
step: visibleStep,
|
||
actionLabel: '添加邮箱并进入验证码页',
|
||
oauthUrl: latestState?.oauthUrl || state?.oauthUrl || '',
|
||
})
|
||
: 60000;
|
||
const result = await sendToContentScriptResilient(
|
||
'openai-auth',
|
||
{
|
||
type: 'SUBMIT_ADD_EMAIL',
|
||
source: 'background',
|
||
payload: {
|
||
email: resolvedEmail,
|
||
nodeId: state?.nodeId || activeFetchLoginCodeStepKey || 'fetch-login-code',
|
||
},
|
||
},
|
||
{
|
||
timeoutMs,
|
||
responseTimeoutMs: timeoutMs,
|
||
retryDelayMs: 700,
|
||
logMessage: `步骤 ${visibleStep}:添加邮箱页面正在切换,等待邮箱验证码页就绪...`,
|
||
logStep: visibleStep,
|
||
logStepKey: activeFetchLoginCodeStepKey || 'fetch-login-code',
|
||
}
|
||
);
|
||
|
||
if (result?.error) {
|
||
throw new Error(result.error);
|
||
}
|
||
|
||
const displayedEmail = normalizeStep8VerificationTargetEmail(result?.displayedEmail || resolvedEmail);
|
||
let persistedState = latestState;
|
||
if (typeof persistRegistrationEmailState === 'function') {
|
||
await persistRegistrationEmailState(latestState, resolvedEmail, {
|
||
source: activeFetchLoginCodeStepKey === 'bind-email' ? 'bind_email' : 'step8_add_email',
|
||
preserveAccountIdentity: true,
|
||
});
|
||
persistedState = typeof getState === 'function' ? await getState() : latestState;
|
||
} else {
|
||
await setState({
|
||
email: resolvedEmail,
|
||
step8VerificationTargetEmail: displayedEmail,
|
||
});
|
||
persistedState = {
|
||
...latestState,
|
||
email: resolvedEmail,
|
||
step8VerificationTargetEmail: displayedEmail,
|
||
};
|
||
}
|
||
|
||
return {
|
||
state: {
|
||
...persistedState,
|
||
email: resolvedEmail,
|
||
step8VerificationTargetEmail: displayedEmail,
|
||
},
|
||
pageState: {
|
||
state: result?.directOAuthConsentPage ? 'oauth_consent_page' : 'verification_page',
|
||
displayedEmail,
|
||
url: result?.url || pageState?.url || '',
|
||
},
|
||
};
|
||
}
|
||
|
||
async function completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, options = {}) {
|
||
await setState({
|
||
step8VerificationTargetEmail: '',
|
||
loginVerificationRequestedAt: null,
|
||
});
|
||
const fromRecovery = Boolean(options.fromRecovery);
|
||
const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code';
|
||
await addLog(
|
||
`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页${fromRecovery ? '(轮询失败后复核)' : ''},跳过登录验证码拉取并继续后续流程。`,
|
||
'warn',
|
||
{ step: visibleStep, stepKey }
|
||
);
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
|
||
loginVerificationRequestedAt: null,
|
||
skipLoginVerificationStep: true,
|
||
directOAuthConsentPage: true,
|
||
});
|
||
}
|
||
}
|
||
|
||
async function completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState = {}, options = {}) {
|
||
await setState({
|
||
step8VerificationTargetEmail: '',
|
||
loginVerificationRequestedAt: null,
|
||
});
|
||
const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code';
|
||
await addLog(
|
||
`步骤 ${visibleStep}:当前认证页已进入手机号验证流程,跳过登录邮箱验证码,交给后续“手机号验证”步骤处理。`,
|
||
'warn',
|
||
{ step: visibleStep, stepKey }
|
||
);
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
|
||
loginVerificationRequestedAt: null,
|
||
skipLoginVerificationStep: true,
|
||
addPhonePage: pageState?.state === 'add_phone_page' || Boolean(pageState?.addPhonePage),
|
||
phoneVerificationPage: pageState?.state === 'phone_verification_page' || Boolean(pageState?.phoneVerificationPage),
|
||
});
|
||
}
|
||
}
|
||
|
||
async function completeStep8WhenDeferredToBindEmail(visibleStep, options = {}) {
|
||
await setState({
|
||
step8VerificationTargetEmail: '',
|
||
loginVerificationRequestedAt: null,
|
||
});
|
||
await addLog(
|
||
`步骤 ${visibleStep}:当前认证页已进入添加邮箱页,跳过登录短信验证码,交给后续“绑定邮箱”步骤处理。`,
|
||
'warn'
|
||
);
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
|
||
loginVerificationRequestedAt: null,
|
||
skipLoginVerificationStep: true,
|
||
addEmailPage: true,
|
||
});
|
||
}
|
||
}
|
||
|
||
function isStep8AddPhoneStateError(error) {
|
||
const message = String(error?.message || error || '');
|
||
return /add-phone|手机号页面|手机号验证页|phone[\s-_]verification|phone\s+number/i.test(message);
|
||
}
|
||
|
||
async function recoverStep8PollingFailure(currentState, visibleStep) {
|
||
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
|
||
try {
|
||
const pageState = await ensureStep8VerificationPageReady({
|
||
visibleStep,
|
||
authLoginStep,
|
||
allowPhoneVerificationPage: true,
|
||
allowAddEmailPage: true,
|
||
timeoutMs: await getStep8ReadyTimeoutMs(
|
||
'登录验证码轮询异常后复核认证页状态',
|
||
currentState?.oauthUrl || '',
|
||
visibleStep
|
||
),
|
||
});
|
||
if (pageState?.state === 'oauth_consent_page') {
|
||
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { fromRecovery: true, nodeId: currentState?.nodeId });
|
||
return { outcome: 'completed' };
|
||
}
|
||
if (pageState?.state === 'verification_page' || pageState?.state === 'phone_verification_page' || pageState?.state === 'add_email_page') {
|
||
await addLog(
|
||
`步骤 ${visibleStep}:检测到邮箱轮询/页面通信异常,但认证页仍在当前登录后续页面,先在当前链路重试,不回到步骤 ${authLoginStep}。`,
|
||
'warn'
|
||
);
|
||
return { outcome: 'retry_without_step7' };
|
||
}
|
||
} catch (inspectError) {
|
||
if (isStep8RestartStep7Error(inspectError)) {
|
||
return { outcome: 'restart_step7', error: inspectError };
|
||
}
|
||
if (isStep8AddPhoneStateError(inspectError)) {
|
||
throw inspectError;
|
||
}
|
||
await addLog(
|
||
`步骤 ${visibleStep}:轮询失败后复核认证页状态异常:${inspectError?.message || inspectError},将回到步骤 ${authLoginStep} 重试。`,
|
||
'warn'
|
||
);
|
||
}
|
||
return { outcome: 'restart_step7' };
|
||
}
|
||
|
||
function getExpectedMail2925MailboxEmail(state = {}) {
|
||
if (Boolean(state?.mail2925UseAccountPool)) {
|
||
const currentAccountId = String(state?.currentMail2925AccountId || '').trim();
|
||
const accounts = Array.isArray(state?.mail2925Accounts) ? state.mail2925Accounts : [];
|
||
const currentAccount = accounts.find((account) => String(account?.id || '') === currentAccountId) || null;
|
||
const accountEmail = String(currentAccount?.email || '').trim().toLowerCase();
|
||
if (accountEmail) {
|
||
return accountEmail;
|
||
}
|
||
}
|
||
|
||
return String(state?.mail2925BaseEmail || '').trim().toLowerCase();
|
||
}
|
||
|
||
async function focusOrOpenMailTab(mail) {
|
||
const alive = await isTabAlive(mail.source);
|
||
if (alive) {
|
||
if (mail.navigateOnReuse) {
|
||
await reuseOrCreateTab(mail.source, mail.url, {
|
||
inject: mail.inject,
|
||
injectSource: mail.injectSource,
|
||
});
|
||
return;
|
||
}
|
||
|
||
const tabId = await getTabId(mail.source);
|
||
await chrome.tabs.update(tabId, { active: true });
|
||
return;
|
||
}
|
||
|
||
await reuseOrCreateTab(mail.source, mail.url, {
|
||
inject: mail.inject,
|
||
injectSource: mail.injectSource,
|
||
});
|
||
}
|
||
|
||
function getStep8ResendIntervalMs(state = {}) {
|
||
const mail = getMailConfig(state);
|
||
if (mail?.provider === LUCKMAIL_PROVIDER) {
|
||
return 15000;
|
||
}
|
||
if (mail?.provider === HOTMAIL_PROVIDER || mail?.provider === '2925') {
|
||
return 0;
|
||
}
|
||
return Math.max(0, Number(STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS) || 0);
|
||
}
|
||
|
||
async function executeLoginPhoneCodeStep(state, signupTabId, visibleStep) {
|
||
if (!Number.isInteger(signupTabId)) {
|
||
throw new Error(`步骤 ${visibleStep}:认证页面标签页已关闭,无法继续手机号登录验证码流程。`);
|
||
}
|
||
if (typeof phoneVerificationHelpers?.completeLoginPhoneVerificationFlow !== 'function') {
|
||
throw new Error(`步骤 ${visibleStep}:手机号登录验证码流程不可用,接码模块尚未初始化。`);
|
||
}
|
||
|
||
const result = await phoneVerificationHelpers.completeLoginPhoneVerificationFlow(signupTabId, {
|
||
state,
|
||
visibleStep,
|
||
});
|
||
|
||
await completeNodeFromBackground(state?.nodeId || 'fetch-login-code', {
|
||
phoneVerification: true,
|
||
loginPhoneVerification: true,
|
||
code: result?.code || '',
|
||
});
|
||
return result || {};
|
||
}
|
||
|
||
async function ensureAuthTabForPostLoginStep(state, visibleStep) {
|
||
const authTabId = await getTabId('openai-auth');
|
||
if (authTabId) {
|
||
await chrome.tabs.update(authTabId, { active: true });
|
||
return authTabId;
|
||
}
|
||
if (!state?.oauthUrl) {
|
||
throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成刷新 OAuth 并登录。`);
|
||
}
|
||
return reuseOrCreateTab('openai-auth', state.oauthUrl);
|
||
}
|
||
|
||
async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) {
|
||
const stepKey = options.stepKey || 'post-login-phone-verification';
|
||
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过手机号验证步骤。`, 'warn', {
|
||
step: visibleStep,
|
||
stepKey,
|
||
});
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(options.nodeId || 'post-login-phone-verification', {
|
||
directOAuthConsentPage: true,
|
||
phoneVerification: false,
|
||
});
|
||
}
|
||
}
|
||
|
||
async function executePostLoginPhoneVerification(state, runtime = {}) {
|
||
const visibleStep = getVisibleStep(state, 9);
|
||
activeFetchLoginCodeStep = visibleStep;
|
||
activeFetchLoginCodeStepKey = runtime.stepKey || 'post-login-phone-verification';
|
||
const authTabId = await ensureAuthTabForPostLoginStep(state, visibleStep);
|
||
const pageState = await getLoginAuthStateFromContent(visibleStep, {
|
||
timeoutMs: await getStep8ReadyTimeoutMs('确认手机号验证页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
|
||
logMessage: `步骤 ${visibleStep}:正在确认是否需要手机号验证...`,
|
||
logStepKey: activeFetchLoginCodeStepKey,
|
||
});
|
||
|
||
if (pageState?.state === 'oauth_consent_page') {
|
||
await completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, {
|
||
nodeId: state?.nodeId || runtime.fallbackNodeId,
|
||
stepKey: activeFetchLoginCodeStepKey,
|
||
});
|
||
return;
|
||
}
|
||
if (pageState?.state !== 'add_phone_page' && pageState?.state !== 'phone_verification_page') {
|
||
throw new Error(`步骤 ${visibleStep}:手机号验证步骤只处理添加手机号页或手机验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
if (!state?.phoneVerificationEnabled) {
|
||
throw new Error(`步骤 ${visibleStep}:检测到需要手机号验证,但手机接码未开启。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
if (typeof phoneVerificationHelpers?.completePhoneVerificationFlow !== 'function') {
|
||
throw new Error(`步骤 ${visibleStep}:手机号验证流程不可用,接码模块尚未初始化。`);
|
||
}
|
||
|
||
const result = await phoneVerificationHelpers.completePhoneVerificationFlow(authTabId, pageState, {
|
||
step: visibleStep,
|
||
visibleStep,
|
||
});
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(state?.nodeId || runtime.fallbackNodeId || 'post-login-phone-verification', {
|
||
phoneVerification: true,
|
||
postLoginPhoneVerification: true,
|
||
code: result?.code || '',
|
||
});
|
||
}
|
||
return result || {};
|
||
}
|
||
|
||
async function executeBindEmail(state) {
|
||
const visibleStep = getVisibleStep(state, 9);
|
||
activeFetchLoginCodeStep = visibleStep;
|
||
activeFetchLoginCodeStepKey = 'bind-email';
|
||
await ensureAuthTabForPostLoginStep(state, visibleStep);
|
||
const pageState = await getLoginAuthStateFromContent(visibleStep, {
|
||
timeoutMs: await getStep8ReadyTimeoutMs('确认添加邮箱页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
|
||
logMessage: `步骤 ${visibleStep}:正在确认是否需要绑定邮箱...`,
|
||
});
|
||
|
||
if (pageState?.state === 'oauth_consent_page') {
|
||
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱步骤。`, 'warn', {
|
||
step: visibleStep,
|
||
stepKey: 'bind-email',
|
||
});
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
|
||
directOAuthConsentPage: true,
|
||
bindEmailSubmitted: false,
|
||
});
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (pageState?.state !== 'add_email_page') {
|
||
throw new Error(`步骤 ${visibleStep}:绑定邮箱步骤只处理添加邮箱页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
|
||
const addEmailPreparation = await submitAddEmailIfNeeded(state, visibleStep, pageState);
|
||
const preparedState = addEmailPreparation?.state || state;
|
||
const nextPageState = addEmailPreparation?.pageState || pageState;
|
||
if (nextPageState?.state !== 'verification_page') {
|
||
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后必须进入邮箱验证码页,当前状态:${nextPageState?.state || 'unknown'}。URL: ${nextPageState?.url || ''}`.trim());
|
||
}
|
||
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
|
||
bindEmailSubmitted: true,
|
||
email: preparedState?.email || '',
|
||
step8VerificationTargetEmail: preparedState?.step8VerificationTargetEmail || nextPageState?.displayedEmail || '',
|
||
});
|
||
}
|
||
}
|
||
|
||
async function pollEmailVerificationCode(preparedState, pageState, visibleStep, runtime = {}) {
|
||
let latestResendAt = Math.max(
|
||
0,
|
||
Number(runtime?.stickyLastResendAt) || 0,
|
||
Number(preparedState?.loginVerificationRequestedAt) || 0
|
||
);
|
||
const notifyResendRequestedAt = typeof runtime?.onResendRequestedAt === 'function'
|
||
? runtime.onResendRequestedAt
|
||
: null;
|
||
const mail = getMailConfig(preparedState);
|
||
if (mail.error) throw new Error(mail.error);
|
||
const stepStartedAt = Date.now();
|
||
const verificationFilterAfterTimestamp = mail.provider === '2925'
|
||
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||
: stepStartedAt;
|
||
const verificationSessionKey = `${visibleStep}:${stepStartedAt}`;
|
||
const shouldCompareVerificationEmail = mail.provider !== '2925';
|
||
const displayedVerificationEmail = shouldCompareVerificationEmail
|
||
? normalizeStep8VerificationTargetEmail(pageState?.displayedEmail)
|
||
: '';
|
||
const fixedTargetEmail = shouldCompareVerificationEmail
|
||
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(preparedState?.step8VerificationTargetEmail || preparedState?.email))
|
||
: '';
|
||
|
||
await setState({
|
||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||
});
|
||
|
||
await addLog(`步骤 ${visibleStep}:邮箱验证码页面已就绪,开始获取验证码。`, 'info');
|
||
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
|
||
await addLog(`步骤 ${visibleStep}:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
|
||
}
|
||
|
||
if (shouldUseCustomRegistrationEmail(preparedState)) {
|
||
await confirmCustomVerificationStepBypass(8, {
|
||
completionStep: visibleStep,
|
||
promptStep: visibleStep,
|
||
});
|
||
return { lastResendAt: latestResendAt };
|
||
}
|
||
|
||
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
|
||
await addLog(`步骤 ${visibleStep}:正在确认 iCloud 邮箱登录态...`, 'info');
|
||
await ensureIcloudMailSession({
|
||
state: preparedState,
|
||
step: 8,
|
||
actionLabel: `步骤 ${visibleStep}:确认 iCloud 邮箱登录态`,
|
||
});
|
||
}
|
||
|
||
throwIfStopped();
|
||
if (
|
||
mail.provider === HOTMAIL_PROVIDER
|
||
|| mail.provider === LUCKMAIL_PROVIDER
|
||
|| mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER
|
||
|| mail.provider === CLOUD_MAIL_PROVIDER
|
||
) {
|
||
await addLog(`步骤 ${visibleStep}:正在通过 ${mail.label} 轮询验证码...`);
|
||
} else {
|
||
await addLog(`步骤 ${visibleStep}:正在打开${mail.label}...`);
|
||
if (mail.provider === '2925' && typeof ensureMail2925MailboxSession === 'function') {
|
||
await ensureMail2925MailboxSession({
|
||
accountId: preparedState.currentMail2925AccountId || null,
|
||
forceRelogin: false,
|
||
allowLoginWhenOnLoginPage: Boolean(preparedState?.mail2925UseAccountPool),
|
||
expectedMailboxEmail: getExpectedMail2925MailboxEmail(preparedState),
|
||
actionLabel: `Step ${visibleStep}: ensure 2925 mailbox session`,
|
||
});
|
||
} else {
|
||
await focusOrOpenMailTab(mail);
|
||
}
|
||
if (mail.provider === '2925') {
|
||
await addLog(`步骤 ${visibleStep}:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info');
|
||
}
|
||
}
|
||
|
||
await resolveVerificationStep(8, {
|
||
...preparedState,
|
||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||
}, mail, {
|
||
completionStep: visibleStep,
|
||
filterAfterTimestamp: verificationFilterAfterTimestamp,
|
||
sessionKey: verificationSessionKey,
|
||
disableTimeBudgetCap: mail.provider === '2925',
|
||
getRemainingTimeMs: getStep8RemainingTimeResolver(preparedState?.oauthUrl || '', visibleStep),
|
||
requestFreshCodeFirst: false,
|
||
lastResendAt: latestResendAt,
|
||
onResendRequestedAt: async (requestedAt) => {
|
||
const numericRequestedAt = Number(requestedAt) || 0;
|
||
if (numericRequestedAt > 0) {
|
||
latestResendAt = Math.max(latestResendAt, numericRequestedAt);
|
||
}
|
||
if (notifyResendRequestedAt) {
|
||
await notifyResendRequestedAt(latestResendAt);
|
||
}
|
||
},
|
||
targetEmail: fixedTargetEmail,
|
||
maxResendRequests: mail.provider === '2925' ? 2 : undefined,
|
||
initialPollMaxAttempts: mail.provider === '2925' ? 5 : undefined,
|
||
pollAttemptPlan: mail.provider === '2925' ? [2, 3, 15] : undefined,
|
||
resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER
|
||
? 15000
|
||
: ((mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
||
? 0
|
||
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS),
|
||
});
|
||
return {
|
||
lastResendAt: latestResendAt,
|
||
};
|
||
}
|
||
|
||
async function completeFetchBindEmailCodeSkippedOnOauth(visibleStep, options = {}) {
|
||
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱验证码步骤。`, 'warn', {
|
||
step: visibleStep,
|
||
stepKey: 'fetch-bind-email-code',
|
||
});
|
||
if (typeof completeNodeFromBackground === 'function') {
|
||
await completeNodeFromBackground(options.nodeId || 'fetch-bind-email-code', {
|
||
directOAuthConsentPage: true,
|
||
bindEmailCodeSkipped: true,
|
||
});
|
||
}
|
||
}
|
||
|
||
async function executeFetchBindEmailCode(state) {
|
||
const visibleStep = getVisibleStep(state, 10);
|
||
activeFetchLoginCodeStep = visibleStep;
|
||
activeFetchLoginCodeStepKey = 'fetch-bind-email-code';
|
||
await ensureAuthTabForPostLoginStep(state, visibleStep);
|
||
const pageState = await getLoginAuthStateFromContent(visibleStep, {
|
||
timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱验证码页已就绪', state?.oauthUrl || '', visibleStep),
|
||
logMessage: `步骤 ${visibleStep}:正在确认绑定邮箱验证码页...`,
|
||
});
|
||
|
||
if (pageState?.state === 'oauth_consent_page') {
|
||
if (state?.bindEmailSubmitted) {
|
||
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后不应直接进入 OAuth 授权页,必须先完成邮箱验证码。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
await completeFetchBindEmailCodeSkippedOnOauth(visibleStep, { nodeId: state?.nodeId });
|
||
return;
|
||
}
|
||
if (pageState?.state !== 'verification_page') {
|
||
throw new Error(`步骤 ${visibleStep}:获取绑定邮箱验证码步骤只处理邮箱验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
if (!state?.bindEmailSubmitted) {
|
||
throw new Error(`步骤 ${visibleStep}:尚未完成绑定邮箱提交,不能直接获取绑定邮箱验证码。`);
|
||
}
|
||
|
||
return pollEmailVerificationCode(state, pageState, visibleStep, {
|
||
stickyLastResendAt: Number(state?.loginVerificationRequestedAt) || 0,
|
||
});
|
||
}
|
||
|
||
async function executeBoundEmailLoginCode(state) {
|
||
const visibleStep = getVisibleStep(state, 11);
|
||
activeFetchLoginCodeStep = visibleStep;
|
||
activeFetchLoginCodeStepKey = 'fetch-bound-email-login-code';
|
||
const preparedState = buildBoundEmailLoginState(state, visibleStep);
|
||
const authTabId = await getTabId('openai-auth');
|
||
|
||
if (authTabId) {
|
||
await chrome.tabs.update(authTabId, { active: true });
|
||
} else {
|
||
if (!preparedState.oauthUrl) {
|
||
throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成绑定邮箱后刷新 OAuth 并登录。`);
|
||
}
|
||
await reuseOrCreateTab('openai-auth', preparedState.oauthUrl);
|
||
}
|
||
|
||
throwIfStopped();
|
||
const pageState = await ensureStep8VerificationPageReady({
|
||
visibleStep,
|
||
authLoginStep: Math.max(1, visibleStep - 1),
|
||
allowPhoneVerificationPage: true,
|
||
allowAddEmailPage: false,
|
||
timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱登录验证码页已就绪', preparedState?.oauthUrl || '', visibleStep),
|
||
});
|
||
|
||
if (pageState?.state === 'oauth_consent_page') {
|
||
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, {
|
||
nodeId: state?.nodeId || 'fetch-bound-email-login-code',
|
||
stepKey: 'fetch-bound-email-login-code',
|
||
});
|
||
return;
|
||
}
|
||
if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') {
|
||
await completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState, {
|
||
nodeId: state?.nodeId || 'fetch-bound-email-login-code',
|
||
stepKey: 'fetch-bound-email-login-code',
|
||
});
|
||
return;
|
||
}
|
||
if (pageState?.state === 'add_email_page') {
|
||
throw new Error(`步骤 ${visibleStep}:绑定邮箱后邮箱模式登录不应再进入添加邮箱页。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
if (pageState?.state !== 'verification_page') {
|
||
throw new Error(`步骤 ${visibleStep}:绑定邮箱后获取登录验证码只处理邮箱登录验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
|
||
return pollEmailVerificationCode(preparedState, pageState, visibleStep, {
|
||
stickyLastResendAt: Number(preparedState?.loginVerificationRequestedAt) || 0,
|
||
});
|
||
}
|
||
|
||
async function executeBoundEmailPostLoginPhoneVerification(state) {
|
||
return executePostLoginPhoneVerification(state, {
|
||
stepKey: 'post-bound-email-phone-verification',
|
||
fallbackNodeId: 'post-bound-email-phone-verification',
|
||
});
|
||
}
|
||
|
||
async function runStep8Attempt(state, runtime = {}) {
|
||
const visibleStep = getVisibleStep(state, 8);
|
||
activeFetchLoginCodeStep = visibleStep;
|
||
activeFetchLoginCodeStepKey = 'fetch-login-code';
|
||
const authTabId = await getTabId('openai-auth');
|
||
|
||
if (authTabId) {
|
||
await chrome.tabs.update(authTabId, { active: true });
|
||
} else {
|
||
if (!state.oauthUrl) {
|
||
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`);
|
||
}
|
||
await reuseOrCreateTab('openai-auth', state.oauthUrl);
|
||
}
|
||
|
||
throwIfStopped();
|
||
let pageState = await ensureStep8VerificationPageReady({
|
||
visibleStep,
|
||
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
|
||
allowPhoneVerificationPage: true,
|
||
allowAddEmailPage: true,
|
||
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
|
||
});
|
||
if (pageState?.state === 'oauth_consent_page') {
|
||
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId });
|
||
return;
|
||
}
|
||
const phoneLoginCodeMode = isPhoneLoginCodeMode(state);
|
||
if (phoneLoginCodeMode) {
|
||
if (pageState?.state === 'phone_verification_page') {
|
||
return executeLoginPhoneCodeStep(state, authTabId, visibleStep);
|
||
}
|
||
if (pageState?.state === 'add_email_page') {
|
||
await completeStep8WhenDeferredToBindEmail(visibleStep, { nodeId: state?.nodeId });
|
||
return;
|
||
}
|
||
if (pageState?.state === 'verification_page') {
|
||
throw new Error(`步骤 ${visibleStep}:手机号注册模式只允许处理手机登录验证码,当前进入了普通邮箱登录验证码页,不会回落到邮箱 provider。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
if (pageState?.state === 'add_phone_page') {
|
||
throw new Error(`步骤 ${visibleStep}:手机号注册模式不应进入添加手机号页。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
throw new Error(`步骤 ${visibleStep}:手机号注册模式登录验证码步骤进入了不允许的页面:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
|
||
if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') {
|
||
await completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState, { nodeId: state?.nodeId });
|
||
return;
|
||
}
|
||
if (pageState?.state === 'add_email_page') {
|
||
throw new Error(`步骤 ${visibleStep}:邮箱注册模式不应进入添加邮箱页。URL: ${pageState?.url || ''}`.trim());
|
||
}
|
||
|
||
return pollEmailVerificationCode(state, pageState, visibleStep, runtime);
|
||
}
|
||
|
||
function isStep8RestartStep7Error(error) {
|
||
const message = String(error?.message || error || '');
|
||
return /STEP8_RESTART_STEP7::/i.test(message);
|
||
}
|
||
|
||
async function executeStep8(state) {
|
||
let currentState = state;
|
||
let mailPollingAttempt = 1;
|
||
let lastMailPollingError = null;
|
||
let stickyLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
|
||
let retryWithoutStep7Streak = 0;
|
||
const maxRetryWithoutStep7Streak = 3;
|
||
|
||
while (true) {
|
||
try {
|
||
const result = await runStep8Attempt(currentState, {
|
||
stickyLastResendAt,
|
||
onResendRequestedAt: async (requestedAt) => {
|
||
const numericRequestedAt = Number(requestedAt) || 0;
|
||
if (numericRequestedAt > 0) {
|
||
stickyLastResendAt = Math.max(stickyLastResendAt, numericRequestedAt);
|
||
}
|
||
},
|
||
});
|
||
if (Number(result?.lastResendAt) > 0) {
|
||
stickyLastResendAt = Math.max(stickyLastResendAt, Number(result.lastResendAt) || 0);
|
||
}
|
||
retryWithoutStep7Streak = 0;
|
||
return;
|
||
} catch (err) {
|
||
const visibleStep = getVisibleStep(currentState, 8);
|
||
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
|
||
let currentError = err;
|
||
let retryWithoutStep7 = false;
|
||
|
||
const isMailPollingError = isVerificationMailPollingError(err);
|
||
if (isMailPollingError && !isStep8RestartStep7Error(err)) {
|
||
const recovery = await recoverStep8PollingFailure(currentState, visibleStep);
|
||
if (recovery?.outcome === 'completed') {
|
||
return;
|
||
}
|
||
if (recovery?.outcome === 'retry_without_step7') {
|
||
retryWithoutStep7 = true;
|
||
}
|
||
if (recovery?.error) {
|
||
currentError = recovery.error;
|
||
}
|
||
}
|
||
if (!isVerificationMailPollingError(currentError) && !isStep8RestartStep7Error(currentError)) {
|
||
throw currentError;
|
||
}
|
||
|
||
lastMailPollingError = currentError;
|
||
if (mailPollingAttempt >= STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS) {
|
||
break;
|
||
}
|
||
|
||
mailPollingAttempt += 1;
|
||
if (retryWithoutStep7) {
|
||
retryWithoutStep7Streak += 1;
|
||
if (retryWithoutStep7Streak > maxRetryWithoutStep7Streak) {
|
||
await addLog(
|
||
`步骤 ${visibleStep}:邮箱通信异常在当前链路已连续重试 ${retryWithoutStep7Streak} 次,改为回到步骤 ${authLoginStep} 重新发起授权链路,避免空轮询循环。`,
|
||
'warn'
|
||
);
|
||
await rerunStep7ForStep8Recovery({
|
||
logMessage: `邮箱通信异常持续未恢复,正在回到步骤 ${authLoginStep} 重新发起登录流程...`,
|
||
logStep: visibleStep,
|
||
logStepKey: 'fetch-login-code',
|
||
});
|
||
currentState = await getState();
|
||
retryWithoutStep7Streak = 0;
|
||
continue;
|
||
}
|
||
await addLog(
|
||
`步骤 ${visibleStep}:认证页仍保持在验证码页,将在当前链路直接重试(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS}),不回到步骤 ${authLoginStep}(连续同链路重试 ${retryWithoutStep7Streak}/${maxRetryWithoutStep7Streak})。`,
|
||
'warn'
|
||
);
|
||
const latestState = await getState();
|
||
const latestStateResendAt = Number(latestState?.loginVerificationRequestedAt) || 0;
|
||
if (latestStateResendAt > 0) {
|
||
stickyLastResendAt = Math.max(stickyLastResendAt, latestStateResendAt);
|
||
}
|
||
currentState = latestState;
|
||
if (stickyLastResendAt > 0 && (!latestStateResendAt || latestStateResendAt < stickyLastResendAt)) {
|
||
currentState = {
|
||
...latestState,
|
||
loginVerificationRequestedAt: stickyLastResendAt,
|
||
};
|
||
}
|
||
const resendIntervalMs = getStep8ResendIntervalMs(currentState);
|
||
const remainingBeforeRetryMs = stickyLastResendAt > 0 && resendIntervalMs > 0
|
||
? Math.max(0, resendIntervalMs - (Date.now() - stickyLastResendAt))
|
||
: 0;
|
||
if (remainingBeforeRetryMs > 0 && typeof sleepWithStop === 'function') {
|
||
await addLog(
|
||
`步骤 ${visibleStep}:上轮已触发重发验证码,为避免重复重发,先等待 ${Math.ceil(remainingBeforeRetryMs / 1000)} 秒后继续当前链路重试。`,
|
||
'info'
|
||
);
|
||
await sleepWithStop(Math.min(remainingBeforeRetryMs, 3000));
|
||
}
|
||
continue;
|
||
}
|
||
retryWithoutStep7Streak = 0;
|
||
await addLog(
|
||
isStep8RestartStep7Error(currentError)
|
||
? `步骤 ${visibleStep}:检测到认证页进入重试/超时报错状态,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`
|
||
: `步骤 ${visibleStep}:检测到邮箱轮询类失败,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS})...`,
|
||
'warn'
|
||
);
|
||
await rerunStep7ForStep8Recovery({
|
||
logMessage: isStep8RestartStep7Error(currentError)
|
||
? `认证页进入重试/超时报错状态,正在回到步骤 ${authLoginStep} 重新发起登录流程...`
|
||
: `正在回到步骤 ${authLoginStep},重新发起登录验证码流程...`,
|
||
logStep: visibleStep,
|
||
logStepKey: 'fetch-login-code',
|
||
});
|
||
currentState = await getState();
|
||
}
|
||
}
|
||
|
||
const visibleStep = getVisibleStep(currentState, 8);
|
||
if (lastMailPollingError) {
|
||
throw new Error(
|
||
`步骤 ${visibleStep}:登录验证码流程在 ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} 轮邮箱轮询恢复后仍未成功。最后一次原因:${lastMailPollingError.message}`
|
||
);
|
||
}
|
||
|
||
throw new Error(`步骤 ${visibleStep}:登录验证码流程未成功完成。`);
|
||
}
|
||
|
||
return {
|
||
executeStep8,
|
||
executePostLoginPhoneVerification,
|
||
executeBindEmail,
|
||
executeFetchBindEmailCode,
|
||
executeBoundEmailLoginCode,
|
||
executeBoundEmailPostLoginPhoneVerification,
|
||
};
|
||
}
|
||
|
||
return { createStep8Executor };
|
||
});
|