fix: 修复HeroSMS运营商与接码配置刷新
This commit is contained in:
@@ -73,6 +73,7 @@ test('background account history settings are normalized independently from hotm
|
||||
extractFunction('normalizePhoneCodePollMaxRounds'),
|
||||
extractFunction('normalizeHeroSmsMaxPrice'),
|
||||
extractFunction('normalizeHeroSmsCountryFallback'),
|
||||
extractFunction('normalizeHeroSmsOperator'),
|
||||
extractFunction('normalizePhoneSmsProvider'),
|
||||
extractFunction('normalizeFiveSimCountryId'),
|
||||
extractFunction('normalizeFiveSimCountryLabel'),
|
||||
@@ -114,6 +115,7 @@ const VERIFICATION_RESEND_COUNT_MIN = 0;
|
||||
const VERIFICATION_RESEND_COUNT_MAX = 20;
|
||||
const HERO_SMS_COUNTRY_ID = 52;
|
||||
const HERO_SMS_COUNTRY_LABEL = 'Thailand';
|
||||
const DEFAULT_HERO_SMS_OPERATOR = 'any';
|
||||
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
||||
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
||||
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
||||
@@ -280,6 +282,8 @@ return {
|
||||
assert.equal(api.normalizePersistentSettingValue('heroSmsMaxPrice', '0.123456'), '0.1235');
|
||||
assert.equal(api.normalizePersistentSettingValue('heroSmsMaxPrice', '0'), '');
|
||||
assert.equal(api.normalizePersistentSettingValue('heroSmsPreferredPrice', '0.051234'), '0.0512');
|
||||
assert.equal(api.normalizePersistentSettingValue('heroSmsOperator', ' AIS!! '), 'ais');
|
||||
assert.equal(api.normalizePersistentSettingValue('heroSmsOperator', ''), 'any');
|
||||
assert.equal(api.normalizePersistentSettingValue('signupMethod', 'phone'), 'phone');
|
||||
assert.equal(api.normalizePersistentSettingValue('signupMethod', 'unknown'), 'email');
|
||||
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'codex'), 'openai');
|
||||
|
||||
@@ -83,6 +83,78 @@ test('phone verification helper requests HeroSMS numbers with fixed OpenAI and T
|
||||
assert.equal(requests[1].searchParams.get('api_key'), 'demo-key');
|
||||
});
|
||||
|
||||
test('phone verification helper appends HeroSMS operator when configured', async () => {
|
||||
const requests = [];
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsOperator: 'ais' }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsOperator: ' AIS!! ' });
|
||||
|
||||
const getNumberRequest = requests.find((requestUrl) => requestUrl.searchParams.get('action') === 'getNumber');
|
||||
assert.equal(getNumberRequest.searchParams.get('operator'), 'ais');
|
||||
});
|
||||
|
||||
test('phone verification helper reads latest HeroSMS operator from persistent state', async () => {
|
||||
const requests = [];
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsOperator: 'dtac' }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
||||
|
||||
const getNumberRequest = requests.find((requestUrl) => requestUrl.searchParams.get('action') === 'getNumber');
|
||||
assert.equal(getNumberRequest.searchParams.get('operator'), 'dtac');
|
||||
});
|
||||
|
||||
test('signup phone helper persists signup runtime state without touching add-phone activation', async () => {
|
||||
const setStateCalls = [];
|
||||
let currentState = {
|
||||
@@ -2208,6 +2280,152 @@ test('phone verification helper acquires a number from 5sim with fallback countr
|
||||
assert.equal(requests[3].pathname, '/v1/user/buy/activation/england/any/openai');
|
||||
});
|
||||
|
||||
test('phone verification helper preserves fallback provider while refreshing latest settings', async () => {
|
||||
const heroRequests = [];
|
||||
const fiveSimRequests = [];
|
||||
const submittedNumbers = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'hero-key',
|
||||
phoneSmsProvider: 'hero-sms',
|
||||
phoneSmsProviderOrder: ['hero-sms', '5sim'],
|
||||
heroSmsCountryId: 52,
|
||||
heroSmsCountryLabel: 'Thailand',
|
||||
fiveSimApiKey: 'five-token',
|
||||
fiveSimCountryOrder: ['vietnam'],
|
||||
fiveSimOperator: 'any',
|
||||
fiveSimProduct: 'openai',
|
||||
phoneSmsReuseEnabled: true,
|
||||
verificationResendCount: 0,
|
||||
phoneVerificationReplacementLimit: 1,
|
||||
phoneCodeWaitSeconds: 15,
|
||||
phoneCodeTimeoutWindows: 1,
|
||||
phoneCodePollIntervalSeconds: 1,
|
||||
phoneCodePollMaxRounds: 1,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsedUrl = new URL(url);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (parsedUrl.hostname === 'hero-sms.com') {
|
||||
heroRequests.push(parsedUrl);
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload({ country: '52', cost: 0.05, count: 5 }),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber' || action === 'getNumberV2') {
|
||||
return { ok: true, text: async () => 'NO_NUMBERS' };
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
}
|
||||
fiveSimRequests.push({ url: parsedUrl, options });
|
||||
if (parsedUrl.pathname === '/v1/guest/prices') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
openai: {
|
||||
vietnam: {
|
||||
any: {
|
||||
cost: 0.08,
|
||||
count: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/v1/user/buy/activation/vietnam/any/openai') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
id: 5020,
|
||||
phone: '+84901122334',
|
||||
country: 'vietnam',
|
||||
country_name: 'Vietnam',
|
||||
product: 'openai',
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/v1/user/check/5020') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
id: 5020,
|
||||
phone: '+84901122334',
|
||||
status: 'RECEIVED',
|
||||
sms: [{ text: 'OpenAI code 123456' }],
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/v1/user/finish/5020') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({ status: 'FINISHED' }),
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected 5sim request: ${parsedUrl.pathname}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
submittedNumbers.push(message.payload.phoneNumber);
|
||||
return { phoneVerificationPage: true, url: 'https://auth.openai.com/phone-verification' };
|
||||
}
|
||||
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.deepStrictEqual(submittedNumbers, ['+84901122334']);
|
||||
assert.equal(currentState.reusablePhoneActivation.provider, '5sim');
|
||||
assert.equal(currentState.reusablePhoneActivation.activationId, '5020');
|
||||
assert.equal(heroRequests.some((requestUrl) => requestUrl.searchParams.get('action') === 'getNumber'), true);
|
||||
assert.equal(heroRequests.some((requestUrl) => requestUrl.searchParams.get('action') === 'getNumberV2'), true);
|
||||
assert.equal(
|
||||
heroRequests.every((requestUrl) => ['getPrices', 'getNumber', 'getNumberV2'].includes(requestUrl.searchParams.get('action'))),
|
||||
true
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
fiveSimRequests.map((entry) => entry.url.pathname),
|
||||
[
|
||||
'/v1/guest/prices',
|
||||
'/v1/user/buy/activation/vietnam/any/openai',
|
||||
'/v1/user/check/5020',
|
||||
'/v1/user/finish/5020',
|
||||
]
|
||||
);
|
||||
assert.equal(fiveSimRequests[1].options.headers.Authorization, 'Bearer five-token');
|
||||
});
|
||||
|
||||
test('phone verification helper prefers phoneSmsReuseEnabled over legacy heroSmsReuseEnabled for 5sim acquisition', async () => {
|
||||
const requests = [];
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
|
||||
@@ -95,6 +95,8 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
||||
assert.match(html, /id="row-hero-sms-country-fallback"/);
|
||||
assert.match(html, /id="row-hero-sms-acquire-priority"/);
|
||||
assert.match(html, /id="select-hero-sms-acquire-priority"/);
|
||||
assert.match(html, /id="row-hero-sms-operator"/);
|
||||
assert.match(html, /id="select-hero-sms-operator"/);
|
||||
assert.match(html, /id="select-hero-sms-country"[^>]*multiple/);
|
||||
assert.doesNotMatch(html, /id="select-hero-sms-country-fallback"/);
|
||||
assert.match(html, /id="row-hero-sms-api-key"/);
|
||||
@@ -660,6 +662,7 @@ const rowHeroSmsPlatform = { style: { display: 'none' } };
|
||||
const rowHeroSmsCountry = { style: { display: 'none' } };
|
||||
const rowHeroSmsCountryFallback = { style: { display: 'none' } };
|
||||
const rowHeroSmsAcquirePriority = { style: { display: 'none' } };
|
||||
const rowHeroSmsOperator = { style: { display: 'none' } };
|
||||
const rowHeroSmsApiKey = { style: { display: 'none' } };
|
||||
const rowHeroSmsMaxPrice = { style: { display: 'none' } };
|
||||
const rowFiveSimApiKey = { style: { display: 'none' } };
|
||||
@@ -741,6 +744,7 @@ return {
|
||||
rowHeroSmsCountry,
|
||||
rowHeroSmsCountryFallback,
|
||||
rowHeroSmsAcquirePriority,
|
||||
rowHeroSmsOperator,
|
||||
rowHeroSmsApiKey,
|
||||
rowHeroSmsMaxPrice,
|
||||
rowFiveSimApiKey,
|
||||
@@ -795,6 +799,7 @@ return {
|
||||
assert.equal(api.rowHeroSmsCountry.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsCountryFallback.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsAcquirePriority.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsOperator.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsApiKey.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsMaxPrice.style.display, 'none');
|
||||
assert.equal(api.rowFiveSimOperator.style.display, 'none');
|
||||
@@ -845,6 +850,7 @@ return {
|
||||
assert.equal(api.rowHeroSmsCountry.style.display, '');
|
||||
assert.equal(api.rowHeroSmsCountryFallback.style.display, '');
|
||||
assert.equal(api.rowHeroSmsAcquirePriority.style.display, '');
|
||||
assert.equal(api.rowHeroSmsOperator.style.display, '');
|
||||
assert.equal(api.rowHeroSmsApiKey.style.display, '');
|
||||
assert.equal(api.rowHeroSmsMaxPrice.style.display, '');
|
||||
assert.equal(api.rowFiveSimOperator.style.display, 'none');
|
||||
@@ -1001,6 +1007,7 @@ const inputNexSmsApiKey = { value: 'nex-key' };
|
||||
const inputNexSmsServiceCode = { value: 'ot' };
|
||||
const inputHeroSmsReuseEnabled = { checked: true };
|
||||
const selectHeroSmsAcquirePriority = { value: 'price' };
|
||||
const selectHeroSmsOperator = { value: 'AIS!!' };
|
||||
function getSelectedPhonePreferredActivation() {
|
||||
return {
|
||||
provider: 'hero-sms',
|
||||
@@ -1039,6 +1046,7 @@ const DEFAULT_HERO_SMS_REUSE_ENABLED = true;
|
||||
const HERO_SMS_ACQUIRE_PRIORITY_COUNTRY = 'country';
|
||||
const HERO_SMS_ACQUIRE_PRIORITY_PRICE = 'price';
|
||||
const DEFAULT_HERO_SMS_ACQUIRE_PRIORITY = HERO_SMS_ACQUIRE_PRIORITY_COUNTRY;
|
||||
const DEFAULT_HERO_SMS_OPERATOR = 'any';
|
||||
const PHONE_REPLACEMENT_LIMIT_MIN = 1;
|
||||
const PHONE_REPLACEMENT_LIMIT_MAX = 20;
|
||||
const DEFAULT_HERO_SMS_COUNTRY_ID = 52;
|
||||
@@ -1101,6 +1109,7 @@ ${extractFunction('normalizeFiveSimCountryFallbackList')}
|
||||
${extractFunction('normalizePhoneSmsMaxPriceValue')}
|
||||
${extractFunction('normalizePhoneSmsMinPriceValue')}
|
||||
${extractFunction('normalizeHeroSmsMaxPriceValue')}
|
||||
${extractFunction('normalizeHeroSmsOperatorValue')}
|
||||
${extractFunction('normalizePhoneVerificationReplacementLimit')}
|
||||
${extractFunction('normalizePhoneCodeWaitSecondsValue')}
|
||||
${extractFunction('normalizePhoneCodeTimeoutWindowsValue')}
|
||||
@@ -1153,6 +1162,7 @@ return { collectSettingsPayload };
|
||||
assert.equal(payload.freePhoneReuseEnabled, false);
|
||||
assert.equal(payload.freePhoneReuseAutoEnabled, false);
|
||||
assert.equal(payload.heroSmsAcquirePriority, 'price');
|
||||
assert.equal(payload.heroSmsOperator, 'ais');
|
||||
assert.equal(payload.heroSmsMinPrice, '0.03');
|
||||
assert.equal(payload.heroSmsMaxPrice, '0.12');
|
||||
assert.equal(payload.heroSmsPreferredPrice, '0.0512');
|
||||
@@ -1201,6 +1211,7 @@ const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
||||
const DEFAULT_FIVE_SIM_COUNTRY_ID = 'vietnam';
|
||||
const DEFAULT_FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)';
|
||||
const DEFAULT_FIVE_SIM_OPERATOR = 'any';
|
||||
const DEFAULT_HERO_SMS_OPERATOR = 'any';
|
||||
const DEFAULT_HERO_SMS_COUNTRY_ID = 52;
|
||||
const DEFAULT_HERO_SMS_COUNTRY_LABEL = 'Thailand';
|
||||
const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(['indonesia', 'thailand', 'vietnam']);
|
||||
@@ -1210,6 +1221,7 @@ const inputHeroSmsApiKey = { value: 'hero-live' };
|
||||
const inputHeroSmsMinPrice = { value: '0.03' };
|
||||
const inputHeroSmsMaxPrice = { value: '0.22' };
|
||||
const inputFiveSimOperator = { value: 'any' };
|
||||
const selectHeroSmsOperator = { value: 'ais', options: [{ value: 'any' }, { value: 'ais' }] };
|
||||
const displayHeroSmsPriceTiers = { textContent: '' };
|
||||
const displayPhoneSmsBalance = { textContent: '' };
|
||||
const rowHeroSmsPriceTiers = { style: { display: '' } };
|
||||
@@ -1229,6 +1241,7 @@ ${extractFunction('normalizePhoneSmsMinPriceValue')}
|
||||
${extractFunction('normalizePhoneSmsMaxPriceValue')}
|
||||
${extractFunction('normalizeHeroSmsCountryId')}
|
||||
${extractFunction('normalizeHeroSmsCountryLabel')}
|
||||
${extractFunction('normalizeHeroSmsOperatorValue')}
|
||||
${extractFunction('normalizeHeroSmsCountryFallbackList')}
|
||||
${extractFunction('normalizeFiveSimCountryFallbackList')}
|
||||
function getSelectedHeroSmsCountryOption() {
|
||||
@@ -1244,6 +1257,8 @@ function syncHeroSmsFallbackSelectionOrderFromSelect() {
|
||||
function syncLatestState(patch) { latestState = { ...latestState, ...patch }; }
|
||||
function loadHeroSmsCountries() { return Promise.resolve(); }
|
||||
function applyHeroSmsFallbackSelection() {}
|
||||
function setHeroSmsOperatorSelectValue(operator = latestState?.heroSmsOperator) { selectHeroSmsOperator.value = normalizeHeroSmsOperatorValue(operator); }
|
||||
function refreshHeroSmsOperatorOptions() { return Promise.resolve(); }
|
||||
function updatePhoneVerificationSettingsUI() {}
|
||||
function markSettingsDirty() {}
|
||||
function saveSettings() { savedPayload = { ...latestState }; return Promise.resolve(); }
|
||||
@@ -1291,6 +1306,26 @@ return {
|
||||
assert.equal(api.savedPayload.fiveSimMinPrice, '0.88');
|
||||
});
|
||||
|
||||
test('HeroSMS operator helpers normalize keyed operator payloads', () => {
|
||||
const api = new Function(`
|
||||
const DEFAULT_HERO_SMS_OPERATOR = 'any';
|
||||
${extractFunction('normalizeHeroSmsOperatorValue')}
|
||||
${extractFunction('parseHeroSmsOperatorsPayload')}
|
||||
return { normalizeHeroSmsOperatorValue, parseHeroSmsOperatorsPayload };
|
||||
`)();
|
||||
|
||||
assert.equal(api.normalizeHeroSmsOperatorValue(' AIS!! '), 'ais');
|
||||
assert.equal(api.normalizeHeroSmsOperatorValue('', 'dtac'), 'dtac');
|
||||
const parsed = api.parseHeroSmsOperatorsPayload({
|
||||
operators: {
|
||||
52: [' AIS ', 'dtac', 'AIS'],
|
||||
6: ['telkomsel'],
|
||||
},
|
||||
});
|
||||
assert.deepStrictEqual(parsed.get('52'), ['ais', 'dtac']);
|
||||
assert.deepStrictEqual(parsed.get('6'), ['telkomsel']);
|
||||
});
|
||||
|
||||
test('formatPhoneSmsPriceEntriesSummary treats HeroSMS physicalCount=0 as out of stock even when count is positive', () => {
|
||||
const api = new Function(`
|
||||
${extractFunction('normalizeHeroSmsPriceForPreview')}
|
||||
|
||||
Reference in New Issue
Block a user