Merge pull request #293 from netcookies/fix/madao-country-iso-dev
fix(madao): 适配国家中文标签和线路归一化
This commit is contained in:
+11
-2
@@ -21,6 +21,7 @@ importScripts(
|
||||
'gopay-utils.js',
|
||||
'phone-sms/providers/hero-sms.js',
|
||||
'phone-sms/providers/five-sim.js',
|
||||
'phone-sms/providers/nexsms.js',
|
||||
'phone-sms/providers/madao.js',
|
||||
'phone-sms/providers/registry.js',
|
||||
'background/phone-verification-flow.js',
|
||||
@@ -1452,6 +1453,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
madaoRoutingPlanId: '',
|
||||
madaoProviderId: '',
|
||||
madaoCountry: '',
|
||||
madaoOperator: '',
|
||||
madaoAutoPickCountry: true,
|
||||
madaoReusePhone: true,
|
||||
madaoMinPrice: '',
|
||||
@@ -2244,6 +2246,13 @@ function normalizeMaDaoPrice(value = '') {
|
||||
return normalizeHeroSmsMaxPrice(value);
|
||||
}
|
||||
|
||||
function normalizeMaDaoOperator(value = '') {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
|
||||
function normalizePhonePreferredActivation(value) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return null;
|
||||
@@ -3594,6 +3603,8 @@ function normalizePersistentSettingValue(key, value) {
|
||||
return normalizeMaDaoProviderId(value);
|
||||
case 'madaoCountry':
|
||||
return normalizeMaDaoCountry(value);
|
||||
case 'madaoOperator':
|
||||
return normalizeMaDaoOperator(value);
|
||||
case 'madaoAutoPickCountry':
|
||||
case 'madaoReusePhone':
|
||||
return Boolean(value);
|
||||
@@ -13876,8 +13887,6 @@ const phoneVerificationHelpers = self.MultiPageBackgroundPhoneVerification?.crea
|
||||
setState,
|
||||
sleepWithStop,
|
||||
throwIfStopped,
|
||||
createFiveSimProvider: self.PhoneSmsFiveSimProvider?.createProvider,
|
||||
createMaDaoProvider: self.PhoneSmsMaDaoProvider?.createProvider,
|
||||
});
|
||||
const step1Executor = self.MultiPageBackgroundStep1?.createStep1Executor({
|
||||
addLog,
|
||||
|
||||
+350
-3373
File diff suppressed because it is too large
Load Diff
@@ -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() {
|
||||
|
||||
@@ -150,6 +150,10 @@
|
||||
return String(value || '').trim() || fallback;
|
||||
}
|
||||
|
||||
function normalizeCountryKey(value) {
|
||||
return normalizeFiveSimCountryId(value, '');
|
||||
}
|
||||
|
||||
function formatFiveSimCountryLabel(id = '', englishValue = '', fallback = DEFAULT_COUNTRY_LABEL) {
|
||||
const countryId = normalizeFiveSimCountryId(id, '');
|
||||
const english = normalizeFiveSimCountryLabel(englishValue || countryId || fallback, fallback);
|
||||
@@ -181,6 +185,42 @@
|
||||
return String(Math.round(numeric * 10000) / 10000);
|
||||
}
|
||||
|
||||
function normalizePriceLimit(value = '') {
|
||||
const normalized = normalizeFiveSimMaxPrice(value);
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
const numeric = Number(normalized);
|
||||
return Number.isFinite(numeric) && numeric > 0 ? numeric : null;
|
||||
}
|
||||
|
||||
function resolvePriceRange(state = {}) {
|
||||
const minPriceLimit = normalizePriceLimit(state?.fiveSimMinPrice);
|
||||
const maxPriceLimit = normalizePriceLimit(state?.fiveSimMaxPrice);
|
||||
return {
|
||||
minPriceLimit,
|
||||
maxPriceLimit,
|
||||
hasMinPriceLimit: minPriceLimit !== null,
|
||||
hasMaxPriceLimit: maxPriceLimit !== null,
|
||||
invalidRange: minPriceLimit !== null && maxPriceLimit !== null && minPriceLimit > maxPriceLimit,
|
||||
};
|
||||
}
|
||||
|
||||
function formatPriceRangeText(minPriceLimit = null, maxPriceLimit = null) {
|
||||
const minPrice = normalizePriceLimit(minPriceLimit);
|
||||
const maxPrice = normalizePriceLimit(maxPriceLimit);
|
||||
if (minPrice !== null && maxPrice !== null) {
|
||||
return `${minPrice}~${maxPrice}`;
|
||||
}
|
||||
if (minPrice !== null) {
|
||||
return `${minPrice}~`;
|
||||
}
|
||||
if (maxPrice !== null) {
|
||||
return `~${maxPrice}`;
|
||||
}
|
||||
return 'unbounded';
|
||||
}
|
||||
|
||||
function normalizeFiveSimCountryFallback(value = []) {
|
||||
const source = Array.isArray(value)
|
||||
? value
|
||||
@@ -371,6 +411,20 @@
|
||||
}
|
||||
|
||||
function resolveCountryCandidates(state = {}) {
|
||||
const orderedCountries = normalizeFiveSimCountryFallback(state.fiveSimCountryOrder);
|
||||
if (orderedCountries.length) {
|
||||
const primaryCountryId = normalizeFiveSimCountryId(state.fiveSimCountryId, '');
|
||||
return orderedCountries.map((entry) => {
|
||||
const id = normalizeFiveSimCountryId(entry.id, '');
|
||||
return {
|
||||
id,
|
||||
label: primaryCountryId && id === primaryCountryId
|
||||
? formatFiveSimCountryLabel(id, state.fiveSimCountryLabel || entry.label || id, id)
|
||||
: formatFiveSimCountryLabel(id, entry.label || id, id),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const primary = resolveCountryConfig(state);
|
||||
const fallbackList = normalizeFiveSimCountryFallback(state.fiveSimCountryFallback);
|
||||
const seen = new Set([primary.id]);
|
||||
@@ -391,6 +445,13 @@
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function resolveCountryLabel(state = {}, countryId = DEFAULT_COUNTRY_ID) {
|
||||
const countryKey = normalizeCountryKey(countryId);
|
||||
const matched = resolveCountryCandidates(state)
|
||||
.find((entry) => normalizeCountryKey(entry.id) === countryKey);
|
||||
return matched?.label || formatFiveSimCountryLabel(countryKey, countryKey, countryKey || DEFAULT_COUNTRY_LABEL);
|
||||
}
|
||||
|
||||
async function fetchBalance(state = {}, deps = {}) {
|
||||
const config = resolveConfig(state, deps);
|
||||
const payload = await fetchJson(config, '/v1/user/profile', {
|
||||
@@ -496,6 +557,8 @@
|
||||
async function resolvePricePlan(state = {}, countryConfig = resolveCountryConfig(state), deps = {}) {
|
||||
const userLimitText = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
|
||||
const userLimit = userLimitText ? Number(userLimitText) : null;
|
||||
const userMinText = normalizeFiveSimMaxPrice(state.fiveSimMinPrice);
|
||||
const userMinLimit = userMinText ? Number(userMinText) : null;
|
||||
let priceCandidates = [];
|
||||
|
||||
try {
|
||||
@@ -526,19 +589,24 @@
|
||||
priceCandidates = buildSortedUniquePriceCandidates(priceCandidates);
|
||||
|
||||
const minCatalogPrice = priceCandidates.length > 0 ? priceCandidates[0] : null;
|
||||
const rangeFilteredPrices = priceCandidates.filter((price) => (
|
||||
(userMinLimit === null || price >= userMinLimit)
|
||||
&& (userLimit === null || price <= userLimit)
|
||||
));
|
||||
if (userLimit !== null) {
|
||||
const bounded = priceCandidates.filter((price) => price <= userLimit);
|
||||
const bounded = rangeFilteredPrices;
|
||||
return {
|
||||
prices: bounded.length > 0 ? [userLimit, ...bounded.filter((price) => price !== userLimit)] : [userLimit],
|
||||
userLimit,
|
||||
userMinLimit,
|
||||
minCatalogPrice,
|
||||
};
|
||||
}
|
||||
|
||||
if (priceCandidates.length > 0) {
|
||||
return { prices: priceCandidates, userLimit: null, minCatalogPrice };
|
||||
if (rangeFilteredPrices.length > 0) {
|
||||
return { prices: rangeFilteredPrices, userLimit: null, userMinLimit, minCatalogPrice };
|
||||
}
|
||||
return { prices: [null], userLimit: null, minCatalogPrice: null };
|
||||
return { prices: [null], userLimit: null, userMinLimit, minCatalogPrice };
|
||||
}
|
||||
|
||||
function normalizeActivation(record, fallback = {}) {
|
||||
@@ -558,6 +626,7 @@
|
||||
provider: PROVIDER_ID,
|
||||
serviceCode: DEFAULT_PRODUCT,
|
||||
countryId,
|
||||
countryCode: countryId,
|
||||
countryLabel,
|
||||
successfulUses: Math.max(0, Math.floor(Number(record.successfulUses) || 0)),
|
||||
maxUses: Math.max(1, Math.floor(Number(record.maxUses) || DEFAULT_MAX_USES)),
|
||||
@@ -570,6 +639,40 @@
|
||||
};
|
||||
}
|
||||
|
||||
function resolveActivationCountry(activation = {}, state = {}) {
|
||||
const normalizedActivation = normalizeActivation(activation)
|
||||
|| (activation && typeof activation === 'object' ? activation : {});
|
||||
const countryId = normalizeFiveSimCountryId(
|
||||
normalizedActivation.countryCode ?? normalizedActivation.countryId ?? normalizedActivation.country,
|
||||
DEFAULT_COUNTRY_ID
|
||||
);
|
||||
const matched = resolveCountryCandidates(state)
|
||||
.find((entry) => normalizeCountryKey(entry.id) === countryId);
|
||||
if (matched) {
|
||||
return matched;
|
||||
}
|
||||
return {
|
||||
id: countryId,
|
||||
code: countryId,
|
||||
label: normalizeFiveSimCountryLabel(
|
||||
normalizedActivation.countryLabel,
|
||||
formatFiveSimCountryLabel(countryId, countryId, countryId)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function getActivationCountryKey(activation = {}) {
|
||||
return normalizeCountryKey(activation?.countryCode ?? activation?.countryId ?? activation?.country);
|
||||
}
|
||||
|
||||
function getActivationPrice(activation = {}) {
|
||||
return normalizePrice(
|
||||
activation?.selectedPrice
|
||||
?? activation?.price
|
||||
?? activation?.maxPrice
|
||||
);
|
||||
}
|
||||
|
||||
function isNoNumbersPayload(payloadOrMessage) {
|
||||
const text = describePayload(payloadOrMessage);
|
||||
return /no\s+free\s+phones|no\s+numbers|not\s+found/i.test(text);
|
||||
@@ -594,10 +697,17 @@
|
||||
|
||||
function assertMaxPriceCompatibleWithOperator(state = {}) {
|
||||
const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
|
||||
const minPrice = normalizeFiveSimMaxPrice(state.fiveSimMinPrice);
|
||||
const operator = normalizeFiveSimOperator(state.fiveSimOperator);
|
||||
if (maxPrice && operator !== DEFAULT_OPERATOR) {
|
||||
throw new Error('5sim 价格上限仅支持运营商为 "any" 时使用;请清空价格上限,或先把运营商切换为 any。');
|
||||
}
|
||||
if (minPrice && operator !== DEFAULT_OPERATOR) {
|
||||
throw new Error('5sim 最低购买价仅支持运营商为 "any" 时使用;请清空最低购买价,或先把运营商切换为 any。');
|
||||
}
|
||||
if (minPrice && maxPrice && Number(minPrice) > Number(maxPrice)) {
|
||||
throw new Error(`5sim 价格区间无效:最低购买价 ${minPrice} 高于价格上限 ${maxPrice}。`);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeReuseEnabled(state = {}) {
|
||||
@@ -777,6 +887,22 @@
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareActivationForReuse(state = {}, activation, _options = {}, deps = {}) {
|
||||
try {
|
||||
const retainedActivation = await reuseActivation(state, activation, deps);
|
||||
return {
|
||||
ok: true,
|
||||
activation: retainedActivation,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: 'reuse_check_failed',
|
||||
message: error.message || '5sim 复用手机号基线检查失败。',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function finishActivation(state = {}, activation, deps = {}) {
|
||||
const normalizedActivation = normalizeActivation(activation);
|
||||
if (!normalizedActivation) return '';
|
||||
@@ -807,6 +933,25 @@
|
||||
return describePayload(payload);
|
||||
}
|
||||
|
||||
async function requestAdditionalSms() {
|
||||
return '';
|
||||
}
|
||||
|
||||
async function rotateActivation(state = {}, activation, options = {}, deps = {}) {
|
||||
const releaseAction = String(options?.releaseAction || '').trim().toLowerCase() === 'ban'
|
||||
? 'ban'
|
||||
: 'cancel';
|
||||
if (releaseAction === 'ban') {
|
||||
await banActivation(state, activation, deps);
|
||||
} else {
|
||||
await cancelActivation(state, activation, deps);
|
||||
}
|
||||
return {
|
||||
currentTicketId: String(activation?.activationId || activation?.id || ''),
|
||||
nextActivation: null,
|
||||
};
|
||||
}
|
||||
|
||||
function extractVerificationCode(rawCodeOrText) {
|
||||
const trimmed = String(rawCodeOrText || '').trim();
|
||||
if (!trimmed) {
|
||||
@@ -913,9 +1058,18 @@
|
||||
addLog: deps.addLog,
|
||||
requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
};
|
||||
const capabilities = Object.freeze({
|
||||
supportsReusableActivation: true,
|
||||
supportsAutomaticFreeReuse: true,
|
||||
supportsFreeReusePreservation: true,
|
||||
supportsPageResend: false,
|
||||
supportsPageResendProbe: true,
|
||||
requiresCountrySelection: true,
|
||||
});
|
||||
return {
|
||||
id: PROVIDER_ID,
|
||||
label: '5sim',
|
||||
capabilities,
|
||||
defaultCountryId: DEFAULT_COUNTRY_ID,
|
||||
defaultCountryLabel: DEFAULT_COUNTRY_LABEL,
|
||||
supportedCountries: SUPPORTED_COUNTRY_ITEMS,
|
||||
@@ -925,18 +1079,38 @@
|
||||
normalizeCountryLabel: normalizeFiveSimCountryLabel,
|
||||
formatCountryLabel: formatFiveSimCountryLabel,
|
||||
normalizeCountryFallback: normalizeFiveSimCountryFallback,
|
||||
normalizeCountryKey,
|
||||
normalizeMaxPrice: normalizeFiveSimMaxPrice,
|
||||
normalizeOperator: normalizeFiveSimOperator,
|
||||
normalizeActivation,
|
||||
resolveCountryCandidates,
|
||||
resolveCountryLabel,
|
||||
resolveActivationCountry,
|
||||
getActivationCountryKey,
|
||||
getActivationPrice,
|
||||
requestActivation: (state, options) => requestActivation(state, options, providerDeps),
|
||||
reuseActivation: (state, activation) => reuseActivation(state, activation, providerDeps),
|
||||
finishActivation: (state, activation) => finishActivation(state, activation, providerDeps),
|
||||
cancelActivation: (state, activation) => cancelActivation(state, activation, providerDeps),
|
||||
banActivation: (state, activation) => banActivation(state, activation, providerDeps),
|
||||
requestAdditionalSms: (state, activation) => requestAdditionalSms(state, activation, providerDeps),
|
||||
rotateActivation: (state, activation, options) => rotateActivation(state, activation, options, providerDeps),
|
||||
pollActivationCode: (state, activation, options) => pollActivationCode(state, activation, options, providerDeps),
|
||||
prepareActivationForReuse: (state, activation, options) => prepareActivationForReuse(state, activation, options, providerDeps),
|
||||
canPersistReusableActivation: () => true,
|
||||
canPreserveActivationForFreeReuse: (_state, activation) => Boolean(
|
||||
normalizeActivation(activation)
|
||||
&& activation
|
||||
&& typeof activation === 'object'
|
||||
&& activation.phoneCodeReceived
|
||||
),
|
||||
shouldUsePageResend: () => false,
|
||||
shouldProbePageResend: () => true,
|
||||
fetchBalance: (state) => fetchBalance(state, providerDeps),
|
||||
fetchCountries: (state) => fetchCountries(state, providerDeps),
|
||||
fetchPrices: (state, countryConfig) => fetchPrices(state, countryConfig, providerDeps),
|
||||
resolvePriceRange,
|
||||
formatPriceRangeText,
|
||||
collectPriceEntries,
|
||||
describePayload,
|
||||
};
|
||||
@@ -954,8 +1128,13 @@
|
||||
normalizeFiveSimCountryFallback,
|
||||
normalizeFiveSimCountryId,
|
||||
normalizeFiveSimCountryLabel,
|
||||
normalizeCountryKey,
|
||||
formatFiveSimCountryLabel,
|
||||
normalizeFiveSimMaxPrice,
|
||||
normalizeFiveSimOperator,
|
||||
resolvePriceRange,
|
||||
formatPriceRangeText,
|
||||
normalizeActivation,
|
||||
resolveActivationCountry,
|
||||
};
|
||||
});
|
||||
|
||||
+1300
-5
File diff suppressed because it is too large
Load Diff
+283
-11
@@ -7,6 +7,19 @@
|
||||
const DEFAULT_SERVICE = 'openai';
|
||||
const DEFAULT_MODE = 'routing_plan';
|
||||
const DEFAULT_REQUEST_TIMEOUT_MS = 20000;
|
||||
const DEFAULT_POLL_TIMEOUT_MS = 180000;
|
||||
const DEFAULT_POLL_INTERVAL_MS = 5000;
|
||||
const PHONE_CODE_TIMEOUT_ERROR_PREFIX = 'PHONE_CODE_TIMEOUT::';
|
||||
const COUNTRY_BY_PHONE_PREFIX = Object.freeze([
|
||||
{ prefix: '84', id: 'VN', label: 'Vietnam' },
|
||||
{ prefix: '66', id: 'TH', label: 'Thailand' },
|
||||
{ prefix: '62', id: 'ID', label: 'Indonesia' },
|
||||
{ prefix: '44', id: 'GB', label: 'United Kingdom' },
|
||||
{ prefix: '81', id: 'JP', label: 'Japan' },
|
||||
{ prefix: '49', id: 'DE', label: 'Germany' },
|
||||
{ prefix: '33', id: 'FR', label: 'France' },
|
||||
{ prefix: '1', id: 'US', label: 'USA' },
|
||||
]);
|
||||
|
||||
function normalizeText(value = '', fallback = '') {
|
||||
return String(value || '').trim() || fallback;
|
||||
@@ -29,6 +42,15 @@
|
||||
return normalizeText(value).toLowerCase().replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
|
||||
function normalizeOperator(value = '') {
|
||||
const rawValue = normalizeText(value);
|
||||
const compactValue = rawValue.toLowerCase().replace(/[^a-z0-9]+/g, '');
|
||||
if (!rawValue || compactValue === 'any' || compactValue === 'anyoperator') {
|
||||
return '';
|
||||
}
|
||||
return rawValue.toLowerCase().replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
|
||||
function normalizeCountry(value = '') {
|
||||
const trimmed = normalizeText(value);
|
||||
if (!trimmed) {
|
||||
@@ -44,6 +66,52 @@
|
||||
return lowered.replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
|
||||
function normalizeCountryKey(value) {
|
||||
return normalizeCountry(value);
|
||||
}
|
||||
|
||||
function getRegionDisplayName(regionCode, locale = 'en') {
|
||||
const normalizedRegionCode = normalizeCountry(regionCode);
|
||||
const normalizedLocale = normalizeText(locale);
|
||||
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 normalizeCountryLabel(value = '', countryCode = '') {
|
||||
const label = normalizeText(value);
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
const normalizedCountryCode = normalizeCountry(countryCode);
|
||||
if (!normalizedCountryCode) {
|
||||
return '';
|
||||
}
|
||||
return getRegionDisplayName(normalizedCountryCode, 'en') || normalizedCountryCode;
|
||||
}
|
||||
|
||||
function inferCountryFromPhoneNumber(phoneNumber = '') {
|
||||
const digits = String(phoneNumber || '').replace(/\D+/g, '');
|
||||
if (!digits) {
|
||||
return null;
|
||||
}
|
||||
const match = COUNTRY_BY_PHONE_PREFIX.find((entry) => digits.startsWith(entry.prefix));
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: normalizeCountry(match.id),
|
||||
label: normalizeCountryLabel(match.label, match.id),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeBoolean(value, fallback = false) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return Boolean(fallback);
|
||||
@@ -196,6 +264,7 @@
|
||||
service: normalizeText(options?.service || state?.madaoServiceName, DEFAULT_SERVICE),
|
||||
};
|
||||
const country = normalizeCountry(options?.country || state?.madaoCountry);
|
||||
const operator = normalizeOperator(options?.operator || state?.madaoOperator);
|
||||
const minPrice = normalizePrice(options?.minPrice ?? state?.madaoMinPrice);
|
||||
const maxPrice = normalizePrice(options?.maxPrice ?? state?.madaoMaxPrice);
|
||||
|
||||
@@ -209,6 +278,9 @@
|
||||
if (country) {
|
||||
request.country = country;
|
||||
}
|
||||
if (operator) {
|
||||
request.metadata = { operator };
|
||||
}
|
||||
if (minPrice !== null) {
|
||||
request.min_price = minPrice;
|
||||
}
|
||||
@@ -245,6 +317,68 @@
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeActivation(record = {}, fallback = {}) {
|
||||
const direct = normalizeActivationFromAcquire(record, fallback);
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
const source = record && typeof record === 'object' && !Array.isArray(record) ? record : {};
|
||||
const ticketId = normalizeText(source.activationId || source.ticketId || source.id || fallback.activationId);
|
||||
const phoneNumber = normalizeText(source.phoneNumber || source.phone || fallback.phoneNumber);
|
||||
if (!ticketId || !phoneNumber) {
|
||||
return null;
|
||||
}
|
||||
const inferredCountry = inferCountryFromPhoneNumber(phoneNumber);
|
||||
const countryId = normalizeCountry(source.countryId ?? source.country ?? fallback.countryId ?? inferredCountry?.id);
|
||||
const price = normalizePrice(source.madaoPrice ?? source.price ?? fallback.madaoPrice ?? fallback.price);
|
||||
return {
|
||||
activationId: ticketId,
|
||||
phoneNumber,
|
||||
provider: PROVIDER_ID,
|
||||
serviceCode: normalizeText(source.serviceCode || source.service || fallback.serviceCode, DEFAULT_SERVICE),
|
||||
countryId,
|
||||
countryLabel: normalizeCountryLabel(source.countryLabel || source.country_label || fallback.countryLabel, countryId),
|
||||
maxUses: Math.max(1, Math.floor(Number(source.maxUses ?? fallback.maxUses) || 1)),
|
||||
successfulUses: Math.max(0, Math.floor(Number(source.successfulUses ?? fallback.successfulUses) || 0)),
|
||||
...(source.source ? { source: normalizeText(source.source) } : {}),
|
||||
...(source.phoneCodeReceived ? { phoneCodeReceived: true } : {}),
|
||||
...(source.phoneCodeReceivedAt ? { phoneCodeReceivedAt: Math.max(0, Number(source.phoneCodeReceivedAt) || 0) } : {}),
|
||||
...(source.madaoProviderId ? { madaoProviderId: normalizeProviderId(source.madaoProviderId) } : {}),
|
||||
...(source.madaoRoutingPlanId ? { madaoRoutingPlanId: normalizeText(source.madaoRoutingPlanId) } : {}),
|
||||
...(source.madaoRoutingPlanName ? { madaoRoutingPlanName: normalizeText(source.madaoRoutingPlanName) } : {}),
|
||||
...(source.madaoRoutingItemId ? { madaoRoutingItemId: normalizeText(source.madaoRoutingItemId) } : {}),
|
||||
...(source.madaoAcquirePath ? { madaoAcquirePath: mapAcquirePath(source.madaoAcquirePath) } : {}),
|
||||
...(source.madaoStatus ? { madaoStatus: mapTicketStatus(source.madaoStatus) } : {}),
|
||||
...(price !== null ? { madaoPrice: price } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCountryLabel(_state = {}, countryId = '') {
|
||||
return normalizeCountryLabel('', countryId);
|
||||
}
|
||||
|
||||
function resolveActivationCountry(activation = {}) {
|
||||
const normalizedActivation = normalizeActivation(activation)
|
||||
|| (activation && typeof activation === 'object' ? activation : {});
|
||||
const inferredCountry = inferCountryFromPhoneNumber(normalizedActivation.phoneNumber);
|
||||
const countryId = normalizeCountry(normalizedActivation.countryId ?? normalizedActivation.country ?? inferredCountry?.id);
|
||||
return {
|
||||
id: countryId,
|
||||
label: normalizeCountryLabel(normalizedActivation.countryLabel || inferredCountry?.label, countryId),
|
||||
};
|
||||
}
|
||||
|
||||
function getActivationCountryKey(activation = {}) {
|
||||
const normalizedActivation = normalizeActivation(activation)
|
||||
|| (activation && typeof activation === 'object' ? activation : {});
|
||||
const inferredCountry = inferCountryFromPhoneNumber(normalizedActivation.phoneNumber);
|
||||
return normalizeCountryKey(normalizedActivation.countryId ?? normalizedActivation.country ?? inferredCountry?.id);
|
||||
}
|
||||
|
||||
function getActivationPrice(activation = {}) {
|
||||
return normalizePrice(activation?.madaoPrice ?? activation?.selectedPrice ?? activation?.price ?? activation?.maxPrice);
|
||||
}
|
||||
|
||||
function extractVerificationCode(value = '') {
|
||||
const text = String(value || '').trim();
|
||||
if (!text) {
|
||||
@@ -315,18 +449,75 @@
|
||||
}
|
||||
|
||||
async function pollActivationCode(state = {}, activation, options = {}, deps = {}) {
|
||||
const payload = await pollActivation(state, activation, deps);
|
||||
const code = extractCodeFromPollPayload(payload);
|
||||
if (code) {
|
||||
return code;
|
||||
const configuredTimeoutMs = Number(options.timeoutMs);
|
||||
const timeoutMs = Number.isFinite(configuredTimeoutMs) && configuredTimeoutMs > 0
|
||||
? Math.max(1000, configuredTimeoutMs)
|
||||
: 0;
|
||||
if (!timeoutMs) {
|
||||
const payload = await pollActivation(state, activation, deps);
|
||||
const code = extractCodeFromPollPayload(payload);
|
||||
if (code) {
|
||||
return code;
|
||||
}
|
||||
if (typeof options.onStatus === 'function') {
|
||||
await options.onStatus({
|
||||
activation,
|
||||
statusText: describePayload(payload) || 'PENDING',
|
||||
});
|
||||
}
|
||||
return '';
|
||||
}
|
||||
if (typeof options.onStatus === 'function') {
|
||||
await options.onStatus({
|
||||
activation,
|
||||
statusText: describePayload(payload) || 'PENDING',
|
||||
});
|
||||
|
||||
const intervalMs = Math.max(1000, Number(options.intervalMs) || DEFAULT_POLL_INTERVAL_MS);
|
||||
const maxRoundsRaw = Math.floor(Number(options.maxRounds));
|
||||
const maxRounds = Number.isFinite(maxRoundsRaw) && maxRoundsRaw > 0 ? maxRoundsRaw : 0;
|
||||
const start = Date.now();
|
||||
let pollCount = 0;
|
||||
let lastResponse = '';
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (maxRounds > 0 && pollCount >= maxRounds) {
|
||||
break;
|
||||
}
|
||||
deps.throwIfStopped?.();
|
||||
const payload = await pollActivation(state, activation, deps);
|
||||
const code = extractCodeFromPollPayload(payload);
|
||||
const statusText = normalizeText(
|
||||
payload?.status
|
||||
|| payload?.madaoStatus
|
||||
|| payload?.message
|
||||
|| payload?.text
|
||||
|| describePayload(payload),
|
||||
'PENDING'
|
||||
);
|
||||
lastResponse = statusText;
|
||||
pollCount += 1;
|
||||
if (typeof options.onStatus === 'function') {
|
||||
await options.onStatus({
|
||||
activation,
|
||||
elapsedMs: Date.now() - start,
|
||||
pollCount,
|
||||
statusText,
|
||||
timeoutMs,
|
||||
});
|
||||
}
|
||||
if (code) {
|
||||
return code;
|
||||
}
|
||||
if (/^(cancelled|canceled|failed|expired|timeout)$/i.test(statusText)) {
|
||||
throw new Error(`MaDao 订单在收到短信前已结束:${statusText}`);
|
||||
}
|
||||
if (typeof options.onWaitingForCode === 'function') {
|
||||
await options.onWaitingForCode({
|
||||
activation,
|
||||
elapsedMs: Date.now() - start,
|
||||
pollCount,
|
||||
statusText,
|
||||
timeoutMs,
|
||||
});
|
||||
}
|
||||
await deps.sleepWithStop?.(intervalMs);
|
||||
}
|
||||
return '';
|
||||
throw new Error(`${PHONE_CODE_TIMEOUT_ERROR_PREFIX}等待手机验证码超时。${lastResponse ? ` MaDao 最后状态:${lastResponse}` : ''}`);
|
||||
}
|
||||
|
||||
async function releaseActivation(state = {}, activation, action = 'cancel', deps = {}) {
|
||||
@@ -366,7 +557,6 @@
|
||||
routing_plan_id: activation?.madaoRoutingPlanId,
|
||||
routing_plan_name: activation?.madaoRoutingPlanName,
|
||||
service: activation?.serviceCode,
|
||||
country: activation?.countryId,
|
||||
});
|
||||
if (!nextTicket) {
|
||||
throw new Error('MaDao 返回的下一条路由激活记录无效。');
|
||||
@@ -399,15 +589,62 @@
|
||||
};
|
||||
}
|
||||
|
||||
async function finishActivation(state = {}, activation, deps = {}) {
|
||||
return releaseActivation(state, activation, 'finish', deps);
|
||||
}
|
||||
|
||||
async function cancelActivation(state = {}, activation, deps = {}) {
|
||||
return releaseActivation(state, activation, 'cancel', deps);
|
||||
}
|
||||
|
||||
async function banActivation(state = {}, activation, deps = {}) {
|
||||
return releaseActivation(state, activation, 'ban', deps);
|
||||
}
|
||||
|
||||
async function reuseActivation(_state = {}, activation) {
|
||||
return activation && typeof activation === 'object' ? { ...activation } : activation;
|
||||
}
|
||||
|
||||
async function requestAdditionalSms() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function resolveCountryCandidates() {
|
||||
return [];
|
||||
}
|
||||
|
||||
function createProvider(deps = {}) {
|
||||
const capabilities = Object.freeze({
|
||||
supportsReusableActivation: false,
|
||||
supportsAutomaticFreeReuse: false,
|
||||
supportsFreeReusePreservation: false,
|
||||
supportsPageResend: false,
|
||||
supportsPageResendProbe: false,
|
||||
supportsRouteReplace: true,
|
||||
requiresCountrySelection: false,
|
||||
});
|
||||
return {
|
||||
id: PROVIDER_ID,
|
||||
label: 'MaDao',
|
||||
capabilities,
|
||||
defaultProduct: DEFAULT_SERVICE,
|
||||
normalizeCountryId: normalizeCountry,
|
||||
normalizeCountryLabel,
|
||||
normalizeCountryKey,
|
||||
normalizeActivation,
|
||||
resolveCountryLabel,
|
||||
resolveActivationCountry,
|
||||
getActivationCountryKey,
|
||||
getActivationPrice,
|
||||
requestActivation: (state, options = {}, runtimeDeps = {}) => acquireActivation(state, options, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
}),
|
||||
acquireActivation: (state, options = {}, runtimeDeps = {}) => acquireActivation(state, options, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
}),
|
||||
reuseActivation,
|
||||
pollActivation: (state, activation, runtimeDeps = {}) => pollActivation(state, activation, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
@@ -420,10 +657,32 @@
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
}),
|
||||
finishActivation: (state, activation, runtimeDeps = {}) => finishActivation(state, activation, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
}),
|
||||
cancelActivation: (state, activation, runtimeDeps = {}) => cancelActivation(state, activation, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
}),
|
||||
banActivation: (state, activation, runtimeDeps = {}) => banActivation(state, activation, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
}),
|
||||
requestAdditionalSms,
|
||||
rotateActivation: (state, activation, options = {}, runtimeDeps = {}) => rotateActivation(state, activation, options, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
}),
|
||||
prepareActivationForReuse: async () => ({
|
||||
ok: false,
|
||||
reason: 'prepare_unsupported',
|
||||
message: 'MaDao 不支持自动白嫖复用准备。',
|
||||
}),
|
||||
canPersistReusableActivation: () => false,
|
||||
canPreserveActivationForFreeReuse: () => false,
|
||||
shouldUsePageResend: () => false,
|
||||
shouldProbePageResend: () => false,
|
||||
replaceRoutingActivation: (state, activation, options = {}, runtimeDeps = {}) => replaceRoutingActivation(state, activation, options, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
@@ -432,7 +691,9 @@
|
||||
extractCodeFromPollPayload,
|
||||
mapAcquirePath,
|
||||
mapTicketStatus,
|
||||
normalizeActivation,
|
||||
normalizeActivationFromAcquire,
|
||||
resolveCountryCandidates,
|
||||
resolveConfig: (state = {}, runtimeDeps = {}) => resolveConfig(state, {
|
||||
...deps,
|
||||
...runtimeDeps,
|
||||
@@ -451,12 +712,23 @@
|
||||
extractCodeFromPollPayload,
|
||||
mapAcquirePath,
|
||||
mapTicketStatus,
|
||||
normalizeActivation,
|
||||
normalizeActivationFromAcquire,
|
||||
normalizeCountry,
|
||||
normalizeCountryKey,
|
||||
normalizeCountryLabel,
|
||||
normalizeOperator,
|
||||
resolveActivationCountry,
|
||||
pollActivation,
|
||||
pollActivationCode,
|
||||
releaseActivation,
|
||||
finishActivation,
|
||||
cancelActivation,
|
||||
banActivation,
|
||||
replaceRoutingActivation,
|
||||
requestAdditionalSms,
|
||||
resolveConfig,
|
||||
resolveCountryCandidates,
|
||||
rotateActivation,
|
||||
};
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+32
-34
@@ -1553,46 +1553,43 @@
|
||||
<span class="data-label">接入模式</span>
|
||||
<select id="select-madao-mode" class="data-select mono">
|
||||
<option value="routing_plan">路由计划</option>
|
||||
<option value="direct">直连参数</option>
|
||||
<option value="direct">直连模式</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-routing-plan-id" style="display:none;">
|
||||
<span class="data-label">路由计划</span>
|
||||
<input type="text" id="input-madao-routing-plan-id" class="data-input mono"
|
||||
placeholder="routing_plan_id" />
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-provider-id" style="display:none;">
|
||||
<span class="data-label">直连平台</span>
|
||||
<input type="text" id="input-madao-provider-id" class="data-input mono"
|
||||
placeholder="auto" />
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-country" style="display:none;">
|
||||
<span class="data-label">直连国家</span>
|
||||
<input type="text" id="input-madao-country" class="data-input mono"
|
||||
placeholder="local / TH / any" />
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-auto-pick-country" style="display:none;">
|
||||
<span class="data-label">自动选国家</span>
|
||||
<div class="data-inline">
|
||||
<label class="toggle-switch" for="input-madao-auto-pick-country">
|
||||
<input type="checkbox" id="input-madao-auto-pick-country" />
|
||||
<span class="toggle-switch-track" aria-hidden="true">
|
||||
<span class="toggle-switch-thumb"></span>
|
||||
</span>
|
||||
</label>
|
||||
<span class="data-value">让 MaDao 根据直连平台自动选择国家</span>
|
||||
<div class="data-inline data-value-actions">
|
||||
<select id="select-madao-routing-plan-id" class="data-select mono">
|
||||
<option value="">请先刷新路由计划</option>
|
||||
</select>
|
||||
<button id="btn-madao-refresh-routing-plans" class="btn btn-ghost btn-xs data-inline-btn" type="button">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-reuse-phone" style="display:none;">
|
||||
<span class="data-label">MaDao 复用</span>
|
||||
<div class="data-inline">
|
||||
<label class="toggle-switch" for="input-madao-reuse-phone">
|
||||
<input type="checkbox" id="input-madao-reuse-phone" />
|
||||
<span class="toggle-switch-track" aria-hidden="true">
|
||||
<span class="toggle-switch-thumb"></span>
|
||||
</span>
|
||||
</label>
|
||||
<span class="data-value">允许 MaDao 复用符合条件的号码</span>
|
||||
<div class="data-row" id="row-madao-provider-id" style="display:none;">
|
||||
<span class="data-label">MaDao 服务商</span>
|
||||
<div class="data-inline data-value-actions">
|
||||
<select id="select-madao-provider-id" class="data-select mono">
|
||||
<option value="">请先刷新服务商</option>
|
||||
</select>
|
||||
<button id="btn-madao-refresh-providers" class="btn btn-ghost btn-xs data-inline-btn" type="button">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-country" style="display:none;">
|
||||
<span class="data-label">MaDao 国家</span>
|
||||
<div class="data-inline data-value-actions">
|
||||
<select id="select-madao-country" class="data-select mono">
|
||||
<option value="">请先选择服务商</option>
|
||||
</select>
|
||||
<button id="btn-madao-refresh-countries" class="btn btn-ghost btn-xs data-inline-btn" type="button">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-operator" style="display:none;">
|
||||
<span class="data-label">MaDao 线路</span>
|
||||
<div class="data-inline data-value-actions">
|
||||
<select id="select-madao-operator" class="data-select mono">
|
||||
<option value="">任意线路</option>
|
||||
</select>
|
||||
<button id="btn-madao-refresh-operators" class="btn btn-ghost btn-xs data-inline-btn" type="button">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-madao-price-range" style="display:none;">
|
||||
@@ -1896,6 +1893,7 @@
|
||||
<script src="../gopay-utils.js"></script>
|
||||
<script src="../phone-sms/providers/hero-sms.js"></script>
|
||||
<script src="../phone-sms/providers/five-sim.js"></script>
|
||||
<script src="../phone-sms/providers/nexsms.js"></script>
|
||||
<script src="../phone-sms/providers/madao.js"></script>
|
||||
<script src="../phone-sms/providers/registry.js"></script>
|
||||
<script src="../icloud-utils.js"></script>
|
||||
|
||||
+698
-61
@@ -460,6 +460,7 @@ const rowMaDaoMode = document.getElementById('row-madao-mode');
|
||||
const rowMaDaoRoutingPlanId = document.getElementById('row-madao-routing-plan-id');
|
||||
const rowMaDaoProviderId = document.getElementById('row-madao-provider-id');
|
||||
const rowMaDaoCountry = document.getElementById('row-madao-country');
|
||||
const rowMaDaoOperator = document.getElementById('row-madao-operator');
|
||||
const rowMaDaoAutoPickCountry = document.getElementById('row-madao-auto-pick-country');
|
||||
const rowMaDaoReusePhone = document.getElementById('row-madao-reuse-phone');
|
||||
const rowMaDaoPriceRange = document.getElementById('row-madao-price-range');
|
||||
@@ -494,9 +495,14 @@ const inputMaDaoBaseUrl = document.getElementById('input-madao-base-url');
|
||||
const inputMaDaoHttpSecret = document.getElementById('input-madao-http-secret');
|
||||
const btnToggleMaDaoHttpSecret = document.getElementById('btn-toggle-madao-http-secret');
|
||||
const selectMaDaoMode = document.getElementById('select-madao-mode');
|
||||
const inputMaDaoRoutingPlanId = document.getElementById('input-madao-routing-plan-id');
|
||||
const inputMaDaoProviderId = document.getElementById('input-madao-provider-id');
|
||||
const inputMaDaoCountry = document.getElementById('input-madao-country');
|
||||
const selectMaDaoRoutingPlanId = document.getElementById('select-madao-routing-plan-id');
|
||||
const btnMaDaoRefreshRoutingPlans = document.getElementById('btn-madao-refresh-routing-plans');
|
||||
const selectMaDaoProviderId = document.getElementById('select-madao-provider-id');
|
||||
const btnMaDaoRefreshProviders = document.getElementById('btn-madao-refresh-providers');
|
||||
const selectMaDaoCountry = document.getElementById('select-madao-country');
|
||||
const btnMaDaoRefreshCountries = document.getElementById('btn-madao-refresh-countries');
|
||||
const selectMaDaoOperator = document.getElementById('select-madao-operator');
|
||||
const btnMaDaoRefreshOperators = document.getElementById('btn-madao-refresh-operators');
|
||||
const inputMaDaoAutoPickCountry = document.getElementById('input-madao-auto-pick-country');
|
||||
const inputMaDaoReusePhone = document.getElementById('input-madao-reuse-phone');
|
||||
const inputMaDaoMinPrice = document.getElementById('input-madao-min-price');
|
||||
@@ -689,6 +695,10 @@ const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||
const MADAO_MODE_ROUTING_PLAN = 'routing_plan';
|
||||
const MADAO_MODE_DIRECT = 'direct';
|
||||
const DEFAULT_MADAO_MODE = MADAO_MODE_ROUTING_PLAN;
|
||||
let maDaoRoutingPlanOptions = [];
|
||||
let maDaoProviderOptions = [];
|
||||
let maDaoCountryOptions = [];
|
||||
let maDaoOperatorOptions = [];
|
||||
const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = Object.freeze({
|
||||
[PHONE_SMS_PROVIDER_HERO]: Object.freeze({
|
||||
rowKeys: Object.freeze([
|
||||
@@ -734,8 +744,7 @@ const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = Object.freeze({
|
||||
directRowKeys: Object.freeze([
|
||||
'rowMaDaoProviderId',
|
||||
'rowMaDaoCountry',
|
||||
'rowMaDaoAutoPickCountry',
|
||||
'rowMaDaoReusePhone',
|
||||
'rowMaDaoOperator',
|
||||
'rowMaDaoPriceRange',
|
||||
]),
|
||||
}),
|
||||
@@ -765,6 +774,7 @@ function getPhoneSmsProviderUiRowMap() {
|
||||
rowMaDaoRoutingPlanId,
|
||||
rowMaDaoProviderId,
|
||||
rowMaDaoCountry,
|
||||
rowMaDaoOperator,
|
||||
rowMaDaoAutoPickCountry,
|
||||
rowMaDaoReusePhone,
|
||||
rowMaDaoPriceRange,
|
||||
@@ -4455,6 +4465,9 @@ function collectSettingsPayload() {
|
||||
const normalizeMaDaoProviderIdSafe = typeof normalizeMaDaoProviderIdValue === 'function'
|
||||
? normalizeMaDaoProviderIdValue
|
||||
: ((value = '') => String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''));
|
||||
const normalizeMaDaoOperatorSafe = typeof normalizeMaDaoOperatorValue === 'function'
|
||||
? normalizeMaDaoOperatorValue
|
||||
: ((value = '') => String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''));
|
||||
const normalizeMaDaoCountrySafe = typeof normalizeMaDaoCountry === 'function'
|
||||
? normalizeMaDaoCountry
|
||||
: ((value = '') => {
|
||||
@@ -4638,15 +4651,22 @@ function collectSettingsPayload() {
|
||||
const maDaoModeValue = typeof selectMaDaoMode !== 'undefined' && selectMaDaoMode
|
||||
? normalizeMaDaoModeSafe(selectMaDaoMode.value || latestState?.madaoMode)
|
||||
: normalizeMaDaoModeSafe(latestState?.madaoMode);
|
||||
const maDaoRoutingPlanIdValue = typeof inputMaDaoRoutingPlanId !== 'undefined' && inputMaDaoRoutingPlanId
|
||||
? normalizeMaDaoIdentifierSafe(inputMaDaoRoutingPlanId.value || '')
|
||||
const maDaoRoutingPlanIdValue = typeof selectMaDaoRoutingPlanId !== 'undefined' && selectMaDaoRoutingPlanId
|
||||
? normalizeMaDaoIdentifierSafe(selectMaDaoRoutingPlanId.value || '')
|
||||
: normalizeMaDaoIdentifierSafe(latestState?.madaoRoutingPlanId || '');
|
||||
const maDaoProviderIdValue = typeof inputMaDaoProviderId !== 'undefined' && inputMaDaoProviderId
|
||||
? normalizeMaDaoProviderIdSafe(inputMaDaoProviderId.value || '')
|
||||
const maDaoDirectModeValue = typeof MADAO_MODE_DIRECT !== 'undefined'
|
||||
? MADAO_MODE_DIRECT
|
||||
: 'direct';
|
||||
const shouldReadMaDaoDirectControls = maDaoModeValue === maDaoDirectModeValue;
|
||||
const maDaoProviderIdValue = shouldReadMaDaoDirectControls && typeof selectMaDaoProviderId !== 'undefined' && selectMaDaoProviderId
|
||||
? normalizeMaDaoProviderIdSafe(selectMaDaoProviderId.value || '')
|
||||
: normalizeMaDaoProviderIdSafe(latestState?.madaoProviderId || '');
|
||||
const maDaoCountryValue = typeof inputMaDaoCountry !== 'undefined' && inputMaDaoCountry
|
||||
? normalizeMaDaoCountrySafe(inputMaDaoCountry.value || '')
|
||||
const maDaoCountryValue = shouldReadMaDaoDirectControls && typeof selectMaDaoCountry !== 'undefined' && selectMaDaoCountry
|
||||
? normalizeMaDaoCountrySafe(selectMaDaoCountry.value || '')
|
||||
: normalizeMaDaoCountrySafe(latestState?.madaoCountry || '');
|
||||
const maDaoOperatorValue = shouldReadMaDaoDirectControls && typeof selectMaDaoOperator !== 'undefined' && selectMaDaoOperator
|
||||
? normalizeMaDaoOperatorSafe(selectMaDaoOperator.value || '')
|
||||
: normalizeMaDaoOperatorSafe(latestState?.madaoOperator || '');
|
||||
const maDaoAutoPickCountryValue = typeof inputMaDaoAutoPickCountry !== 'undefined' && inputMaDaoAutoPickCountry
|
||||
? Boolean(inputMaDaoAutoPickCountry.checked)
|
||||
: (latestState?.madaoAutoPickCountry !== undefined ? Boolean(latestState.madaoAutoPickCountry) : true);
|
||||
@@ -5273,6 +5293,7 @@ function collectSettingsPayload() {
|
||||
madaoRoutingPlanId: maDaoRoutingPlanIdValue,
|
||||
madaoProviderId: maDaoProviderIdValue,
|
||||
madaoCountry: maDaoCountryValue,
|
||||
madaoOperator: maDaoOperatorValue,
|
||||
madaoAutoPickCountry: maDaoAutoPickCountryValue,
|
||||
madaoReusePhone: maDaoReusePhoneValue,
|
||||
madaoMinPrice: maDaoMinPriceValue,
|
||||
@@ -5675,10 +5696,23 @@ function normalizeMaDaoIdentifierValue(value = '') {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function normalizeMaDaoRoutingPlanIdValue(value = '') {
|
||||
return normalizeMaDaoIdentifierValue(value);
|
||||
}
|
||||
|
||||
function normalizeMaDaoProviderIdValue(value = '') {
|
||||
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
|
||||
function normalizeMaDaoOperatorValue(value = '') {
|
||||
const rawValue = String(value || '').trim();
|
||||
const compactValue = rawValue.toLowerCase().replace(/[^a-z0-9]+/g, '');
|
||||
if (!rawValue || compactValue === 'any' || compactValue === 'anyoperator') {
|
||||
return '';
|
||||
}
|
||||
return rawValue.toLowerCase().replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
|
||||
function normalizeMaDaoCountry(value = '') {
|
||||
const trimmed = String(value || '').trim();
|
||||
if (!trimmed) {
|
||||
@@ -5694,6 +5728,22 @@ function normalizeMaDaoCountry(value = '') {
|
||||
return lowered.replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
|
||||
function formatMaDaoCountryDisplayLabel(value = '', label = '', labelZh = '') {
|
||||
const country = normalizeMaDaoCountry(value);
|
||||
const sourceLabelZh = String(labelZh || '').trim();
|
||||
const sourceLabel = String(label || '').trim();
|
||||
if (!country) {
|
||||
return sourceLabelZh || sourceLabel;
|
||||
}
|
||||
if (country === 'local') {
|
||||
return sourceLabelZh || '本地';
|
||||
}
|
||||
if (country === 'any') {
|
||||
return sourceLabelZh || '任意国家';
|
||||
}
|
||||
return sourceLabelZh || sourceLabel || country;
|
||||
}
|
||||
|
||||
function normalizeMaDaoPriceValue(value = '') {
|
||||
const rawValue = String(value ?? '').trim();
|
||||
if (!rawValue) {
|
||||
@@ -5706,6 +5756,493 @@ function normalizeMaDaoPriceValue(value = '') {
|
||||
return String(Math.round(numeric * 10000) / 10000);
|
||||
}
|
||||
|
||||
function createSelectOptionElement(value = '', label = '', title = '') {
|
||||
const option = typeof document !== 'undefined' && typeof document.createElement === 'function'
|
||||
? document.createElement('option')
|
||||
: { value: '', textContent: '', title: '', selected: false };
|
||||
option.value = String(value || '');
|
||||
option.textContent = String(label || value || '').trim();
|
||||
const normalizedTitle = String(title || '').trim();
|
||||
if (normalizedTitle) {
|
||||
option.title = normalizedTitle;
|
||||
}
|
||||
return option;
|
||||
}
|
||||
|
||||
function setSelectOptions(selectEl, items = [], options = {}) {
|
||||
if (!selectEl) {
|
||||
return;
|
||||
}
|
||||
const placeholder = String(options.placeholder || '').trim();
|
||||
const includeEmpty = options.includeEmpty !== false;
|
||||
const emptyValue = options.emptyValue !== undefined ? String(options.emptyValue) : '';
|
||||
const selectedValue = String(options.value !== undefined ? options.value : selectEl.value || '').trim();
|
||||
const optionItems = [];
|
||||
if (includeEmpty) {
|
||||
optionItems.push({
|
||||
value: emptyValue,
|
||||
label: placeholder || '请选择',
|
||||
});
|
||||
}
|
||||
(Array.isArray(items) ? items : []).forEach((item) => {
|
||||
const value = String(item?.value ?? '').trim();
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
optionItems.push({
|
||||
value,
|
||||
label: String(item?.label || value).trim(),
|
||||
hint: String(item?.hint || '').trim(),
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof selectEl.replaceChildren === 'function') {
|
||||
selectEl.replaceChildren(...optionItems.map((item) => createSelectOptionElement(item.value, item.label, item.hint)));
|
||||
} else if (typeof selectEl.appendChild === 'function') {
|
||||
selectEl.innerHTML = '';
|
||||
optionItems.forEach((item) => {
|
||||
selectEl.appendChild(createSelectOptionElement(item.value, item.label, item.hint));
|
||||
});
|
||||
} else {
|
||||
selectEl.options = optionItems.map((item) => createSelectOptionElement(item.value, item.label, item.hint));
|
||||
}
|
||||
|
||||
const values = new Set(optionItems.map((item) => item.value));
|
||||
const nextValue = values.has(selectedValue)
|
||||
? selectedValue
|
||||
: (includeEmpty ? emptyValue : (optionItems[0]?.value || ''));
|
||||
selectEl.value = nextValue;
|
||||
Array.from(selectEl.options || []).forEach((option) => {
|
||||
option.selected = String(option.value || '') === nextValue;
|
||||
});
|
||||
}
|
||||
|
||||
function buildMaDaoRoutingPlanOptions(items = [], selectedValue = '') {
|
||||
const selectedPlanId = normalizeMaDaoRoutingPlanIdValue(selectedValue);
|
||||
const seen = new Set();
|
||||
const normalizedItems = [];
|
||||
(Array.isArray(items) ? items : []).forEach((item) => {
|
||||
const value = normalizeMaDaoRoutingPlanIdValue(
|
||||
item?.value
|
||||
|| item?.id
|
||||
|| item?.routing_plan_id
|
||||
|| item?.routingPlanId
|
||||
|| ''
|
||||
);
|
||||
if (!value || seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
normalizedItems.push({
|
||||
value,
|
||||
label: String(item?.label || item?.name || value).trim() || value,
|
||||
hint: String(item?.hint || item?.description || '').trim(),
|
||||
service: String(item?.service || '').trim().toLowerCase(),
|
||||
});
|
||||
});
|
||||
if (selectedPlanId && !seen.has(selectedPlanId)) {
|
||||
normalizedItems.unshift({
|
||||
value: selectedPlanId,
|
||||
label: selectedPlanId,
|
||||
hint: '已保存的路由计划',
|
||||
});
|
||||
}
|
||||
return normalizedItems;
|
||||
}
|
||||
|
||||
function setMaDaoRoutingPlanSelectOptions(selectedValue = latestState?.madaoRoutingPlanId || '') {
|
||||
const normalizedSelected = normalizeMaDaoRoutingPlanIdValue(selectedValue);
|
||||
const options = buildMaDaoRoutingPlanOptions(maDaoRoutingPlanOptions, normalizedSelected);
|
||||
setSelectOptions(selectMaDaoRoutingPlanId, options, {
|
||||
placeholder: '请先刷新路由计划',
|
||||
value: normalizedSelected,
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeMaDaoOptionListItems(items = [], selectedValue = '', normalizeValue = normalizeMaDaoIdentifierValue, selectedHint = '已保存的选项') {
|
||||
const normalizedSelected = normalizeValue(selectedValue);
|
||||
const seen = new Set();
|
||||
const normalizedItems = [];
|
||||
(Array.isArray(items) ? items : []).forEach((item) => {
|
||||
const value = normalizeValue(
|
||||
item?.value
|
||||
|| item?.id
|
||||
|| item?.provider
|
||||
|| item?.provider_value
|
||||
|| item?.providerValue
|
||||
|| item?.country
|
||||
|| item?.operator
|
||||
|| ''
|
||||
);
|
||||
if (!value || seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
normalizedItems.push({
|
||||
value,
|
||||
label: String(item?.label || item?.name || item?.display_name || item?.displayName || value).trim() || value,
|
||||
labelZh: String(item?.label_zh || item?.labelZh || item?.display_name_zh || item?.displayNameZh || '').trim(),
|
||||
hint: String(item?.hint || item?.description || item?.provider_value || item?.providerValue || '').trim(),
|
||||
enabled: item?.enabled !== false,
|
||||
});
|
||||
});
|
||||
if (normalizedSelected && !seen.has(normalizedSelected)) {
|
||||
normalizedItems.unshift({
|
||||
value: normalizedSelected,
|
||||
label: normalizedSelected,
|
||||
hint: selectedHint,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
return normalizedItems.filter((item) => item.enabled !== false);
|
||||
}
|
||||
|
||||
function resolveMaDaoOptionSelectedValue(items = [], selectedValue = '', normalizeValue = normalizeMaDaoIdentifierValue) {
|
||||
const normalizedSelected = normalizeValue(selectedValue);
|
||||
if (!normalizedSelected) {
|
||||
return '';
|
||||
}
|
||||
const sourceItems = Array.isArray(items) ? items : [];
|
||||
for (const item of sourceItems) {
|
||||
const value = normalizeValue(
|
||||
item?.value
|
||||
|| item?.id
|
||||
|| item?.provider
|
||||
|| item?.provider_value
|
||||
|| item?.providerValue
|
||||
|| item?.country
|
||||
|| item?.operator
|
||||
|| ''
|
||||
);
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
const aliases = [
|
||||
item?.value,
|
||||
item?.id,
|
||||
item?.provider,
|
||||
item?.provider_value,
|
||||
item?.providerValue,
|
||||
item?.country,
|
||||
item?.operator,
|
||||
item?.label,
|
||||
item?.label_zh,
|
||||
item?.labelZh,
|
||||
item?.name,
|
||||
item?.display_name,
|
||||
item?.displayName,
|
||||
];
|
||||
if (aliases.some((alias) => normalizeValue(alias || '') === normalizedSelected)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return normalizedSelected;
|
||||
}
|
||||
|
||||
function setMaDaoProviderSelectOptions(selectedValue = latestState?.madaoProviderId || '') {
|
||||
const normalizedSelected = resolveMaDaoOptionSelectedValue(
|
||||
maDaoProviderOptions,
|
||||
selectedValue,
|
||||
normalizeMaDaoProviderIdValue
|
||||
);
|
||||
const options = normalizeMaDaoOptionListItems(
|
||||
maDaoProviderOptions,
|
||||
normalizedSelected,
|
||||
normalizeMaDaoProviderIdValue,
|
||||
'已保存的服务商'
|
||||
);
|
||||
setSelectOptions(selectMaDaoProviderId, options, {
|
||||
placeholder: '请先刷新服务商',
|
||||
value: normalizedSelected,
|
||||
});
|
||||
}
|
||||
|
||||
function setMaDaoCountrySelectOptions(selectedValue = latestState?.madaoCountry || '') {
|
||||
const normalizedSelected = resolveMaDaoOptionSelectedValue(
|
||||
maDaoCountryOptions,
|
||||
selectedValue,
|
||||
normalizeMaDaoCountry
|
||||
);
|
||||
const options = normalizeMaDaoOptionListItems(
|
||||
maDaoCountryOptions,
|
||||
normalizedSelected,
|
||||
normalizeMaDaoCountry,
|
||||
'已保存的国家'
|
||||
).map((item) => ({
|
||||
...item,
|
||||
label: formatMaDaoCountryDisplayLabel(item.value, item.label, item.labelZh),
|
||||
}));
|
||||
setSelectOptions(selectMaDaoCountry, options, {
|
||||
placeholder: '请先选择服务商',
|
||||
value: normalizedSelected,
|
||||
});
|
||||
}
|
||||
|
||||
function setMaDaoOperatorSelectOptions(selectedValue = latestState?.madaoOperator || '') {
|
||||
const normalizedSelected = resolveMaDaoOptionSelectedValue(
|
||||
maDaoOperatorOptions,
|
||||
selectedValue,
|
||||
normalizeMaDaoOperatorValue
|
||||
);
|
||||
const options = normalizeMaDaoOptionListItems(
|
||||
maDaoOperatorOptions,
|
||||
normalizedSelected,
|
||||
normalizeMaDaoOperatorValue,
|
||||
'已保存的线路'
|
||||
);
|
||||
setSelectOptions(selectMaDaoOperator, options, {
|
||||
placeholder: '任意线路',
|
||||
value: normalizedSelected,
|
||||
});
|
||||
}
|
||||
|
||||
function buildMaDaoRequestUrl(path = '', baseUrl = '') {
|
||||
const normalizedBaseUrl = normalizeMaDaoBaseUrlValue(baseUrl || latestState?.madaoBaseUrl || DEFAULT_MADAO_BASE_URL);
|
||||
return new URL(String(path || '').replace(/^\/+/, ''), `${normalizedBaseUrl.replace(/\/+$/, '')}/`).toString();
|
||||
}
|
||||
|
||||
function buildMaDaoRequestHeaders(httpSecret = '') {
|
||||
const headers = { Accept: 'application/json' };
|
||||
const normalizedSecret = String(httpSecret || '').trim();
|
||||
if (normalizedSecret) {
|
||||
headers.Authorization = `Bearer ${normalizedSecret}`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function fetchMaDaoJson(path = '', options = {}) {
|
||||
const baseUrl = typeof inputMaDaoBaseUrl !== 'undefined' && inputMaDaoBaseUrl
|
||||
? normalizeMaDaoBaseUrlValue(inputMaDaoBaseUrl.value || latestState?.madaoBaseUrl)
|
||||
: normalizeMaDaoBaseUrlValue(latestState?.madaoBaseUrl);
|
||||
const httpSecret = typeof inputMaDaoHttpSecret !== 'undefined' && inputMaDaoHttpSecret
|
||||
? String(inputMaDaoHttpSecret.value || '')
|
||||
: String(latestState?.madaoHttpSecret || '');
|
||||
const response = await fetch(buildMaDaoRequestUrl(path, baseUrl), {
|
||||
method: options.method || 'GET',
|
||||
headers: {
|
||||
...buildMaDaoRequestHeaders(httpSecret),
|
||||
...(options.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
||||
},
|
||||
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
||||
cache: 'no-store',
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = null;
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch {
|
||||
payload = { message: text };
|
||||
}
|
||||
}
|
||||
if (!response.ok) {
|
||||
const message = String(payload?.message || payload?.error || response.statusText || `HTTP ${response.status}`).trim();
|
||||
throw new Error(message || 'MaDao 请求失败');
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function getMaDaoRoutingPlansFromPayload(payload = {}) {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
}
|
||||
if (Array.isArray(payload?.plans)) {
|
||||
return payload.plans;
|
||||
}
|
||||
if (Array.isArray(payload?.data?.plans)) {
|
||||
return payload.data.plans;
|
||||
}
|
||||
if (Array.isArray(payload?.data)) {
|
||||
return payload.data;
|
||||
}
|
||||
if (Array.isArray(payload?.items)) {
|
||||
return payload.items;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getMaDaoProvidersFromPayload(payload = {}) {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
}
|
||||
if (Array.isArray(payload?.providers)) {
|
||||
return payload.providers;
|
||||
}
|
||||
if (Array.isArray(payload?.data?.providers)) {
|
||||
return payload.data.providers;
|
||||
}
|
||||
if (Array.isArray(payload?.data)) {
|
||||
return payload.data;
|
||||
}
|
||||
if (Array.isArray(payload?.items)) {
|
||||
return payload.items;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getMaDaoOptionItemsFromPayload(payload = {}) {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
}
|
||||
if (Array.isArray(payload?.items)) {
|
||||
return payload.items;
|
||||
}
|
||||
if (Array.isArray(payload?.data?.items)) {
|
||||
return payload.data.items;
|
||||
}
|
||||
if (Array.isArray(payload?.data)) {
|
||||
return payload.data;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getSelectedMaDaoProviderId() {
|
||||
return normalizeMaDaoProviderIdValue(
|
||||
typeof selectMaDaoProviderId !== 'undefined' && selectMaDaoProviderId
|
||||
? selectMaDaoProviderId.value
|
||||
: latestState?.madaoProviderId
|
||||
);
|
||||
}
|
||||
|
||||
function getSelectedMaDaoCountry() {
|
||||
return normalizeMaDaoCountry(
|
||||
typeof selectMaDaoCountry !== 'undefined' && selectMaDaoCountry
|
||||
? selectMaDaoCountry.value
|
||||
: latestState?.madaoCountry
|
||||
);
|
||||
}
|
||||
|
||||
async function loadMaDaoRoutingPlans(options = {}) {
|
||||
const selectedValue = normalizeMaDaoRoutingPlanIdValue(
|
||||
typeof selectMaDaoRoutingPlanId !== 'undefined' && selectMaDaoRoutingPlanId
|
||||
? selectMaDaoRoutingPlanId.value
|
||||
: latestState?.madaoRoutingPlanId
|
||||
);
|
||||
const payload = await fetchMaDaoJson('/api/routing-plans');
|
||||
const plans = getMaDaoRoutingPlansFromPayload(payload);
|
||||
maDaoRoutingPlanOptions = plans
|
||||
.filter((plan) => plan?.enabled !== false)
|
||||
.filter((plan) => {
|
||||
const service = String(plan?.service || '').trim().toLowerCase();
|
||||
return !service || service === 'openai';
|
||||
})
|
||||
.map((plan) => ({
|
||||
value: normalizeMaDaoRoutingPlanIdValue(plan?.id || plan?.routing_plan_id || plan?.routingPlanId || plan?.value || ''),
|
||||
label: String(plan?.name || plan?.label || plan?.id || plan?.routing_plan_id || '').trim(),
|
||||
hint: String(plan?.description || plan?.hint || '').trim(),
|
||||
service: String(plan?.service || '').trim().toLowerCase(),
|
||||
}))
|
||||
.filter((plan) => plan.value);
|
||||
setMaDaoRoutingPlanSelectOptions(selectedValue);
|
||||
updateHeroSmsPlatformDisplay();
|
||||
if (!options.silent && typeof showToast === 'function') {
|
||||
showToast('已刷新 MaDao 路由计划。', 'info', 1600);
|
||||
}
|
||||
return maDaoRoutingPlanOptions;
|
||||
}
|
||||
|
||||
async function loadMaDaoProviders(options = {}) {
|
||||
const selectedValue = normalizeMaDaoProviderIdValue(
|
||||
(typeof selectMaDaoProviderId !== 'undefined' && selectMaDaoProviderId
|
||||
? selectMaDaoProviderId.value
|
||||
: '')
|
||||
|| latestState?.madaoProviderId
|
||||
);
|
||||
const payload = await fetchMaDaoJson('/api/providers');
|
||||
maDaoProviderOptions = getMaDaoProvidersFromPayload(payload)
|
||||
.filter((provider) => provider?.enabled !== false)
|
||||
.map((provider) => ({
|
||||
value: normalizeMaDaoProviderIdValue(provider?.id || provider?.value || provider?.provider || ''),
|
||||
label: String(provider?.name || provider?.label || provider?.id || provider?.value || '').trim(),
|
||||
hint: String(provider?.description || provider?.hint || provider?.protocol_label || provider?.protocolLabel || provider?.kind || '').trim(),
|
||||
enabled: provider?.enabled !== false,
|
||||
}))
|
||||
.filter((provider) => provider.value);
|
||||
setMaDaoProviderSelectOptions(selectedValue);
|
||||
if (!options.skipChildren && getSelectedMaDaoProviderId()) {
|
||||
await loadMaDaoCountries({ silent: true }).catch(() => {
|
||||
setMaDaoCountrySelectOptions(latestState?.madaoCountry || '');
|
||||
setMaDaoOperatorSelectOptions(latestState?.madaoOperator || '');
|
||||
});
|
||||
}
|
||||
updateHeroSmsPlatformDisplay();
|
||||
if (!options.silent && typeof showToast === 'function') {
|
||||
showToast('已刷新 MaDao 服务商。', 'info', 1600);
|
||||
}
|
||||
return maDaoProviderOptions;
|
||||
}
|
||||
|
||||
async function loadMaDaoCountries(options = {}) {
|
||||
const providerId = getSelectedMaDaoProviderId();
|
||||
const selectedValue = normalizeMaDaoCountry(
|
||||
(typeof selectMaDaoCountry !== 'undefined' && selectMaDaoCountry
|
||||
? selectMaDaoCountry.value
|
||||
: '')
|
||||
|| latestState?.madaoCountry
|
||||
);
|
||||
if (!providerId) {
|
||||
maDaoCountryOptions = [];
|
||||
maDaoOperatorOptions = [];
|
||||
setMaDaoCountrySelectOptions(selectedValue);
|
||||
setMaDaoOperatorSelectOptions(latestState?.madaoOperator || '');
|
||||
return maDaoCountryOptions;
|
||||
}
|
||||
const payload = await fetchMaDaoJson(`/api/providers/${encodeURIComponent(providerId)}/countries`);
|
||||
maDaoCountryOptions = getMaDaoOptionItemsFromPayload(payload);
|
||||
setMaDaoCountrySelectOptions(selectedValue);
|
||||
if (!options.skipChildren) {
|
||||
await loadMaDaoOperators({ silent: true }).catch(() => {
|
||||
setMaDaoOperatorSelectOptions(latestState?.madaoOperator || '');
|
||||
});
|
||||
}
|
||||
updateHeroSmsPlatformDisplay();
|
||||
if (!options.silent && typeof showToast === 'function') {
|
||||
showToast('已刷新 MaDao 国家。', 'info', 1600);
|
||||
}
|
||||
return maDaoCountryOptions;
|
||||
}
|
||||
|
||||
async function loadMaDaoOperators(options = {}) {
|
||||
const providerId = getSelectedMaDaoProviderId();
|
||||
const country = getSelectedMaDaoCountry();
|
||||
const selectedValue = normalizeMaDaoOperatorValue(
|
||||
typeof selectMaDaoOperator !== 'undefined' && selectMaDaoOperator
|
||||
? selectMaDaoOperator.value
|
||||
: latestState?.madaoOperator
|
||||
);
|
||||
if (!providerId) {
|
||||
maDaoOperatorOptions = [];
|
||||
setMaDaoOperatorSelectOptions(selectedValue);
|
||||
return maDaoOperatorOptions;
|
||||
}
|
||||
const payload = await fetchMaDaoJson(`/api/providers/${encodeURIComponent(providerId)}/operators`, {
|
||||
method: 'POST',
|
||||
body: country ? { country } : {},
|
||||
});
|
||||
maDaoOperatorOptions = getMaDaoOptionItemsFromPayload(payload);
|
||||
setMaDaoOperatorSelectOptions(selectedValue);
|
||||
updateHeroSmsPlatformDisplay();
|
||||
if (!options.silent && typeof showToast === 'function') {
|
||||
showToast('已刷新 MaDao 线路。', 'info', 1600);
|
||||
}
|
||||
return maDaoOperatorOptions;
|
||||
}
|
||||
|
||||
function getSelectedMaDaoRoutingPlanLabel(value = '') {
|
||||
const selectedValue = normalizeMaDaoRoutingPlanIdValue(
|
||||
value
|
||||
|| (typeof selectMaDaoRoutingPlanId !== 'undefined' && selectMaDaoRoutingPlanId
|
||||
? selectMaDaoRoutingPlanId.value
|
||||
: latestState?.madaoRoutingPlanId)
|
||||
|| ''
|
||||
);
|
||||
if (!selectedValue) {
|
||||
return 'routing plan';
|
||||
}
|
||||
const matched = maDaoRoutingPlanOptions.find((item) => normalizeMaDaoRoutingPlanIdValue(item?.value) === selectedValue);
|
||||
return String(matched?.label || selectedValue).trim() || 'routing plan';
|
||||
}
|
||||
|
||||
function normalizePhoneSmsProviderValue(value = '') {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderId) {
|
||||
@@ -7071,7 +7608,7 @@ function updateHeroSmsPlatformDisplay() {
|
||||
: (provider === PHONE_SMS_PROVIDER_NEXSMS
|
||||
? (getSelectedNexSmsCountries()[0] || { id: DEFAULT_NEX_SMS_COUNTRY_ORDER[0], label: `Country #${DEFAULT_NEX_SMS_COUNTRY_ORDER[0]}` })
|
||||
: (provider === PHONE_SMS_PROVIDER_MADAO
|
||||
? { id: normalizeMaDaoCountry(inputMaDaoCountry?.value || latestState?.madaoCountry || ''), label: normalizeMaDaoModeValue(selectMaDaoMode?.value || latestState?.madaoMode) === MADAO_MODE_DIRECT ? (normalizeMaDaoCountry(inputMaDaoCountry?.value || latestState?.madaoCountry || '') || 'auto') : 'routing plan' }
|
||||
? { id: '', label: normalizeMaDaoModeValue(selectMaDaoMode?.value || latestState?.madaoMode) === MADAO_MODE_DIRECT ? 'direct' : getSelectedMaDaoRoutingPlanLabel() }
|
||||
: getSelectedHeroSmsCountryOption()));
|
||||
const countryText = selected?.label ? ` / ${selected.label}` : '';
|
||||
displayHeroSmsPlatform.textContent = `${getPhoneSmsProviderLabel(provider)} / OpenAI${countryText}`;
|
||||
@@ -11817,7 +12354,18 @@ function applySettingsState(state) {
|
||||
updatePhoneSmsProviderOrderSummary(restoredPhoneSmsProviderOrder);
|
||||
if (previousPhoneSmsProvider !== restoredPhoneSmsProvider) {
|
||||
heroSmsCountrySelectionOrder = [];
|
||||
loadHeroSmsCountries({ silent: true }).catch(() => { });
|
||||
if (restoredPhoneSmsProvider === PHONE_SMS_PROVIDER_MADAO) {
|
||||
loadMaDaoRoutingPlans({ silent: true }).catch(() => {
|
||||
setMaDaoRoutingPlanSelectOptions(state?.madaoRoutingPlanId || '');
|
||||
});
|
||||
loadMaDaoProviders({ silent: true }).catch(() => {
|
||||
setMaDaoProviderSelectOptions(state?.madaoProviderId || '');
|
||||
setMaDaoCountrySelectOptions(state?.madaoCountry || '');
|
||||
setMaDaoOperatorSelectOptions(state?.madaoOperator || '');
|
||||
});
|
||||
} else {
|
||||
loadHeroSmsCountries({ silent: true }).catch(() => { });
|
||||
}
|
||||
}
|
||||
if (inputHeroSmsApiKey) {
|
||||
inputHeroSmsApiKey.value = restoredPhoneSmsProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||||
@@ -11854,14 +12402,17 @@ function applySettingsState(state) {
|
||||
if (typeof selectMaDaoMode !== 'undefined' && selectMaDaoMode) {
|
||||
selectMaDaoMode.value = normalizeMaDaoModeValue(state?.madaoMode);
|
||||
}
|
||||
if (typeof inputMaDaoRoutingPlanId !== 'undefined' && inputMaDaoRoutingPlanId) {
|
||||
inputMaDaoRoutingPlanId.value = normalizeMaDaoIdentifierValue(state?.madaoRoutingPlanId || '');
|
||||
if (typeof selectMaDaoRoutingPlanId !== 'undefined' && selectMaDaoRoutingPlanId) {
|
||||
setMaDaoRoutingPlanSelectOptions(state?.madaoRoutingPlanId || '');
|
||||
}
|
||||
if (typeof inputMaDaoProviderId !== 'undefined' && inputMaDaoProviderId) {
|
||||
inputMaDaoProviderId.value = normalizeMaDaoProviderIdValue(state?.madaoProviderId || '');
|
||||
if (typeof selectMaDaoProviderId !== 'undefined' && selectMaDaoProviderId) {
|
||||
setMaDaoProviderSelectOptions(state?.madaoProviderId || '');
|
||||
}
|
||||
if (typeof inputMaDaoCountry !== 'undefined' && inputMaDaoCountry) {
|
||||
inputMaDaoCountry.value = normalizeMaDaoCountry(state?.madaoCountry || '');
|
||||
if (typeof selectMaDaoCountry !== 'undefined' && selectMaDaoCountry) {
|
||||
setMaDaoCountrySelectOptions(state?.madaoCountry || '');
|
||||
}
|
||||
if (typeof selectMaDaoOperator !== 'undefined' && selectMaDaoOperator) {
|
||||
setMaDaoOperatorSelectOptions(state?.madaoOperator || '');
|
||||
}
|
||||
if (typeof inputMaDaoAutoPickCountry !== 'undefined' && inputMaDaoAutoPickCountry) {
|
||||
inputMaDaoAutoPickCountry.checked = state?.madaoAutoPickCountry !== undefined
|
||||
@@ -17159,15 +17710,35 @@ function buildPhoneSmsProviderStatePatch(provider = getSelectedPhoneSmsProvider(
|
||||
};
|
||||
}
|
||||
if (normalizedProvider === PHONE_SMS_PROVIDER_MADAO) {
|
||||
const maDaoMode = normalizeMaDaoModeValue(selectMaDaoMode?.value || latestState?.madaoMode);
|
||||
const maDaoDirectModeValue = typeof MADAO_MODE_DIRECT !== 'undefined'
|
||||
? MADAO_MODE_DIRECT
|
||||
: 'direct';
|
||||
const shouldReadMaDaoDirectControls = maDaoMode === maDaoDirectModeValue;
|
||||
return {
|
||||
madaoBaseUrl: normalizeMaDaoBaseUrlValue(inputMaDaoBaseUrl?.value || latestState?.madaoBaseUrl),
|
||||
madaoHttpSecret: String(inputMaDaoHttpSecret?.value || ''),
|
||||
madaoMode: normalizeMaDaoModeValue(selectMaDaoMode?.value || latestState?.madaoMode),
|
||||
madaoRoutingPlanId: normalizeMaDaoIdentifierValue(inputMaDaoRoutingPlanId?.value || ''),
|
||||
madaoProviderId: normalizeMaDaoProviderIdValue(inputMaDaoProviderId?.value || ''),
|
||||
madaoCountry: normalizeMaDaoCountry(inputMaDaoCountry?.value || ''),
|
||||
madaoAutoPickCountry: Boolean(inputMaDaoAutoPickCountry?.checked),
|
||||
madaoReusePhone: Boolean(inputMaDaoReusePhone?.checked),
|
||||
madaoMode: maDaoMode,
|
||||
madaoRoutingPlanId: normalizeMaDaoRoutingPlanIdValue(
|
||||
typeof selectMaDaoRoutingPlanId !== 'undefined' && selectMaDaoRoutingPlanId
|
||||
? selectMaDaoRoutingPlanId.value
|
||||
: latestState?.madaoRoutingPlanId
|
||||
),
|
||||
madaoProviderId: shouldReadMaDaoDirectControls
|
||||
? normalizeMaDaoProviderIdValue(selectMaDaoProviderId?.value || '')
|
||||
: normalizeMaDaoProviderIdValue(latestState?.madaoProviderId || ''),
|
||||
madaoCountry: shouldReadMaDaoDirectControls
|
||||
? normalizeMaDaoCountry(selectMaDaoCountry?.value || '')
|
||||
: normalizeMaDaoCountry(latestState?.madaoCountry || ''),
|
||||
madaoOperator: shouldReadMaDaoDirectControls
|
||||
? normalizeMaDaoOperatorValue(selectMaDaoOperator?.value || '')
|
||||
: normalizeMaDaoOperatorValue(latestState?.madaoOperator || ''),
|
||||
madaoAutoPickCountry: typeof inputMaDaoAutoPickCountry !== 'undefined' && inputMaDaoAutoPickCountry
|
||||
? Boolean(inputMaDaoAutoPickCountry.checked)
|
||||
: (latestState?.madaoAutoPickCountry !== undefined ? Boolean(latestState.madaoAutoPickCountry) : true),
|
||||
madaoReusePhone: typeof inputMaDaoReusePhone !== 'undefined' && inputMaDaoReusePhone
|
||||
? Boolean(inputMaDaoReusePhone.checked)
|
||||
: (latestState?.madaoReusePhone !== undefined ? Boolean(latestState.madaoReusePhone) : true),
|
||||
madaoMinPrice: normalizeMaDaoPriceValue(inputMaDaoMinPrice?.value || ''),
|
||||
madaoMaxPrice: normalizeMaDaoPriceValue(inputMaDaoMaxPrice?.value || ''),
|
||||
};
|
||||
@@ -17210,14 +17781,17 @@ function applyPhoneSmsProviderFieldsToInputs(provider = getSelectedPhoneSmsProvi
|
||||
if (typeof selectMaDaoMode !== 'undefined' && selectMaDaoMode) {
|
||||
selectMaDaoMode.value = normalizeMaDaoModeValue(state?.madaoMode);
|
||||
}
|
||||
if (typeof inputMaDaoRoutingPlanId !== 'undefined' && inputMaDaoRoutingPlanId) {
|
||||
inputMaDaoRoutingPlanId.value = normalizeMaDaoIdentifierValue(state?.madaoRoutingPlanId || '');
|
||||
if (typeof selectMaDaoRoutingPlanId !== 'undefined' && selectMaDaoRoutingPlanId) {
|
||||
setMaDaoRoutingPlanSelectOptions(state?.madaoRoutingPlanId || '');
|
||||
}
|
||||
if (typeof inputMaDaoProviderId !== 'undefined' && inputMaDaoProviderId) {
|
||||
inputMaDaoProviderId.value = normalizeMaDaoProviderIdValue(state?.madaoProviderId || '');
|
||||
if (typeof selectMaDaoProviderId !== 'undefined' && selectMaDaoProviderId) {
|
||||
setMaDaoProviderSelectOptions(state?.madaoProviderId || '');
|
||||
}
|
||||
if (typeof inputMaDaoCountry !== 'undefined' && inputMaDaoCountry) {
|
||||
inputMaDaoCountry.value = normalizeMaDaoCountry(state?.madaoCountry || '');
|
||||
if (typeof selectMaDaoCountry !== 'undefined' && selectMaDaoCountry) {
|
||||
setMaDaoCountrySelectOptions(state?.madaoCountry || '');
|
||||
}
|
||||
if (typeof selectMaDaoOperator !== 'undefined' && selectMaDaoOperator) {
|
||||
setMaDaoOperatorSelectOptions(state?.madaoOperator || '');
|
||||
}
|
||||
if (typeof inputMaDaoAutoPickCountry !== 'undefined' && inputMaDaoAutoPickCountry) {
|
||||
inputMaDaoAutoPickCountry.checked = state?.madaoAutoPickCountry !== undefined
|
||||
@@ -17296,6 +17870,15 @@ async function switchPhoneSmsProvider(nextProvider) {
|
||||
applyNexSmsCountrySelection(
|
||||
Array.isArray(latestState?.nexSmsCountryOrder) ? latestState.nexSmsCountryOrder : []
|
||||
);
|
||||
} else if (normalizedNextProvider === PHONE_SMS_PROVIDER_MADAO) {
|
||||
await loadMaDaoRoutingPlans({ silent: true }).catch(() => {
|
||||
setMaDaoRoutingPlanSelectOptions(latestState?.madaoRoutingPlanId || '');
|
||||
});
|
||||
await loadMaDaoProviders({ silent: true }).catch(() => {
|
||||
setMaDaoProviderSelectOptions(latestState?.madaoProviderId || '');
|
||||
setMaDaoCountrySelectOptions(latestState?.madaoCountry || '');
|
||||
setMaDaoOperatorSelectOptions(latestState?.madaoOperator || '');
|
||||
});
|
||||
} else if (normalizedNextProvider === PHONE_SMS_PROVIDER_HERO_SMS) {
|
||||
await loadHeroSmsCountries({ silent: true });
|
||||
const restoredPrimary = {
|
||||
@@ -17502,38 +18085,89 @@ selectMaDaoMode?.addEventListener('change', () => {
|
||||
selectMaDaoMode.value = normalizeMaDaoModeValue(selectMaDaoMode.value);
|
||||
updateHeroSmsPlatformDisplay();
|
||||
updatePhoneVerificationSettingsUI();
|
||||
if (selectMaDaoMode.value === MADAO_MODE_ROUTING_PLAN) {
|
||||
loadMaDaoRoutingPlans({ silent: true }).catch(() => {
|
||||
setMaDaoRoutingPlanSelectOptions(latestState?.madaoRoutingPlanId || '');
|
||||
});
|
||||
} else {
|
||||
loadMaDaoProviders({ silent: true }).catch(() => {
|
||||
setMaDaoProviderSelectOptions(latestState?.madaoProviderId || '');
|
||||
setMaDaoCountrySelectOptions(latestState?.madaoCountry || '');
|
||||
setMaDaoOperatorSelectOptions(latestState?.madaoOperator || '');
|
||||
});
|
||||
}
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
|
||||
inputMaDaoRoutingPlanId?.addEventListener('input', () => {
|
||||
markSettingsDirty(true);
|
||||
scheduleSettingsAutoSave();
|
||||
});
|
||||
inputMaDaoRoutingPlanId?.addEventListener('blur', () => {
|
||||
inputMaDaoRoutingPlanId.value = normalizeMaDaoIdentifierValue(inputMaDaoRoutingPlanId.value);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
|
||||
inputMaDaoProviderId?.addEventListener('input', () => {
|
||||
markSettingsDirty(true);
|
||||
scheduleSettingsAutoSave();
|
||||
});
|
||||
inputMaDaoProviderId?.addEventListener('blur', () => {
|
||||
inputMaDaoProviderId.value = normalizeMaDaoProviderIdValue(inputMaDaoProviderId.value);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
|
||||
inputMaDaoCountry?.addEventListener('input', () => {
|
||||
markSettingsDirty(true);
|
||||
scheduleSettingsAutoSave();
|
||||
});
|
||||
inputMaDaoCountry?.addEventListener('blur', () => {
|
||||
inputMaDaoCountry.value = normalizeMaDaoCountry(inputMaDaoCountry.value);
|
||||
selectMaDaoRoutingPlanId?.addEventListener('change', () => {
|
||||
updateHeroSmsPlatformDisplay();
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
|
||||
btnMaDaoRefreshRoutingPlans?.addEventListener('click', () => {
|
||||
loadMaDaoRoutingPlans().catch((error) => {
|
||||
showToast(`刷新 MaDao 路由计划失败:${error?.message || error}`, 'warn', 2200);
|
||||
});
|
||||
});
|
||||
|
||||
selectMaDaoProviderId?.addEventListener('change', () => {
|
||||
maDaoCountryOptions = [];
|
||||
maDaoOperatorOptions = [];
|
||||
setMaDaoCountrySelectOptions('');
|
||||
setMaDaoOperatorSelectOptions('');
|
||||
syncLatestState({
|
||||
madaoProviderId: normalizeMaDaoProviderIdValue(selectMaDaoProviderId.value),
|
||||
madaoCountry: '',
|
||||
madaoOperator: '',
|
||||
});
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
loadMaDaoCountries({ silent: true }).catch((error) => {
|
||||
showToast(`刷新 MaDao 国家失败:${error?.message || error}`, 'warn', 2200);
|
||||
});
|
||||
});
|
||||
|
||||
btnMaDaoRefreshProviders?.addEventListener('click', () => {
|
||||
loadMaDaoProviders().catch((error) => {
|
||||
showToast(`刷新 MaDao 服务商失败:${error?.message || error}`, 'warn', 2200);
|
||||
});
|
||||
});
|
||||
|
||||
selectMaDaoCountry?.addEventListener('change', () => {
|
||||
maDaoOperatorOptions = [];
|
||||
setMaDaoOperatorSelectOptions('');
|
||||
updateHeroSmsPlatformDisplay();
|
||||
syncLatestState({
|
||||
madaoCountry: normalizeMaDaoCountry(selectMaDaoCountry.value),
|
||||
madaoOperator: '',
|
||||
});
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
loadMaDaoOperators({ silent: true }).catch((error) => {
|
||||
showToast(`刷新 MaDao 线路失败:${error?.message || error}`, 'warn', 2200);
|
||||
});
|
||||
});
|
||||
|
||||
btnMaDaoRefreshCountries?.addEventListener('click', () => {
|
||||
loadMaDaoCountries().catch((error) => {
|
||||
showToast(`刷新 MaDao 国家失败:${error?.message || error}`, 'warn', 2200);
|
||||
});
|
||||
});
|
||||
|
||||
selectMaDaoOperator?.addEventListener('change', () => {
|
||||
updateHeroSmsPlatformDisplay();
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
|
||||
btnMaDaoRefreshOperators?.addEventListener('click', () => {
|
||||
loadMaDaoOperators().catch((error) => {
|
||||
showToast(`刷新 MaDao 线路失败:${error?.message || error}`, 'warn', 2200);
|
||||
});
|
||||
});
|
||||
|
||||
inputMaDaoAutoPickCountry?.addEventListener('change', () => {
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
@@ -18597,14 +19231,17 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
selectMaDaoMode.value = normalizeMaDaoModeValue(message.payload.madaoMode);
|
||||
updatePhoneVerificationSettingsUI();
|
||||
}
|
||||
if (message.payload.madaoRoutingPlanId !== undefined && inputMaDaoRoutingPlanId) {
|
||||
inputMaDaoRoutingPlanId.value = normalizeMaDaoIdentifierValue(message.payload.madaoRoutingPlanId);
|
||||
if (message.payload.madaoRoutingPlanId !== undefined && selectMaDaoRoutingPlanId) {
|
||||
setMaDaoRoutingPlanSelectOptions(message.payload.madaoRoutingPlanId);
|
||||
}
|
||||
if (message.payload.madaoProviderId !== undefined && inputMaDaoProviderId) {
|
||||
inputMaDaoProviderId.value = normalizeMaDaoProviderIdValue(message.payload.madaoProviderId);
|
||||
if (message.payload.madaoProviderId !== undefined && selectMaDaoProviderId) {
|
||||
setMaDaoProviderSelectOptions(message.payload.madaoProviderId);
|
||||
}
|
||||
if (message.payload.madaoCountry !== undefined && inputMaDaoCountry) {
|
||||
inputMaDaoCountry.value = normalizeMaDaoCountry(message.payload.madaoCountry);
|
||||
if (message.payload.madaoCountry !== undefined && selectMaDaoCountry) {
|
||||
setMaDaoCountrySelectOptions(message.payload.madaoCountry);
|
||||
}
|
||||
if (message.payload.madaoOperator !== undefined && selectMaDaoOperator) {
|
||||
setMaDaoOperatorSelectOptions(message.payload.madaoOperator);
|
||||
}
|
||||
if (message.payload.madaoAutoPickCountry !== undefined && inputMaDaoAutoPickCountry) {
|
||||
inputMaDaoAutoPickCountry.checked = Boolean(message.payload.madaoAutoPickCountry);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -100,6 +104,37 @@ test('MaDao routing acquire sends routing plan only', async () => {
|
||||
assert.equal(activation.madaoRoutingItemId, 'route-1');
|
||||
});
|
||||
|
||||
test('MaDao direct acquire treats any operator as default route', async () => {
|
||||
const requests = [];
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async (_url, options = {}) => {
|
||||
requests.push({ body: JSON.parse(options.body) });
|
||||
return createJsonResponse({
|
||||
ticket_id: 'ticket-any',
|
||||
phone_number: '+66111111111',
|
||||
country: 'TH',
|
||||
provider: 'fivesim',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await provider.acquireActivation({
|
||||
madaoMode: 'direct',
|
||||
madaoProviderId: 'fivesim',
|
||||
madaoCountry: 'TH',
|
||||
madaoOperator: 'Any operator',
|
||||
});
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
assert.deepEqual(requests[0].body, {
|
||||
provider: 'fivesim',
|
||||
service: 'openai',
|
||||
auto_pick_country: true,
|
||||
reuse_phone: true,
|
||||
country: 'TH',
|
||||
});
|
||||
});
|
||||
|
||||
test('MaDao poll extracts codes from nested messages and reports pending status', async () => {
|
||||
const statusEvents = [];
|
||||
const provider = api.createProvider({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,6 +3,8 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('phone-sms/providers/registry.js', 'utf8');
|
||||
const maDaoSource = fs.readFileSync('phone-sms/providers/madao.js', 'utf8');
|
||||
const nexSmsSource = fs.readFileSync('phone-sms/providers/nexsms.js', 'utf8');
|
||||
|
||||
function loadRegistry(root = {}) {
|
||||
return new Function('self', `${source}; return self.PhoneSmsProviderRegistry;`)(root);
|
||||
@@ -16,6 +18,9 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
||||
PhoneSmsFiveSimProvider: {
|
||||
createProvider: (deps = {}) => ({ provider: '5sim', deps }),
|
||||
},
|
||||
PhoneSmsNexSmsProvider: {
|
||||
createProvider: (deps = {}) => ({ provider: 'nexsms', deps }),
|
||||
},
|
||||
PhoneSmsMaDaoProvider: {
|
||||
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
|
||||
},
|
||||
@@ -52,5 +57,43 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
||||
registry.createProvider('madao', { foo: 2 }),
|
||||
{ provider: 'madao', deps: { foo: 2 } }
|
||||
);
|
||||
assert.throws(() => registry.createProvider('nexsms'), /接码平台模块未加载:nexsms/);
|
||||
assert.deepStrictEqual(
|
||||
registry.createProvider('nexsms', { foo: 3 }),
|
||||
{ provider: 'nexsms', deps: { foo: 3 } }
|
||||
);
|
||||
});
|
||||
|
||||
test('phone sms provider registry can create the real MaDao provider module', () => {
|
||||
const maDaoModule = new Function('self', `${maDaoSource}; return self.PhoneSmsMaDaoProvider;`)({});
|
||||
const registry = loadRegistry({
|
||||
PhoneSmsMaDaoProvider: maDaoModule,
|
||||
});
|
||||
|
||||
const provider = registry.createProvider('madao', { fetchImpl: 'demo-fetch' });
|
||||
|
||||
assert.equal(provider.id, 'madao');
|
||||
assert.equal(provider.label, 'MaDao');
|
||||
assert.equal(provider.defaultProduct, 'openai');
|
||||
assert.equal(typeof provider.acquireActivation, 'function');
|
||||
assert.equal(typeof provider.pollActivation, 'function');
|
||||
assert.equal(typeof provider.releaseActivation, 'function');
|
||||
assert.equal(provider.mapTicketStatus('waiting_code'), 'waiting_code');
|
||||
});
|
||||
|
||||
test('phone sms provider registry can create the real NexSMS provider module', () => {
|
||||
const nexSmsModule = new Function('self', `${nexSmsSource}; return self.PhoneSmsNexSmsProvider;`)({});
|
||||
const registry = loadRegistry({
|
||||
PhoneSmsNexSmsProvider: nexSmsModule,
|
||||
});
|
||||
|
||||
const provider = registry.createProvider('nexsms', { fetchImpl: 'demo-fetch' });
|
||||
|
||||
assert.equal(provider.id, 'nexsms');
|
||||
assert.equal(provider.label, 'NexSMS');
|
||||
assert.equal(provider.defaultProduct, 'OpenAI');
|
||||
assert.equal(provider.defaultServiceCode, 'ot');
|
||||
assert.equal(typeof provider.fetchBalance, 'function');
|
||||
assert.equal(typeof provider.fetchPrices, 'function');
|
||||
assert.equal(provider.normalizeCountryId('8'), 8);
|
||||
assert.equal(provider.normalizeServiceCode(' OT '), 'ot');
|
||||
});
|
||||
|
||||
@@ -3,10 +3,19 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/phone-verification-flow.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(globalScope);
|
||||
const heroSmsSource = fs.readFileSync('phone-sms/providers/hero-sms.js', 'utf8');
|
||||
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||
const nexSmsSource = fs.readFileSync('phone-sms/providers/nexsms.js', 'utf8');
|
||||
const maDaoSource = fs.readFileSync('phone-sms/providers/madao.js', 'utf8');
|
||||
const maDaoModule = new Function('self', `${maDaoSource}; return self.PhoneSmsMaDaoProvider;`)({});
|
||||
const registrySource = fs.readFileSync('phone-sms/providers/registry.js', 'utf8');
|
||||
const globalScope = {};
|
||||
new Function('self', `${heroSmsSource}; return self.PhoneSmsHeroSmsProvider;`)(globalScope);
|
||||
new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)(globalScope);
|
||||
new Function('self', `${nexSmsSource}; return self.PhoneSmsNexSmsProvider;`)(globalScope);
|
||||
new Function('self', `${maDaoSource}; return self.PhoneSmsMaDaoProvider;`)(globalScope);
|
||||
new Function('self', `${registrySource}; return self.PhoneSmsProviderRegistry;`)(globalScope);
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(globalScope);
|
||||
const maDaoModule = globalScope.PhoneSmsMaDaoProvider;
|
||||
|
||||
function buildHeroSmsPricesPayload({ country = '52', service = 'dr', cost = 0.08, count = 25370, physicalCount = 14528 } = {}) {
|
||||
return JSON.stringify({
|
||||
@@ -157,6 +166,110 @@ test('phone verification helper reads latest HeroSMS operator from persistent st
|
||||
assert.equal(getNumberRequest.searchParams.get('operator'), 'dtac');
|
||||
});
|
||||
|
||||
test('phone verification helper creates 5sim adapter through provider registry when available', async () => {
|
||||
const createCalls = [];
|
||||
const root = {
|
||||
PhoneSmsProviderRegistry: {
|
||||
createProvider: (providerId, deps = {}) => {
|
||||
createCalls.push({ providerId, deps });
|
||||
return {
|
||||
requestActivation: async () => ({
|
||||
activationId: '5sim-registry-1',
|
||||
phoneNumber: '+447700900001',
|
||||
provider: '5sim',
|
||||
serviceCode: 'openai',
|
||||
countryId: 'england',
|
||||
}),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
const registryApi = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(root);
|
||||
const helpers = registryApi.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async () => {
|
||||
throw new Error('5sim registry adapter should own network access');
|
||||
},
|
||||
getState: async () => ({ phoneSmsProvider: '5sim' }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const activation = await helpers.requestPhoneActivation({ phoneSmsProvider: '5sim' });
|
||||
|
||||
assert.equal(createCalls.length, 1);
|
||||
assert.equal(createCalls[0].providerId, '5sim');
|
||||
assert.equal(typeof createCalls[0].deps.fetchImpl, 'function');
|
||||
assert.equal(activation.activationId, '5sim-registry-1');
|
||||
});
|
||||
|
||||
test('phone verification helper creates MaDao adapter through provider registry when available', async () => {
|
||||
const createCalls = [];
|
||||
const requests = [];
|
||||
const root = {
|
||||
PhoneSmsProviderRegistry: {
|
||||
createProvider: (providerId, deps = {}) => {
|
||||
createCalls.push({ providerId, deps });
|
||||
return maDaoModule.createProvider(deps);
|
||||
},
|
||||
},
|
||||
};
|
||||
const registryApi = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(root);
|
||||
const currentState = {
|
||||
phoneSmsProvider: 'madao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822/',
|
||||
madaoHttpSecret: 'secret-token',
|
||||
madaoMode: 'routing_plan',
|
||||
madaoRoutingPlanId: 'rp-openai',
|
||||
};
|
||||
const helpers = registryApi.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push({ url: parsedUrl, options, body: options.body ? JSON.parse(options.body) : null });
|
||||
if (parsedUrl.pathname === '/api/acquire') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
ticket_id: 'madao-registry-1',
|
||||
phone_number: '+14155550123',
|
||||
service: 'openai',
|
||||
country: 'CA',
|
||||
provider: 'upstream-a',
|
||||
routing_plan_id: 'rp-openai',
|
||||
routing_item_id: 'route-1',
|
||||
status: 'waiting_code',
|
||||
}),
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`);
|
||||
},
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const activation = await helpers.requestPhoneActivation(currentState);
|
||||
|
||||
assert.equal(createCalls.length, 1);
|
||||
assert.equal(createCalls[0].providerId, 'madao');
|
||||
assert.equal(typeof createCalls[0].deps.fetchImpl, 'function');
|
||||
assert.equal(activation.activationId, 'madao-registry-1');
|
||||
assert.equal(activation.countryId, 'CA');
|
||||
assert.deepStrictEqual(requests[0].body, {
|
||||
provider: 'auto',
|
||||
service: 'openai',
|
||||
routing_plan_id: 'rp-openai',
|
||||
});
|
||||
});
|
||||
|
||||
test('phone verification helper acquires, polls and releases MaDao activation through provider adapter', async () => {
|
||||
const requests = [];
|
||||
let currentState = {
|
||||
@@ -172,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);
|
||||
@@ -252,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 = {
|
||||
@@ -974,8 +1241,6 @@ test('signup phone helper does not let a hung page-state probe stall HeroSMS pol
|
||||
});
|
||||
|
||||
test('signup phone helper fails stale email-verification on 5sim RECEIVED without code during SMS polling', async () => {
|
||||
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
|
||||
let checkCount = 0;
|
||||
let pageStateReads = 0;
|
||||
const contentMessages = [];
|
||||
@@ -1005,7 +1270,6 @@ test('signup phone helper fails stale email-verification on 5sim RECEIVED withou
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
createFiveSimProvider: fiveSimModule.createProvider,
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
if (parsedUrl.pathname === '/v1/user/check/5001') {
|
||||
@@ -2352,29 +2616,28 @@ test('phone verification helper acquires a number from 5sim with fallback countr
|
||||
heroSmsActivationRetryRounds: 1,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(activation, {
|
||||
activationId: '9876543',
|
||||
phoneNumber: '+447911123456',
|
||||
provider: '5sim',
|
||||
serviceCode: 'openai',
|
||||
countryId: 'england',
|
||||
countryCode: 'england',
|
||||
countryLabel: 'England',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.equal(requests.length, 4);
|
||||
assert.equal(requests[0].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[0].search.get('country'), 'thailand');
|
||||
assert.equal(requests[0].search.get('product'), 'openai');
|
||||
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
|
||||
assert.equal(requests[1].search.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[1].search.get('reuse'), '1');
|
||||
assert.equal(requests[1].headers.Authorization, 'Bearer five-token');
|
||||
assert.equal(requests[2].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[2].search.get('country'), 'england');
|
||||
assert.equal(requests[2].search.get('product'), 'openai');
|
||||
assert.equal(requests[3].pathname, '/v1/user/buy/activation/england/any/openai');
|
||||
assert.equal(activation.activationId, '9876543');
|
||||
assert.equal(activation.phoneNumber, '+447911123456');
|
||||
assert.equal(activation.provider, '5sim');
|
||||
assert.equal(activation.serviceCode, 'openai');
|
||||
assert.equal(activation.countryId, 'england');
|
||||
assert.equal(activation.countryCode, 'england');
|
||||
assert.equal(activation.successfulUses, 0);
|
||||
assert.equal(activation.maxUses, 3);
|
||||
assert.equal(requests.length, 6);
|
||||
assert.equal(requests[0].pathname, '/v1/guest/products/thailand/any');
|
||||
assert.equal(requests[1].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[1].search.get('country'), 'thailand');
|
||||
assert.equal(requests[1].search.get('product'), 'openai');
|
||||
assert.equal(requests[2].pathname, '/v1/user/buy/activation/thailand/any/openai');
|
||||
assert.equal(requests[2].search.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[2].search.get('reuse'), '1');
|
||||
assert.equal(requests[2].headers.Authorization, 'Bearer five-token');
|
||||
assert.equal(requests[3].pathname, '/v1/guest/products/england/any');
|
||||
assert.equal(requests[4].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[4].search.get('country'), 'england');
|
||||
assert.equal(requests[4].search.get('product'), 'openai');
|
||||
assert.equal(requests[5].pathname, '/v1/user/buy/activation/england/any/openai');
|
||||
});
|
||||
|
||||
test('phone verification helper preserves fallback provider while refreshing latest settings', async () => {
|
||||
@@ -2514,13 +2777,14 @@ test('phone verification helper preserves fallback provider while refreshing lat
|
||||
assert.deepStrictEqual(
|
||||
fiveSimRequests.map((entry) => entry.url.pathname),
|
||||
[
|
||||
'/v1/guest/products/vietnam/any',
|
||||
'/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');
|
||||
assert.equal(fiveSimRequests[2].options.headers.Authorization, 'Bearer five-token');
|
||||
});
|
||||
|
||||
test('phone verification helper prefers phoneSmsReuseEnabled over legacy heroSmsReuseEnabled for 5sim acquisition', async () => {
|
||||
@@ -2593,20 +2857,18 @@ test('phone verification helper prefers phoneSmsReuseEnabled over legacy heroSms
|
||||
heroSmsActivationRetryRounds: 1,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(activation, {
|
||||
activationId: '1234567',
|
||||
phoneNumber: '+66880000000',
|
||||
provider: '5sim',
|
||||
serviceCode: 'openai',
|
||||
countryId: 'thailand',
|
||||
countryCode: 'thailand',
|
||||
countryLabel: 'Thailand',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.equal(requests[0].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
|
||||
assert.equal(requests[1].search.get('reuse'), null);
|
||||
assert.equal(activation.activationId, '1234567');
|
||||
assert.equal(activation.phoneNumber, '+66880000000');
|
||||
assert.equal(activation.provider, '5sim');
|
||||
assert.equal(activation.serviceCode, 'openai');
|
||||
assert.equal(activation.countryId, 'thailand');
|
||||
assert.equal(activation.countryCode, 'thailand');
|
||||
assert.equal(activation.successfulUses, 0);
|
||||
assert.equal(activation.maxUses, 3);
|
||||
assert.equal(requests[0].pathname, '/v1/guest/products/thailand/any');
|
||||
assert.equal(requests[1].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[2].pathname, '/v1/user/buy/activation/thailand/any/openai');
|
||||
assert.equal(requests[2].search.get('reuse'), null);
|
||||
});
|
||||
|
||||
test('phone verification helper treats fiveSimReuseEnabled as legacy-only when phoneSmsReuseEnabled is absent', async () => {
|
||||
@@ -2679,20 +2941,18 @@ test('phone verification helper treats fiveSimReuseEnabled as legacy-only when p
|
||||
heroSmsActivationRetryRounds: 1,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(activation, {
|
||||
activationId: '1234568',
|
||||
phoneNumber: '+66880000001',
|
||||
provider: '5sim',
|
||||
serviceCode: 'openai',
|
||||
countryId: 'thailand',
|
||||
countryCode: 'thailand',
|
||||
countryLabel: 'Thailand',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.equal(requests[0].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
|
||||
assert.equal(requests[1].search.get('reuse'), '1');
|
||||
assert.equal(activation.activationId, '1234568');
|
||||
assert.equal(activation.phoneNumber, '+66880000001');
|
||||
assert.equal(activation.provider, '5sim');
|
||||
assert.equal(activation.serviceCode, 'openai');
|
||||
assert.equal(activation.countryId, 'thailand');
|
||||
assert.equal(activation.countryCode, 'thailand');
|
||||
assert.equal(activation.successfulUses, 0);
|
||||
assert.equal(activation.maxUses, 3);
|
||||
assert.equal(requests[0].pathname, '/v1/guest/products/thailand/any');
|
||||
assert.equal(requests[1].pathname, '/v1/guest/prices');
|
||||
assert.equal(requests[2].pathname, '/v1/user/buy/activation/thailand/any/openai');
|
||||
assert.equal(requests[2].search.get('reuse'), '1');
|
||||
});
|
||||
|
||||
test('phone verification helper rejects 5sim maxPrice with custom operator before buying', async () => {
|
||||
@@ -3115,7 +3375,7 @@ test('phone verification helper polls and parses 5sim verification codes', async
|
||||
|
||||
assert.equal(code, '246810');
|
||||
assert.equal(checkCount, 2);
|
||||
assert.deepStrictEqual(statusUpdates, ['PENDING']);
|
||||
assert.deepStrictEqual(statusUpdates, ['PENDING', 'RECEIVED']);
|
||||
});
|
||||
|
||||
test('phone verification helper treats HeroSMS STATUS_WAIT_RETRY payload status as pending', async () => {
|
||||
@@ -3242,18 +3502,16 @@ test('phone verification helper reuses 5sim by keeping the original activation',
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(nextActivation, {
|
||||
activationId: '600001',
|
||||
phoneNumber: '+44 7911-123-456',
|
||||
provider: '5sim',
|
||||
serviceCode: 'openai',
|
||||
countryId: 'england',
|
||||
countryCode: 'england',
|
||||
successfulUses: 0,
|
||||
maxUses: 1,
|
||||
source: '5sim-retained-reuse',
|
||||
ignoredPhoneCodeKeys: ['old::111111'],
|
||||
});
|
||||
assert.equal(nextActivation.activationId, '600001');
|
||||
assert.equal(nextActivation.phoneNumber, '+44 7911-123-456');
|
||||
assert.equal(nextActivation.provider, '5sim');
|
||||
assert.equal(nextActivation.serviceCode, 'openai');
|
||||
assert.equal(nextActivation.countryId, 'england');
|
||||
assert.equal(nextActivation.countryCode, 'england');
|
||||
assert.equal(nextActivation.successfulUses, 0);
|
||||
assert.equal(nextActivation.maxUses, 1);
|
||||
assert.equal(nextActivation.source, '5sim-retained-reuse');
|
||||
assert.deepStrictEqual(nextActivation.ignoredPhoneCodeKeys, ['old::111111']);
|
||||
assert.deepStrictEqual(requests, ['/v1/user/check/600001']);
|
||||
});
|
||||
|
||||
@@ -6448,12 +6706,9 @@ test('phone verification helper auto free-reuses 5sim by polling the retained or
|
||||
},
|
||||
};
|
||||
|
||||
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
createFiveSimProvider: fiveSimModule.createProvider,
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
@@ -6547,12 +6802,9 @@ test('phone verification helper retires failed 5sim free-reuse record instead of
|
||||
},
|
||||
};
|
||||
|
||||
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
createFiveSimProvider: fiveSimModule.createProvider,
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
@@ -8724,12 +8976,9 @@ test('phone verification helper routes 5sim buy, check, and finish by current ac
|
||||
reusablePhoneActivation: null,
|
||||
};
|
||||
|
||||
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
createFiveSimProvider: fiveSimModule.createProvider,
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push({ url: parsedUrl, options });
|
||||
@@ -8838,7 +9087,6 @@ test('phone verification helper uses MaDao routing replace when add-phone reject
|
||||
};
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
createMaDaoProvider: maDaoModule.createProvider,
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsedUrl = new URL(url);
|
||||
@@ -8966,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',
|
||||
@@ -8981,7 +9230,6 @@ test('phone verification helper reacquires MaDao direct numbers after releasing
|
||||
};
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
createMaDaoProvider: maDaoModule.createProvider,
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsedUrl = new URL(url);
|
||||
@@ -9076,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,
|
||||
});
|
||||
@@ -9114,12 +9365,9 @@ test('phone verification helper keeps 5sim reusable activation on the original o
|
||||
},
|
||||
};
|
||||
|
||||
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
createFiveSimProvider: fiveSimModule.createProvider,
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
|
||||
@@ -156,15 +156,28 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
||||
assert.match(html, /id="row-madao-mode"/);
|
||||
assert.match(html, /id="select-madao-mode"/);
|
||||
assert.match(html, /id="row-madao-routing-plan-id"/);
|
||||
assert.match(html, /id="input-madao-routing-plan-id"/);
|
||||
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.match(html, /id="row-madao-provider-id"/);
|
||||
assert.match(html, /id="input-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.match(html, /id="row-madao-country"/);
|
||||
assert.match(html, /id="input-madao-country"/);
|
||||
assert.match(html, /id="row-madao-auto-pick-country"/);
|
||||
assert.match(html, /id="input-madao-auto-pick-country"/);
|
||||
assert.match(html, /id="row-madao-reuse-phone"/);
|
||||
assert.match(html, /id="input-madao-reuse-phone"/);
|
||||
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"/);
|
||||
assert.doesNotMatch(html, /id="input-madao-reuse-phone"/);
|
||||
assert.doesNotMatch(html, /直连平台/);
|
||||
assert.doesNotMatch(html, /直连国家/);
|
||||
assert.doesNotMatch(html, /自动选国家/);
|
||||
assert.doesNotMatch(html, /MaDao 复用/);
|
||||
assert.match(html, /id="row-madao-price-range"/);
|
||||
assert.match(html, /id="input-madao-min-price"/);
|
||||
assert.match(html, /id="input-madao-max-price"/);
|
||||
@@ -190,6 +203,228 @@ test('sidepanel loads live SMS country lists silently during startup', () => {
|
||||
assert.doesNotMatch(sidepanelSource, /console\.error\('加载 (?:HeroSMS|5sim|NexSMS) 国家列表失败:'/);
|
||||
});
|
||||
|
||||
test('MaDao routing plan select loads options from helper API and preserves saved plan', async () => {
|
||||
const requests = [];
|
||||
const fetchImpl = async (url, options = {}) => {
|
||||
requests.push({ url, options });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
async text() {
|
||||
return JSON.stringify({
|
||||
plans: [
|
||||
{ id: 'openai-plan', name: 'OpenAI Plan', service: 'openai', enabled: true },
|
||||
{ id: 'kiro-plan', name: 'Kiro Plan', service: 'kiro', enabled: true },
|
||||
{ id: 'disabled-plan', name: 'Disabled Plan', service: 'openai', enabled: false },
|
||||
],
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
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',
|
||||
madaoRoutingPlanId: 'stored-plan',
|
||||
};
|
||||
let maDaoRoutingPlanOptions = [];
|
||||
let displayText = '';
|
||||
let toastText = '';
|
||||
const inputMaDaoBaseUrl = { value: 'http://madao.local/api/acquire' };
|
||||
const inputMaDaoHttpSecret = { value: 'madao-secret' };
|
||||
const selectMaDaoRoutingPlanId = {
|
||||
value: 'stored-plan',
|
||||
options: [],
|
||||
replaceChildren(...children) {
|
||||
this.options = children;
|
||||
},
|
||||
};
|
||||
function updateHeroSmsPlatformDisplay() {
|
||||
displayText = getSelectedMaDaoRoutingPlanLabel();
|
||||
}
|
||||
function showToast(message) {
|
||||
toastText = message;
|
||||
}
|
||||
${extractFunction('normalizeMaDaoBaseUrlValue')}
|
||||
${extractFunction('normalizeMaDaoIdentifierValue')}
|
||||
${extractFunction('normalizeMaDaoRoutingPlanIdValue')}
|
||||
${extractFunction('createSelectOptionElement')}
|
||||
${extractFunction('setSelectOptions')}
|
||||
${extractFunction('buildMaDaoRoutingPlanOptions')}
|
||||
${extractFunction('setMaDaoRoutingPlanSelectOptions')}
|
||||
${extractFunction('buildMaDaoRequestUrl')}
|
||||
${extractFunction('buildMaDaoRequestHeaders')}
|
||||
${extractFunction('fetchMaDaoJson')}
|
||||
${extractFunction('getMaDaoRoutingPlansFromPayload')}
|
||||
${extractFunction('loadMaDaoRoutingPlans')}
|
||||
${extractFunction('getSelectedMaDaoRoutingPlanLabel')}
|
||||
return {
|
||||
loadMaDaoRoutingPlans,
|
||||
selectMaDaoRoutingPlanId,
|
||||
get options() { return selectMaDaoRoutingPlanId.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
|
||||
get displayText() { return displayText; },
|
||||
get toastText() { return toastText; },
|
||||
};
|
||||
`)(fetchImpl);
|
||||
|
||||
const plans = await api.loadMaDaoRoutingPlans();
|
||||
|
||||
assert.deepStrictEqual(plans.map((plan) => plan.value), ['openai-plan']);
|
||||
assert.equal(api.selectMaDaoRoutingPlanId.value, 'stored-plan');
|
||||
assert.deepStrictEqual(api.options.map((option) => option.value), ['', 'stored-plan', 'openai-plan']);
|
||||
assert.equal(api.options.find((option) => option.value === 'stored-plan').selected, true);
|
||||
assert.equal(api.displayText, 'stored-plan');
|
||||
assert.equal(api.toastText, '已刷新 MaDao 路由计划。');
|
||||
assert.equal(requests[0].url, 'http://madao.local/api/routing-plans');
|
||||
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', label_zh: '英国', provider_value: 'england' },
|
||||
{ value: 'TH', label: 'Thailand', label_zh: '泰国', provider_value: '52' },
|
||||
],
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
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: 'any', label: 'Any operator' },
|
||||
{ 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: 'england',
|
||||
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: 'england', 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('formatMaDaoCountryDisplayLabel')}
|
||||
${extractFunction('createSelectOptionElement')}
|
||||
${extractFunction('setSelectOptions')}
|
||||
${extractFunction('normalizeMaDaoOptionListItems')}
|
||||
${extractFunction('resolveMaDaoOptionSelectedValue')}
|
||||
${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.countryOptions.map((option) => option.label), ['请先选择服务商', '英国', '泰国']);
|
||||
assert.deepStrictEqual(api.operatorOptions.map((option) => option.value), ['', 'operator-a', 'operator-b']);
|
||||
assert.deepStrictEqual(api.operatorOptions.map((option) => option.label), ['任意线路', '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')}
|
||||
@@ -704,6 +939,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' } };
|
||||
@@ -785,8 +1021,7 @@ const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = ${JSON.stringify({
|
||||
directRowKeys: [
|
||||
'rowMaDaoProviderId',
|
||||
'rowMaDaoCountry',
|
||||
'rowMaDaoAutoPickCountry',
|
||||
'rowMaDaoReusePhone',
|
||||
'rowMaDaoOperator',
|
||||
'rowMaDaoPriceRange',
|
||||
],
|
||||
},
|
||||
@@ -862,6 +1097,7 @@ return {
|
||||
rowMaDaoRoutingPlanId,
|
||||
rowMaDaoProviderId,
|
||||
rowMaDaoCountry,
|
||||
rowMaDaoOperator,
|
||||
rowMaDaoAutoPickCountry,
|
||||
rowMaDaoReusePhone,
|
||||
rowMaDaoPriceRange,
|
||||
@@ -941,6 +1177,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');
|
||||
@@ -1057,6 +1294,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');
|
||||
@@ -1069,8 +1307,9 @@ return {
|
||||
assert.equal(api.rowMaDaoRoutingPlanId.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoProviderId.style.display, '');
|
||||
assert.equal(api.rowMaDaoCountry.style.display, '');
|
||||
assert.equal(api.rowMaDaoAutoPickCountry.style.display, '');
|
||||
assert.equal(api.rowMaDaoReusePhone.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, '');
|
||||
});
|
||||
|
||||
@@ -1160,9 +1399,10 @@ const inputNexSmsServiceCode = { value: 'ot' };
|
||||
const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/acquire' };
|
||||
const inputMaDaoHttpSecret = { value: 'madao-secret' };
|
||||
const selectMaDaoMode = { value: 'direct' };
|
||||
const inputMaDaoRoutingPlanId = { value: 'plan-1' };
|
||||
const inputMaDaoProviderId = { value: 'Local Provider!!' };
|
||||
const inputMaDaoCountry = { value: 'th' };
|
||||
const selectMaDaoRoutingPlanId = { value: 'plan-1' };
|
||||
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' };
|
||||
@@ -1266,7 +1506,9 @@ ${extractFunction('normalizeMaDaoBaseUrlValue')}
|
||||
${extractFunction('normalizeMaDaoModeValue')}
|
||||
${extractFunction('normalizeMaDaoIdentifierValue')}
|
||||
${extractFunction('normalizeMaDaoProviderIdValue')}
|
||||
${extractFunction('normalizeMaDaoOperatorValue')}
|
||||
${extractFunction('normalizeMaDaoCountry')}
|
||||
${extractFunction('formatMaDaoCountryDisplayLabel')}
|
||||
${extractFunction('normalizeMaDaoPriceValue')}
|
||||
${extractFunction('normalizeFiveSimCountryCode')}
|
||||
${extractFunction('normalizeFiveSimCountryOrderValue')}
|
||||
@@ -1338,6 +1580,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');
|
||||
@@ -1389,6 +1632,7 @@ let latestState = {
|
||||
madaoRoutingPlanId: 'plan-old',
|
||||
madaoProviderId: 'provider-old',
|
||||
madaoCountry: 'TH',
|
||||
madaoOperator: 'operator-old',
|
||||
madaoAutoPickCountry: true,
|
||||
madaoReusePhone: true,
|
||||
madaoMinPrice: '0.01',
|
||||
@@ -1434,9 +1678,34 @@ const inputNexSmsServiceCode = { value: 'ot' };
|
||||
const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/poll' };
|
||||
const inputMaDaoHttpSecret = { value: 'madao-live-secret' };
|
||||
const selectMaDaoMode = { value: 'direct' };
|
||||
const inputMaDaoRoutingPlanId = { value: 'plan-live' };
|
||||
const inputMaDaoProviderId = { value: 'Provider Live!' };
|
||||
const inputMaDaoCountry = { value: 'local' };
|
||||
const selectMaDaoRoutingPlanId = {
|
||||
value: 'plan-live',
|
||||
options: [],
|
||||
replaceChildren(...children) {
|
||||
this.options = children;
|
||||
},
|
||||
};
|
||||
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' };
|
||||
@@ -1454,6 +1723,10 @@ let heroSmsCountrySelectionOrder = [];
|
||||
let phoneSmsProviderOrderSelection = ['hero-sms', '5sim'];
|
||||
let lastPhoneSmsProviderBeforeChange = null;
|
||||
let savedPayload = null;
|
||||
let maDaoRoutingPlanOptions = [];
|
||||
let maDaoProviderOptions = [];
|
||||
let maDaoCountryOptions = [];
|
||||
let maDaoOperatorOptions = [];
|
||||
|
||||
${extractFunction('normalizePhoneSmsProvider')}
|
||||
${extractFunction('normalizePhoneSmsProviderValue')}
|
||||
@@ -1483,9 +1756,21 @@ ${extractFunction('normalizeNexSmsServiceCodeValue')}
|
||||
${extractFunction('normalizeMaDaoBaseUrlValue')}
|
||||
${extractFunction('normalizeMaDaoModeValue')}
|
||||
${extractFunction('normalizeMaDaoIdentifierValue')}
|
||||
${extractFunction('normalizeMaDaoRoutingPlanIdValue')}
|
||||
${extractFunction('normalizeMaDaoProviderIdValue')}
|
||||
${extractFunction('normalizeMaDaoOperatorValue')}
|
||||
${extractFunction('normalizeMaDaoCountry')}
|
||||
${extractFunction('normalizeMaDaoPriceValue')}
|
||||
${extractFunction('formatMaDaoCountryDisplayLabel')}
|
||||
${extractFunction('createSelectOptionElement')}
|
||||
${extractFunction('setSelectOptions')}
|
||||
${extractFunction('normalizeMaDaoOptionListItems')}
|
||||
${extractFunction('resolveMaDaoOptionSelectedValue')}
|
||||
${extractFunction('buildMaDaoRoutingPlanOptions')}
|
||||
${extractFunction('setMaDaoRoutingPlanSelectOptions')}
|
||||
${extractFunction('setMaDaoProviderSelectOptions')}
|
||||
${extractFunction('setMaDaoCountrySelectOptions')}
|
||||
${extractFunction('setMaDaoOperatorSelectOptions')}
|
||||
${extractFunction('normalizePhoneSmsMinPriceValue')}
|
||||
${extractFunction('normalizePhoneSmsMaxPriceValue')}
|
||||
${extractFunction('normalizeHeroSmsCountryId')}
|
||||
@@ -1508,6 +1793,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() {}
|
||||
@@ -1564,6 +1850,71 @@ return {
|
||||
assert.equal(api.savedPayload.fiveSimMinPrice, '0.88');
|
||||
});
|
||||
|
||||
test('buildPhoneSmsProviderStatePatch preserves MaDao hidden reuse defaults when controls are absent', () => {
|
||||
const api = new Function(`
|
||||
let latestState = {
|
||||
phoneSmsProvider: 'madao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822',
|
||||
madaoHttpSecret: 'secret-old',
|
||||
madaoMode: 'direct',
|
||||
madaoRoutingPlanId: 'plan-old',
|
||||
madaoProviderId: 'provider-old',
|
||||
madaoCountry: 'TH',
|
||||
madaoOperator: 'any',
|
||||
madaoAutoPickCountry: true,
|
||||
madaoReusePhone: true,
|
||||
madaoMinPrice: '0.01',
|
||||
madaoMaxPrice: '0.09',
|
||||
};
|
||||
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
||||
const PHONE_SMS_PROVIDER_HERO = PHONE_SMS_PROVIDER_HERO_SMS;
|
||||
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
||||
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
||||
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
||||
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS;
|
||||
const DEFAULT_FIVE_SIM_COUNTRY_ID = 'vietnam';
|
||||
const DEFAULT_FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)';
|
||||
const DEFAULT_FIVE_SIM_OPERATOR = 'any';
|
||||
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
|
||||
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
|
||||
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||
const MADAO_MODE_ROUTING_PLAN = 'routing_plan';
|
||||
const MADAO_MODE_DIRECT = 'direct';
|
||||
const DEFAULT_MADAO_MODE = MADAO_MODE_ROUTING_PLAN;
|
||||
const selectMaDaoMode = { value: 'direct' };
|
||||
const selectMaDaoRoutingPlanId = { value: 'plan-live' };
|
||||
const selectMaDaoProviderId = { value: 'provider-live' };
|
||||
const selectMaDaoCountry = { value: 'GB' };
|
||||
const selectMaDaoOperator = { value: 'Any operator' };
|
||||
const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/acquire' };
|
||||
const inputMaDaoHttpSecret = { value: 'secret-live' };
|
||||
const inputMaDaoMinPrice = { value: '0.02' };
|
||||
const inputMaDaoMaxPrice = { value: '0.20' };
|
||||
|
||||
${extractFunction('normalizePhoneSmsProvider')}
|
||||
${extractFunction('normalizeMaDaoBaseUrlValue')}
|
||||
${extractFunction('normalizeMaDaoModeValue')}
|
||||
${extractFunction('normalizeMaDaoIdentifierValue')}
|
||||
${extractFunction('normalizeMaDaoRoutingPlanIdValue')}
|
||||
${extractFunction('normalizeMaDaoProviderIdValue')}
|
||||
${extractFunction('normalizeMaDaoOperatorValue')}
|
||||
${extractFunction('normalizeMaDaoCountry')}
|
||||
${extractFunction('normalizeMaDaoPriceValue')}
|
||||
${extractFunction('normalizePhoneSmsMinPriceValue')}
|
||||
${extractFunction('normalizePhoneSmsMaxPriceValue')}
|
||||
${extractFunction('buildPhoneSmsProviderStatePatch')}
|
||||
return { buildPhoneSmsProviderStatePatch };
|
||||
`)();
|
||||
|
||||
const patch = api.buildPhoneSmsProviderStatePatch('madao');
|
||||
|
||||
assert.equal(patch.madaoAutoPickCountry, true);
|
||||
assert.equal(patch.madaoReusePhone, true);
|
||||
assert.equal(patch.madaoOperator, '');
|
||||
assert.equal(patch.madaoProviderId, 'provider-live');
|
||||
assert.equal(patch.madaoCountry, 'GB');
|
||||
});
|
||||
|
||||
test('HeroSMS operator helpers normalize keyed operator payloads', () => {
|
||||
const api = new Function(`
|
||||
const DEFAULT_HERO_SMS_OPERATOR = 'any';
|
||||
|
||||
Reference in New Issue
Block a user