fix: 兼容中文 add-phone 页的国家匹配

This commit is contained in:
zhangkun
2026-04-26 18:43:38 +08:00
parent 48bc07b94c
commit 28d0e56702
2 changed files with 275 additions and 8 deletions
+74 -8
View File
@@ -50,6 +50,69 @@
.trim();
}
function normalizeCountryOptionValue(value) {
return String(value || '').trim().toUpperCase();
}
function getRegionDisplayName(regionCode, locale) {
const normalizedRegionCode = normalizeCountryOptionValue(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 getCountryOptionMatchLabels(option) {
const labels = new Set();
const pushLabel = (value) => {
const label = String(value || '').replace(/\s+/g, ' ').trim();
if (label) {
labels.add(label);
}
};
pushLabel(getOptionLabel(option));
const regionCode = normalizeCountryOptionValue(option?.value);
if (/^[A-Z]{2}$/.test(regionCode)) {
pushLabel(regionCode);
pushLabel(getRegionDisplayName(regionCode, 'en'));
const pageLocale = String(
document?.documentElement?.lang
|| document?.documentElement?.getAttribute?.('lang')
|| self?.navigator?.language
|| ''
).trim();
if (pageLocale && !/^en(?:[-_]|$)/i.test(pageLocale)) {
pushLabel(getRegionDisplayName(regionCode, pageLocale));
}
}
return Array.from(labels);
}
function isSameCountryOption(left, right) {
if (!left || !right) {
return false;
}
const leftValue = normalizeCountryOptionValue(left.value);
const rightValue = normalizeCountryOptionValue(right.value);
if (leftValue && rightValue) {
return leftValue === rightValue;
}
return normalizeCountryLabel(getOptionLabel(left)) === normalizeCountryLabel(getOptionLabel(right));
}
function extractDialCodeFromText(value) {
const match = String(value || '').match(/\(\+\s*(\d{1,4})\s*\)|\+\s*(\d{1,4})\b/);
return String(match?.[1] || match?.[2] || '').trim();
@@ -161,10 +224,16 @@
}
const options = Array.from(select.options);
return options.find((option) => normalizeCountryLabel(getOptionLabel(option)) === normalizedTarget)
return options.find((option) => (
getCountryOptionMatchLabels(option).some((label) => normalizeCountryLabel(label) === normalizedTarget)
))
|| options.find((option) => {
const optionLabel = normalizeCountryLabel(getOptionLabel(option));
return optionLabel && (optionLabel.includes(normalizedTarget) || normalizedTarget.includes(optionLabel));
const normalizedLabels = getCountryOptionMatchLabels(option)
.map((label) => normalizeCountryLabel(label))
.filter(Boolean);
return normalizedLabels.some((optionLabel) => (
optionLabel.includes(normalizedTarget) || normalizedTarget.includes(optionLabel)
));
})
|| null;
}
@@ -181,7 +250,7 @@
}
const selectedOption = getSelectedCountryOption();
if (selectedOption && normalizeCountryLabel(getOptionLabel(selectedOption)) === normalizeCountryLabel(getOptionLabel(targetOption))) {
if (selectedOption && isSameCountryOption(selectedOption, targetOption)) {
return true;
}
@@ -190,10 +259,7 @@
await sleep(250);
const nextSelectedOption = getSelectedCountryOption();
return Boolean(
nextSelectedOption
&& normalizeCountryLabel(getOptionLabel(nextSelectedOption)) === normalizeCountryLabel(getOptionLabel(targetOption))
);
return Boolean(nextSelectedOption && isSameCountryOption(nextSelectedOption, targetOption));
}
function getAddPhoneSubmitButton() {
+201
View File
@@ -0,0 +1,201 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/phone-auth.js', 'utf8');
const globalScope = { navigator: { language: 'zh-CN' } };
const api = new Function('self', `${source}; return self.MultiPagePhoneAuth;`)(globalScope);
function createFakeAddPhoneDom() {
const selectEvents = [];
const hiddenInputEvents = [];
let submitClicked = false;
const options = [
{ value: 'US', textContent: '美国' },
{ value: 'TH', textContent: '泰国' },
];
const select = {
options,
selectedIndex: 0,
dispatchEvent(event) {
selectEvents.push(event?.type || '');
return true;
},
};
Object.defineProperty(select, 'value', {
get() {
return options[select.selectedIndex]?.value || '';
},
set(nextValue) {
const nextIndex = options.findIndex((option) => option.value === String(nextValue || ''));
if (nextIndex >= 0) {
select.selectedIndex = nextIndex;
}
},
});
const phoneInput = {
value: '',
type: 'tel',
dispatchEvent() {
return true;
},
closest() {
return null;
},
};
const hiddenPhoneInput = {
value: '',
dispatchEvent(event) {
hiddenInputEvents.push(event?.type || '');
return true;
},
};
const selectValueNode = {
get textContent() {
return select.value === 'TH' ? '泰国 (+66)' : '美国 (+1)';
},
};
const countryButton = {
querySelector(selector) {
return selector === '.react-aria-SelectValue' ? selectValueNode : null;
},
get textContent() {
return selectValueNode.textContent;
},
};
const submitButton = {
type: 'submit',
click() {
submitClicked = true;
},
};
const form = {
querySelector(selector) {
switch (selector) {
case 'select':
return select;
case 'input[type="tel"], input[name="__reservedForPhoneNumberInput_tel"], input[autocomplete="tel"]':
return phoneInput;
case 'input[name="phoneNumber"]':
return hiddenPhoneInput;
case 'button[aria-haspopup="listbox"]':
return countryButton;
default:
return null;
}
},
querySelectorAll(selector) {
if (selector === 'button[type="submit"], input[type="submit"]') {
return [submitButton];
}
return [];
},
};
const document = {
documentElement: {
lang: 'zh-CN',
getAttribute(name) {
return name === 'lang' ? 'zh-CN' : '';
},
},
querySelector(selector) {
if (selector === 'form[action*="/add-phone" i]') {
return form;
}
return null;
},
};
return {
document,
hiddenInputEvents,
hiddenPhoneInput,
phoneInput,
select,
selectEvents,
submitButton,
wasSubmitClicked: () => submitClicked,
};
}
test('phone auth matches english HeroSMS country labels against localized add-phone options', async () => {
const originalDocument = global.document;
const originalEvent = global.Event;
const originalLocation = global.location;
const OriginalDisplayNames = Intl.DisplayNames;
const dom = createFakeAddPhoneDom();
let phoneVerificationReady = false;
global.document = dom.document;
global.Event = class Event {
constructor(type) {
this.type = type;
}
};
global.location = { href: 'https://auth.openai.com/add-phone' };
Intl.DisplayNames = class DisplayNames {
of(regionCode) {
if (regionCode === 'TH') return 'Thailand';
if (regionCode === 'US') return 'United States';
return regionCode;
}
};
try {
const helpers = api.createPhoneAuthHelpers({
fillInput: (element, value) => {
element.value = value;
},
getActionText: () => '',
getPageTextSnapshot: () => '',
getVerificationErrorText: () => '',
humanPause: async () => {},
isActionEnabled: () => true,
isAddPhonePageReady: () => true,
isConsentReady: () => false,
isPhoneVerificationPageReady: () => phoneVerificationReady,
isVisibleElement: () => true,
simulateClick: (element) => {
element.click?.();
phoneVerificationReady = true;
global.location.href = 'https://auth.openai.com/phone-verification';
},
sleep: async () => {},
throwIfStopped: () => {},
waitForElement: async () => null,
});
const result = await helpers.submitPhoneNumber({
countryLabel: 'Thailand',
phoneNumber: '66959916439',
});
assert.equal(dom.select.value, 'TH');
assert.deepStrictEqual(dom.selectEvents, ['input', 'change']);
assert.equal(dom.phoneInput.value, '959916439');
assert.equal(dom.hiddenPhoneInput.value, '+66959916439');
assert.deepStrictEqual(dom.hiddenInputEvents, ['input', 'change']);
assert.equal(dom.wasSubmitClicked(), true);
assert.deepStrictEqual(result, {
phoneVerificationPage: true,
displayedPhone: '',
url: 'https://auth.openai.com/phone-verification',
});
} finally {
global.document = originalDocument;
global.Event = originalEvent;
global.location = originalLocation;
Intl.DisplayNames = OriginalDisplayNames;
}
});