feat: add MaDao phone SMS provider
This commit is contained in:
@@ -3,6 +3,8 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
const DEFAULT_MADAO_BASE_URL_FOR_TEST = 'http://127.0.0.1:7822';
|
||||
const DEFAULT_MADAO_MODE_FOR_TEST = 'routing_plan';
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
@@ -65,6 +67,12 @@ test('background account history settings are normalized independently from hotm
|
||||
extractFunction('normalizeNexSmsCountryId'),
|
||||
extractFunction('normalizeNexSmsCountryOrder'),
|
||||
extractFunction('normalizeNexSmsServiceCode'),
|
||||
extractFunction('normalizeMaDaoBaseUrl'),
|
||||
extractFunction('normalizeMaDaoMode'),
|
||||
extractFunction('normalizeMaDaoIdentifier'),
|
||||
extractFunction('normalizeMaDaoProviderId'),
|
||||
extractFunction('normalizeMaDaoCountry'),
|
||||
extractFunction('normalizeMaDaoPrice'),
|
||||
extractFunction('normalizePhonePreferredActivation'),
|
||||
extractFunction('normalizePhoneVerificationReplacementLimit'),
|
||||
extractFunction('normalizePhoneCodeWaitSeconds'),
|
||||
@@ -119,8 +127,11 @@ const DEFAULT_HERO_SMS_OPERATOR = 'any';
|
||||
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
||||
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
||||
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms'];
|
||||
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms', 'madao'];
|
||||
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS;
|
||||
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||
const DEFAULT_MADAO_MODE = 'routing_plan';
|
||||
const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||
@@ -293,8 +304,12 @@ return {
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroRsKey', ' key-1 '), 'key-1');
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim');
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'NEXSMS'), 'nexsms');
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'MaDao'), 'madao');
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'unknown'), 'hero-sms');
|
||||
assert.deepStrictEqual(api.normalizePersistentSettingValue('phoneSmsProviderOrder', ['nexsms', '5sim', 'nexsms']), ['nexsms', '5sim']);
|
||||
assert.deepStrictEqual(
|
||||
api.normalizePersistentSettingValue('phoneSmsProviderOrder', ['madao', 'nexsms', '5sim', 'nexsms']),
|
||||
['madao', 'nexsms', '5sim']
|
||||
);
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsReuseEnabled', false), false);
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsReuseEnabled', true), true);
|
||||
assert.equal(api.normalizePersistentSettingValue('fiveSimApiKey', ' demo-five '), ' demo-five ');
|
||||
@@ -373,6 +388,22 @@ return {
|
||||
[1, 6]
|
||||
);
|
||||
assert.equal(api.normalizePersistentSettingValue('nexSmsServiceCode', ' OT! '), 'ot');
|
||||
assert.equal(
|
||||
api.normalizePersistentSettingValue('madaoBaseUrl', 'http://127.0.0.1:7822/api/acquire?x=1'),
|
||||
DEFAULT_MADAO_BASE_URL_FOR_TEST
|
||||
);
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoBaseUrl', 'ftp://invalid'), DEFAULT_MADAO_BASE_URL_FOR_TEST);
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoHttpSecret', ' secret-token '), ' secret-token ');
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoMode', 'direct'), 'direct');
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoMode', 'unknown'), DEFAULT_MADAO_MODE_FOR_TEST);
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoRoutingPlanId', ' rp/openai! '), 'rp/openai!');
|
||||
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('madaoAutoPickCountry', 1), true);
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoReusePhone', 0), false);
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoMinPrice', '0.123456'), '0.1235');
|
||||
assert.equal(api.normalizePersistentSettingValue('madaoMaxPrice', '-1'), '');
|
||||
const rangePayload = api.buildPersistentSettingsPayload({
|
||||
heroSmsMinPrice: '0.023456',
|
||||
fiveSimMinPrice: '0.0789',
|
||||
|
||||
@@ -412,6 +412,17 @@ return {
|
||||
cloudMailToken: 'cloud-token',
|
||||
gopayHelperApiKey: 'gpc-key',
|
||||
heroSmsApiKey: 'hero-key',
|
||||
phoneSmsProvider: 'madao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822',
|
||||
madaoHttpSecret: 'madao-secret',
|
||||
madaoMode: 'routing_plan',
|
||||
madaoRoutingPlanId: 'rp-openai',
|
||||
madaoProviderId: 'upstream-a',
|
||||
madaoCountry: 'GB',
|
||||
madaoAutoPickCountry: false,
|
||||
madaoReusePhone: true,
|
||||
madaoMinPrice: '0.12',
|
||||
madaoMaxPrice: '0.45',
|
||||
hotmailAccounts: [
|
||||
{ id: 'hotmail-1', email: 'owner@example.com', password: 'mail-pass' },
|
||||
],
|
||||
@@ -434,6 +445,17 @@ return {
|
||||
assert.equal(api.getPayloadInput().cloudMailToken, 'cloud-token');
|
||||
assert.equal(api.getPayloadInput().gopayHelperApiKey, 'gpc-key');
|
||||
assert.equal(api.getPayloadInput().heroSmsApiKey, 'hero-key');
|
||||
assert.equal(api.getPayloadInput().phoneSmsProvider, 'madao');
|
||||
assert.equal(api.getPayloadInput().madaoBaseUrl, 'http://127.0.0.1:7822');
|
||||
assert.equal(api.getPayloadInput().madaoHttpSecret, 'madao-secret');
|
||||
assert.equal(api.getPayloadInput().madaoMode, 'routing_plan');
|
||||
assert.equal(api.getPayloadInput().madaoRoutingPlanId, 'rp-openai');
|
||||
assert.equal(api.getPayloadInput().madaoProviderId, 'upstream-a');
|
||||
assert.equal(api.getPayloadInput().madaoCountry, 'GB');
|
||||
assert.equal(api.getPayloadInput().madaoAutoPickCountry, false);
|
||||
assert.equal(api.getPayloadInput().madaoReusePhone, true);
|
||||
assert.equal(api.getPayloadInput().madaoMinPrice, '0.12');
|
||||
assert.equal(api.getPayloadInput().madaoMaxPrice, '0.45');
|
||||
assert.deepEqual(api.getPayloadInput().hotmailAccounts, [
|
||||
{ id: 'hotmail-1', email: 'owner@example.com', password: 'mail-pass' },
|
||||
]);
|
||||
@@ -441,5 +463,6 @@ return {
|
||||
assert.equal(api.getPayloadInput().paypalAccounts[0].email, 'paypal@example.com');
|
||||
assert.equal(api.getPersistedUpdates().settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'schema-key');
|
||||
assert.equal(api.getPersistedUpdates().cloudflareTempEmailAdminAuth, 'admin-secret');
|
||||
assert.equal(api.getPersistedUpdates().madaoRoutingPlanId, 'rp-openai');
|
||||
assert.deepEqual(api.getPersistOptions(), { replaceExisting: true });
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@ const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
|
||||
const flowRegistrySource = readFlowRegistryBundle();
|
||||
const settingsSchemaSource = fs.readFileSync('core/flow-kernel/settings-schema.js', 'utf8');
|
||||
const backgroundSource = fs.readFileSync('background.js', 'utf8');
|
||||
const DEFAULT_MADAO_BASE_URL_FOR_TEST = 'http://127.0.0.1:7822';
|
||||
const DEFAULT_MADAO_MODE_FOR_TEST = 'routing_plan';
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
@@ -91,6 +93,8 @@ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
||||
'stepExecutionRangeByFlow',
|
||||
]);
|
||||
const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS);
|
||||
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||
const DEFAULT_MADAO_MODE = 'routing_plan';
|
||||
const PERSISTED_SETTING_DEFAULTS = {
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
targetId: 'cpa',
|
||||
@@ -107,6 +111,17 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
ipProxyMode: 'account',
|
||||
kiroRsUrl: '',
|
||||
kiroRsKey: '',
|
||||
phoneSmsProvider: 'hero-sms',
|
||||
madaoBaseUrl: DEFAULT_MADAO_BASE_URL,
|
||||
madaoHttpSecret: '',
|
||||
madaoMode: DEFAULT_MADAO_MODE,
|
||||
madaoRoutingPlanId: '',
|
||||
madaoProviderId: '',
|
||||
madaoCountry: '',
|
||||
madaoAutoPickCountry: true,
|
||||
madaoReusePhone: true,
|
||||
madaoMinPrice: '',
|
||||
madaoMaxPrice: '',
|
||||
stepExecutionRangeByFlow: {},
|
||||
};
|
||||
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||
@@ -158,6 +173,68 @@ function normalizeCustomMailHelperBaseUrl(value = '') {
|
||||
}
|
||||
}
|
||||
function normalizeStepExecutionRangeByFlow(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
|
||||
function normalizePhoneSmsProvider(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === '5sim' || normalized === 'nexsms' || normalized === 'madao') {
|
||||
return normalized;
|
||||
}
|
||||
return 'hero-sms';
|
||||
}
|
||||
function normalizePhoneSmsProviderOrder(value = []) {
|
||||
const source = Array.isArray(value) ? value : String(value || '').split(/[\\r\\n,]+/);
|
||||
const seen = new Set();
|
||||
return source
|
||||
.map((entry) => normalizePhoneSmsProvider(entry))
|
||||
.filter((provider) => {
|
||||
if (!provider || seen.has(provider)) return false;
|
||||
seen.add(provider);
|
||||
return true;
|
||||
})
|
||||
.slice(0, 4);
|
||||
}
|
||||
function normalizeLocalHttpBaseUrl(value = '', fallback = DEFAULT_MADAO_BASE_URL) {
|
||||
const rawValue = String(value || fallback).trim();
|
||||
try {
|
||||
const parsed = new URL(rawValue);
|
||||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||||
return fallback;
|
||||
}
|
||||
return parsed.toString().replace(/\\/$/, '');
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
function normalizeMaDaoBaseUrl(value = '') {
|
||||
const normalized = normalizeLocalHttpBaseUrl(value, DEFAULT_MADAO_BASE_URL);
|
||||
try {
|
||||
const parsed = new URL(normalized);
|
||||
parsed.pathname = parsed.pathname.replace(/\\/api\\/(?:acquire|poll|release|routing\\/replace)$/i, '');
|
||||
parsed.search = '';
|
||||
parsed.hash = '';
|
||||
return parsed.toString().replace(/\\/$/, '');
|
||||
} catch {
|
||||
return DEFAULT_MADAO_BASE_URL;
|
||||
}
|
||||
}
|
||||
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 normalizeMaDaoCountry(value = '') {
|
||||
const trimmed = String(value || '').trim();
|
||||
if (!trimmed) return '';
|
||||
const lowered = trimmed.toLowerCase();
|
||||
if (lowered === 'any' || lowered === 'local') return lowered;
|
||||
if (/^[a-z]{2}$/i.test(trimmed)) return trimmed.toUpperCase();
|
||||
return lowered.replace(/[^a-z0-9_-]+/g, '');
|
||||
}
|
||||
function normalizeHeroSmsMaxPrice(value = '') {
|
||||
const rawValue = String(value ?? '').trim();
|
||||
if (!rawValue) return '';
|
||||
const numeric = Number(rawValue);
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) return '';
|
||||
return String(Math.round(numeric * 10000) / 10000);
|
||||
}
|
||||
function normalizeMaDaoPrice(value = '') { return normalizeHeroSmsMaxPrice(value); }
|
||||
function normalizeIpProxyProviderValue(value) { return String(value || '711proxy').trim() || '711proxy'; }
|
||||
function normalizeIpProxyMode(value) { return String(value || 'account').trim() || 'account'; }
|
||||
function normalizeIpProxyServiceProfiles(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
|
||||
@@ -226,6 +303,11 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
|
||||
assert.equal(payload.targetId, 'kiro-rs');
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'secret-key');
|
||||
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.madaoAutoPickCountry, true);
|
||||
assert.equal(payload.madaoReusePhone, true);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 5);
|
||||
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||
@@ -335,6 +417,8 @@ function getRequestedKeys() {
|
||||
assert.ok(api.getRequestedKeys().includes('plusAccountAccessStrategy'));
|
||||
assert.equal(state.settingsSchemaVersion, 5);
|
||||
assert.equal(state.settingsState.activeFlowId, 'openai');
|
||||
assert.ok(api.getRequestedKeys().includes('madaoBaseUrl'));
|
||||
assert.ok(api.getRequestedKeys().includes('madaoMode'));
|
||||
});
|
||||
|
||||
test('getPersistedSettings can project schema-only storage back into legacy flat settings', async () => {
|
||||
@@ -629,6 +713,37 @@ function getRemovedKeys() {
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'customMailReceiveMode'), false);
|
||||
});
|
||||
|
||||
test('buildPersistentSettingsPayload persists normalized MaDao flat settings outside canonical settingsState', () => {
|
||||
const api = buildHarness();
|
||||
|
||||
const payload = api.buildPersistentSettingsPayload({
|
||||
phoneSmsProvider: 'MaDao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822/api/acquire?x=1',
|
||||
madaoHttpSecret: ' secret-token ',
|
||||
madaoMode: 'direct',
|
||||
madaoRoutingPlanId: ' rp-openai ',
|
||||
madaoProviderId: ' Upstream A! ',
|
||||
madaoCountry: ' gb ',
|
||||
madaoAutoPickCountry: 0,
|
||||
madaoReusePhone: 1,
|
||||
madaoMinPrice: '0.123456',
|
||||
madaoMaxPrice: '-1',
|
||||
});
|
||||
|
||||
assert.equal(payload.phoneSmsProvider, 'madao');
|
||||
assert.equal(payload.madaoBaseUrl, DEFAULT_MADAO_BASE_URL_FOR_TEST);
|
||||
assert.equal(payload.madaoHttpSecret, ' secret-token ');
|
||||
assert.equal(payload.madaoMode, 'direct');
|
||||
assert.equal(payload.madaoRoutingPlanId, 'rp-openai');
|
||||
assert.equal(payload.madaoProviderId, 'upstreama');
|
||||
assert.equal(payload.madaoCountry, 'GB');
|
||||
assert.equal(payload.madaoAutoPickCountry, false);
|
||||
assert.equal(payload.madaoReusePhone, true);
|
||||
assert.equal(payload.madaoMinPrice, '0.1235');
|
||||
assert.equal(payload.madaoMaxPrice, '');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload.settingsState || {}, 'madaoBaseUrl'), false);
|
||||
});
|
||||
|
||||
test('setPersistentSettings mirrors flat schema updates without resetting other canonical settings', async () => {
|
||||
const api = buildHarness(`
|
||||
const persistedWrites = [];
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('phone-sms/providers/madao.js', 'utf8');
|
||||
const api = new Function('self', `${source}; return self.PhoneSmsMaDaoProvider;`)({});
|
||||
|
||||
function createJsonResponse(payload, ok = true, status = ok ? 200 : 400, statusText = ok ? 'OK' : 'Bad Request') {
|
||||
return {
|
||||
ok,
|
||||
status,
|
||||
statusText,
|
||||
text: async () => (typeof payload === 'string' ? payload : JSON.stringify(payload)),
|
||||
};
|
||||
}
|
||||
|
||||
test('MaDao direct acquire sends only direct fields and normalizes activation data', async () => {
|
||||
const requests = [];
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
requests.push({ url: new URL(url), options, body: JSON.parse(options.body) });
|
||||
return createJsonResponse({
|
||||
ticket_id: 'ticket-1',
|
||||
phone_number: '+441111111111',
|
||||
service: 'openai',
|
||||
country: 'gb',
|
||||
provider: 'Upstream A!',
|
||||
price: 0.123456,
|
||||
status: 'code_received',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const activation = await provider.acquireActivation({
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822/',
|
||||
madaoHttpSecret: 'token-1',
|
||||
madaoMode: 'direct',
|
||||
madaoRoutingPlanId: 'route-plan-should-not-be-sent',
|
||||
madaoProviderId: 'Upstream A!',
|
||||
madaoCountry: 'gb',
|
||||
madaoAutoPickCountry: 'false',
|
||||
madaoReusePhone: '1',
|
||||
madaoMinPrice: '0.01',
|
||||
madaoMaxPrice: '0.20',
|
||||
});
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(requests[0].url.toString(), 'http://127.0.0.1:7822/api/acquire');
|
||||
assert.equal(requests[0].options.method, 'POST');
|
||||
assert.equal(requests[0].options.headers.Authorization, 'Bearer token-1');
|
||||
assert.deepEqual(requests[0].body, {
|
||||
provider: 'upstreama',
|
||||
service: 'openai',
|
||||
auto_pick_country: false,
|
||||
reuse_phone: true,
|
||||
country: 'GB',
|
||||
min_price: 0.01,
|
||||
max_price: 0.2,
|
||||
});
|
||||
assert.equal(activation.provider, 'madao');
|
||||
assert.equal(activation.activationId, 'ticket-1');
|
||||
assert.equal(activation.phoneNumber, '+441111111111');
|
||||
assert.equal(activation.countryId, 'GB');
|
||||
assert.equal(activation.madaoProviderId, 'upstreama');
|
||||
assert.equal(activation.madaoPrice, 0.1235);
|
||||
assert.equal(activation.madaoStatus, 'code_received');
|
||||
});
|
||||
|
||||
test('MaDao routing acquire sends routing plan only', async () => {
|
||||
const requests = [];
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
requests.push({ url: new URL(url), body: JSON.parse(options.body) });
|
||||
return createJsonResponse({
|
||||
ticket_id: 'ticket-2',
|
||||
phone_number: '+442222222222',
|
||||
routing_plan_id: 'rp-openai',
|
||||
routing_item_id: 'route-1',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const activation = await provider.acquireActivation({
|
||||
madaoMode: 'routing_plan',
|
||||
madaoRoutingPlanId: 'rp-openai',
|
||||
madaoProviderId: 'direct-provider',
|
||||
madaoCountry: 'TH',
|
||||
madaoAutoPickCountry: false,
|
||||
madaoReusePhone: false,
|
||||
madaoMinPrice: '0.01',
|
||||
madaoMaxPrice: '0.20',
|
||||
});
|
||||
|
||||
assert.deepEqual(requests[0].body, {
|
||||
provider: 'auto',
|
||||
service: 'openai',
|
||||
routing_plan_id: 'rp-openai',
|
||||
});
|
||||
assert.equal(activation.madaoRoutingPlanId, 'rp-openai');
|
||||
assert.equal(activation.madaoRoutingItemId, 'route-1');
|
||||
});
|
||||
|
||||
test('MaDao poll extracts codes from nested messages and reports pending status', async () => {
|
||||
const statusEvents = [];
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async (_url, options = {}) => {
|
||||
const body = JSON.parse(options.body);
|
||||
if (body.ticket_id === 'pending-ticket') {
|
||||
return createJsonResponse({ status: 'waiting_code', message: 'still waiting' });
|
||||
}
|
||||
return createJsonResponse({
|
||||
messages: [
|
||||
{ text: 'old text without code' },
|
||||
{ body: 'OpenAI verification code: 765432' },
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const pending = await provider.pollActivationCode({}, { activationId: 'pending-ticket' }, {
|
||||
onStatus: async (event) => statusEvents.push(event),
|
||||
});
|
||||
const code = await provider.pollActivationCode({}, { activationId: 'ready-ticket' });
|
||||
|
||||
assert.equal(pending, '');
|
||||
assert.equal(statusEvents.length, 1);
|
||||
assert.equal(statusEvents[0].statusText, 'still waiting');
|
||||
assert.equal(code, '765432');
|
||||
});
|
||||
|
||||
test('MaDao direct rotate releases the current ticket without acquiring a new one', async () => {
|
||||
const requests = [];
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
requests.push({ url: new URL(url), body: JSON.parse(options.body) });
|
||||
return createJsonResponse({ released: true });
|
||||
},
|
||||
});
|
||||
|
||||
const result = await provider.rotateActivation(
|
||||
{ madaoMode: 'direct' },
|
||||
{ activationId: 'ticket-old', phoneNumber: '+441111111111' },
|
||||
{ releaseAction: 'ban' }
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
requests.map((request) => request.url.pathname),
|
||||
['/api/release']
|
||||
);
|
||||
assert.deepEqual(requests[0].body, { ticket_id: 'ticket-old', action: 'ban' });
|
||||
assert.equal(result.currentTicketId, 'ticket-old');
|
||||
assert.equal(result.nextActivation, null);
|
||||
});
|
||||
|
||||
test('MaDao routing rotate rejects invalid replacement payloads', async () => {
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async () => createJsonResponse({
|
||||
current_ticket_id: 'ticket-old',
|
||||
next_ticket: {
|
||||
ticket_id: 'ticket-new',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => provider.rotateActivation(
|
||||
{ madaoMode: 'routing_plan' },
|
||||
{
|
||||
activationId: 'ticket-old',
|
||||
phoneNumber: '+441111111111',
|
||||
madaoRoutingPlanId: 'rp-openai',
|
||||
madaoRoutingItemId: 'route-old',
|
||||
},
|
||||
{ releaseAction: 'ban', reason: 'phone_number_used' }
|
||||
),
|
||||
/MaDao/
|
||||
);
|
||||
});
|
||||
|
||||
test('MaDao provider surfaces HTTP errors and request timeouts', async () => {
|
||||
const httpProvider = api.createProvider({
|
||||
fetchImpl: async () => createJsonResponse({ error: 'upstream unavailable' }, false, 503, 'Service Unavailable'),
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => httpProvider.acquireActivation({}),
|
||||
(error) => {
|
||||
assert.equal(error.status, 503);
|
||||
assert.equal(error.payload.error, 'upstream unavailable');
|
||||
assert.match(error.message, /upstream unavailable/);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
const timeoutProvider = api.createProvider({
|
||||
fetchImpl: async (_url, options = {}) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
options.signal.addEventListener('abort', () => {
|
||||
const error = new Error('aborted');
|
||||
error.name = 'AbortError';
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
requestTimeoutMs: 1,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => timeoutProvider.acquireActivation({}),
|
||||
/MaDao/
|
||||
);
|
||||
});
|
||||
@@ -16,29 +16,41 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
||||
PhoneSmsFiveSimProvider: {
|
||||
createProvider: (deps = {}) => ({ provider: '5sim', deps }),
|
||||
},
|
||||
PhoneSmsMaDaoProvider: {
|
||||
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(registry.getProviderIds(), ['hero-sms', '5sim', 'nexsms']);
|
||||
assert.deepStrictEqual(registry.getProviderIds(), ['hero-sms', '5sim', 'nexsms', 'madao']);
|
||||
assert.equal(registry.normalizeProviderId(' NEXSMS '), 'nexsms');
|
||||
assert.equal(registry.normalizeProviderId(' MaDao '), 'madao');
|
||||
assert.equal(registry.normalizeProviderId('unknown-provider'), 'hero-sms');
|
||||
assert.equal(registry.getProviderLabel('nexsms'), 'NexSMS');
|
||||
assert.equal(registry.getProviderLabel('madao'), 'MaDao');
|
||||
assert.equal(registry.getProviderDefinition('nexsms').moduleKey, 'PhoneSmsNexSmsProvider');
|
||||
assert.equal(registry.getProviderDefinition('madao').moduleKey, 'PhoneSmsMaDaoProvider');
|
||||
assert.deepStrictEqual(
|
||||
registry.normalizeProviderOrder([
|
||||
{ provider: 'madao' },
|
||||
{ provider: 'nexsms' },
|
||||
{ id: '5sim' },
|
||||
{ value: 'hero-sms' },
|
||||
'MADAO',
|
||||
'NEXSMS',
|
||||
]),
|
||||
['nexsms', '5sim', 'hero-sms']
|
||||
['madao', 'nexsms', '5sim', 'hero-sms']
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
registry.normalizeProviderOrder([], ['nexsms', '5sim', 'nexsms']),
|
||||
['nexsms', '5sim']
|
||||
registry.normalizeProviderOrder([], ['madao', 'nexsms', '5sim', 'nexsms']),
|
||||
['madao', 'nexsms', '5sim']
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
registry.createProvider('5sim', { foo: 1 }),
|
||||
{ provider: '5sim', deps: { foo: 1 } }
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
registry.createProvider('madao', { foo: 2 }),
|
||||
{ provider: 'madao', deps: { foo: 2 } }
|
||||
);
|
||||
assert.throws(() => registry.createProvider('nexsms'), /接码平台模块未加载:nexsms/);
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ 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 maDaoSource = fs.readFileSync('phone-sms/providers/madao.js', 'utf8');
|
||||
const maDaoModule = new Function('self', `${maDaoSource}; return self.PhoneSmsMaDaoProvider;`)({});
|
||||
|
||||
function buildHeroSmsPricesPayload({ country = '52', service = 'dr', cost = 0.08, count = 25370, physicalCount = 14528 } = {}) {
|
||||
return JSON.stringify({
|
||||
@@ -155,6 +157,101 @@ test('phone verification helper reads latest HeroSMS operator from persistent st
|
||||
assert.equal(getNumberRequest.searchParams.get('operator'), 'dtac');
|
||||
});
|
||||
|
||||
test('phone verification helper acquires, polls and releases MaDao activation through provider adapter', async () => {
|
||||
const requests = [];
|
||||
let currentState = {
|
||||
phoneSmsProvider: 'madao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822/',
|
||||
madaoHttpSecret: 'secret-token',
|
||||
madaoMode: 'routing_plan',
|
||||
madaoRoutingPlanId: 'rp-openai',
|
||||
phoneCodeWaitSeconds: 15,
|
||||
phoneCodeTimeoutWindows: 1,
|
||||
phoneCodePollIntervalSeconds: 1,
|
||||
phoneCodePollMaxRounds: 2,
|
||||
};
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
createMaDaoProvider: maDaoModule.createProvider,
|
||||
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-1',
|
||||
phone_number: '+441111111111',
|
||||
service: 'openai',
|
||||
country: 'GB',
|
||||
provider: 'upstream-a',
|
||||
routing_plan_id: 'rp-openai',
|
||||
routing_item_id: 'route-1',
|
||||
status: 'waiting_code',
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/api/poll') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
ticket_id: 'madao-1',
|
||||
status: 'code_received',
|
||||
code: '123456',
|
||||
}),
|
||||
};
|
||||
}
|
||||
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 () => ({}),
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const activation = await helpers.requestPhoneActivation(currentState);
|
||||
const code = await helpers.pollPhoneActivationCode(currentState, activation, {
|
||||
timeoutMs: 15000,
|
||||
intervalMs: 1000,
|
||||
maxRounds: 1,
|
||||
});
|
||||
await helpers.finalizeSignupPhoneActivationAfterSuccess(currentState, activation);
|
||||
|
||||
assert.equal(activation.provider, 'madao');
|
||||
assert.equal(activation.activationId, 'madao-1');
|
||||
assert.equal(activation.phoneNumber, '+441111111111');
|
||||
assert.equal(activation.countryId, 'GB');
|
||||
assert.equal(activation.madaoRoutingPlanId, 'rp-openai');
|
||||
assert.equal(activation.madaoRoutingItemId, 'route-1');
|
||||
assert.equal(code, '123456');
|
||||
assert.deepStrictEqual(
|
||||
requests.map((entry) => entry.url.pathname),
|
||||
['/api/acquire', '/api/poll', '/api/release']
|
||||
);
|
||||
assert.deepStrictEqual(requests[0].body, {
|
||||
provider: 'auto',
|
||||
service: 'openai',
|
||||
routing_plan_id: 'rp-openai',
|
||||
});
|
||||
assert.equal(requests[0].options.headers.Authorization, 'Bearer secret-token');
|
||||
assert.deepStrictEqual(requests[1].body, { ticket_id: 'madao-1' });
|
||||
assert.deepStrictEqual(requests[2].body, { ticket_id: 'madao-1', action: 'finish' });
|
||||
});
|
||||
|
||||
test('signup phone helper persists signup runtime state without touching add-phone activation', async () => {
|
||||
const setStateCalls = [];
|
||||
let currentState = {
|
||||
@@ -8722,6 +8819,273 @@ test('phone verification helper routes 5sim buy, check, and finish by current ac
|
||||
);
|
||||
});
|
||||
|
||||
test('phone verification helper uses MaDao routing replace when add-phone rejects current number', async () => {
|
||||
const requests = [];
|
||||
const submittedPhones = [];
|
||||
let currentState = {
|
||||
phoneSmsProvider: 'madao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822',
|
||||
madaoMode: 'routing_plan',
|
||||
madaoRoutingPlanId: 'rp-openai',
|
||||
verificationResendCount: 0,
|
||||
phoneVerificationReplacementLimit: 2,
|
||||
phoneCodeWaitSeconds: 15,
|
||||
phoneCodeTimeoutWindows: 1,
|
||||
phoneCodePollIntervalSeconds: 1,
|
||||
phoneCodePollMaxRounds: 1,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
};
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
createMaDaoProvider: maDaoModule.createProvider,
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push({ url: parsedUrl, 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-old',
|
||||
phone_number: '+441111111111',
|
||||
service: 'openai',
|
||||
country: 'GB',
|
||||
routing_plan_id: 'rp-openai',
|
||||
routing_item_id: 'route-old',
|
||||
status: 'waiting_code',
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/api/routing/replace') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
current_ticket_id: 'madao-old',
|
||||
next_ticket: {
|
||||
ticket_id: 'madao-new',
|
||||
phone_number: '+442222222222',
|
||||
service: 'openai',
|
||||
country: 'GB',
|
||||
routing_plan_id: 'rp-openai',
|
||||
routing_item_id: 'route-new',
|
||||
status: 'waiting_code',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/api/poll') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
ticket_id: 'madao-new',
|
||||
status: 'code_received',
|
||||
code: '654321',
|
||||
}),
|
||||
};
|
||||
}
|
||||
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) => {
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
submittedPhones.push(message.payload.phoneNumber);
|
||||
if (message.payload.phoneNumber === '+441111111111') {
|
||||
return {
|
||||
addPhoneRejected: true,
|
||||
errorText: 'This phone number is already linked to another account',
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
};
|
||||
}
|
||||
return { phoneVerificationPage: true, url: 'https://auth.openai.com/phone-verification' };
|
||||
}
|
||||
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
|
||||
assert.equal(message.payload.code, '654321');
|
||||
return { success: true, consentReady: true, url: 'https://auth.openai.com/authorize' };
|
||||
}
|
||||
if (message.type === 'RETURN_TO_ADD_PHONE' || message.type === 'STEP8_GET_STATE') {
|
||||
return { addPhonePage: true, phoneVerificationPage: false, url: 'https://auth.openai.com/add-phone' };
|
||||
}
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const result = await helpers.completePhoneVerificationFlow(1, {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(result, {
|
||||
success: true,
|
||||
consentReady: true,
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
});
|
||||
assert.deepStrictEqual(submittedPhones, ['+441111111111', '+442222222222']);
|
||||
assert.deepStrictEqual(
|
||||
requests.map((entry) => entry.url.pathname),
|
||||
['/api/acquire', '/api/routing/replace', '/api/poll', '/api/release']
|
||||
);
|
||||
assert.deepStrictEqual(requests[1].body, {
|
||||
ticket_id: 'madao-old',
|
||||
release_action: 'ban',
|
||||
failed_item_id: 'route-old',
|
||||
reason: 'phone_number_used',
|
||||
});
|
||||
assert.deepStrictEqual(requests[2].body, { ticket_id: 'madao-new' });
|
||||
assert.deepStrictEqual(requests[3].body, { ticket_id: 'madao-new', action: 'finish' });
|
||||
assert.equal(currentState.currentPhoneActivation, null);
|
||||
});
|
||||
|
||||
test('phone verification helper reacquires MaDao direct numbers after releasing rejected number', async () => {
|
||||
const requests = [];
|
||||
const submittedPhones = [];
|
||||
let acquireCount = 0;
|
||||
let currentState = {
|
||||
phoneSmsProvider: 'madao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822',
|
||||
madaoMode: 'direct',
|
||||
madaoRoutingPlanId: 'rp-stale',
|
||||
madaoProviderId: 'upstream-a',
|
||||
madaoCountry: 'gb',
|
||||
madaoAutoPickCountry: false,
|
||||
madaoReusePhone: true,
|
||||
madaoMinPrice: '0.01',
|
||||
madaoMaxPrice: '0.2',
|
||||
verificationResendCount: 0,
|
||||
phoneVerificationReplacementLimit: 2,
|
||||
phoneCodeWaitSeconds: 15,
|
||||
phoneCodeTimeoutWindows: 1,
|
||||
phoneCodePollIntervalSeconds: 1,
|
||||
phoneCodePollMaxRounds: 1,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
};
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
createMaDaoProvider: maDaoModule.createProvider,
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push({ url: parsedUrl, body: options.body ? JSON.parse(options.body) : null });
|
||||
if (parsedUrl.pathname === '/api/acquire') {
|
||||
acquireCount += 1;
|
||||
const isFirst = acquireCount === 1;
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
ticket_id: isFirst ? 'madao-direct-old' : 'madao-direct-new',
|
||||
phone_number: isFirst ? '+441111111111' : '+442222222222',
|
||||
service: 'openai',
|
||||
country: 'GB',
|
||||
provider: 'upstream-a',
|
||||
status: 'waiting_code',
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/api/release') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({ ok: true }),
|
||||
};
|
||||
}
|
||||
if (parsedUrl.pathname === '/api/poll') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({
|
||||
ticket_id: 'madao-direct-new',
|
||||
status: 'code_received',
|
||||
code: '246810',
|
||||
}),
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
submittedPhones.push(message.payload.phoneNumber);
|
||||
if (message.payload.phoneNumber === '+441111111111') {
|
||||
return {
|
||||
addPhoneRejected: true,
|
||||
errorText: 'This phone number is already linked to another account',
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
};
|
||||
}
|
||||
return { phoneVerificationPage: true, url: 'https://auth.openai.com/phone-verification' };
|
||||
}
|
||||
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
|
||||
assert.equal(message.payload.code, '246810');
|
||||
return { success: true, consentReady: true, url: 'https://auth.openai.com/authorize' };
|
||||
}
|
||||
if (message.type === 'RETURN_TO_ADD_PHONE' || message.type === 'STEP8_GET_STATE') {
|
||||
return { addPhonePage: true, phoneVerificationPage: false, url: 'https://auth.openai.com/add-phone' };
|
||||
}
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const result = await helpers.completePhoneVerificationFlow(1, {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(result, {
|
||||
success: true,
|
||||
consentReady: true,
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
});
|
||||
assert.deepStrictEqual(submittedPhones, ['+441111111111', '+442222222222']);
|
||||
assert.deepStrictEqual(
|
||||
requests.map((entry) => entry.url.pathname),
|
||||
['/api/acquire', '/api/release', '/api/acquire', '/api/poll', '/api/release']
|
||||
);
|
||||
assert.deepStrictEqual(requests[0].body, {
|
||||
provider: 'upstream-a',
|
||||
service: 'openai',
|
||||
auto_pick_country: false,
|
||||
reuse_phone: true,
|
||||
country: 'GB',
|
||||
min_price: 0.01,
|
||||
max_price: 0.2,
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(requests[0].body, 'routing_plan_id'), false);
|
||||
assert.deepStrictEqual(requests[1].body, { ticket_id: 'madao-direct-old', action: 'ban' });
|
||||
assert.deepStrictEqual(requests[3].body, { ticket_id: 'madao-direct-new' });
|
||||
assert.deepStrictEqual(requests[4].body, { ticket_id: 'madao-direct-new', action: 'finish' });
|
||||
assert.equal(currentState.currentPhoneActivation, null);
|
||||
});
|
||||
|
||||
test('phone verification helper keeps 5sim reusable activation on the original order', async () => {
|
||||
const requests = [];
|
||||
let checkCount = 0;
|
||||
|
||||
@@ -88,9 +88,11 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
||||
assert.match(html, /id="select-phone-sms-provider"/);
|
||||
assert.match(html, /\.\.\/phone-sms\/providers\/hero-sms\.js/);
|
||||
assert.match(html, /\.\.\/phone-sms\/providers\/five-sim\.js/);
|
||||
assert.match(html, /\.\.\/phone-sms\/providers\/madao\.js/);
|
||||
assert.match(html, /\.\.\/phone-sms\/providers\/registry\.js/);
|
||||
assert.match(html, /<option value="hero-sms">HeroSMS<\/option>/);
|
||||
assert.match(html, /<option value="5sim">5sim<\/option>/);
|
||||
assert.match(html, /<option value="madao">MaDao<\/option>/);
|
||||
assert.match(html, /id="row-hero-sms-country"/);
|
||||
assert.match(html, /id="row-hero-sms-country-fallback"/);
|
||||
assert.match(html, /id="row-hero-sms-acquire-priority"/);
|
||||
@@ -147,6 +149,26 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
||||
assert.match(html, /id="row-nex-sms-country-fallback"/);
|
||||
assert.match(html, /id="row-nex-sms-service-code"/);
|
||||
assert.match(html, /id="input-nex-sms-service-code"/);
|
||||
assert.match(html, /id="row-madao-base-url"/);
|
||||
assert.match(html, /id="input-madao-base-url"/);
|
||||
assert.match(html, /id="row-madao-http-secret"/);
|
||||
assert.match(html, /id="input-madao-http-secret"/);
|
||||
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="row-madao-provider-id"/);
|
||||
assert.match(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="row-madao-price-range"/);
|
||||
assert.match(html, /id="input-madao-min-price"/);
|
||||
assert.match(html, /id="input-madao-max-price"/);
|
||||
assert.doesNotMatch(html, /id="btn-open-madao-github"/);
|
||||
assert.doesNotMatch(html, /id="input-account-run-history-text-enabled"/);
|
||||
});
|
||||
|
||||
@@ -623,19 +645,21 @@ const rowPhoneSmsProvider = { style: { display: 'none' } };
|
||||
const rowPhoneSmsProviderOrder = { style: { display: 'none' } };
|
||||
const rowPhoneSmsProviderOrderActions = { style: { display: 'none' } };
|
||||
const selectPhoneSmsProvider = { value: 'hero-sms' };
|
||||
const selectMaDaoMode = { value: 'routing_plan' };
|
||||
const btnTogglePhoneVerificationSection = {
|
||||
disabled: false,
|
||||
textContent: '',
|
||||
title: '',
|
||||
setAttribute: () => {},
|
||||
};
|
||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms'];
|
||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms', 'madao'];
|
||||
const phoneSmsProviderOrderSelection = [];
|
||||
function getPhoneSmsProviderCount() { return DEFAULT_PHONE_SMS_PROVIDER_ORDER.length; }
|
||||
function normalizePhoneSmsProviderOrderValue(value = [], fallbackOrder = DEFAULT_PHONE_SMS_PROVIDER_ORDER) {
|
||||
const source = Array.isArray(value) ? value : [];
|
||||
const normalized = [...source];
|
||||
if (normalized.length) {
|
||||
return normalized.slice(0, 3);
|
||||
return normalized.slice(0, getPhoneSmsProviderCount());
|
||||
}
|
||||
if (!Array.isArray(fallbackOrder) || !fallbackOrder.length) {
|
||||
return [];
|
||||
@@ -646,7 +670,7 @@ const btnTogglePhoneVerificationSection = {
|
||||
fallbackNormalized.push(provider);
|
||||
}
|
||||
}
|
||||
return fallbackNormalized.slice(0, 3);
|
||||
return fallbackNormalized.slice(0, getPhoneSmsProviderCount());
|
||||
}
|
||||
function resolveNormalizedProviderOrderForRuntime(state = {}) {
|
||||
const rawOrder = Array.isArray(state?.phoneSmsProviderOrder) ? state.phoneSmsProviderOrder : [];
|
||||
@@ -674,6 +698,15 @@ const rowNexSmsApiKey = { style: { display: 'none' } };
|
||||
const rowNexSmsCountry = { style: { display: 'none' } };
|
||||
const rowNexSmsCountryFallback = { style: { display: 'none' } };
|
||||
const rowNexSmsServiceCode = { style: { display: 'none' } };
|
||||
const rowMaDaoBaseUrl = { style: { display: 'none' } };
|
||||
const rowMaDaoHttpSecret = { style: { display: 'none' } };
|
||||
const rowMaDaoMode = { style: { display: 'none' } };
|
||||
const rowMaDaoRoutingPlanId = { style: { display: 'none' } };
|
||||
const rowMaDaoProviderId = { style: { display: 'none' } };
|
||||
const rowMaDaoCountry = { style: { display: 'none' } };
|
||||
const rowMaDaoAutoPickCountry = { style: { display: 'none' } };
|
||||
const rowMaDaoReusePhone = { style: { display: 'none' } };
|
||||
const rowMaDaoPriceRange = { style: { display: 'none' } };
|
||||
const rowHeroSmsRuntimePair = { style: { display: 'none' } };
|
||||
const rowHeroSmsCurrentNumber = { style: { display: 'none' } };
|
||||
const rowHeroSmsCurrentCountdown = { style: { display: 'none' } };
|
||||
@@ -689,6 +722,8 @@ const rowPhoneCodePollMaxRounds = { style: { display: 'none' } };
|
||||
const rowFreePhoneReuseEnabled = createMockRow();
|
||||
const rowFreePhoneReuseAutoEnabled = createMockRow();
|
||||
const rowFreeReusablePhone = createMockRow();
|
||||
const rowPhoneSmsPreferredPriceControl = { style: { display: 'none' } };
|
||||
const rowPhoneSmsReuseControl = { style: { display: 'none' } };
|
||||
const heroSmsReuseRow = createMockRow();
|
||||
const inputHeroSmsReuseEnabled = { checked: true, disabled: false, closest: () => heroSmsReuseRow };
|
||||
const inputFreePhoneReuseEnabled = { checked: true, disabled: false };
|
||||
@@ -698,10 +733,75 @@ const inputFreeReusablePhone = { disabled: false };
|
||||
const btnSaveFreeReusablePhone = { disabled: false };
|
||||
const btnClearFreeReusablePhone = { disabled: false };
|
||||
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 MADAO_MODE_ROUTING_PLAN = 'routing_plan';
|
||||
const MADAO_MODE_DIRECT = 'direct';
|
||||
const DEFAULT_MADAO_MODE = MADAO_MODE_ROUTING_PLAN;
|
||||
const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = ${JSON.stringify({
|
||||
'hero-sms': {
|
||||
rowKeys: [
|
||||
'rowHeroSmsCountry',
|
||||
'rowHeroSmsCountryFallback',
|
||||
'rowHeroSmsAcquirePriority',
|
||||
'rowHeroSmsOperator',
|
||||
'rowHeroSmsApiKey',
|
||||
'rowHeroSmsMaxPrice',
|
||||
],
|
||||
priceControlKeys: [
|
||||
'rowPhoneSmsPreferredPriceControl',
|
||||
'rowPhoneSmsReuseControl',
|
||||
],
|
||||
},
|
||||
'5sim': {
|
||||
rowKeys: [
|
||||
'rowFiveSimApiKey',
|
||||
'rowFiveSimCountry',
|
||||
'rowFiveSimCountryFallback',
|
||||
'rowFiveSimOperator',
|
||||
'rowFiveSimProduct',
|
||||
'rowHeroSmsMaxPrice',
|
||||
],
|
||||
},
|
||||
nexsms: {
|
||||
rowKeys: [
|
||||
'rowNexSmsApiKey',
|
||||
'rowNexSmsCountry',
|
||||
'rowNexSmsCountryFallback',
|
||||
'rowNexSmsServiceCode',
|
||||
],
|
||||
},
|
||||
madao: {
|
||||
rowKeys: [
|
||||
'rowMaDaoBaseUrl',
|
||||
'rowMaDaoHttpSecret',
|
||||
'rowMaDaoMode',
|
||||
],
|
||||
routingRowKeys: [
|
||||
'rowMaDaoRoutingPlanId',
|
||||
],
|
||||
directRowKeys: [
|
||||
'rowMaDaoProviderId',
|
||||
'rowMaDaoCountry',
|
||||
'rowMaDaoAutoPickCountry',
|
||||
'rowMaDaoReusePhone',
|
||||
'rowMaDaoPriceRange',
|
||||
],
|
||||
},
|
||||
})};
|
||||
function getSelectedPhoneSmsProvider() { return selectPhoneSmsProvider.value; }
|
||||
function isFiveSimProviderSelected() { return getSelectedPhoneSmsProvider() === PHONE_SMS_PROVIDER_FIVE_SIM; }
|
||||
function normalizePhoneSmsProviderValue(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return DEFAULT_PHONE_SMS_PROVIDER_ORDER.includes(normalized) ? normalized : PHONE_SMS_PROVIDER_HERO_SMS;
|
||||
}
|
||||
function normalizeMaDaoModeValue(value = '') { return String(value || '').trim().toLowerCase() === MADAO_MODE_DIRECT ? MADAO_MODE_DIRECT : DEFAULT_MADAO_MODE; }
|
||||
${extractFunction('getPhoneSmsProviderUiRowMap')}
|
||||
${extractFunction('getProviderUiRows')}
|
||||
${extractFunction('getAllProviderUiRows')}
|
||||
${extractFunction('updateProviderPriceControls')}
|
||||
function updateHeroSmsPlatformDisplay() {}
|
||||
function updateSignupMethodUI() {
|
||||
rowSignupMethod.style.display = inputPhoneVerificationEnabled.checked ? '' : 'none';
|
||||
@@ -756,6 +856,18 @@ return {
|
||||
rowNexSmsCountry,
|
||||
rowNexSmsCountryFallback,
|
||||
rowNexSmsServiceCode,
|
||||
rowMaDaoBaseUrl,
|
||||
rowMaDaoHttpSecret,
|
||||
rowMaDaoMode,
|
||||
rowMaDaoRoutingPlanId,
|
||||
rowMaDaoProviderId,
|
||||
rowMaDaoCountry,
|
||||
rowMaDaoAutoPickCountry,
|
||||
rowMaDaoReusePhone,
|
||||
rowMaDaoPriceRange,
|
||||
rowPhoneSmsPreferredPriceControl,
|
||||
rowPhoneSmsReuseControl,
|
||||
selectMaDaoMode,
|
||||
rowHeroSmsRuntimePair,
|
||||
rowHeroSmsCurrentNumber,
|
||||
rowHeroSmsCurrentCountdown,
|
||||
@@ -823,6 +935,15 @@ return {
|
||||
assert.equal(api.rowNexSmsCountry.style.display, 'none');
|
||||
assert.equal(api.rowNexSmsCountryFallback.style.display, 'none');
|
||||
assert.equal(api.rowNexSmsServiceCode.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoBaseUrl.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoHttpSecret.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoMode.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoRoutingPlanId.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoProviderId.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoCountry.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoPriceRange.style.display, 'none');
|
||||
|
||||
api.inputPhoneVerificationEnabled.checked = true;
|
||||
api.setLatestState({ signupPhoneNumber: '66959916439' });
|
||||
@@ -853,6 +974,8 @@ return {
|
||||
assert.equal(api.rowHeroSmsOperator.style.display, '');
|
||||
assert.equal(api.rowHeroSmsApiKey.style.display, '');
|
||||
assert.equal(api.rowHeroSmsMaxPrice.style.display, '');
|
||||
assert.equal(api.rowPhoneSmsPreferredPriceControl.style.display, '');
|
||||
assert.equal(api.rowPhoneSmsReuseControl.style.display, '');
|
||||
assert.equal(api.rowFiveSimOperator.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsCurrentNumber.style.display, '');
|
||||
assert.equal(api.rowHeroSmsCurrentCountdown.style.display, '');
|
||||
@@ -913,6 +1036,8 @@ return {
|
||||
assert.equal(api.rowFiveSimCountryFallback.style.display, '');
|
||||
assert.equal(api.rowFiveSimOperator.style.display, '');
|
||||
assert.equal(api.rowFiveSimProduct.style.display, '');
|
||||
assert.equal(api.rowPhoneSmsPreferredPriceControl.style.display, 'none');
|
||||
assert.equal(api.rowPhoneSmsReuseControl.style.display, 'none');
|
||||
|
||||
api.setSelectedPhoneSmsProvider('nexsms');
|
||||
api.updatePhoneVerificationSettingsUI();
|
||||
@@ -920,6 +1045,33 @@ return {
|
||||
assert.equal(api.rowNexSmsCountry.style.display, '');
|
||||
assert.equal(api.rowNexSmsCountryFallback.style.display, '');
|
||||
assert.equal(api.rowNexSmsServiceCode.style.display, '');
|
||||
assert.equal(api.rowHeroSmsMaxPrice.style.display, 'none');
|
||||
assert.equal(api.rowFiveSimOperator.style.display, 'none');
|
||||
|
||||
api.setSelectedPhoneSmsProvider('madao');
|
||||
api.selectMaDaoMode.value = 'routing_plan';
|
||||
api.updatePhoneVerificationSettingsUI();
|
||||
assert.equal(api.rowMaDaoBaseUrl.style.display, '');
|
||||
assert.equal(api.rowMaDaoHttpSecret.style.display, '');
|
||||
assert.equal(api.rowMaDaoMode.style.display, '');
|
||||
assert.equal(api.rowMaDaoRoutingPlanId.style.display, '');
|
||||
assert.equal(api.rowMaDaoProviderId.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoCountry.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoPriceRange.style.display, 'none');
|
||||
|
||||
api.selectMaDaoMode.value = 'direct';
|
||||
api.updatePhoneVerificationSettingsUI();
|
||||
assert.equal(api.rowMaDaoBaseUrl.style.display, '');
|
||||
assert.equal(api.rowMaDaoHttpSecret.style.display, '');
|
||||
assert.equal(api.rowMaDaoMode.style.display, '');
|
||||
assert.equal(api.rowMaDaoRoutingPlanId.style.display, 'none');
|
||||
assert.equal(api.rowMaDaoProviderId.style.display, '');
|
||||
assert.equal(api.rowMaDaoCountry.style.display, '');
|
||||
assert.equal(api.rowMaDaoAutoPickCountry.style.display, '');
|
||||
assert.equal(api.rowMaDaoReusePhone.style.display, '');
|
||||
assert.equal(api.rowMaDaoPriceRange.style.display, '');
|
||||
});
|
||||
|
||||
test('collectSettingsPayload keeps local helper sync enabled while persisting sms toggle state', () => {
|
||||
@@ -1005,6 +1157,16 @@ const inputFiveSimOperator = { value: 'any' };
|
||||
const inputFiveSimProduct = { value: 'openai' };
|
||||
const inputNexSmsApiKey = { value: 'nex-key' };
|
||||
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 inputMaDaoAutoPickCountry = { checked: false };
|
||||
const inputMaDaoReusePhone = { checked: true };
|
||||
const inputMaDaoMinPrice = { value: '0.02' };
|
||||
const inputMaDaoMaxPrice = { value: '0.2' };
|
||||
const inputHeroSmsReuseEnabled = { checked: true };
|
||||
const selectHeroSmsAcquirePriority = { value: 'price' };
|
||||
const selectHeroSmsOperator = { value: 'AIS!!' };
|
||||
@@ -1052,9 +1214,15 @@ const PHONE_REPLACEMENT_LIMIT_MAX = 20;
|
||||
const DEFAULT_HERO_SMS_COUNTRY_ID = 52;
|
||||
const DEFAULT_HERO_SMS_COUNTRY_LABEL = 'Thailand';
|
||||
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_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 SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||
@@ -1093,6 +1261,12 @@ function normalizePlusAccountAccessStrategy(value = '') { return String(value ||
|
||||
function resolvePlusAccountAccessStrategyForTarget(value = '') { return normalizePlusAccountAccessStrategy(value); }
|
||||
${extractFunction('normalizePhoneSmsProvider')}
|
||||
${extractFunction('normalizePhoneSmsProviderValue')}
|
||||
${extractFunction('normalizeMaDaoBaseUrlValue')}
|
||||
${extractFunction('normalizeMaDaoModeValue')}
|
||||
${extractFunction('normalizeMaDaoIdentifierValue')}
|
||||
${extractFunction('normalizeMaDaoProviderIdValue')}
|
||||
${extractFunction('normalizeMaDaoCountry')}
|
||||
${extractFunction('normalizeMaDaoPriceValue')}
|
||||
${extractFunction('normalizeFiveSimCountryCode')}
|
||||
${extractFunction('normalizeFiveSimCountryOrderValue')}
|
||||
${extractFunction('normalizeFiveSimProductValue')}
|
||||
@@ -1157,6 +1331,16 @@ return { collectSettingsPayload };
|
||||
assert.equal(payload.nexSmsApiKey, 'nex-key');
|
||||
assert.deepStrictEqual(payload.nexSmsCountryOrder, [1]);
|
||||
assert.equal(payload.nexSmsServiceCode, 'ot');
|
||||
assert.equal(payload.madaoBaseUrl, 'http://127.0.0.1:7822');
|
||||
assert.equal(payload.madaoHttpSecret, 'madao-secret');
|
||||
assert.equal(payload.madaoMode, 'direct');
|
||||
assert.equal(payload.madaoRoutingPlanId, 'plan-1');
|
||||
assert.equal(payload.madaoProviderId, 'localprovider');
|
||||
assert.equal(payload.madaoCountry, 'TH');
|
||||
assert.equal(payload.madaoAutoPickCountry, false);
|
||||
assert.equal(payload.madaoReusePhone, true);
|
||||
assert.equal(payload.madaoMinPrice, '0.02');
|
||||
assert.equal(payload.madaoMaxPrice, '0.2');
|
||||
assert.equal(payload.phoneSmsReuseEnabled, false);
|
||||
assert.equal(payload.heroSmsReuseEnabled, false);
|
||||
assert.equal(payload.freePhoneReuseEnabled, false);
|
||||
@@ -1192,8 +1376,22 @@ test('switchPhoneSmsProvider saves API keys independently when the select value
|
||||
const api = new Function(`
|
||||
let latestState = {
|
||||
phoneSmsProvider: 'hero-sms',
|
||||
phoneSmsProviderOrder: ['hero-sms', '5sim'],
|
||||
heroSmsApiKey: 'hero-old',
|
||||
fiveSimApiKey: 'five-old',
|
||||
nexSmsApiKey: 'nex-old',
|
||||
nexSmsCountryOrder: [1],
|
||||
nexSmsServiceCode: 'ot',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822',
|
||||
madaoHttpSecret: 'madao-old-secret',
|
||||
madaoMode: 'routing_plan',
|
||||
madaoRoutingPlanId: 'plan-old',
|
||||
madaoProviderId: 'provider-old',
|
||||
madaoCountry: 'TH',
|
||||
madaoAutoPickCountry: true,
|
||||
madaoReusePhone: true,
|
||||
madaoMinPrice: '0.01',
|
||||
madaoMaxPrice: '0.09',
|
||||
heroSmsMinPrice: '0.04',
|
||||
heroSmsMaxPrice: '0.11',
|
||||
fiveSimMinPrice: '0.88',
|
||||
@@ -1207,10 +1405,21 @@ let latestState = {
|
||||
fiveSimOperator: 'any',
|
||||
};
|
||||
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_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms', 'madao'];
|
||||
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 DEFAULT_HERO_SMS_OPERATOR = 'any';
|
||||
const DEFAULT_HERO_SMS_COUNTRY_ID = 52;
|
||||
const DEFAULT_HERO_SMS_COUNTRY_LABEL = 'Thailand';
|
||||
@@ -1218,25 +1427,64 @@ const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(['indonesia', 'thailand', 'vie
|
||||
const HERO_SMS_SUPPORTED_COUNTRY_ID_SET = new Set(['6', '52', '10']);
|
||||
const selectPhoneSmsProvider = { value: 'hero-sms', dataset: { activeProvider: 'hero-sms' } };
|
||||
const inputHeroSmsApiKey = { value: 'hero-live' };
|
||||
const inputFiveSimApiKey = { value: 'five-old' };
|
||||
const inputNexSmsApiKey = { value: 'nex-live' };
|
||||
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 inputMaDaoAutoPickCountry = { checked: false };
|
||||
const inputMaDaoReusePhone = { checked: false };
|
||||
const inputMaDaoMinPrice = { value: '0.02' };
|
||||
const inputMaDaoMaxPrice = { value: '0.2' };
|
||||
const inputHeroSmsMinPrice = { value: '0.03' };
|
||||
const inputHeroSmsMaxPrice = { value: '0.22' };
|
||||
const inputFiveSimOperator = { value: 'any' };
|
||||
const inputFiveSimProduct = { value: 'openai' };
|
||||
const selectHeroSmsOperator = { value: 'ais', options: [{ value: 'any' }, { value: 'ais' }] };
|
||||
const inputHeroSmsPreferredPrice = { value: '' };
|
||||
const displayHeroSmsPriceTiers = { textContent: '' };
|
||||
const displayPhoneSmsBalance = { textContent: '' };
|
||||
const rowHeroSmsPriceTiers = { style: { display: '' } };
|
||||
let heroSmsCountrySelectionOrder = [];
|
||||
let phoneSmsProviderOrderSelection = ['hero-sms', '5sim'];
|
||||
let lastPhoneSmsProviderBeforeChange = null;
|
||||
let savedPayload = null;
|
||||
|
||||
${extractFunction('normalizePhoneSmsProvider')}
|
||||
${extractFunction('normalizePhoneSmsProviderValue')}
|
||||
function getPhoneSmsProviderCount() { return DEFAULT_PHONE_SMS_PROVIDER_ORDER.length; }
|
||||
${extractFunction('normalizePhoneSmsProviderOrderValue')}
|
||||
${extractFunction('setPhoneSmsProviderSelectValue')}
|
||||
${extractFunction('getLastAppliedPhoneSmsProvider')}
|
||||
function getSelectedPhoneSmsProvider() { return normalizePhoneSmsProvider(selectPhoneSmsProvider?.value || latestState?.phoneSmsProvider); }
|
||||
function applyPhoneSmsProviderOrderSelection(order = [], options = {}) {
|
||||
phoneSmsProviderOrderSelection = normalizePhoneSmsProviderOrderValue(order, []);
|
||||
if (options.syncProvider && phoneSmsProviderOrderSelection.length) {
|
||||
selectPhoneSmsProvider.value = phoneSmsProviderOrderSelection[0];
|
||||
}
|
||||
return [...phoneSmsProviderOrderSelection];
|
||||
}
|
||||
${extractFunction('setPhoneSmsProviderOrderPrimary')}
|
||||
${extractFunction('normalizeFiveSimCountryId')}
|
||||
${extractFunction('normalizeFiveSimCountryLabel')}
|
||||
${extractFunction('normalizeFiveSimCountryCode')}
|
||||
${extractFunction('normalizeFiveSimOperator')}
|
||||
${extractFunction('normalizeFiveSimProductValue')}
|
||||
${extractFunction('normalizeFiveSimMaxPriceValue')}
|
||||
${extractFunction('normalizeHeroSmsMaxPriceValue')}
|
||||
${extractFunction('normalizeNexSmsCountryIdValue')}
|
||||
${extractFunction('normalizeNexSmsCountryOrderValue')}
|
||||
${extractFunction('normalizeNexSmsServiceCodeValue')}
|
||||
${extractFunction('normalizeMaDaoBaseUrlValue')}
|
||||
${extractFunction('normalizeMaDaoModeValue')}
|
||||
${extractFunction('normalizeMaDaoIdentifierValue')}
|
||||
${extractFunction('normalizeMaDaoProviderIdValue')}
|
||||
${extractFunction('normalizeMaDaoCountry')}
|
||||
${extractFunction('normalizeMaDaoPriceValue')}
|
||||
${extractFunction('normalizePhoneSmsMinPriceValue')}
|
||||
${extractFunction('normalizePhoneSmsMaxPriceValue')}
|
||||
${extractFunction('normalizeHeroSmsCountryId')}
|
||||
@@ -1254,11 +1502,18 @@ function syncHeroSmsFallbackSelectionOrderFromSelect() {
|
||||
? [{ id: 'vietnam', label: '越南 (Vietnam)' }]
|
||||
: [{ id: 52, label: 'Thailand' }];
|
||||
}
|
||||
function getSelectedNexSmsCountries() { return [{ id: 1, label: 'Country #1' }]; }
|
||||
function syncLatestState(patch) { latestState = { ...latestState, ...patch }; }
|
||||
function loadHeroSmsCountries() { return Promise.resolve(); }
|
||||
function loadFiveSimCountries() { return Promise.resolve(); }
|
||||
function loadNexSmsCountries() { return Promise.resolve(); }
|
||||
function applyHeroSmsFallbackSelection() {}
|
||||
function applyFiveSimCountrySelection() {}
|
||||
function applyNexSmsCountrySelection() {}
|
||||
function setHeroSmsOperatorSelectValue(operator = latestState?.heroSmsOperator) { selectHeroSmsOperator.value = normalizeHeroSmsOperatorValue(operator); }
|
||||
function refreshHeroSmsOperatorOptions() { return Promise.resolve(); }
|
||||
${extractFunction('buildPhoneSmsProviderStatePatch')}
|
||||
${extractFunction('applyPhoneSmsProviderFieldsToInputs')}
|
||||
function updatePhoneVerificationSettingsUI() {}
|
||||
function markSettingsDirty() {}
|
||||
function saveSettings() { savedPayload = { ...latestState }; return Promise.resolve(); }
|
||||
@@ -1268,6 +1523,7 @@ ${extractFunction('switchPhoneSmsProvider')}
|
||||
return {
|
||||
selectPhoneSmsProvider,
|
||||
inputHeroSmsApiKey,
|
||||
inputFiveSimApiKey,
|
||||
inputHeroSmsMinPrice,
|
||||
get latestState() { return latestState; },
|
||||
get savedPayload() { return savedPayload; },
|
||||
@@ -1284,11 +1540,12 @@ return {
|
||||
assert.equal(api.latestState.fiveSimApiKey, 'five-old');
|
||||
assert.equal(api.latestState.heroSmsMinPrice, '0.03');
|
||||
assert.equal(api.latestState.fiveSimMinPrice, '0.88');
|
||||
assert.equal(api.inputHeroSmsApiKey.value, 'five-old');
|
||||
assert.equal(api.inputHeroSmsApiKey.value, 'hero-live');
|
||||
assert.equal(api.inputFiveSimApiKey.value, 'five-old');
|
||||
assert.equal(api.inputHeroSmsMinPrice.value, '0.88');
|
||||
assert.equal(api.selectPhoneSmsProvider.dataset.activeProvider, '5sim');
|
||||
|
||||
api.inputHeroSmsApiKey.value = 'five-live';
|
||||
api.inputFiveSimApiKey.value = 'five-live';
|
||||
api.selectPhoneSmsProvider.value = 'hero-sms';
|
||||
await api.switchPhoneSmsProvider(api.selectPhoneSmsProvider.value);
|
||||
|
||||
@@ -1355,6 +1612,7 @@ test('previewHeroSmsPriceTiers prefers 5sim products price for buy-compatible an
|
||||
let latestState = { phoneSmsProvider: '5sim', fiveSimOperator: 'any' };
|
||||
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
||||
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
||||
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
||||
const DEFAULT_FIVE_SIM_COUNTRY_ID = 'vietnam';
|
||||
const DEFAULT_FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)';
|
||||
const DEFAULT_FIVE_SIM_OPERATOR = 'any';
|
||||
|
||||
Reference in New Issue
Block a user