diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 34d76f0..32f36fd 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -5827,6 +5827,34 @@ function normalizeHeroSmsFetchErrorMessage(error) { return message; } +function normalizeHeroSmsCountryPayloadEntries(source) { + if (Array.isArray(source)) { + return source; + } + if (!source || typeof source !== 'object') { + return []; + } + return Object.values(source) + .filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry)); +} + +function parseHeroSmsCountryPayload(payload) { + const candidateSources = [ + payload?.value, + payload?.data, + payload?.countries, + payload?.result, + payload, + ]; + for (const source of candidateSources) { + const entries = normalizeHeroSmsCountryPayloadEntries(source); + if (entries.length) { + return entries; + } + } + return []; +} + function normalizeHeroSmsPriceForPreview(value) { const direct = Number(value); if (Number.isFinite(direct) && direct >= 0) { @@ -7147,7 +7175,7 @@ async function loadHeroSmsCountries(options = {}) { }); clearTimeout(timeoutId); const payload = await response.json(); - const countries = Array.isArray(payload?.value) ? payload.value : (Array.isArray(payload) ? payload : []); + const countries = parseHeroSmsCountryPayload(payload); if (!countries.length) { throw new Error('国家列表为空'); } @@ -16247,7 +16275,7 @@ async function switchPhoneSmsProvider(nextProvider) { if (displayPhoneSmsBalance) displayPhoneSmsBalance.textContent = '余额未获取'; if (rowHeroSmsPriceTiers) rowHeroSmsPriceTiers.style.display = 'none'; - await loadHeroSmsCountries(); + await loadHeroSmsCountries({ silent: true }); const restoredPrimary = normalizedNextProvider === PHONE_SMS_PROVIDER_FIVE_SIM ? { id: normalizeFiveSimCountryId(latestState?.fiveSimCountryId), diff --git a/tests/sidepanel-phone-verification-settings.test.js b/tests/sidepanel-phone-verification-settings.test.js index 6d36a6a..6ba1299 100644 --- a/tests/sidepanel-phone-verification-settings.test.js +++ b/tests/sidepanel-phone-verification-settings.test.js @@ -160,9 +160,39 @@ test('sidepanel loads SMS country lists silently during startup fallback', () => assert.doesNotMatch(fiveSimLoader, /console\.(?:warn|error)\('加载 5sim 国家列表失败:'/); assert.match(sidepanelSource, /loadHeroSmsCountries\(\{ silent: true, preferFallbackOnly: true \}\)/); assert.match(sidepanelSource, /loadFiveSimCountries\(\{ silent: true, preferFallbackOnly: true \}\)/); + assert.match(sidepanelSource, /await loadHeroSmsCountries\(\{ silent: true \}\);/); assert.doesNotMatch(sidepanelSource, /console\.error\('加载 (?:HeroSMS|5sim|NexSMS) 国家列表失败:'/); }); +test('HeroSMS country parser accepts keyed country maps from the live API', () => { + const api = new Function(` +${extractFunction('normalizeHeroSmsCountryPayloadEntries')} +${extractFunction('parseHeroSmsCountryPayload')} +return { parseHeroSmsCountryPayload }; +`)(); + + const keyedPayload = { + 52: { id: 52, eng: 'Thailand', chn: '泰国' }, + 6: { id: 6, eng: 'Indonesia', chn: '印度尼西亚' }, + }; + const ids = (payload) => api.parseHeroSmsCountryPayload(payload) + .map((entry) => entry.id) + .sort((left, right) => left - right); + + assert.deepStrictEqual( + ids(keyedPayload), + [6, 52] + ); + assert.deepStrictEqual( + ids({ value: keyedPayload }), + [6, 52] + ); + assert.deepStrictEqual( + ids({ value: Object.values(keyedPayload) }), + [6, 52] + ); +}); + test('sidepanel source wires free reusable phone save and clear actions to runtime messages', () => { assert.match(sidepanelSource, /const inputFreePhoneReuseEnabled = document\.getElementById\('input-free-phone-reuse-enabled'\);/); assert.match(sidepanelSource, /const inputFreePhoneReuseAutoEnabled = document\.getElementById\('input-free-phone-reuse-auto-enabled'\);/);