feat: 增强注册方法管理,支持动态解析步骤标题及相关测试

This commit is contained in:
QLHazyCoder
2026-05-04 15:06:19 +08:00
parent 5d70d2b681
commit 5f7450acf5
8 changed files with 178 additions and 32 deletions
+22 -3
View File
@@ -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 = {}) {
+44 -16
View File
@@ -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,
};
});
+43 -10
View File
@@ -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';
@@ -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, '注册并输入手机号');
});
+21 -1
View File
@@ -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'),
+6
View File
@@ -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' }), '');
+1 -1
View File
@@ -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) 负责把“步骤元数据”映射到“步骤执行器”。
这意味着:
+1 -1
View File
@@ -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/`