diff --git a/background.js b/background.js index 9a307ae..2311da4 100644 --- a/background.js +++ b/background.js @@ -444,7 +444,19 @@ function resolveContributionModeRoutingState(state = {}) { }; } +function getSignupMethodForStepDefinitions(state = {}) { + return normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod); +} + function getStepDefinitionsForState(state = {}) { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + if (rootScope.MultiPageStepDefinitions?.getSteps) { + return rootScope.MultiPageStepDefinitions.getSteps({ + plusModeEnabled: isPlusModeState(state), + plusPaymentMethod: normalizePlusPaymentMethod(state?.plusPaymentMethod), + signupMethod: getSignupMethodForStepDefinitions(state), + }); + } if (!isPlusModeState(state)) { return NORMAL_STEP_DEFINITIONS; } @@ -454,6 +466,13 @@ function getStepDefinitionsForState(state = {}) { } function getStepIdsForState(state = {}) { + const definitions = getStepDefinitionsForState(state); + if (Array.isArray(definitions) && definitions.length) { + return definitions + .map((definition) => Number(definition?.id)) + .filter(Number.isFinite) + .sort((left, right) => left - right); + } if (!isPlusModeState(state)) { return NORMAL_STEP_IDS; } @@ -1070,9 +1089,9 @@ function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) { } function normalizeSignupMethod(value = '') { - return String(value || '').trim().toLowerCase() === SIGNUP_METHOD_PHONE - ? SIGNUP_METHOD_PHONE - : SIGNUP_METHOD_EMAIL; + return String(value || '').trim().toLowerCase() === 'phone' + ? 'phone' + : 'email'; } function canUsePhoneSignup(state = {}) { diff --git a/data/step-definitions.js b/data/step-definitions.js index ef8b115..f2b14ca 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -4,6 +4,8 @@ const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_STEP_KEY = 'paypal-approve'; + const SIGNUP_METHOD_EMAIL = 'email'; + const SIGNUP_METHOD_PHONE = 'phone'; const NORMAL_STEP_DEFINITIONS = [ { id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网' }, @@ -25,7 +27,7 @@ { id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码' }, { id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日' }, { id: 6, order: 60, key: 'plus-checkout-create', title: '创建 Plus Checkout' }, - { id: 7, order: 70, key: 'plus-checkout-billing', title: '填写账单并提交订阅' }, + { id: 7, order: 70, key: 'plus-checkout-billing', title: '填写账单并提交订单' }, { id: 8, order: 80, key: 'paypal-approve', title: 'PayPal 登录与授权' }, { id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认' }, { id: 10, order: 100, key: 'oauth-login', title: '刷新 OAuth 并登录' }, @@ -48,6 +50,11 @@ { id: 13, order: 130, key: 'platform-verify', title: '平台回调验证' }, ]; + const PHONE_SIGNUP_TITLE_OVERRIDES = Object.freeze({ + 'submit-signup-email': '注册并输入手机号', + 'fetch-signup-code': '获取手机验证码', + }); + function isPlusModeEnabled(options = {}) { return Boolean(options?.plusModeEnabled || options?.plusMode); } @@ -58,6 +65,16 @@ : PLUS_PAYMENT_METHOD_PAYPAL; } + function normalizeSignupMethod(value = '') { + return String(value || '').trim().toLowerCase() === SIGNUP_METHOD_PHONE + ? SIGNUP_METHOD_PHONE + : SIGNUP_METHOD_EMAIL; + } + + function getResolvedSignupMethod(options = {}) { + return normalizeSignupMethod(options?.resolvedSignupMethod || options?.signupMethod); + } + function getModeStepDefinitions(options = {}) { if (!isPlusModeEnabled(options)) { return NORMAL_STEP_DEFINITIONS; @@ -67,13 +84,32 @@ : PLUS_PAYPAL_STEP_DEFINITIONS; } + function getPlusPaymentStepTitle(options = {}) { + if (!isPlusModeEnabled(options)) { + return ''; + } + const paymentStep = getModeStepDefinitions({ + ...options, + plusModeEnabled: true, + }).find((step) => step.key === PLUS_PAYMENT_STEP_KEY); + return paymentStep?.title || ''; + } + + function getResolvedStepTitle(step = {}, options = {}) { + if (isPlusModeEnabled(options) && step.key === PLUS_PAYMENT_STEP_KEY) { + return getPlusPaymentStepTitle(options) || step.title; + } + const signupMethod = getResolvedSignupMethod(options); + if (signupMethod === SIGNUP_METHOD_PHONE && PHONE_SIGNUP_TITLE_OVERRIDES[step.key]) { + return PHONE_SIGNUP_TITLE_OVERRIDES[step.key]; + } + return step.title; + } + function cloneSteps(steps = [], options = {}) { - const plusModeEnabled = isPlusModeEnabled(options); return steps.map((step) => ({ ...step, - title: plusModeEnabled && step.key === PLUS_PAYMENT_STEP_KEY - ? getPlusPaymentStepTitle(options) - : step.title, + title: getResolvedStepTitle(step, options), })); } @@ -116,23 +152,14 @@ return match ? cloneSteps([match], options)[0] : null; } - function getPlusPaymentStepTitle(options = {}) { - if (!isPlusModeEnabled(options)) { - return ''; - } - const paymentStep = getModeStepDefinitions({ - ...options, - plusModeEnabled: true, - }).find((step) => step.key === 'paypal-approve'); - return paymentStep?.title || ''; - } - return { STEP_DEFINITIONS: NORMAL_STEP_DEFINITIONS, NORMAL_STEP_DEFINITIONS, PLUS_STEP_DEFINITIONS: PLUS_PAYPAL_STEP_DEFINITIONS, PLUS_PAYPAL_STEP_DEFINITIONS, PLUS_GOPAY_STEP_DEFINITIONS, + SIGNUP_METHOD_EMAIL, + SIGNUP_METHOD_PHONE, getAllSteps, getLastStepId, getPlusPaymentStepTitle, @@ -141,5 +168,6 @@ getSteps, isPlusModeEnabled, normalizePlusPaymentMethod, + normalizeSignupMethod, }; }); diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 214493c..f2d7a1c 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -441,8 +441,12 @@ const stepsList = document.querySelector('.steps-list'); const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL; +const SIGNUP_METHOD_EMAIL = 'email'; +const SIGNUP_METHOD_PHONE = 'phone'; +const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL; let currentPlusModeEnabled = false; let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD; +let currentSignupMethod = DEFAULT_SIGNUP_METHOD; let heroSmsCountrySelectionOrder = []; let phoneSmsProviderOrderSelection = []; let heroSmsCountryMenuSearchKeyword = ''; @@ -458,7 +462,10 @@ const fiveSimCountrySearchTextByCode = new Map(); let nexSmsCountrySelectionOrder = []; let nexSmsCountryMenuSearchKeyword = ''; const nexSmsCountrySearchTextById = new Map(); -let stepDefinitions = getStepDefinitionsForMode(false, currentPlusPaymentMethod); +let stepDefinitions = getStepDefinitionsForMode(false, { + plusPaymentMethod: currentPlusPaymentMethod, + signupMethod: currentSignupMethod, +}); let STEP_IDS = stepDefinitions.map((step) => Number(step.id)).filter(Number.isFinite); let STEP_DEFAULT_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending'])); let SKIPPABLE_STEPS = new Set(STEP_IDS); @@ -494,9 +501,6 @@ const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim'; const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO; const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms'; const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO; -const SIGNUP_METHOD_EMAIL = 'email'; -const SIGNUP_METHOD_PHONE = 'phone'; -const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL; const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([ PHONE_SMS_PROVIDER_HERO, PHONE_SMS_PROVIDER_FIVE_SIM, @@ -714,9 +718,13 @@ function getStepDefinitionsForMode(plusModeEnabled = false, options = {}) { const rawPaymentMethod = typeof options === 'string' ? options : (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod); + const rawSignupMethod = typeof options === 'string' + ? currentSignupMethod + : (options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD); return (window.MultiPageStepDefinitions?.getSteps?.({ plusModeEnabled, plusPaymentMethod: normalizePlusPaymentMethod(rawPaymentMethod), + signupMethod: normalizeSignupMethod(rawSignupMethod), }) || []) .sort((left, right) => { const leftOrder = Number.isFinite(left.order) ? left.order : left.id; @@ -732,8 +740,15 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) { const rawPaymentMethod = typeof options === 'string' ? options : (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod); + const rawSignupMethod = typeof options === 'string' + ? currentSignupMethod + : (options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD); currentPlusPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod); - stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, currentPlusPaymentMethod); + currentSignupMethod = normalizeSignupMethod(rawSignupMethod); + stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, { + plusPaymentMethod: currentPlusPaymentMethod, + signupMethod: currentSignupMethod, + }); STEP_IDS = stepDefinitions.map((step) => Number(step.id)).filter(Number.isFinite); STEP_DEFAULT_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending'])); SKIPPABLE_STEPS = new Set(STEP_IDS); @@ -6637,9 +6652,9 @@ function updateAccountRunHistorySettingsUI() { } function normalizeSignupMethod(value = '') { - return String(value || '').trim().toLowerCase() === SIGNUP_METHOD_PHONE - ? SIGNUP_METHOD_PHONE - : SIGNUP_METHOD_EMAIL; + return String(value || '').trim().toLowerCase() === 'phone' + ? 'phone' + : 'email'; } function getSelectedSignupMethod() { @@ -6712,6 +6727,15 @@ function updateSignupMethodUI(options = {}) { } } }); + syncStepDefinitionsForMode( + typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled + ? Boolean(inputPlusModeEnabled.checked) + : Boolean(latestState?.plusModeEnabled), + { + plusPaymentMethod: getSelectedPlusPaymentMethod(latestState), + signupMethod: selectedMethod, + } + ); } function updatePhoneVerificationSettingsUI() { @@ -7224,23 +7248,29 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr const rawPaymentMethod = typeof plusPaymentMethodOrOptions === 'string' ? plusPaymentMethodOrOptions : (options.plusPaymentMethod || getSelectedPlusPaymentMethod(latestState)); + const nextSignupMethod = normalizeSignupMethod(options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD); const nextPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod); const rootScope = typeof window !== 'undefined' ? window : globalThis; const currentPaymentStep = stepDefinitions.find((step) => step.key === 'paypal-approve'); const nextPaymentTitle = rootScope.MultiPageStepDefinitions?.getPlusPaymentStepTitle?.({ plusModeEnabled: nextPlusModeEnabled, plusPaymentMethod: nextPaymentMethod, + signupMethod: nextSignupMethod, }); const paymentTitleChanged = Boolean(nextPlusModeEnabled && currentPaymentStep && nextPaymentTitle && currentPaymentStep.title !== nextPaymentTitle); const shouldRender = Boolean(options.render) || nextPlusModeEnabled !== currentPlusModeEnabled || nextPaymentMethod !== currentPlusPaymentMethod + || nextSignupMethod !== currentSignupMethod || paymentTitleChanged; if (!shouldRender) { return; } - rebuildStepDefinitionState(nextPlusModeEnabled, nextPaymentMethod); + rebuildStepDefinitionState(nextPlusModeEnabled, { + plusPaymentMethod: nextPaymentMethod, + signupMethod: nextSignupMethod, + }); renderStepsList(); } @@ -7250,7 +7280,10 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr function applySettingsState(state) { if (typeof syncStepDefinitionsForMode === 'function') { - syncStepDefinitionsForMode(Boolean(state?.plusModeEnabled), state?.plusPaymentMethod); + syncStepDefinitionsForMode(Boolean(state?.plusModeEnabled), { + plusPaymentMethod: state?.plusPaymentMethod, + signupMethod: state?.signupMethod, + }); } const fallbackIpProxyService = '711proxy'; const fallbackIpProxyMode = 'account'; diff --git a/tests/background-signup-method-settings.test.js b/tests/background-signup-method-settings.test.js index a6c90a9..8b757f7 100644 --- a/tests/background-signup-method-settings.test.js +++ b/tests/background-signup-method-settings.test.js @@ -95,3 +95,43 @@ return { assert.equal(api.logs.some((entry) => /固定为邮箱注册/.test(entry.message)), true); }); +test('background step definitions resolve titles from the frozen signup method', () => { + const api = new Function(` +const captured = []; +const self = { + MultiPageStepDefinitions: { + getSteps(options) { + captured.push(options); + return [{ + id: 2, + key: 'submit-signup-email', + title: options.signupMethod === 'phone' ? '注册并输入手机号' : '注册并输入邮箱', + }]; + }, + }, +}; +${extractFunction('isPlusModeState')} +${extractFunction('normalizePlusPaymentMethod')} +${extractFunction('normalizeSignupMethod')} +${extractFunction('getSignupMethodForStepDefinitions')} +${extractFunction('getStepDefinitionsForState')} +return { + getCaptured: () => captured.slice(), + getStepDefinitionsForState, +}; +`)(); + + const steps = api.getStepDefinitionsForState({ + plusModeEnabled: true, + plusPaymentMethod: 'gopay', + signupMethod: 'email', + resolvedSignupMethod: 'phone', + }); + + assert.deepEqual(api.getCaptured(), [{ + plusModeEnabled: true, + plusPaymentMethod: 'gopay', + signupMethod: 'phone', + }]); + assert.equal(steps[0].title, '注册并输入手机号'); +}); diff --git a/tests/sidepanel-plus-payment-method.test.js b/tests/sidepanel-plus-payment-method.test.js index 824a109..3b57df0 100644 --- a/tests/sidepanel-plus-payment-method.test.js +++ b/tests/sidepanel-plus-payment-method.test.js @@ -34,6 +34,7 @@ function extractFunction(name) { test('sidepanel step definitions keep the selected Plus payment method', () => { const bundle = [ + extractFunction('normalizeSignupMethod'), extractFunction('normalizePlusPaymentMethod'), extractFunction('getStepDefinitionsForMode'), extractFunction('rebuildStepDefinitionState'), @@ -52,6 +53,8 @@ const window = { }; let currentPlusModeEnabled = false; let currentPlusPaymentMethod = 'paypal'; +let currentSignupMethod = 'email'; +const DEFAULT_SIGNUP_METHOD = 'email'; let stepDefinitions = []; let STEP_IDS = []; let STEP_DEFAULT_STATUSES = {}; @@ -74,11 +77,28 @@ return { assert.deepEqual(api.getStepIds(), [7]); assert.deepEqual(api.calls[0], { type: 'getSteps', - options: { plusModeEnabled: true, plusPaymentMethod: 'gopay' }, + options: { plusModeEnabled: true, plusPaymentMethod: 'gopay', signupMethod: 'email' }, }); assert.deepEqual(api.calls[1], { type: 'render', stepIds: [7] }); }); +test('sidepanel normalizeSignupMethod stays independent from signup constants during bootstrap', () => { + const source = extractFunction('normalizeSignupMethod'); + assert.doesNotMatch(source, /SIGNUP_METHOD_(PHONE|EMAIL)/); +}); + +test('sidepanel signup method UI syncs shared step definitions with the selected signup method', () => { + const source = extractFunction('updateSignupMethodUI'); + assert.match(source, /syncStepDefinitionsForMode\(/); + assert.match(source, /signupMethod:\s*selectedMethod/); +}); + +test('sidepanel applies restored signup method when rebuilding shared step definitions on load', () => { + const source = extractFunction('applySettingsState'); + assert.match(source, /syncStepDefinitionsForMode\(Boolean\(state\?\.plusModeEnabled\),\s*\{/); + assert.match(source, /signupMethod:\s*state\?\.signupMethod/); +}); + test('sidepanel Plus UI hides PayPal account selector while GoPay is selected', () => { const bundle = [ extractFunction('normalizePlusPaymentMethod'), diff --git a/tests/step-definitions-module.test.js b/tests/step-definitions-module.test.js index 0407819..303ed4e 100644 --- a/tests/step-definitions-module.test.js +++ b/tests/step-definitions-module.test.js @@ -8,7 +8,9 @@ test('step definitions module exposes ordered normal and Plus step metadata', () const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope); const steps = api.getSteps(); + const phoneSteps = api.getSteps({ signupMethod: 'phone' }); const plusSteps = api.getSteps({ plusModeEnabled: true }); + const plusPhoneSteps = api.getSteps({ plusModeEnabled: true, signupMethod: 'phone' }); const goPaySteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }); assert.equal(Array.isArray(steps), true); @@ -34,6 +36,8 @@ test('step definitions module exposes ordered normal and Plus step metadata', () ); assert.equal(steps[0].title, '打开 ChatGPT 官网'); assert.equal(steps[5].title, '清理登录 Cookies'); + assert.equal(phoneSteps[1].title, '注册并输入手机号'); + assert.equal(phoneSteps[3].title, '获取手机验证码'); assert.deepStrictEqual( plusSteps.map((step) => step.key), @@ -56,6 +60,8 @@ test('step definitions module exposes ordered normal and Plus step metadata', () assert.equal(plusSteps.some((step) => step.key === 'clear-login-cookies'), false); assert.equal(plusSteps.some((step) => step.key === 'fetch-login-code'), true); assert.equal(plusSteps.find((step) => step.key === 'paypal-approve')?.title, 'PayPal 登录与授权'); + assert.equal(plusPhoneSteps[1].title, '注册并输入手机号'); + assert.equal(plusPhoneSteps[3].title, '获取手机验证码'); assert.equal(goPaySteps.some((step) => step.key === 'paypal-approve'), false); assert.equal(api.getStepById(8, { plusModeEnabled: true, plusPaymentMethod: 'gopay' }), null); assert.equal(api.getPlusPaymentStepTitle({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), ''); diff --git a/项目完整链路说明.md b/项目完整链路说明.md index c9476af..c935da9 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -109,7 +109,7 @@ ### 3.3 步骤注册 -[data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js) 提供共享步骤元数据。 +[data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js) 提供共享步骤元数据,并会按 `plusPaymentMethod / signupMethod` 解析当前运行态应显示的步骤标题。 [background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js) 负责把“步骤元数据”映射到“步骤执行器”。 这意味着: diff --git a/项目文件结构说明.md b/项目文件结构说明.md index 30695e0..7138cad 100644 --- a/项目文件结构说明.md +++ b/项目文件结构说明.md @@ -101,7 +101,7 @@ - `data/names.js`:随机姓名、生日等测试数据源。 - `data/address-sources.js`:Plus 模式本地地址 seed 表,负责按国家选择用于触发 checkout 内置 Google 地址推荐的查询词和结构化地址 fallback;第一版不实时抓取外部地址网站。 -- `data/step-definitions.js`:共享步骤元数据,前后台共同使用,用于动态渲染和步骤注册;当前同时提供普通 10 步定义与 Plus 模式 13 步定义。 +- `data/step-definitions.js`:共享步骤元数据,前后台共同使用,用于动态渲染和步骤注册;当前同时提供普通 10 步定义与 Plus 模式 13 步定义,并会按 `plusPaymentMethod / signupMethod` 动态解析实际步骤标题。 ## `docs/`