diff --git a/background/steps/fetch-signup-code.js b/background/steps/fetch-signup-code.js index 02f9aa5..88778a3 100644 --- a/background/steps/fetch-signup-code.js +++ b/background/steps/fetch-signup-code.js @@ -103,7 +103,7 @@ throw new Error(prepareResult.error); } if (prepareResult?.alreadyVerified) { - await completeStepFromBackground(4, {}); + await completeStepFromBackground(4, prepareResult?.skipProfileStep ? { skipProfileStep: true } : {}); return; } diff --git a/content/signup-page.js b/content/signup-page.js index 4883f3e..74311df 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -1098,6 +1098,67 @@ function isStep5Ready() { ); } +function isSignupProfilePageUrl(rawUrl = location.href) { + 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; + } +} + +function isLikelyLoggedInChatgptHomeUrl(rawUrl = location.href) { + 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', 'chat.openai.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 getStep4PostVerificationState() { + if (isStep5Ready() || isSignupProfilePageUrl()) { + return { + state: 'step5', + url: location.href, + }; + } + + if (isLikelyLoggedInChatgptHomeUrl()) { + return { + state: 'logged_in_home', + skipProfileStep: true, + url: location.href, + }; + } + + return null; +} + function getPageTextSnapshot() { return (document.body?.innerText || document.body?.textContent || '') .replace(/\s+/g, ' ') @@ -2056,10 +2117,19 @@ function isSignupEmailAlreadyExistsPage() { } function inspectSignupVerificationState() { - if (isStep5Ready()) { + const postVerificationState = getStep4PostVerificationState(); + if (postVerificationState?.state === 'step5') { return { state: 'step5' }; } + if (postVerificationState?.state === 'logged_in_home') { + return { + state: 'logged_in_home', + skipProfileStep: true, + url: postVerificationState.url || location.href, + }; + } + if (isSignupPasswordErrorPage()) { const timeoutPage = getSignupPasswordTimeoutErrorPageState(); return { @@ -2096,7 +2166,13 @@ async function waitForSignupVerificationTransition(timeout = 5000) { throwIfStopped(); const snapshot = inspectSignupVerificationState(); - if (snapshot.state === 'step5' || snapshot.state === 'verification' || snapshot.state === 'error' || snapshot.state === 'email_exists') { + if ( + snapshot.state === 'step5' + || snapshot.state === 'logged_in_home' + || snapshot.state === 'verification' + || snapshot.state === 'error' + || snapshot.state === 'email_exists' + ) { return snapshot; } @@ -2128,6 +2204,20 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { return { ready: true, alreadyVerified: true, retried: recoveryRound, prepareSource }; } + if (snapshot.state === 'logged_in_home') { + /* + log(`${prepareLogLabel}锛氶〉闈㈠凡鐩存帴杩涘叆 ChatGPT 宸茬櫥褰曟€侊紝鏈楠?鎸夊凡瀹屾垚澶勭悊锛屽苟灏嗚烦杩囨楠?5銆俙, 'ok'); + */ + log(`${prepareLogLabel}:页面已直接进入 ChatGPT 已登录态,本步骤按已完成处理,并将跳过步骤 5。`, 'ok'); + return { + ready: true, + alreadyVerified: true, + skipProfileStep: true, + retried: recoveryRound, + prepareSource, + }; + } + if (snapshot.state === 'verification') { log(`${prepareLogLabel}:验证码页面已就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok'); return { ready: true, retried: recoveryRound, prepareSource }; @@ -2215,15 +2305,25 @@ async function waitForVerificationSubmitOutcome(step, timeout) { continue; } + if (step === 4) { + const postVerificationState = getStep4PostVerificationState(); + if (postVerificationState?.state === 'logged_in_home') { + return { + success: true, + skipProfileStep: true, + url: postVerificationState.url || location.href, + }; + } + if (postVerificationState?.state === 'step5') { + return { success: true }; + } + } + const errorText = getVerificationErrorText(); if (errorText) { return { invalidCode: true, errorText }; } - if (step === 4 && isStep5Ready()) { - return { success: true }; - } - if (step === 8 && isStep8Ready()) { return { success: true }; } @@ -2240,6 +2340,18 @@ async function waitForVerificationSubmitOutcome(step, timeout) { if (signupRetryState?.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } + + const postVerificationState = getStep4PostVerificationState(); + if (postVerificationState?.state === 'logged_in_home') { + return { + success: true, + skipProfileStep: true, + url: postVerificationState.url || location.href, + }; + } + if (postVerificationState?.state === 'step5') { + return { success: true }; + } } if (isVerificationPageStillVisible()) { diff --git a/tests/background-step4-filter-window.test.js b/tests/background-step4-filter-window.test.js index 66adfc8..6c4a389 100644 --- a/tests/background-step4-filter-window.test.js +++ b/tests/background-step4-filter-window.test.js @@ -165,3 +165,57 @@ test('step 4 checks iCloud session before polling iCloud mailbox', async () => { assert.equal(icloudChecks, 1); assert.equal(resolved, true); }); + +test('step 4 forwards skipProfileStep when prepare stage already reached logged-in home', async () => { + const completions = []; + let resolveCalls = 0; + + const executor = api.createStep4Executor({ + addLog: async () => {}, + chrome: { + tabs: { + update: async () => {}, + }, + }, + completeStepFromBackground: async (step, payload) => { + completions.push({ step, payload }); + }, + confirmCustomVerificationStepBypass: async () => {}, + ensureMail2925MailboxSession: async () => {}, + getMailConfig: () => ({ + provider: '163', + label: '163 邮箱', + source: 'mail-163', + url: 'https://mail.163.com', + }), + getTabId: async () => 1, + HOTMAIL_PROVIDER: 'hotmail-api', + isTabAlive: async () => true, + LUCKMAIL_PROVIDER: 'luckmail-api', + CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email', + resolveVerificationStep: async () => { + resolveCalls += 1; + }, + reuseOrCreateTab: async () => {}, + sendToContentScriptResilient: async () => ({ + alreadyVerified: true, + skipProfileStep: true, + }), + shouldUseCustomRegistrationEmail: () => false, + STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000, + throwIfStopped: () => {}, + }); + + await executor.executeStep4({ + email: 'user@example.com', + password: 'secret', + }); + + assert.deepStrictEqual(completions, [ + { + step: 4, + payload: { skipProfileStep: true }, + }, + ]); + assert.equal(resolveCalls, 0); +}); diff --git a/tests/signup-verification-state-guard.test.js b/tests/signup-verification-state-guard.test.js index 4414636..98e757e 100644 --- a/tests/signup-verification-state-guard.test.js +++ b/tests/signup-verification-state-guard.test.js @@ -98,6 +98,10 @@ return { test('signup verification state should prioritize retry error page over verification visibility', () => { const api = new Function(` +const location = { + href: 'https://auth.openai.com/email-verification', +}; + function isStep5Ready() { return false; } @@ -126,6 +130,9 @@ function getSignupPasswordSubmitButton() { return null; } +${extractFunction('isSignupProfilePageUrl')} +${extractFunction('isLikelyLoggedInChatgptHomeUrl')} +${extractFunction('getStep4PostVerificationState')} ${extractFunction('inspectSignupVerificationState')} return { @@ -177,6 +184,9 @@ function getSignupPasswordSubmitButton() { ${extractFunction('getSignupAuthRetryPathPatterns')} ${extractFunction('getSignupPasswordTimeoutErrorPageState')} ${extractFunction('isSignupPasswordErrorPage')} +${extractFunction('isSignupProfilePageUrl')} +${extractFunction('isLikelyLoggedInChatgptHomeUrl')} +${extractFunction('getStep4PostVerificationState')} ${extractFunction('inspectSignupVerificationState')} return { @@ -192,3 +202,54 @@ return { userAlreadyExistsBlocked: false, }); }); + +test('signup verification state treats profile url as step5 before fields finish rendering', () => { + const api = new Function(` +const location = { + href: 'https://auth.openai.com/create-account/profile', +}; + +function isStep5Ready() { + return false; +} + +function isVerificationPageStillVisible() { + return false; +} + +function isSignupPasswordErrorPage() { + return false; +} + +function getSignupPasswordTimeoutErrorPageState() { + return null; +} + +function isSignupEmailAlreadyExistsPage() { + return false; +} + +function getSignupPasswordInput() { + return null; +} + +function getSignupPasswordSubmitButton() { + return null; +} + +${extractFunction('isSignupProfilePageUrl')} +${extractFunction('isLikelyLoggedInChatgptHomeUrl')} +${extractFunction('getStep4PostVerificationState')} +${extractFunction('inspectSignupVerificationState')} + +return { + run() { + return inspectSignupVerificationState(); + }, +}; +`)(); + + assert.deepStrictEqual(api.run(), { + state: 'step5', + }); +}); diff --git a/tests/step4-split-code-submit.test.js b/tests/step4-split-code-submit.test.js index 2ed0d24..6362a78 100644 --- a/tests/step4-split-code-submit.test.js +++ b/tests/step4-split-code-submit.test.js @@ -131,6 +131,9 @@ ${extractFunction('getVerificationSubmitButtonForTarget')} ${extractFunction('waitForVerificationSubmitButton')} ${extractFunction('waitForVerificationCodeTarget')} ${extractFunction('waitForSplitVerificationInputsFilled')} +${extractFunction('isSignupProfilePageUrl')} +${extractFunction('isLikelyLoggedInChatgptHomeUrl')} +${extractFunction('getStep4PostVerificationState')} ${extractFunction('fillVerificationCode')} return { diff --git a/tests/step4-submit-retry-recovery.test.js b/tests/step4-submit-retry-recovery.test.js index b774b19..3324cb3 100644 --- a/tests/step4-submit-retry-recovery.test.js +++ b/tests/step4-submit-retry-recovery.test.js @@ -84,6 +84,9 @@ async function recoverCurrentAuthRetryPage() { } async function sleep() {} +${extractFunction('isSignupProfilePageUrl')} +${extractFunction('isLikelyLoggedInChatgptHomeUrl')} +${extractFunction('getStep4PostVerificationState')} ${extractFunction('waitForVerificationSubmitOutcome')} return { @@ -131,6 +134,9 @@ async function recoverCurrentAuthRetryPage() { } async function sleep() {} +${extractFunction('isSignupProfilePageUrl')} +${extractFunction('isLikelyLoggedInChatgptHomeUrl')} +${extractFunction('getStep4PostVerificationState')} ${extractFunction('waitForVerificationSubmitOutcome')} return { @@ -149,3 +155,46 @@ return { ); assert.equal(api.snapshot().recoverCalls, 2); }); + +test('waitForVerificationSubmitOutcome marks step 5 skipped when step 4 already lands on chatgpt home', async () => { + const api = new Function(` +const location = { href: 'https://chatgpt.com/' }; + +function throwIfStopped() {} +function log() {} +function getVerificationErrorText() { return ''; } +function isStep5Ready() { return false; } +function isStep8Ready() { return false; } +function isAddPhonePageReady() { return false; } +function isVerificationPageStillVisible() { return false; } +function createSignupUserAlreadyExistsError() { + return new Error('SIGNUP_USER_ALREADY_EXISTS::步骤 4:检测到 user_already_exists,说明当前用户已存在,当前轮将直接停止。'); +} +function getCurrentAuthRetryPageState() { + return null; +} +async function recoverCurrentAuthRetryPage() { + throw new Error('should not recover retry page'); +} +async function sleep() {} + +${extractFunction('isSignupProfilePageUrl')} +${extractFunction('isLikelyLoggedInChatgptHomeUrl')} +${extractFunction('getStep4PostVerificationState')} +${extractFunction('waitForVerificationSubmitOutcome')} + +return { + run() { + return waitForVerificationSubmitOutcome(4, 1000); + }, +}; +`)(); + + const result = await api.run(); + + assert.deepStrictEqual(result, { + success: true, + skipProfileStep: true, + url: 'https://chatgpt.com/', + }); +});