diff --git a/background.js b/background.js index 6f5cc95..0f6fb20 100644 --- a/background.js +++ b/background.js @@ -73,6 +73,12 @@ const NORMAL_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.( plusModeEnabled: false, signupMethod: 'phone', }) || NORMAL_STEP_DEFINITIONS; +const NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: false, + signupMethod: 'phone', + phoneSignupReloginAfterBindEmailEnabled: true, +}) || NORMAL_PHONE_STEP_DEFINITIONS; const PLUS_PAYPAL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, plusModeEnabled: true, @@ -84,6 +90,13 @@ const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSte plusPaymentMethod: 'paypal', signupMethod: 'phone', }) || PLUS_PAYPAL_STEP_DEFINITIONS; +const PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: true, + plusPaymentMethod: 'paypal', + signupMethod: 'phone', + phoneSignupReloginAfterBindEmailEnabled: true, +}) || PLUS_PAYPAL_PHONE_STEP_DEFINITIONS; const PLUS_GOPAY_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, plusModeEnabled: true, @@ -95,6 +108,13 @@ const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getStep plusPaymentMethod: 'gopay', signupMethod: 'phone', }) || PLUS_GOPAY_STEP_DEFINITIONS; +const PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: true, + plusPaymentMethod: 'gopay', + signupMethod: 'phone', + phoneSignupReloginAfterBindEmailEnabled: true, +}) || PLUS_GOPAY_PHONE_STEP_DEFINITIONS; const PLUS_GPC_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, plusModeEnabled: true, @@ -106,18 +126,29 @@ const PLUS_GPC_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps? plusPaymentMethod: 'gpc-helper', signupMethod: 'phone', }) || PLUS_GPC_STEP_DEFINITIONS; +const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: true, + plusPaymentMethod: 'gpc-helper', + signupMethod: 'phone', + phoneSignupReloginAfterBindEmailEnabled: true, +}) || PLUS_GPC_PHONE_STEP_DEFINITIONS; const PLUS_STEP_DEFINITIONS = PLUS_PAYPAL_STEP_DEFINITIONS; const ALL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getAllSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, }) || [ ...NORMAL_STEP_DEFINITIONS, ...NORMAL_PHONE_STEP_DEFINITIONS, + ...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ...PLUS_PAYPAL_STEP_DEFINITIONS, ...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS, + ...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ...PLUS_GOPAY_STEP_DEFINITIONS, ...PLUS_GOPAY_PHONE_STEP_DEFINITIONS, + ...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ...PLUS_GPC_STEP_DEFINITIONS, ...PLUS_GPC_PHONE_STEP_DEFINITIONS, + ...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ]; const STEP_IDS = Array.from(new Set(ALL_STEP_DEFINITIONS .map((definition) => Number(definition?.id)) @@ -644,6 +675,7 @@ function getStepDefinitionsForState(state = {}) { plusModeEnabled: isPlusModeState(state), plusPaymentMethod: normalizePlusPaymentMethod(state?.plusPaymentMethod), signupMethod: getSignupMethodForStepDefinitions(state), + phoneSignupReloginAfterBindEmailEnabled: Boolean(state?.phoneSignupReloginAfterBindEmailEnabled), }); if (Array.isArray(definitions)) { return definitions; @@ -908,6 +940,7 @@ const PERSISTED_SETTING_DEFAULTS = { autoStepDelaySeconds: null, step6CookieCleanupEnabled: false, phoneVerificationEnabled: false, + phoneSignupReloginAfterBindEmailEnabled: false, phoneSmsReuseEnabled: DEFAULT_HERO_SMS_REUSE_ENABLED, freePhoneReuseEnabled: true, freePhoneReuseAutoEnabled: true, @@ -2799,6 +2832,7 @@ function normalizePersistentSettingValue(key, value) { return typeof value === 'boolean' ? value : true; case 'step6CookieCleanupEnabled': case 'phoneVerificationEnabled': + case 'phoneSignupReloginAfterBindEmailEnabled': case 'phoneSmsReuseEnabled': case 'freePhoneReuseEnabled': case 'freePhoneReuseAutoEnabled': @@ -8600,7 +8634,12 @@ function getDownstreamStateResets(step, state = {}) { currentPhoneVerificationCountdownWindowTotal: 0, }; } - if (stepKey === 'oauth-login' || stepKey === 'fetch-login-code') { + if ( + stepKey === 'oauth-login' + || stepKey === 'fetch-login-code' + || stepKey === 'relogin-bound-email' + || stepKey === 'fetch-bound-email-login-code' + ) { return { lastLoginCode: null, loginVerificationRequestedAt: null, @@ -9626,6 +9665,9 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([ 'post-login-phone-verification', 'bind-email', 'fetch-bind-email-code', + 'relogin-bound-email', + 'fetch-bound-email-login-code', + 'post-bound-email-phone-verification', 'confirm-oauth', ]); const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([ @@ -10081,6 +10123,9 @@ const AUTH_CHAIN_NODE_IDS = new Set([ 'post-login-phone-verification', 'bind-email', 'fetch-bind-email-code', + 'relogin-bound-email', + 'fetch-bound-email-login-code', + 'post-bound-email-phone-verification', 'confirm-oauth', 'platform-verify', ]); @@ -12394,6 +12439,39 @@ const step10Executor = self.MultiPageBackgroundStep10?.createStep10Executor({ DEFAULT_SUB2API_GROUP_NAME, SUB2API_STEP9_RESPONSE_TIMEOUT_MS, }); + +function resolveBoundEmailForReloginState(state = {}) { + return String( + state?.step8VerificationTargetEmail + || state?.email + || state?.registrationEmailState?.current + || '' + ).trim(); +} + +async function executeReloginBoundEmail(state = {}) { + const visibleStep = Math.floor(Number(state?.visibleStep) || 0) || 10; + const boundEmail = resolveBoundEmailForReloginState(state); + if (!boundEmail) { + throw new Error(`步骤 ${visibleStep}:缺少绑定邮箱,无法在绑定邮箱后切入邮箱模式 OAuth 登录。`); + } + await addLog(`步骤 ${visibleStep}:绑定邮箱已提交,正在刷新 OAuth 并使用绑定邮箱 ${boundEmail} 登录...`, 'info', { + step: visibleStep, + stepKey: 'relogin-bound-email', + }); + return step7Executor.executeStep7({ + ...state, + forceLoginIdentifierType: 'email', + forceEmailLogin: true, + signupMethod: 'email', + resolvedSignupMethod: 'email', + accountIdentifierType: 'email', + accountIdentifier: boundEmail, + email: boundEmail, + step8VerificationTargetEmail: boundEmail, + }); +} + const stepExecutorsByKey = { 'open-chatgpt': () => step1Executor.executeStep1(), 'submit-signup-email': (state) => step2Executor.executeStep2(state), @@ -12413,6 +12491,9 @@ const stepExecutorsByKey = { 'post-login-phone-verification': (state) => step8Executor.executePostLoginPhoneVerification(state), 'bind-email': (state) => step8Executor.executeBindEmail(state), 'fetch-bind-email-code': (state) => step8Executor.executeFetchBindEmailCode(state), + 'relogin-bound-email': (state) => executeReloginBoundEmail(state), + 'fetch-bound-email-login-code': (state) => step8Executor.executeBoundEmailLoginCode(state), + 'post-bound-email-phone-verification': (state) => step8Executor.executeBoundEmailPostLoginPhoneVerification(state), 'confirm-oauth': (state) => step9Executor.executeStep9(state), 'platform-verify': (state) => executeStep10(state), }; @@ -12590,12 +12671,16 @@ async function acquireTopLevelAuthChainExecution(step, state = {}) { const normalStepRegistry = buildStepRegistry(NORMAL_STEP_DEFINITIONS); const normalPhoneStepRegistry = buildStepRegistry(NORMAL_PHONE_STEP_DEFINITIONS); +const normalPhoneBoundEmailReloginStepRegistry = buildStepRegistry(NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS); const plusPayPalStepRegistry = buildStepRegistry(PLUS_PAYPAL_STEP_DEFINITIONS); const plusPayPalPhoneStepRegistry = buildStepRegistry(PLUS_PAYPAL_PHONE_STEP_DEFINITIONS); +const plusPayPalPhoneBoundEmailReloginStepRegistry = buildStepRegistry(PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS); const plusGoPayStepRegistry = buildStepRegistry(PLUS_GOPAY_STEP_DEFINITIONS); const plusGoPayPhoneStepRegistry = buildStepRegistry(PLUS_GOPAY_PHONE_STEP_DEFINITIONS); +const plusGoPayPhoneBoundEmailReloginStepRegistry = buildStepRegistry(PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS); const plusGpcStepRegistry = buildStepRegistry(PLUS_GPC_STEP_DEFINITIONS); const plusGpcPhoneStepRegistry = buildStepRegistry(PLUS_GPC_PHONE_STEP_DEFINITIONS); +const plusGpcPhoneBoundEmailReloginStepRegistry = buildStepRegistry(PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS); function getStepRegistryForState(state = {}) { const activeFlowId = String(state?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID; @@ -12603,17 +12688,31 @@ function getStepRegistryForState(state = {}) { throw new Error(`当前尚未注册 flow=${activeFlowId} 的步骤执行器。`); } const signupMethod = getSignupMethodForStepDefinitions(state); + const useBoundEmailRelogin = signupMethod === SIGNUP_METHOD_PHONE + && Boolean(state?.phoneSignupReloginAfterBindEmailEnabled); if (!isPlusModeState(state)) { - return signupMethod === SIGNUP_METHOD_PHONE ? normalPhoneStepRegistry : normalStepRegistry; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return useBoundEmailRelogin ? normalPhoneBoundEmailReloginStepRegistry : normalPhoneStepRegistry; + } + return normalStepRegistry; } const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod); if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) { - return signupMethod === SIGNUP_METHOD_PHONE ? plusGpcPhoneStepRegistry : plusGpcStepRegistry; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return useBoundEmailRelogin ? plusGpcPhoneBoundEmailReloginStepRegistry : plusGpcPhoneStepRegistry; + } + return plusGpcStepRegistry; } if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) { - return signupMethod === SIGNUP_METHOD_PHONE ? plusGoPayPhoneStepRegistry : plusGoPayStepRegistry; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return useBoundEmailRelogin ? plusGoPayPhoneBoundEmailReloginStepRegistry : plusGoPayPhoneStepRegistry; + } + return plusGoPayStepRegistry; } - return signupMethod === SIGNUP_METHOD_PHONE ? plusPayPalPhoneStepRegistry : plusPayPalStepRegistry; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return useBoundEmailRelogin ? plusPayPalPhoneBoundEmailReloginStepRegistry : plusPayPalPhoneStepRegistry; + } + return plusPayPalStepRegistry; } async function requestOAuthUrlFromPanel(state, options = {}) { @@ -13043,12 +13142,22 @@ async function getPostStep6AutoRestartDecision(step, error) { : (typeof LAST_STEP_ID === 'number' ? LAST_STEP_ID : 10); const currentNodeKey = resolveStepKey(normalizedStep, latestState); const confirmOauthStep = findStepIdByKeyForState('confirm-oauth', latestState); + const boundEmailReloginStep = findStepIdByKeyForState('relogin-bound-email', latestState); + const isBoundEmailReloginTailStep = [ + 'relogin-bound-email', + 'fetch-bound-email-login-code', + 'post-bound-email-phone-verification', + ].includes(currentNodeKey); const shouldRetryFromConfirmStep = currentNodeKey === 'platform-verify' && Number.isFinite(confirmOauthStep) && confirmOauthStep > 0 && confirmOauthStep < normalizedStep && isPlatformVerifyTransientRetryError(errorMessage); - const restartAnchorStep = shouldRetryFromConfirmStep ? confirmOauthStep : authChainStartStep; + const restartAnchorStep = shouldRetryFromConfirmStep + ? confirmOauthStep + : (isBoundEmailReloginTailStep && Number.isFinite(boundEmailReloginStep) && boundEmailReloginStep > 0 + ? boundEmailReloginStep + : authChainStartStep); if (isPhoneSmsPlatformRateLimitFailure(errorMessage)) { return { shouldRestart: false, diff --git a/background/message-router.js b/background/message-router.js index cec1633..87ff927 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -270,6 +270,8 @@ 13: 'confirm-oauth', 14: 'platform-verify', 15: 'platform-verify', + 16: 'confirm-oauth', + 17: 'platform-verify', }); function getStepKeyForState(step, state = {}) { @@ -613,12 +615,18 @@ const stateForStep = await getState(); const stepKey = getStepKeyForState(step, stateForStep); - if (stepKey === 'oauth-login') { - await syncStepAccountIdentityFromPayload(payload); + if (stepKey === 'oauth-login' || stepKey === 'relogin-bound-email') { + if (stepKey === 'oauth-login') { + await syncStepAccountIdentityFromPayload(payload); + } if (payload.skipLoginVerificationStep) { await setState({ loginVerificationRequestedAt: null }); const latestState = await getState(); - const loginCodeStep = findStepByKeyAfter(step, 'fetch-login-code', latestState); + const loginCodeStep = findStepByKeyAfter( + step, + stepKey === 'relogin-bound-email' ? 'fetch-bound-email-login-code' : 'fetch-login-code', + latestState + ); if (loginCodeStep) { const currentStatus = getNodeStatusByStep(loginCodeStep, latestState); if (!isStepProtectedFromAutoSkip(currentStatus)) { @@ -635,7 +643,7 @@ return; } - if (stepKey === 'fetch-login-code') { + if (stepKey === 'fetch-login-code' || stepKey === 'fetch-bound-email-login-code') { await setState({ ...(payload.phoneVerification || payload.loginPhoneVerification ? { currentPhoneVerificationCode: '', @@ -649,7 +657,7 @@ return; } - if (stepKey === 'post-login-phone-verification') { + if (stepKey === 'post-login-phone-verification' || stepKey === 'post-bound-email-phone-verification') { await setState({ currentPhoneVerificationCode: '', signupPhoneVerificationRequestedAt: null, @@ -1333,10 +1341,14 @@ const plusPaymentChanged = Object.prototype.hasOwnProperty.call(updates, 'plusPaymentMethod') && normalizePlusPaymentMethodForDisplay(currentState?.plusPaymentMethod || 'paypal') !== normalizePlusPaymentMethodForDisplay(updates.plusPaymentMethod || 'paypal'); + const phoneSignupReloginAfterBindEmailChanged = Object.prototype.hasOwnProperty.call(updates, 'phoneSignupReloginAfterBindEmailEnabled') + && Boolean(currentState?.phoneSignupReloginAfterBindEmailEnabled) !== Boolean(updates.phoneSignupReloginAfterBindEmailEnabled); const nextPlusModeEnabled = Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled') ? Boolean(updates.plusModeEnabled) : Boolean(currentState?.plusModeEnabled); - const stepModeChanged = modeChanged || (nextPlusModeEnabled && plusPaymentChanged); + const stepModeChanged = modeChanged + || (nextPlusModeEnabled && plusPaymentChanged) + || phoneSignupReloginAfterBindEmailChanged; const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled') && updates.oauthFlowTimeoutEnabled === false; await setPersistentSettings(updates); diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index cf41913..5ecd1e7 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -124,6 +124,34 @@ 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 {}; @@ -142,7 +170,7 @@ retryDelayMs: 600, logMessage: options.logMessage || `步骤 ${visibleStep}:认证页正在切换,等待页面重新就绪...`, logStep: visibleStep, - logStepKey: 'fetch-login-code', + logStepKey: options.logStepKey || activeFetchLoginCodeStepKey || 'fetch-login-code', } ); if (result?.error) { @@ -240,9 +268,11 @@ loginVerificationRequestedAt: null, }); const fromRecovery = Boolean(options.fromRecovery); + const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code'; await addLog( `步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页${fromRecovery ? '(轮询失败后复核)' : ''},跳过登录验证码拉取并继续后续流程。`, - 'warn' + 'warn', + { step: visibleStep, stepKey } ); if (typeof completeNodeFromBackground === 'function') { await completeNodeFromBackground(options.nodeId || 'fetch-login-code', { @@ -258,9 +288,11 @@ step8VerificationTargetEmail: '', loginVerificationRequestedAt: null, }); + const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code'; await addLog( `步骤 ${visibleStep}:当前认证页已进入手机号验证流程,跳过登录邮箱验证码,交给后续“手机号验证”步骤处理。`, - 'warn' + 'warn', + { step: visibleStep, stepKey } ); if (typeof completeNodeFromBackground === 'function') { await completeNodeFromBackground(options.nodeId || 'fetch-login-code', { @@ -416,9 +448,10 @@ } async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) { + const stepKey = options.stepKey || 'post-login-phone-verification'; await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过手机号验证步骤。`, 'warn', { step: visibleStep, - stepKey: 'post-login-phone-verification', + stepKey, }); if (typeof completeNodeFromBackground === 'function') { await completeNodeFromBackground(options.nodeId || 'post-login-phone-verification', { @@ -428,18 +461,22 @@ } } - async function executePostLoginPhoneVerification(state) { + async function executePostLoginPhoneVerification(state, runtime = {}) { const visibleStep = getVisibleStep(state, 9); activeFetchLoginCodeStep = visibleStep; - activeFetchLoginCodeStepKey = 'post-login-phone-verification'; + 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 }); + await completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, { + nodeId: state?.nodeId || runtime.fallbackNodeId, + stepKey: activeFetchLoginCodeStepKey, + }); return; } if (pageState?.state !== 'add_phone_page' && pageState?.state !== 'phone_verification_page') { @@ -457,7 +494,7 @@ visibleStep, }); if (typeof completeNodeFromBackground === 'function') { - await completeNodeFromBackground(state?.nodeId || 'post-login-phone-verification', { + await completeNodeFromBackground(state?.nodeId || runtime.fallbackNodeId || 'post-login-phone-verification', { phoneVerification: true, postLoginPhoneVerification: true, code: result?.code || '', @@ -663,6 +700,64 @@ }); } + 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('signup-page'); + + if (authTabId) { + await chrome.tabs.update(authTabId, { active: true }); + } else { + if (!preparedState.oauthUrl) { + throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成绑定邮箱后刷新 OAuth 并登录。`); + } + await reuseOrCreateTab('signup-page', 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; @@ -855,6 +950,8 @@ executePostLoginPhoneVerification, executeBindEmail, executeFetchBindEmailCode, + executeBoundEmailLoginCode, + executeBoundEmailPostLoginPhoneVerification, }; } diff --git a/background/steps/oauth-login.js b/background/steps/oauth-login.js index 7849966..5c2747d 100644 --- a/background/steps/oauth-login.js +++ b/background/steps/oauth-login.js @@ -50,6 +50,11 @@ return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'; } + function shouldForceStep7EmailLogin(state = {}) { + return normalizeStep7IdentifierType(state?.forceLoginIdentifierType) === 'email' + || Boolean(state?.forceEmailLogin); + } + function canUseConfiguredPhoneSignup(state = {}) { return normalizeStep7SignupMethod(state?.signupMethod) === 'phone' && Boolean(state?.phoneVerificationEnabled) @@ -75,6 +80,10 @@ } function resolveStep7LoginIdentifierType(state = {}, fallbackType = '') { + if (shouldForceStep7EmailLogin(state)) { + return 'email'; + } + if (shouldPreferStep7PhoneSignupIdentity(state)) { return 'phone'; } @@ -234,7 +243,19 @@ throwIfStopped(); attempt += 1; try { - const currentState = attempt === 1 ? state : await getState(); + const rawCurrentState = attempt === 1 ? state : await getState(); + const currentState = shouldForceStep7EmailLogin(state) + ? { + ...rawCurrentState, + forceLoginIdentifierType: 'email', + forceEmailLogin: true, + signupMethod: 'email', + resolvedSignupMethod: 'email', + accountIdentifierType: 'email', + accountIdentifier: email, + email, + } + : rawCurrentState; const password = currentState.password || currentState.customPassword || ''; const currentIdentifierType = resolveStep7LoginIdentifierType(currentState, resolvedIdentifierType); const currentPhoneNumber = currentIdentifierType === 'phone' diff --git a/content/signup-page.js b/content/signup-page.js index b9199ec..4eb0d34 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -91,7 +91,8 @@ function resolveCommandNodeId(message = {}) { if (visibleStep === 8 || visibleStep === 11) return 'fetch-login-code'; if (visibleStep === 9 || visibleStep === 12) return 'post-login-phone-verification'; if (visibleStep === 10 || visibleStep === 13) return 'confirm-oauth'; - if (visibleStep === 14 || visibleStep === 15) return 'platform-verify'; + if (visibleStep === 16) return 'confirm-oauth'; + if (visibleStep === 14 || visibleStep === 15 || visibleStep === 17) return 'platform-verify'; if (visibleStep === 7) return 'oauth-login'; if (visibleStep === 5) return 'fill-profile'; if (visibleStep === 3) return 'fill-password'; diff --git a/content/utils.js b/content/utils.js index 77455c5..f650814 100644 --- a/content/utils.js +++ b/content/utils.js @@ -290,6 +290,8 @@ const DEFAULT_OPENAI_NODE_BY_STEP = Object.freeze({ 13: 'confirm-oauth', 14: 'platform-verify', 15: 'platform-verify', + 16: 'confirm-oauth', + 17: 'platform-verify', }); function resolveReportNodeId(stepOrNodeId, data = {}) { diff --git a/data/step-definitions.js b/data/step-definitions.js index facb7c1..102f9ac 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -50,7 +50,11 @@ { id: 7, order: 70, key: 'plus-checkout-billing', title: '等待 GPC 任务完成', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-billing' }, ]; - function createOpenAiAuthTail(startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL) { + function isPhoneSignupReloginAfterBindEmailEnabled(options = {}) { + return Boolean(options?.phoneSignupReloginAfterBindEmailEnabled); + } + + function createOpenAiAuthTail(startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) { const id = Number(startId) || 7; const order = Number(startOrder) || id * 10; const commonStart = [ @@ -59,6 +63,17 @@ ]; if (signupMethod === SIGNUP_METHOD_PHONE) { + if (isPhoneSignupReloginAfterBindEmailEnabled(options)) { + return [ + ...commonStart, + { id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' }, + { id: id + 3, order: order + 30, key: 'relogin-bound-email', title: '绑定邮箱后刷新 OAuth 并登录(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' }, + { id: id + 4, order: order + 40, key: 'fetch-bound-email-login-code', title: '获取登录验证码(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' }, + { id: id + 5, order: order + 50, key: 'post-bound-email-phone-verification', title: '手机号验证', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'post-login-phone-verification' }, + { id: id + 6, order: order + 60, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' }, + { id: id + 7, order: order + 70, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' }, + ]; + } return [ ...commonStart, { id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' }, @@ -76,21 +91,25 @@ ]; } - function createOpenAiSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL) { + function createOpenAiSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) { return [ ...prefixSteps, - ...createOpenAiAuthTail(startId, startOrder, signupMethod), + ...createOpenAiAuthTail(startId, startOrder, signupMethod, options), ]; } const NORMAL_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_EMAIL); const NORMAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE); + const NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); const PLUS_PAYPAL_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL); const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE); + const PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); const PLUS_GOPAY_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL); const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE); + const PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); const PLUS_GPC_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL); const PLUS_GPC_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE); + const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); const PHONE_SIGNUP_TITLE_OVERRIDES = Object.freeze({ 'submit-signup-email': '注册并输入手机号', @@ -130,17 +149,39 @@ function getOpenAiModeStepDefinitions(options = {}) { const signupMethod = getResolvedSignupMethod(options); + const reloginAfterBindEmail = signupMethod === SIGNUP_METHOD_PHONE + && isPhoneSignupReloginAfterBindEmailEnabled(options); if (!isPlusModeEnabled(options)) { - return signupMethod === SIGNUP_METHOD_PHONE ? NORMAL_PHONE_STEP_DEFINITIONS : NORMAL_STEP_DEFINITIONS; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail + ? NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS + : NORMAL_PHONE_STEP_DEFINITIONS; + } + return NORMAL_STEP_DEFINITIONS; } const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod); if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) { - return signupMethod === SIGNUP_METHOD_PHONE ? PLUS_GPC_PHONE_STEP_DEFINITIONS : PLUS_GPC_STEP_DEFINITIONS; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail + ? PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS + : PLUS_GPC_PHONE_STEP_DEFINITIONS; + } + return PLUS_GPC_STEP_DEFINITIONS; } if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) { - return signupMethod === SIGNUP_METHOD_PHONE ? PLUS_GOPAY_PHONE_STEP_DEFINITIONS : PLUS_GOPAY_STEP_DEFINITIONS; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail + ? PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS + : PLUS_GOPAY_PHONE_STEP_DEFINITIONS; + } + return PLUS_GOPAY_STEP_DEFINITIONS; } - return signupMethod === SIGNUP_METHOD_PHONE ? PLUS_PAYPAL_PHONE_STEP_DEFINITIONS : PLUS_PAYPAL_STEP_DEFINITIONS; + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail + ? PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS + : PLUS_PAYPAL_PHONE_STEP_DEFINITIONS; + } + return PLUS_PAYPAL_STEP_DEFINITIONS; } function getOpenAiPlusPaymentStepTitle(options = {}) { @@ -172,12 +213,16 @@ for (const step of [ ...NORMAL_STEP_DEFINITIONS, ...NORMAL_PHONE_STEP_DEFINITIONS, + ...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ...PLUS_PAYPAL_STEP_DEFINITIONS, ...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS, + ...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ...PLUS_GOPAY_STEP_DEFINITIONS, ...PLUS_GOPAY_PHONE_STEP_DEFINITIONS, + ...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ...PLUS_GPC_STEP_DEFINITIONS, ...PLUS_GPC_PHONE_STEP_DEFINITIONS, + ...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, ]) { keyed.set(`${step.id}:${step.key}`, step); } @@ -352,13 +397,17 @@ STEP_DEFINITIONS: NORMAL_STEP_DEFINITIONS, NORMAL_STEP_DEFINITIONS, NORMAL_PHONE_STEP_DEFINITIONS, + NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, PLUS_STEP_DEFINITIONS: PLUS_PAYPAL_STEP_DEFINITIONS, PLUS_PAYPAL_STEP_DEFINITIONS, PLUS_PAYPAL_PHONE_STEP_DEFINITIONS, + PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, PLUS_GOPAY_STEP_DEFINITIONS, PLUS_GOPAY_PHONE_STEP_DEFINITIONS, + PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, PLUS_GPC_STEP_DEFINITIONS, PLUS_GPC_PHONE_STEP_DEFINITIONS, + PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, SIGNUP_METHOD_EMAIL, SIGNUP_METHOD_PHONE, getAllSteps, diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index ede2cdc..f41ff69 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -1295,6 +1295,17 @@ +