fix: 补齐 MaDao 直连动态选项

恢复 MaDao 直连模式的服务商、国家、线路下拉,并通过 daemon providers/countries/operators 接口级联加载。保存 madaoOperator 并在 direct acquire 中通过 metadata.operator 下发,routing 模式保留已保存的直连配置。
This commit is contained in:
Isulew
2026-05-29 17:53:28 +08:00
parent 0938f5dfba
commit ed1d62687c
10 changed files with 596 additions and 41 deletions
@@ -71,6 +71,7 @@ test('background account history settings are normalized independently from hotm
extractFunction('normalizeMaDaoIdentifier'),
extractFunction('normalizeMaDaoProviderId'),
extractFunction('normalizeMaDaoCountry'),
extractFunction('normalizeMaDaoOperator'),
extractFunction('normalizeMaDaoPrice'),
extractFunction('normalizePhonePreferredActivation'),
extractFunction('normalizePhoneVerificationReplacementLimit'),
@@ -382,6 +383,7 @@ return {
assert.equal(api.normalizePersistentSettingValue('madaoProviderId', ' Upstream A! '), 'upstreama');
assert.equal(api.normalizePersistentSettingValue('madaoCountry', ' gb '), 'GB');
assert.equal(api.normalizePersistentSettingValue('madaoCountry', 'ANY'), 'any');
assert.equal(api.normalizePersistentSettingValue('madaoOperator', ' Operator A! '), 'operatora');
assert.equal(api.normalizePersistentSettingValue('madaoAutoPickCountry', 1), true);
assert.equal(api.normalizePersistentSettingValue('madaoReusePhone', 0), false);
assert.equal(api.normalizePersistentSettingValue('madaoMinPrice', '0.123456'), '0.1235');
@@ -419,6 +419,7 @@ return {
madaoRoutingPlanId: 'rp-openai',
madaoProviderId: 'upstream-a',
madaoCountry: 'GB',
madaoOperator: 'operator-a',
madaoAutoPickCountry: false,
madaoReusePhone: true,
madaoMinPrice: '0.12',
@@ -452,6 +453,7 @@ return {
assert.equal(api.getPayloadInput().madaoRoutingPlanId, 'rp-openai');
assert.equal(api.getPayloadInput().madaoProviderId, 'upstream-a');
assert.equal(api.getPayloadInput().madaoCountry, 'GB');
assert.equal(api.getPayloadInput().madaoOperator, 'operator-a');
assert.equal(api.getPayloadInput().madaoAutoPickCountry, false);
assert.equal(api.getPayloadInput().madaoReusePhone, true);
assert.equal(api.getPayloadInput().madaoMinPrice, '0.12');
@@ -118,6 +118,7 @@ const PERSISTED_SETTING_DEFAULTS = {
madaoRoutingPlanId: '',
madaoProviderId: '',
madaoCountry: '',
madaoOperator: '',
madaoAutoPickCountry: true,
madaoReusePhone: true,
madaoMinPrice: '',
@@ -219,6 +220,7 @@ function normalizeMaDaoBaseUrl(value = '') {
function normalizeMaDaoMode(value = '') { return String(value || '').trim().toLowerCase() === 'direct' ? 'direct' : DEFAULT_MADAO_MODE; }
function normalizeMaDaoIdentifier(value = '') { return String(value || '').trim(); }
function normalizeMaDaoProviderId(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); }
function normalizeMaDaoOperator(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); }
function normalizeMaDaoCountry(value = '') {
const trimmed = String(value || '').trim();
if (!trimmed) return '';
@@ -306,6 +308,7 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
assert.equal(payload.phoneSmsProvider, 'hero-sms');
assert.equal(payload.madaoBaseUrl, DEFAULT_MADAO_BASE_URL_FOR_TEST);
assert.equal(payload.madaoMode, DEFAULT_MADAO_MODE_FOR_TEST);
assert.equal(payload.madaoOperator, '');
assert.equal(payload.madaoAutoPickCountry, true);
assert.equal(payload.madaoReusePhone, true);
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
@@ -724,6 +727,7 @@ test('buildPersistentSettingsPayload persists normalized MaDao flat settings out
madaoRoutingPlanId: ' rp-openai ',
madaoProviderId: ' Upstream A! ',
madaoCountry: ' gb ',
madaoOperator: ' Operator A! ',
madaoAutoPickCountry: 0,
madaoReusePhone: 1,
madaoMinPrice: '0.123456',
@@ -737,6 +741,7 @@ test('buildPersistentSettingsPayload persists normalized MaDao flat settings out
assert.equal(payload.madaoRoutingPlanId, 'rp-openai');
assert.equal(payload.madaoProviderId, 'upstreama');
assert.equal(payload.madaoCountry, 'GB');
assert.equal(payload.madaoOperator, 'operatora');
assert.equal(payload.madaoAutoPickCountry, false);
assert.equal(payload.madaoReusePhone, true);
assert.equal(payload.madaoMinPrice, '0.1235');
+4
View File
@@ -38,6 +38,7 @@ test('MaDao direct acquire sends only direct fields and normalizes activation da
madaoRoutingPlanId: 'route-plan-should-not-be-sent',
madaoProviderId: 'Upstream A!',
madaoCountry: 'gb',
madaoOperator: 'Operator A!',
madaoAutoPickCountry: 'false',
madaoReusePhone: '1',
madaoMinPrice: '0.01',
@@ -54,6 +55,9 @@ test('MaDao direct acquire sends only direct fields and normalizes activation da
auto_pick_country: false,
reuse_phone: true,
country: 'GB',
metadata: {
operator: 'operatora',
},
min_price: 0.01,
max_price: 0.2,
});
+4
View File
@@ -9214,6 +9214,7 @@ test('phone verification helper reacquires MaDao direct numbers after releasing
madaoRoutingPlanId: 'rp-stale',
madaoProviderId: 'upstream-a',
madaoCountry: 'gb',
madaoOperator: 'operator-a',
madaoAutoPickCountry: false,
madaoReusePhone: true,
madaoMinPrice: '0.01',
@@ -9323,6 +9324,9 @@ test('phone verification helper reacquires MaDao direct numbers after releasing
auto_pick_country: false,
reuse_phone: true,
country: 'GB',
metadata: {
operator: 'operator-a',
},
min_price: 0.01,
max_price: 0.2,
});
@@ -159,10 +159,17 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
assert.match(html, /id="select-madao-routing-plan-id"/);
assert.match(html, /id="btn-madao-refresh-routing-plans"/);
assert.doesNotMatch(html, /id="input-madao-routing-plan-id"/);
assert.doesNotMatch(html, /id="row-madao-provider-id"/);
assert.match(html, /id="row-madao-provider-id"/);
assert.match(html, /id="select-madao-provider-id"/);
assert.match(html, /id="btn-madao-refresh-providers"/);
assert.doesNotMatch(html, /id="input-madao-provider-id"/);
assert.doesNotMatch(html, /id="row-madao-country"/);
assert.match(html, /id="row-madao-country"/);
assert.match(html, /id="select-madao-country"/);
assert.match(html, /id="btn-madao-refresh-countries"/);
assert.doesNotMatch(html, /id="input-madao-country"/);
assert.match(html, /id="row-madao-operator"/);
assert.match(html, /id="select-madao-operator"/);
assert.match(html, /id="btn-madao-refresh-operators"/);
assert.doesNotMatch(html, /id="row-madao-auto-pick-country"/);
assert.doesNotMatch(html, /id="input-madao-auto-pick-country"/);
assert.doesNotMatch(html, /id="row-madao-reuse-phone"/);
@@ -275,6 +282,144 @@ return {
assert.equal(requests[0].options.headers.Authorization, 'Bearer madao-secret');
});
test('MaDao direct selects load provider country and operator options from daemon API', async () => {
const requests = [];
const fetchImpl = async (url, options = {}) => {
const parsedUrl = new URL(url);
requests.push({
pathname: parsedUrl.pathname,
options,
body: options.body ? JSON.parse(options.body) : null,
});
if (parsedUrl.pathname === '/api/providers') {
return {
ok: true,
status: 200,
statusText: 'OK',
async text() {
return JSON.stringify({
providers: [
{ id: 'stored-provider', name: 'Stored Provider', enabled: true, protocol_label: 'REST' },
{ id: 'disabled-provider', name: 'Disabled Provider', enabled: false },
],
});
},
};
}
if (parsedUrl.pathname === '/api/providers/stored-provider/countries') {
return {
ok: true,
status: 200,
statusText: 'OK',
async text() {
return JSON.stringify({
provider: 'stored-provider',
items: [
{ value: 'GB', label: 'United Kingdom' },
{ value: 'TH', label: 'Thailand' },
],
});
},
};
}
if (parsedUrl.pathname === '/api/providers/stored-provider/operators') {
return {
ok: true,
status: 200,
statusText: 'OK',
async text() {
return JSON.stringify({
provider: 'stored-provider',
items: [
{ value: 'operator-a', label: 'Operator A' },
{ value: 'operator-b', label: 'Operator B' },
],
});
},
};
}
throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`);
};
const api = new Function('fetch', `
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
let latestState = {
madaoBaseUrl: 'http://madao.local/api/acquire',
madaoHttpSecret: 'madao-secret',
madaoProviderId: 'stored-provider',
madaoCountry: 'GB',
madaoOperator: 'operator-a',
};
let maDaoProviderOptions = [];
let maDaoCountryOptions = [];
let maDaoOperatorOptions = [];
let displayText = '';
let toastText = '';
const inputMaDaoBaseUrl = { value: 'http://madao.local/api/acquire' };
const inputMaDaoHttpSecret = { value: 'madao-secret' };
const selectMaDaoProviderId = { value: 'stored-provider', options: [], replaceChildren(...children) { this.options = children; } };
const selectMaDaoCountry = { value: 'GB', options: [], replaceChildren(...children) { this.options = children; } };
const selectMaDaoOperator = { value: 'operator-a', options: [], replaceChildren(...children) { this.options = children; } };
function updateHeroSmsPlatformDisplay() {
displayText = [selectMaDaoProviderId.value, selectMaDaoCountry.value, selectMaDaoOperator.value].filter(Boolean).join('/');
}
function showToast(message) {
toastText = message;
}
${extractFunction('normalizeMaDaoBaseUrlValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoProviderIdValue')}
${extractFunction('normalizeMaDaoOperatorValue')}
${extractFunction('normalizeMaDaoCountry')}
${extractFunction('createSelectOptionElement')}
${extractFunction('setSelectOptions')}
${extractFunction('normalizeMaDaoOptionListItems')}
${extractFunction('setMaDaoProviderSelectOptions')}
${extractFunction('setMaDaoCountrySelectOptions')}
${extractFunction('setMaDaoOperatorSelectOptions')}
${extractFunction('buildMaDaoRequestUrl')}
${extractFunction('buildMaDaoRequestHeaders')}
${extractFunction('fetchMaDaoJson')}
${extractFunction('getMaDaoProvidersFromPayload')}
${extractFunction('getMaDaoOptionItemsFromPayload')}
${extractFunction('getSelectedMaDaoProviderId')}
${extractFunction('getSelectedMaDaoCountry')}
${extractFunction('loadMaDaoProviders')}
${extractFunction('loadMaDaoCountries')}
${extractFunction('loadMaDaoOperators')}
return {
loadMaDaoProviders,
selectMaDaoProviderId,
selectMaDaoCountry,
selectMaDaoOperator,
get providerOptions() { return selectMaDaoProviderId.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
get countryOptions() { return selectMaDaoCountry.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
get operatorOptions() { return selectMaDaoOperator.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
get displayText() { return displayText; },
get toastText() { return toastText; },
};
`)(fetchImpl);
const providers = await api.loadMaDaoProviders();
assert.deepStrictEqual(providers.map((provider) => provider.value), ['stored-provider']);
assert.equal(api.selectMaDaoProviderId.value, 'stored-provider');
assert.equal(api.selectMaDaoCountry.value, 'GB');
assert.equal(api.selectMaDaoOperator.value, 'operator-a');
assert.deepStrictEqual(api.providerOptions.map((option) => option.value), ['', 'stored-provider']);
assert.deepStrictEqual(api.countryOptions.map((option) => option.value), ['', 'GB', 'TH']);
assert.deepStrictEqual(api.operatorOptions.map((option) => option.value), ['', 'operator-a', 'operator-b']);
assert.equal(api.displayText, 'stored-provider/GB/operator-a');
assert.equal(api.toastText, '已刷新 MaDao 服务商。');
assert.deepStrictEqual(requests.map((request) => request.pathname), [
'/api/providers',
'/api/providers/stored-provider/countries',
'/api/providers/stored-provider/operators',
]);
assert.deepStrictEqual(requests[2].body, { country: 'GB' });
assert.equal(requests[0].options.headers.Authorization, 'Bearer madao-secret');
});
test('HeroSMS country parser accepts keyed country maps from the live API', () => {
const api = new Function(`
${extractFunction('normalizeHeroSmsCountryPayloadEntries')}
@@ -789,6 +934,7 @@ const rowMaDaoMode = { style: { display: 'none' } };
const rowMaDaoRoutingPlanId = { style: { display: 'none' } };
const rowMaDaoProviderId = { style: { display: 'none' } };
const rowMaDaoCountry = { style: { display: 'none' } };
const rowMaDaoOperator = { style: { display: 'none' } };
const rowMaDaoAutoPickCountry = { style: { display: 'none' } };
const rowMaDaoReusePhone = { style: { display: 'none' } };
const rowMaDaoPriceRange = { style: { display: 'none' } };
@@ -868,6 +1014,9 @@ const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = ${JSON.stringify({
'rowMaDaoRoutingPlanId',
],
directRowKeys: [
'rowMaDaoProviderId',
'rowMaDaoCountry',
'rowMaDaoOperator',
'rowMaDaoPriceRange',
],
},
@@ -943,6 +1092,7 @@ return {
rowMaDaoRoutingPlanId,
rowMaDaoProviderId,
rowMaDaoCountry,
rowMaDaoOperator,
rowMaDaoAutoPickCountry,
rowMaDaoReusePhone,
rowMaDaoPriceRange,
@@ -1022,6 +1172,7 @@ return {
assert.equal(api.rowMaDaoRoutingPlanId.style.display, 'none');
assert.equal(api.rowMaDaoProviderId.style.display, 'none');
assert.equal(api.rowMaDaoCountry.style.display, 'none');
assert.equal(api.rowMaDaoOperator.style.display, 'none');
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
assert.equal(api.rowMaDaoPriceRange.style.display, 'none');
@@ -1138,6 +1289,7 @@ return {
assert.equal(api.rowMaDaoRoutingPlanId.style.display, '');
assert.equal(api.rowMaDaoProviderId.style.display, 'none');
assert.equal(api.rowMaDaoCountry.style.display, 'none');
assert.equal(api.rowMaDaoOperator.style.display, 'none');
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
assert.equal(api.rowMaDaoPriceRange.style.display, 'none');
@@ -1148,8 +1300,9 @@ return {
assert.equal(api.rowMaDaoHttpSecret.style.display, '');
assert.equal(api.rowMaDaoMode.style.display, '');
assert.equal(api.rowMaDaoRoutingPlanId.style.display, 'none');
assert.equal(api.rowMaDaoProviderId.style.display, 'none');
assert.equal(api.rowMaDaoCountry.style.display, 'none');
assert.equal(api.rowMaDaoProviderId.style.display, '');
assert.equal(api.rowMaDaoCountry.style.display, '');
assert.equal(api.rowMaDaoOperator.style.display, '');
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
assert.equal(api.rowMaDaoPriceRange.style.display, '');
@@ -1242,8 +1395,9 @@ const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/acquire' };
const inputMaDaoHttpSecret = { value: 'madao-secret' };
const selectMaDaoMode = { value: 'direct' };
const selectMaDaoRoutingPlanId = { value: 'plan-1' };
const inputMaDaoProviderId = { value: 'Local Provider!!' };
const inputMaDaoCountry = { value: 'th' };
const selectMaDaoProviderId = { value: 'Local Provider!!' };
const selectMaDaoCountry = { value: 'th' };
const selectMaDaoOperator = { value: 'Operator A!' };
const inputMaDaoAutoPickCountry = { checked: false };
const inputMaDaoReusePhone = { checked: true };
const inputMaDaoMinPrice = { value: '0.02' };
@@ -1347,6 +1501,7 @@ ${extractFunction('normalizeMaDaoBaseUrlValue')}
${extractFunction('normalizeMaDaoModeValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoProviderIdValue')}
${extractFunction('normalizeMaDaoOperatorValue')}
${extractFunction('normalizeMaDaoCountry')}
${extractFunction('normalizeMaDaoPriceValue')}
${extractFunction('normalizeFiveSimCountryCode')}
@@ -1419,6 +1574,7 @@ return { collectSettingsPayload };
assert.equal(payload.madaoRoutingPlanId, 'plan-1');
assert.equal(payload.madaoProviderId, 'localprovider');
assert.equal(payload.madaoCountry, 'TH');
assert.equal(payload.madaoOperator, 'operatora');
assert.equal(payload.madaoAutoPickCountry, false);
assert.equal(payload.madaoReusePhone, true);
assert.equal(payload.madaoMinPrice, '0.02');
@@ -1470,6 +1626,7 @@ let latestState = {
madaoRoutingPlanId: 'plan-old',
madaoProviderId: 'provider-old',
madaoCountry: 'TH',
madaoOperator: 'operator-old',
madaoAutoPickCountry: true,
madaoReusePhone: true,
madaoMinPrice: '0.01',
@@ -1522,8 +1679,27 @@ const selectMaDaoRoutingPlanId = {
this.options = children;
},
};
const inputMaDaoProviderId = { value: 'Provider Live!' };
const inputMaDaoCountry = { value: 'local' };
const selectMaDaoProviderId = {
value: 'Provider Live!',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
const selectMaDaoCountry = {
value: 'local',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
const selectMaDaoOperator = {
value: 'Operator Live!',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
const inputMaDaoAutoPickCountry = { checked: false };
const inputMaDaoReusePhone = { checked: false };
const inputMaDaoMinPrice = { value: '0.02' };
@@ -1542,6 +1718,9 @@ let phoneSmsProviderOrderSelection = ['hero-sms', '5sim'];
let lastPhoneSmsProviderBeforeChange = null;
let savedPayload = null;
let maDaoRoutingPlanOptions = [];
let maDaoProviderOptions = [];
let maDaoCountryOptions = [];
let maDaoOperatorOptions = [];
${extractFunction('normalizePhoneSmsProvider')}
${extractFunction('normalizePhoneSmsProviderValue')}
@@ -1573,12 +1752,17 @@ ${extractFunction('normalizeMaDaoModeValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoRoutingPlanIdValue')}
${extractFunction('normalizeMaDaoProviderIdValue')}
${extractFunction('normalizeMaDaoOperatorValue')}
${extractFunction('normalizeMaDaoCountry')}
${extractFunction('normalizeMaDaoPriceValue')}
${extractFunction('createSelectOptionElement')}
${extractFunction('setSelectOptions')}
${extractFunction('normalizeMaDaoOptionListItems')}
${extractFunction('buildMaDaoRoutingPlanOptions')}
${extractFunction('setMaDaoRoutingPlanSelectOptions')}
${extractFunction('setMaDaoProviderSelectOptions')}
${extractFunction('setMaDaoCountrySelectOptions')}
${extractFunction('setMaDaoOperatorSelectOptions')}
${extractFunction('normalizePhoneSmsMinPriceValue')}
${extractFunction('normalizePhoneSmsMaxPriceValue')}
${extractFunction('normalizeHeroSmsCountryId')}
@@ -1601,6 +1785,7 @@ function syncLatestState(patch) { latestState = { ...latestState, ...patch }; }
function loadHeroSmsCountries() { return Promise.resolve(); }
function loadFiveSimCountries() { return Promise.resolve(); }
function loadNexSmsCountries() { return Promise.resolve(); }
function loadMaDaoProviders() { return Promise.resolve(); }
function applyHeroSmsFallbackSelection() {}
function applyFiveSimCountrySelection() {}
function applyNexSmsCountrySelection() {}