From e50b0fbfebad0455eb9b200c7889361ed0893591 Mon Sep 17 00:00:00 2001 From: Isulew <224964+netcookies@users.noreply.github.com> Date: Fri, 29 May 2026 12:12:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(madao):=20=E4=BF=AE=E5=A4=8D=E6=8D=A2?= =?UTF-8?q?=E5=8F=B7=E5=90=8E=E5=9B=BD=E5=AE=B6=E5=8C=BA=E5=8F=B7=E6=AE=8B?= =?UTF-8?q?=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 让 MaDao activation 使用 ISO country code,并避免 routing replace 沿用旧 ticket 的 country。 提交 OpenAI add-phone 前校验当前国家区号与手机号匹配,防止页面残留 Thailand 区号。 --- background/phone-verification-flow.js | 110 +++++++++++++---- flows/openai/content/phone-auth.js | 25 +++- phone-sms/providers/madao.js | 1 - tests/phone-auth-country-match.test.js | 62 ++++++++++ tests/phone-verification-flow.test.js | 156 ++++++++++++++++++++++++- 5 files changed, 325 insertions(+), 29 deletions(-) diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index 7cf1900..2e6815f 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -107,14 +107,14 @@ const PHONE_SMS_FAILURE_SKIP_THRESHOLD = 2; const MAX_ACTIVATION_PRICE_HINTS = 256; const HERO_SMS_COUNTRY_BY_PHONE_PREFIX = Object.freeze([ - { prefix: '84', id: 10, label: 'Vietnam' }, - { prefix: '66', id: 52, label: 'Thailand' }, - { prefix: '62', id: 6, label: 'Indonesia' }, - { prefix: '44', id: 16, label: 'United Kingdom' }, - { prefix: '81', id: 151, label: 'Japan' }, - { prefix: '49', id: 43, label: 'Germany' }, - { prefix: '33', id: 73, label: 'France' }, - { prefix: '1', id: 187, label: 'USA' }, + { prefix: '84', id: 10, iso: 'VN', label: 'Vietnam' }, + { prefix: '66', id: 52, iso: 'TH', label: 'Thailand' }, + { prefix: '62', id: 6, iso: 'ID', label: 'Indonesia' }, + { prefix: '44', id: 16, iso: 'GB', label: 'United Kingdom' }, + { prefix: '81', id: 151, iso: 'JP', label: 'Japan' }, + { prefix: '49', id: 43, iso: 'DE', label: 'Germany' }, + { prefix: '33', id: 73, iso: 'FR', label: 'France' }, + { prefix: '1', id: 187, iso: 'US', label: 'USA' }, ]); const activationPriceHintsByKey = new Map(); let activePhoneVerificationLogStep = null; @@ -587,6 +587,44 @@ return fallbackNormalized || DEFAULT_HERO_SMS_OPERATOR; } + function normalizeMaDaoCountryCode(value = '', fallback = '') { + const raw = String(value || '').trim() || String(fallback || '').trim(); + if (!raw) { + return ''; + } + if (/^[a-z]{2}$/i.test(raw)) { + return raw.toUpperCase(); + } + return raw; + } + + function getRegionDisplayName(regionCode, locale = 'en') { + const normalizedRegionCode = normalizeMaDaoCountryCode(regionCode, ''); + const normalizedLocale = String(locale || '').trim(); + if (!/^[A-Z]{2}$/.test(normalizedRegionCode) || !normalizedLocale || typeof Intl?.DisplayNames !== 'function') { + return ''; + } + try { + return String( + new Intl.DisplayNames([normalizedLocale], { type: 'region' }).of(normalizedRegionCode) || '' + ).trim(); + } catch { + return ''; + } + } + + function normalizeMaDaoCountryLabel(value = '', countryCode = '') { + const label = String(value || '').trim(); + if (label) { + return label; + } + const normalizedCountryCode = normalizeMaDaoCountryCode(countryCode, ''); + if (!normalizedCountryCode) { + return ''; + } + return getRegionDisplayName(normalizedCountryCode, 'en') || normalizedCountryCode; + } + function inferHeroSmsCountryFromPhoneNumber(phoneNumber = '') { const digits = String(phoneNumber || '').replace(/\D+/g, ''); if (!digits) { @@ -598,10 +636,22 @@ } return { id: normalizeCountryId(match.id, 0), + iso: normalizeMaDaoCountryCode(match.iso, ''), label: normalizeCountryLabel(match.label, `Country #${match.id}`), }; } + function inferMaDaoCountryFromPhoneNumber(phoneNumber = '') { + const inferred = inferHeroSmsCountryFromPhoneNumber(phoneNumber); + if (!inferred?.iso) { + return null; + } + return { + id: inferred.iso, + label: normalizeMaDaoCountryLabel(inferred.label, inferred.iso), + }; + } + function normalizePhoneCodeWaitSeconds(value) { const parsed = Math.floor(Number(value)); if (!Number.isFinite(parsed) || parsed <= 0) { @@ -1391,6 +1441,9 @@ const rawProvider = String(record.provider || '').trim(); const provider = normalizePhoneSmsProvider(rawProvider); const rawCountryId = record.countryId ?? record.country; + const inferredCountry = provider === PHONE_SMS_PROVIDER_MADAO + ? inferMaDaoCountryFromPhoneNumber(phoneNumber) + : null; const fallbackCountryId = provider === PHONE_SMS_PROVIDER_FIVE_SIM ? 'england' : (provider === PHONE_SMS_PROVIDER_MADAO ? '' : HERO_SMS_COUNTRY_ID); @@ -1412,10 +1465,13 @@ ? normalizeNexSmsCountryId(rawCountryId, 0) : ( provider === PHONE_SMS_PROVIDER_MADAO - ? String(rawCountryId || '').trim() + ? normalizeMaDaoCountryCode(rawCountryId, inferredCountry?.id || '') : normalizeCountryId(rawCountryId, fallbackCountryId) ) ); + const normalizedCountryLabel = provider === PHONE_SMS_PROVIDER_MADAO + ? normalizeMaDaoCountryLabel(countryLabel, countryId) + : countryLabel; const ignoredPhoneCodeKeys = normalizeStringList(record.ignoredPhoneCodeKeys); return { activationId, @@ -1424,7 +1480,7 @@ serviceCode, countryId, ...(provider === PHONE_SMS_PROVIDER_FIVE_SIM ? { countryCode: countryId } : {}), - ...(countryLabel ? { countryLabel } : {}), + ...(normalizedCountryLabel ? { countryLabel: normalizedCountryLabel } : {}), successfulUses: normalizeUseCount(record.successfulUses), maxUses: Math.max(1, Math.floor(Number(record.maxUses) || DEFAULT_PHONE_NUMBER_MAX_USES)), ...(expiresAt > 0 ? { expiresAt } : {}), @@ -4888,6 +4944,18 @@ const providerId = getActivationProviderId(activation, fallbackState); const candidates = resolveCountryCandidatesForProvider(fallbackState, providerId); if (activation && typeof activation === 'object') { + if (providerId === PHONE_SMS_PROVIDER_MADAO) { + const countryId = normalizeMaDaoCountryCode(activation.countryId ?? activation.country, ''); + const inferredCountry = inferMaDaoCountryFromPhoneNumber(activation.phoneNumber); + const resolvedCountryId = countryId || inferredCountry?.id || ''; + return { + id: resolvedCountryId, + label: normalizeMaDaoCountryLabel( + activation.countryLabel || inferredCountry?.label || '', + resolvedCountryId + ), + }; + } if (providerId === PHONE_SMS_PROVIDER_FIVE_SIM) { const countryId = normalizeFiveSimCountryId(activation.countryId, ''); if (countryId) { @@ -4898,14 +4966,6 @@ label: normalizeFiveSimCountryLabel(activation.countryLabel, countryId), }; } - } else if (providerId === PHONE_SMS_PROVIDER_MADAO) { - const countryId = String(activation.countryId || '').trim(); - if (countryId) { - return { - id: countryId, - label: normalizeCountryLabel(activation.countryLabel, countryId), - }; - } } else { const inferredCountry = inferHeroSmsCountryFromPhoneNumber(activation.phoneNumber); const rawCountryId = normalizeCountryId(activation.countryId, 0); @@ -6668,8 +6728,8 @@ return normalizedCountryId >= 0 ? `${normalizedProvider}:${normalizedCountryId}` : ''; } if (normalizedProvider === PHONE_SMS_PROVIDER_MADAO) { - const normalizedCountryId = String(countryId || '').trim(); - return normalizedCountryId ? `${normalizedProvider}:${normalizedCountryId}` : ''; + const normalizedCountryCode = normalizeMaDaoCountryCode(countryId, ''); + return normalizedCountryCode ? `${normalizedProvider}:${normalizedCountryCode}` : ''; } const normalizedCountryId = normalizeCountryId(countryId, 0); return normalizedCountryId > 0 ? `${normalizedProvider}:${normalizedCountryId}` : ''; @@ -6712,7 +6772,7 @@ return matched?.label || `Country #${normalizedCountryId}`; } if (normalizedProvider === PHONE_SMS_PROVIDER_MADAO) { - return normalizedCountryKey || 'MaDao'; + return normalizeMaDaoCountryLabel('', normalizedCountryKey) || normalizedCountryKey || 'Unknown country'; } const normalizedCountryId = normalizeCountryId(normalizedCountryKey, 0); const matched = resolveCountryCandidates(state) @@ -6816,9 +6876,11 @@ const getCountryFailureKey = (countryId, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) => ( normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_FIVE_SIM ? normalizeFiveSimCountryId(countryId, '') - : (normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_MADAO - ? String(countryId || '').trim() - : String(normalizeCountryId(countryId, 0) || '')) + : ( + normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_MADAO + ? normalizeMaDaoCountryCode(countryId, '') + : String(normalizeCountryId(countryId, 0) || '') + ) ); const getCountryFailureCount = (countryId, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) => { diff --git a/flows/openai/content/phone-auth.js b/flows/openai/content/phone-auth.js index 0c55e75..85a6c08 100644 --- a/flows/openai/content/phone-auth.js +++ b/flows/openai/content/phone-auth.js @@ -199,6 +199,23 @@ return String(visibleSpan?.textContent || '').trim(); } + function phoneNumberMatchesDialCode(phoneNumber = '', dialCode = '') { + const digits = normalizePhoneDigits(phoneNumber); + const normalizedDialCode = normalizePhoneDigits(dialCode); + if (!digits || !normalizedDialCode) { + return false; + } + return digits.startsWith(normalizedDialCode) && digits.length > normalizedDialCode.length; + } + + function selectedCountryMatchesPhoneNumber(phoneNumber = '') { + const digits = normalizePhoneDigits(phoneNumber); + if (!digits) { + return true; + } + return phoneNumberMatchesDialCode(phoneNumber, getDisplayedDialCode()); + } + function toNationalPhoneNumber(value, dialCode) { const digits = normalizePhoneDigits(value); const normalizedDialCode = normalizePhoneDigits(dialCode); @@ -364,15 +381,17 @@ const byLabel = findCountryOptionByLabel(countryLabel); if (await trySelectCountryOption(select, byLabel)) { - return true; + if (selectedCountryMatchesPhoneNumber(phoneNumber)) { + return true; + } } const byPhoneNumber = findCountryOptionByPhoneNumber(phoneNumber); if (await trySelectCountryOption(select, byPhoneNumber)) { - return true; + return selectedCountryMatchesPhoneNumber(phoneNumber); } - return Boolean(getSelectedCountryOption()); + return selectedCountryMatchesPhoneNumber(phoneNumber); } function getAddPhoneSubmitButton() { diff --git a/phone-sms/providers/madao.js b/phone-sms/providers/madao.js index 0fda48c..e625785 100644 --- a/phone-sms/providers/madao.js +++ b/phone-sms/providers/madao.js @@ -366,7 +366,6 @@ routing_plan_id: activation?.madaoRoutingPlanId, routing_plan_name: activation?.madaoRoutingPlanName, service: activation?.serviceCode, - country: activation?.countryId, }); if (!nextTicket) { throw new Error('MaDao 返回的下一条路由激活记录无效。'); diff --git a/tests/phone-auth-country-match.test.js b/tests/phone-auth-country-match.test.js index 3a58ffd..de59c72 100644 --- a/tests/phone-auth-country-match.test.js +++ b/tests/phone-auth-country-match.test.js @@ -356,6 +356,68 @@ test('phone auth can auto-select country by dial code even when number has no pl } }); +test('phone auth rejects stale selected country when it does not match phone dial code', async () => { + const originalDocument = global.document; + const originalEvent = global.Event; + const originalLocation = global.location; + + const dom = createFakeAddPhoneDom({ + options: [ + { value: 'TH', textContent: 'Thailand (+66)', buttonText: 'Thailand (+66)' }, + { value: 'GB', textContent: 'United Kingdom (+44)', buttonText: 'United Kingdom (+44)' }, + ], + selectedIndex: 0, + }); + + global.document = dom.document; + global.Event = class Event { + constructor(type) { + this.type = type; + } + }; + global.location = { href: 'https://auth.openai.com/add-phone' }; + + try { + const helpers = api.createPhoneAuthHelpers({ + fillInput: (element, value) => { + element.value = value; + }, + getActionText: () => '', + getPageTextSnapshot: () => '', + getVerificationErrorText: () => '', + humanPause: async () => {}, + isActionEnabled: () => true, + isAddPhonePageReady: () => true, + isConsentReady: () => false, + isPhoneVerificationPageReady: () => false, + isVisibleElement: () => true, + simulateClick: (element) => { + element.click?.(); + }, + sleep: async () => {}, + throwIfStopped: () => {}, + waitForElement: async () => null, + }); + + await assert.rejects( + () => helpers.submitPhoneNumber({ + countryLabel: 'Country #10', + phoneNumber: '+84943328460', + }), + /Failed to select "Country #10" on the add-phone page\./ + ); + + assert.equal(dom.select.value, 'TH'); + assert.equal(dom.phoneInput.value, ''); + assert.equal(dom.hiddenPhoneInput.value, ''); + assert.equal(dom.wasSubmitClicked(), false); + } finally { + global.document = originalDocument; + global.Event = originalEvent; + global.location = originalLocation; + } +}); + test('phone auth resend stops with PHONE_ROUTE_405_RECOVERY_FAILED instead of endless Try-again loop', async () => { const originalDocument = global.document; const originalLocation = global.location; diff --git a/tests/phone-verification-flow.test.js b/tests/phone-verification-flow.test.js index 0b5c391..e45c2b6 100644 --- a/tests/phone-verification-flow.test.js +++ b/tests/phone-verification-flow.test.js @@ -285,7 +285,6 @@ test('phone verification helper acquires, polls and releases MaDao activation th }; const helpers = api.createPhoneVerificationHelpers({ addLog: async () => {}, - createMaDaoProvider: maDaoModule.createProvider, ensureStep8SignupPageReady: async () => {}, fetchImpl: async (url, options = {}) => { const parsedUrl = new URL(url); @@ -365,6 +364,161 @@ test('phone verification helper acquires, polls and releases MaDao activation th assert.deepStrictEqual(requests[2].body, { ticket_id: 'madao-1', action: 'finish' }); }); +test('phone verification helper keeps MaDao routing replacement country as ISO code on resubmit', async () => { + const requests = []; + const messages = []; + let currentState = { + phoneSmsProvider: 'madao', + madaoBaseUrl: 'http://127.0.0.1:7822/', + madaoHttpSecret: 'secret-token', + madaoMode: 'routing_plan', + madaoRoutingPlanId: 'rp-openai', + verificationResendCount: 0, + phoneVerificationReplacementLimit: 2, + phoneCodeWaitSeconds: 15, + phoneCodeTimeoutWindows: 1, + phoneCodePollIntervalSeconds: 1, + phoneCodePollMaxRounds: 1, + currentPhoneActivation: null, + reusablePhoneActivation: null, + }; + let acquireCalls = 0; + let replaceCalls = 0; + + const helpers = api.createPhoneVerificationHelpers({ + addLog: async () => {}, + ensureStep8SignupPageReady: async () => {}, + fetchImpl: async (url, options = {}) => { + const parsedUrl = new URL(url); + const body = options.body ? JSON.parse(options.body) : null; + requests.push({ url: parsedUrl, options, body }); + if (parsedUrl.pathname === '/api/acquire') { + acquireCalls += 1; + return { + ok: true, + status: 200, + text: async () => JSON.stringify({ + ticket_id: 'madao-ticket-routing-1', + provider: 'herosms', + service: 'openai', + country: 'TH', + phone_number: '+66950002222', + routing_plan_id: 'rp-openai', + routing_plan_name: 'OpenAI Plan', + routing_item_id: 'route-1', + status: 'waiting_code', + }), + }; + } + if (parsedUrl.pathname === '/api/poll') { + return { + ok: true, + status: 200, + text: async () => JSON.stringify({ + ticket_id: body.ticket_id, + provider: body.ticket_id === 'madao-ticket-routing-1' ? 'herosms' : 'smsbower', + status: body.ticket_id === 'madao-ticket-routing-1' ? 'waiting_code' : 'code_received', + code: body.ticket_id === 'madao-ticket-routing-1' ? null : '654321', + }), + }; + } + if (parsedUrl.pathname === '/api/routing/replace') { + replaceCalls += 1; + return { + ok: true, + status: 200, + text: async () => JSON.stringify({ + current_ticket_id: 'madao-ticket-routing-1', + current_ticket_release: { + ticket_id: 'madao-ticket-routing-1', + provider: 'herosms', + status: 'cancelled', + }, + next_ticket: { + ticket_id: 'madao-ticket-routing-2', + provider: 'smsbower', + service: 'openai', + country: 'VN', + phone_number: '+84943328460', + routing_plan_id: 'rp-openai', + routing_plan_name: 'OpenAI Plan', + routing_item_id: 'route-2', + status: 'waiting_code', + }, + }), + }; + } + if (parsedUrl.pathname === '/api/release') { + return { + ok: true, + status: 200, + text: async () => JSON.stringify({ ok: true }), + }; + } + throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`); + }, + getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs, + getState: async () => ({ ...currentState }), + sendToContentScriptResilient: async (_source, message) => { + messages.push(message); + if (message.type === 'STEP8_GET_STATE') { + return { + addPhonePage: true, + phoneVerificationPage: false, + url: 'https://auth.openai.com/add-phone', + }; + } + if (message.type === 'SUBMIT_PHONE_NUMBER') { + return { + phoneVerificationPage: true, + url: 'https://auth.openai.com/phone-verification', + }; + } + if (message.type === 'RETURN_TO_ADD_PHONE') { + return { + addPhonePage: true, + url: 'https://auth.openai.com/add-phone', + }; + } + if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') { + return { + success: true, + consentReady: true, + url: 'https://auth.openai.com/authorize', + }; + } + throw new Error(`Unexpected content-script message: ${message.type}`); + }, + setState: async (updates) => { + currentState = { ...currentState, ...updates }; + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + const result = await helpers.completePhoneVerificationFlow(1, { + addPhonePage: true, + phoneVerificationPage: false, + url: 'https://auth.openai.com/add-phone', + }); + + assert.deepStrictEqual(result, { + success: true, + consentReady: true, + url: 'https://auth.openai.com/authorize', + }); + assert.equal(acquireCalls, 1); + assert.equal(replaceCalls, 1); + const phoneSubmissions = messages.filter((message) => message.type === 'SUBMIT_PHONE_NUMBER'); + assert.equal(phoneSubmissions.length, 2); + assert.equal(phoneSubmissions[0].payload.phoneNumber, '+66950002222'); + assert.equal(phoneSubmissions[0].payload.countryId, 'TH'); + assert.equal(phoneSubmissions[0].payload.countryLabel, 'Thailand'); + assert.equal(phoneSubmissions[1].payload.phoneNumber, '+84943328460'); + assert.equal(phoneSubmissions[1].payload.countryId, 'VN'); + assert.equal(phoneSubmissions[1].payload.countryLabel, 'Vietnam'); +}); + test('signup phone helper persists signup runtime state without touching add-phone activation', async () => { const setStateCalls = []; let currentState = {