feat: add SMS Bower phone SMS provider
This commit is contained in:
@@ -23,6 +23,7 @@ importScripts(
|
|||||||
'phone-sms/providers/five-sim.js',
|
'phone-sms/providers/five-sim.js',
|
||||||
'phone-sms/providers/nexsms.js',
|
'phone-sms/providers/nexsms.js',
|
||||||
'phone-sms/providers/madao.js',
|
'phone-sms/providers/madao.js',
|
||||||
|
'phone-sms/providers/sms-bower.js',
|
||||||
'phone-sms/providers/registry.js',
|
'phone-sms/providers/registry.js',
|
||||||
'background/phone-verification-flow.js',
|
'background/phone-verification-flow.js',
|
||||||
'background/account-run-history.js',
|
'background/account-run-history.js',
|
||||||
@@ -702,12 +703,14 @@ const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO;
|
|||||||
const PHONE_SMS_PROVIDER_FIVE_SIM = PHONE_SMS_PROVIDER_5SIM;
|
const PHONE_SMS_PROVIDER_FIVE_SIM = PHONE_SMS_PROVIDER_5SIM;
|
||||||
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
||||||
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
||||||
|
const PHONE_SMS_PROVIDER_SMS_BOWER = 'sms-bower';
|
||||||
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO;
|
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO;
|
||||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([
|
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([
|
||||||
PHONE_SMS_PROVIDER_HERO,
|
PHONE_SMS_PROVIDER_HERO,
|
||||||
PHONE_SMS_PROVIDER_5SIM,
|
PHONE_SMS_PROVIDER_5SIM,
|
||||||
PHONE_SMS_PROVIDER_NEXSMS,
|
PHONE_SMS_PROVIDER_NEXSMS,
|
||||||
PHONE_SMS_PROVIDER_MADAO,
|
PHONE_SMS_PROVIDER_MADAO,
|
||||||
|
PHONE_SMS_PROVIDER_SMS_BOWER,
|
||||||
]);
|
]);
|
||||||
const DEFAULT_FIVE_SIM_BASE_URL = 'https://5sim.net/v1';
|
const DEFAULT_FIVE_SIM_BASE_URL = 'https://5sim.net/v1';
|
||||||
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
|
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
|
||||||
@@ -716,6 +719,8 @@ const DEFAULT_FIVE_SIM_COUNTRY_ORDER = Object.freeze(['thailand']);
|
|||||||
const DEFAULT_NEX_SMS_BASE_URL = 'https://api.nexsms.net';
|
const DEFAULT_NEX_SMS_BASE_URL = 'https://api.nexsms.net';
|
||||||
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
|
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
|
||||||
const DEFAULT_NEX_SMS_COUNTRY_ORDER = Object.freeze([1]);
|
const DEFAULT_NEX_SMS_COUNTRY_ORDER = Object.freeze([1]);
|
||||||
|
const DEFAULT_SMS_BOWER_SERVICE_CODE = 'ot';
|
||||||
|
const DEFAULT_SMS_BOWER_COUNTRY_ORDER = Object.freeze([52]);
|
||||||
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||||
const DEFAULT_MADAO_MODE = 'routing_plan';
|
const DEFAULT_MADAO_MODE = 'routing_plan';
|
||||||
const DEFAULT_HERO_SMS_REUSE_ENABLED = true;
|
const DEFAULT_HERO_SMS_REUSE_ENABLED = true;
|
||||||
@@ -1447,6 +1452,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
nexSmsApiKey: '',
|
nexSmsApiKey: '',
|
||||||
nexSmsCountryOrder: [...DEFAULT_NEX_SMS_COUNTRY_ORDER],
|
nexSmsCountryOrder: [...DEFAULT_NEX_SMS_COUNTRY_ORDER],
|
||||||
nexSmsServiceCode: DEFAULT_NEX_SMS_SERVICE_CODE,
|
nexSmsServiceCode: DEFAULT_NEX_SMS_SERVICE_CODE,
|
||||||
|
smsBowerApiKey: '',
|
||||||
madaoBaseUrl: DEFAULT_MADAO_BASE_URL,
|
madaoBaseUrl: DEFAULT_MADAO_BASE_URL,
|
||||||
madaoHttpSecret: '',
|
madaoHttpSecret: '',
|
||||||
madaoMode: DEFAULT_MADAO_MODE,
|
madaoMode: DEFAULT_MADAO_MODE,
|
||||||
@@ -1848,6 +1854,9 @@ function normalizePhoneSmsProvider(value = '') {
|
|||||||
if (normalized === PHONE_SMS_PROVIDER_MADAO) {
|
if (normalized === PHONE_SMS_PROVIDER_MADAO) {
|
||||||
return PHONE_SMS_PROVIDER_MADAO;
|
return PHONE_SMS_PROVIDER_MADAO;
|
||||||
}
|
}
|
||||||
|
if (normalized === PHONE_SMS_PROVIDER_SMS_BOWER) {
|
||||||
|
return PHONE_SMS_PROVIDER_SMS_BOWER;
|
||||||
|
}
|
||||||
return PHONE_SMS_PROVIDER_HERO_SMS;
|
return PHONE_SMS_PROVIDER_HERO_SMS;
|
||||||
}
|
}
|
||||||
function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) {
|
function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) {
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
const PROVIDER_FIVE_SIM = '5sim';
|
const PROVIDER_FIVE_SIM = '5sim';
|
||||||
const PROVIDER_NEXSMS = 'nexsms';
|
const PROVIDER_NEXSMS = 'nexsms';
|
||||||
const PROVIDER_MADAO = 'madao';
|
const PROVIDER_MADAO = 'madao';
|
||||||
|
const PROVIDER_SMS_BOWER = 'sms-bower';
|
||||||
const DEFAULT_PROVIDER = PROVIDER_HERO_SMS;
|
const DEFAULT_PROVIDER = PROVIDER_HERO_SMS;
|
||||||
const DEFAULT_PROVIDER_ORDER = Object.freeze([
|
const DEFAULT_PROVIDER_ORDER = Object.freeze([
|
||||||
PROVIDER_HERO_SMS,
|
PROVIDER_HERO_SMS,
|
||||||
PROVIDER_FIVE_SIM,
|
PROVIDER_FIVE_SIM,
|
||||||
PROVIDER_NEXSMS,
|
PROVIDER_NEXSMS,
|
||||||
PROVIDER_MADAO,
|
PROVIDER_MADAO,
|
||||||
|
PROVIDER_SMS_BOWER,
|
||||||
]);
|
]);
|
||||||
const PROVIDER_DEFINITIONS = Object.freeze({
|
const PROVIDER_DEFINITIONS = Object.freeze({
|
||||||
[PROVIDER_HERO_SMS]: Object.freeze({
|
[PROVIDER_HERO_SMS]: Object.freeze({
|
||||||
@@ -34,6 +36,11 @@
|
|||||||
label: 'MaDao',
|
label: 'MaDao',
|
||||||
moduleKey: 'PhoneSmsMaDaoProvider',
|
moduleKey: 'PhoneSmsMaDaoProvider',
|
||||||
}),
|
}),
|
||||||
|
[PROVIDER_SMS_BOWER]: Object.freeze({
|
||||||
|
id: PROVIDER_SMS_BOWER,
|
||||||
|
label: 'SMS Bower',
|
||||||
|
moduleKey: 'PhoneSmsBowerProvider',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
function resolveProviderKey(value = '') {
|
function resolveProviderKey(value = '') {
|
||||||
@@ -133,6 +140,7 @@
|
|||||||
PROVIDER_FIVE_SIM,
|
PROVIDER_FIVE_SIM,
|
||||||
PROVIDER_NEXSMS,
|
PROVIDER_NEXSMS,
|
||||||
PROVIDER_MADAO,
|
PROVIDER_MADAO,
|
||||||
|
PROVIDER_SMS_BOWER,
|
||||||
DEFAULT_PROVIDER,
|
DEFAULT_PROVIDER,
|
||||||
DEFAULT_PROVIDER_ORDER,
|
DEFAULT_PROVIDER_ORDER,
|
||||||
PROVIDER_DEFINITIONS,
|
PROVIDER_DEFINITIONS,
|
||||||
|
|||||||
@@ -0,0 +1,471 @@
|
|||||||
|
// phone-sms/providers/sms-bower.js — SMS Bower 接码平台适配层
|
||||||
|
(function attachSmsBowerProvider(root, factory) {
|
||||||
|
root.PhoneSmsBowerProvider = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createSmsBowerProviderModule() {
|
||||||
|
const PROVIDER_ID = 'sms-bower';
|
||||||
|
const DEFAULT_BASE_URL = 'https://smsbower.page/stubs/handler_api.php';
|
||||||
|
const DEFAULT_SERVICE_CODE = 'ot';
|
||||||
|
const DEFAULT_SERVICE_LABEL = 'OpenAI';
|
||||||
|
const DEFAULT_COUNTRY_ID = 52;
|
||||||
|
const DEFAULT_COUNTRY_LABEL = 'Thailand';
|
||||||
|
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 MAX_PRICE_CANDIDATES = 8;
|
||||||
|
|
||||||
|
function normalizeText(value = '', fallback = '') {
|
||||||
|
return String(value || '').trim() || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSmsBowerCountryId(value, fallback = DEFAULT_COUNTRY_ID) {
|
||||||
|
const parsed = Math.floor(Number(value));
|
||||||
|
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
||||||
|
const fallbackParsed = Math.floor(Number(fallback));
|
||||||
|
return Number.isFinite(fallbackParsed) && fallbackParsed >= 0 ? fallbackParsed : DEFAULT_COUNTRY_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSmsBowerCountryLabel(value = '', fallback = DEFAULT_COUNTRY_LABEL) {
|
||||||
|
return normalizeText(value, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCountryKey(value) {
|
||||||
|
const countryId = normalizeSmsBowerCountryId(value, -1);
|
||||||
|
return countryId >= 0 ? String(countryId) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSmsBowerServiceCode(value = '', fallback = DEFAULT_SERVICE_CODE) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '');
|
||||||
|
if (normalized) return normalized;
|
||||||
|
const fallbackNormalized = String(fallback || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '');
|
||||||
|
return fallbackNormalized || DEFAULT_SERVICE_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePriceLimit(value) {
|
||||||
|
if (value === undefined || value === null || String(value).trim() === '') return null;
|
||||||
|
const parsed = Number(value);
|
||||||
|
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
||||||
|
return Math.round(parsed * 10000) / 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSmsBowerMaxPrice(value = '') {
|
||||||
|
const normalized = normalizePriceLimit(value);
|
||||||
|
return normalized === null ? '' : String(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCsvIds(value = '') {
|
||||||
|
const source = Array.isArray(value) ? value : String(value || '').split(/[\r\n,,;;]+/);
|
||||||
|
const seen = new Set();
|
||||||
|
return source
|
||||||
|
.map((entry) => String(entry || '').trim().replace(/[^0-9a-zA-Z_-]+/g, ''))
|
||||||
|
.filter((entry) => {
|
||||||
|
if (!entry || seen.has(entry)) return false;
|
||||||
|
seen.add(entry);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCountryOrder(value = []) {
|
||||||
|
const source = Array.isArray(value)
|
||||||
|
? value
|
||||||
|
: String(value || '').split(/[\r\n,,;;]+/).map((entry) => String(entry || '').trim()).filter(Boolean);
|
||||||
|
const seen = new Set();
|
||||||
|
const normalized = [];
|
||||||
|
source.forEach((entry) => {
|
||||||
|
let id = -1;
|
||||||
|
let label = '';
|
||||||
|
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
||||||
|
id = normalizeSmsBowerCountryId(entry.id ?? entry.countryId, -1);
|
||||||
|
label = normalizeText(entry.label ?? entry.countryLabel, '');
|
||||||
|
} else {
|
||||||
|
const text = String(entry || '').trim();
|
||||||
|
const structured = text.match(/^(\d+)\s*(?:[:|/-]\s*(.+))?$/);
|
||||||
|
id = normalizeSmsBowerCountryId(structured?.[1] || text, -1);
|
||||||
|
label = normalizeText(structured?.[2], '');
|
||||||
|
}
|
||||||
|
if (id < 0 || seen.has(id)) return;
|
||||||
|
seen.add(id);
|
||||||
|
normalized.push({ id, label: label || `Country #${id}` });
|
||||||
|
});
|
||||||
|
return normalized.slice(0, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBaseUrl(value = '') {
|
||||||
|
const trimmed = String(value || '').trim() || DEFAULT_BASE_URL;
|
||||||
|
try {
|
||||||
|
const parsed = new URL(trimmed);
|
||||||
|
return parsed.toString();
|
||||||
|
} catch {
|
||||||
|
return DEFAULT_BASE_URL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePayload(text) {
|
||||||
|
const trimmed = String(text || '').trim();
|
||||||
|
if (!trimmed) return '';
|
||||||
|
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
||||||
|
try { return JSON.parse(trimmed); } catch { return trimmed; }
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function describePayload(raw) {
|
||||||
|
if (typeof raw === 'string') return raw.trim();
|
||||||
|
if (raw && typeof raw === 'object') {
|
||||||
|
const direct = normalizeText(raw.message || raw.msg || raw.error || raw.title || raw.status, '');
|
||||||
|
if (direct) return direct;
|
||||||
|
try { return JSON.stringify(raw); } catch { return String(raw); }
|
||||||
|
}
|
||||||
|
return String(raw || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveConfig(state = {}, deps = {}) {
|
||||||
|
return {
|
||||||
|
apiKey: normalizeText(state.smsBowerApiKey),
|
||||||
|
baseUrl: normalizeBaseUrl(state.smsBowerBaseUrl || DEFAULT_BASE_URL),
|
||||||
|
serviceCode: normalizeSmsBowerServiceCode(state.smsBowerServiceCode, DEFAULT_SERVICE_CODE),
|
||||||
|
fetchImpl: deps.fetchImpl || (typeof fetch === 'function' ? fetch.bind(globalThis) : null),
|
||||||
|
requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUrl(config = {}, query = {}) {
|
||||||
|
const url = new URL(config.baseUrl || DEFAULT_BASE_URL);
|
||||||
|
url.searchParams.set('api_key', config.apiKey);
|
||||||
|
Object.entries(query || {}).forEach(([key, value]) => {
|
||||||
|
if (value === undefined || value === null || value === '') return;
|
||||||
|
url.searchParams.set(key, String(value));
|
||||||
|
});
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPayload(config = {}, query = {}, actionLabel = 'SMS Bower 请求') {
|
||||||
|
if (!config.fetchImpl) throw new Error('SMS Bower 网络请求实现不可用。');
|
||||||
|
if (!config.apiKey) throw new Error('SMS Bower API Key 缺失,请先在侧边栏保存接码 API Key。');
|
||||||
|
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
||||||
|
const timeoutId = controller ? setTimeout(() => controller.abort(), Number(config.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS) : null;
|
||||||
|
try {
|
||||||
|
const response = await config.fetchImpl(buildUrl(config, query), {
|
||||||
|
method: 'GET',
|
||||||
|
signal: controller?.signal,
|
||||||
|
headers: { Accept: 'application/json, text/plain, */*' },
|
||||||
|
});
|
||||||
|
const text = await response.text();
|
||||||
|
const payload = parsePayload(text);
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = new Error(`${actionLabel}失败:${describePayload(payload) || response.status}`);
|
||||||
|
error.payload = payload;
|
||||||
|
error.status = response.status;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const message = describePayload(payload);
|
||||||
|
if (/^(BAD_KEY|BAD_ACTION|BAD_SERVICE|BAD_STATUS|NO_ACTIVATION|EARLY_CANCEL_DENIED|BAD_COUNTRY)\b/i.test(message)) {
|
||||||
|
const error = new Error(`${actionLabel}失败:${message}`);
|
||||||
|
error.payload = payload;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.name === 'AbortError') throw new Error(`${actionLabel}超时。`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCountryCandidates(state = {}) {
|
||||||
|
const candidates = normalizeCountryOrder(state.smsBowerCountryOrder);
|
||||||
|
if (candidates.length) return candidates;
|
||||||
|
return [{
|
||||||
|
id: normalizeSmsBowerCountryId(state.smsBowerCountryId, DEFAULT_COUNTRY_ID),
|
||||||
|
label: normalizeSmsBowerCountryLabel(state.smsBowerCountryLabel, DEFAULT_COUNTRY_LABEL),
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCountryLabel(state = {}, countryId = DEFAULT_COUNTRY_ID) {
|
||||||
|
const key = normalizeCountryKey(countryId);
|
||||||
|
const matched = resolveCountryCandidates(state).find((entry) => normalizeCountryKey(entry.id) === key);
|
||||||
|
return matched?.label || (key ? `Country #${key}` : DEFAULT_COUNTRY_LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePriceRange(state = {}) {
|
||||||
|
const minPriceLimit = normalizePriceLimit(state.smsBowerMinPrice);
|
||||||
|
const maxPriceLimit = normalizePriceLimit(state.smsBowerMaxPrice);
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchBalance(state = {}, deps = {}) {
|
||||||
|
const config = resolveConfig(state, deps);
|
||||||
|
const payload = await fetchPayload(config, { action: 'getBalance' }, 'SMS Bower 查询余额');
|
||||||
|
const matched = describePayload(payload).match(/ACCESS_BALANCE\s*:\s*([\d.]+)/i);
|
||||||
|
return { balance: matched ? Number(matched[1]) : null, raw: payload };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPrices(state = {}, countryConfig = {}, deps = {}) {
|
||||||
|
const config = resolveConfig(state, deps);
|
||||||
|
return fetchPayload(config, {
|
||||||
|
action: 'getPrices',
|
||||||
|
service: config.serviceCode,
|
||||||
|
country: normalizeSmsBowerCountryId(countryConfig?.id ?? state.smsBowerCountryId, DEFAULT_COUNTRY_ID),
|
||||||
|
}, 'SMS Bower 查询价格');
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectPriceEntries(payload, entries = []) {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
payload.forEach((entry) => collectPriceEntries(entry, entries));
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
if (!payload || typeof payload !== 'object') return entries;
|
||||||
|
const cost = Number(payload.cost ?? payload.price ?? payload.Price);
|
||||||
|
if (Number.isFinite(cost) && cost >= 0) {
|
||||||
|
const count = Number(payload.count ?? payload.qty ?? payload.Qty);
|
||||||
|
entries.push({ cost, count: Number.isFinite(count) ? count : 0, inStock: !Number.isFinite(count) || count > 0 });
|
||||||
|
}
|
||||||
|
Object.values(payload).forEach((entry) => collectPriceEntries(entry, entries));
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeActivation(payload, fallback = {}) {
|
||||||
|
if (typeof payload === 'string') {
|
||||||
|
const matched = payload.match(/^ACCESS_NUMBER\s*:\s*([^:]+)\s*:\s*(.+)$/i);
|
||||||
|
if (matched) {
|
||||||
|
payload = { activationId: matched[1], phoneNumber: matched[2] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const source = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {};
|
||||||
|
const activationId = normalizeText(source.activationId ?? source.id ?? fallback.activationId);
|
||||||
|
const phoneNumber = normalizeText(source.phoneNumber ?? source.phone ?? fallback.phoneNumber);
|
||||||
|
if (!activationId || !phoneNumber) return null;
|
||||||
|
const countryId = normalizeSmsBowerCountryId(source.countryId ?? source.country ?? fallback.countryId, DEFAULT_COUNTRY_ID);
|
||||||
|
return {
|
||||||
|
activationId,
|
||||||
|
phoneNumber,
|
||||||
|
provider: PROVIDER_ID,
|
||||||
|
serviceCode: normalizeSmsBowerServiceCode(source.serviceCode || fallback.serviceCode || DEFAULT_SERVICE_CODE),
|
||||||
|
countryId,
|
||||||
|
countryCode: countryId,
|
||||||
|
countryLabel: normalizeSmsBowerCountryLabel(source.countryLabel || fallback.countryLabel, `Country #${countryId}`),
|
||||||
|
successfulUses: Math.max(0, Math.floor(Number(source.successfulUses ?? fallback.successfulUses) || 0)),
|
||||||
|
maxUses: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveActivationCountry(activation = {}, state = {}) {
|
||||||
|
const normalized = normalizeActivation(activation) || (activation && typeof activation === 'object' ? activation : {});
|
||||||
|
const id = normalizeSmsBowerCountryId(normalized.countryId ?? normalized.countryCode ?? normalized.country, DEFAULT_COUNTRY_ID);
|
||||||
|
return { id, label: resolveCountryLabel(state, id) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActivationCountryKey(activation = {}) {
|
||||||
|
return normalizeCountryKey(activation?.countryCode ?? activation?.countryId ?? activation?.country);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActivationPrice(activation = {}) {
|
||||||
|
const price = Number(activation?.selectedPrice ?? activation?.price ?? activation?.maxPrice);
|
||||||
|
return Number.isFinite(price) && price >= 0 ? price : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAcquireQuery(state = {}, config = resolveConfig(state), countryConfig = {}) {
|
||||||
|
const range = resolvePriceRange(state);
|
||||||
|
if (range.invalidRange) {
|
||||||
|
throw new Error(`SMS Bower 价格区间无效:最低购买价 ${range.minPriceLimit} 高于价格上限 ${range.maxPriceLimit}。`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
action: 'getNumber',
|
||||||
|
service: config.serviceCode,
|
||||||
|
country: normalizeSmsBowerCountryId(countryConfig.id, DEFAULT_COUNTRY_ID),
|
||||||
|
maxPrice: range.maxPriceLimit,
|
||||||
|
minPrice: range.minPriceLimit,
|
||||||
|
providerIds: normalizeCsvIds(state.smsBowerProviderIds),
|
||||||
|
exceptProviderIds: normalizeCsvIds(state.smsBowerExceptProviderIds),
|
||||||
|
phoneException: normalizeCsvIds(state.smsBowerPhoneException),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestActivation(state = {}, options = {}, deps = {}) {
|
||||||
|
const config = resolveConfig(state, deps);
|
||||||
|
const blocked = new Set((Array.isArray(options.blockedCountryIds) ? options.blockedCountryIds : []).map((v) => normalizeCountryKey(v)).filter(Boolean));
|
||||||
|
const candidates = resolveCountryCandidates(state).filter((entry) => !blocked.has(normalizeCountryKey(entry.id)));
|
||||||
|
const countryList = candidates.length ? candidates : resolveCountryCandidates(state);
|
||||||
|
const errors = [];
|
||||||
|
for (const countryConfig of countryList) {
|
||||||
|
try {
|
||||||
|
const payload = await fetchPayload(config, buildAcquireQuery(state, config, countryConfig), `SMS Bower 购买手机号(${countryConfig.label || countryConfig.id})`);
|
||||||
|
const activation = normalizeActivation(payload, {
|
||||||
|
countryId: countryConfig.id,
|
||||||
|
countryLabel: countryConfig.label,
|
||||||
|
serviceCode: config.serviceCode,
|
||||||
|
});
|
||||||
|
if (activation) return activation;
|
||||||
|
throw new Error(`SMS Bower 购买手机号返回不可用响应:${describePayload(payload) || '空响应'}`);
|
||||||
|
} catch (error) {
|
||||||
|
errors.push(error.message || String(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`SMS Bower 购买手机号失败:${errors.join(' | ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setActivationStatus(state = {}, activation, status, actionLabel, deps = {}) {
|
||||||
|
const normalized = normalizeActivation(activation);
|
||||||
|
if (!normalized) return '';
|
||||||
|
const config = resolveConfig(state, deps);
|
||||||
|
const payload = await fetchPayload(config, {
|
||||||
|
action: 'setStatus',
|
||||||
|
status,
|
||||||
|
id: normalized.activationId,
|
||||||
|
}, actionLabel);
|
||||||
|
return describePayload(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finishActivation(state = {}, activation, deps = {}) {
|
||||||
|
return setActivationStatus(state, activation, 6, 'SMS Bower 完成订单', deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cancelActivation(state = {}, activation, deps = {}) {
|
||||||
|
return setActivationStatus(state, activation, 8, 'SMS Bower 取消订单', deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function banActivation(state = {}, activation, deps = {}) {
|
||||||
|
return cancelActivation(state, activation, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestAdditionalSms(state = {}, activation, deps = {}) {
|
||||||
|
return setActivationStatus(state, activation, 3, 'SMS Bower 请求下一条短信', deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reuseActivation(state = {}, activation, deps = {}) {
|
||||||
|
await requestAdditionalSms(state, activation, deps);
|
||||||
|
return normalizeActivation(activation);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rotateActivation(state = {}, activation, options = {}, deps = {}) {
|
||||||
|
await (String(options?.releaseAction || '').trim().toLowerCase() === 'ban'
|
||||||
|
? banActivation(state, activation, deps)
|
||||||
|
: cancelActivation(state, activation, deps));
|
||||||
|
return { currentTicketId: String(activation?.activationId || activation?.id || ''), nextActivation: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractVerificationCode(raw = '') {
|
||||||
|
const trimmed = String(raw || '').trim();
|
||||||
|
if (!trimmed) return '';
|
||||||
|
const matched = trimmed.match(/\b(\d{4,8})\b/);
|
||||||
|
return matched?.[1] || trimmed.replace(/^STATUS_OK\s*:\s*/i, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollActivationCode(state = {}, activation, options = {}, deps = {}) {
|
||||||
|
const normalized = normalizeActivation(activation);
|
||||||
|
if (!normalized) throw new Error('缺少手机号接码订单。');
|
||||||
|
const config = resolveConfig(state, deps);
|
||||||
|
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || DEFAULT_POLL_TIMEOUT_MS);
|
||||||
|
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 fetchPayload(config, { action: 'getStatus', id: normalized.activationId }, 'SMS Bower 查询验证码');
|
||||||
|
pollCount += 1;
|
||||||
|
lastResponse = describePayload(payload);
|
||||||
|
if (typeof options.onStatus === 'function') {
|
||||||
|
await options.onStatus({ activation: normalized, elapsedMs: Date.now() - start, pollCount, statusText: lastResponse, timeoutMs });
|
||||||
|
}
|
||||||
|
if (/^STATUS_OK\s*:/i.test(lastResponse)) return extractVerificationCode(lastResponse);
|
||||||
|
if (/^STATUS_CANCEL\b/i.test(lastResponse)) throw new Error('SMS Bower 查询验证码失败:订单已取消');
|
||||||
|
if (typeof options.onWaitingForCode === 'function') {
|
||||||
|
await options.onWaitingForCode({ activation: normalized, elapsedMs: Date.now() - start, pollCount, statusText: lastResponse, timeoutMs });
|
||||||
|
}
|
||||||
|
await deps.sleepWithStop?.(intervalMs);
|
||||||
|
}
|
||||||
|
throw new Error(`${PHONE_CODE_TIMEOUT_ERROR_PREFIX}等待手机验证码超时。${lastResponse ? ` SMS Bower 最后状态:${lastResponse}` : ''}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createProvider(deps = {}) {
|
||||||
|
const providerDeps = {
|
||||||
|
fetchImpl: deps.fetchImpl,
|
||||||
|
sleepWithStop: deps.sleepWithStop,
|
||||||
|
throwIfStopped: deps.throwIfStopped,
|
||||||
|
requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
||||||
|
};
|
||||||
|
const capabilities = Object.freeze({
|
||||||
|
supportsReusableActivation: true,
|
||||||
|
supportsAutomaticFreeReuse: true,
|
||||||
|
supportsFreeReusePreservation: false,
|
||||||
|
supportsPageResend: false,
|
||||||
|
supportsPageResendProbe: true,
|
||||||
|
requiresCountrySelection: true,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
id: PROVIDER_ID,
|
||||||
|
label: 'SMS Bower',
|
||||||
|
capabilities,
|
||||||
|
defaultCountryId: DEFAULT_COUNTRY_ID,
|
||||||
|
defaultCountryLabel: DEFAULT_COUNTRY_LABEL,
|
||||||
|
defaultProduct: DEFAULT_SERVICE_LABEL,
|
||||||
|
defaultServiceCode: DEFAULT_SERVICE_CODE,
|
||||||
|
normalizeCountryId: normalizeSmsBowerCountryId,
|
||||||
|
normalizeCountryLabel: normalizeSmsBowerCountryLabel,
|
||||||
|
normalizeCountryKey,
|
||||||
|
normalizeServiceCode: normalizeSmsBowerServiceCode,
|
||||||
|
normalizeMaxPrice: normalizeSmsBowerMaxPrice,
|
||||||
|
normalizeCountryFallback: normalizeCountryOrder,
|
||||||
|
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: async (_state, activation) => ({ activation: normalizeActivation(activation), ignoredPhoneCodeKeys: [] }),
|
||||||
|
canPersistReusableActivation: () => false,
|
||||||
|
canPreserveActivationForFreeReuse: () => false,
|
||||||
|
shouldUsePageResend: () => false,
|
||||||
|
shouldProbePageResend: () => true,
|
||||||
|
fetchBalance: (state) => fetchBalance(state, providerDeps),
|
||||||
|
fetchPrices: (state, countryConfig) => fetchPrices(state, countryConfig, providerDeps),
|
||||||
|
resolvePriceRange,
|
||||||
|
formatPriceRangeText,
|
||||||
|
collectPriceEntries,
|
||||||
|
describePayload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
PROVIDER_ID,
|
||||||
|
DEFAULT_COUNTRY_ID,
|
||||||
|
DEFAULT_COUNTRY_LABEL,
|
||||||
|
DEFAULT_SERVICE_CODE,
|
||||||
|
createProvider,
|
||||||
|
normalizeSmsBowerCountryId,
|
||||||
|
normalizeSmsBowerCountryLabel,
|
||||||
|
normalizeCountryKey,
|
||||||
|
normalizeSmsBowerServiceCode,
|
||||||
|
normalizeSmsBowerMaxPrice,
|
||||||
|
normalizeActivation,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1367,6 +1367,7 @@
|
|||||||
<option value="5sim">5sim</option>
|
<option value="5sim">5sim</option>
|
||||||
<option value="nexsms">NexSMS</option>
|
<option value="nexsms">NexSMS</option>
|
||||||
<option value="madao">MaDao</option>
|
<option value="madao">MaDao</option>
|
||||||
|
<option value="sms-bower">SMS Bower</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-phone-sms-provider-order" style="display:none;">
|
<div class="data-row" id="row-phone-sms-provider-order" style="display:none;">
|
||||||
@@ -1377,6 +1378,7 @@
|
|||||||
<option value="5sim" selected>5sim</option>
|
<option value="5sim" selected>5sim</option>
|
||||||
<option value="nexsms" selected>NexSMS</option>
|
<option value="nexsms" selected>NexSMS</option>
|
||||||
<option value="madao" selected>MaDao</option>
|
<option value="madao" selected>MaDao</option>
|
||||||
|
<option value="sms-bower" selected>SMS Bower</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="hero-sms-country-mainline">
|
<div class="hero-sms-country-mainline">
|
||||||
<div id="phone-sms-provider-order-menu-shell" class="hero-sms-country-menu">
|
<div id="phone-sms-provider-order-menu-shell" class="hero-sms-country-menu">
|
||||||
@@ -1534,6 +1536,16 @@
|
|||||||
<input type="text" id="input-nex-sms-service-code" class="data-input mono"
|
<input type="text" id="input-nex-sms-service-code" class="data-input mono"
|
||||||
placeholder="ot" />
|
placeholder="ot" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row" id="row-sms-bower-api-key" style="display:none;">
|
||||||
|
<span class="data-label">SMS Bower</span>
|
||||||
|
<div class="input-with-icon">
|
||||||
|
<input type="password" id="input-sms-bower-api-key" class="data-input data-input-with-icon mono"
|
||||||
|
placeholder="只需填写 SMS Bower API Token" />
|
||||||
|
<button id="btn-toggle-sms-bower-api-key" class="input-icon-btn" type="button"
|
||||||
|
data-password-toggle="input-sms-bower-api-key" data-show-label="显示 SMS Bower Token"
|
||||||
|
data-hide-label="隐藏 SMS Bower Token" aria-label="显示 SMS Bower Token" title="显示 SMS Bower Token"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="data-row" id="row-madao-base-url" style="display:none;">
|
<div class="data-row" id="row-madao-base-url" style="display:none;">
|
||||||
<span class="data-label">MaDao 地址</span>
|
<span class="data-label">MaDao 地址</span>
|
||||||
<input type="text" id="input-madao-base-url" class="data-input mono"
|
<input type="text" id="input-madao-base-url" class="data-input mono"
|
||||||
@@ -1895,6 +1907,7 @@
|
|||||||
<script src="../phone-sms/providers/five-sim.js"></script>
|
<script src="../phone-sms/providers/five-sim.js"></script>
|
||||||
<script src="../phone-sms/providers/nexsms.js"></script>
|
<script src="../phone-sms/providers/nexsms.js"></script>
|
||||||
<script src="../phone-sms/providers/madao.js"></script>
|
<script src="../phone-sms/providers/madao.js"></script>
|
||||||
|
<script src="../phone-sms/providers/sms-bower.js"></script>
|
||||||
<script src="../phone-sms/providers/registry.js"></script>
|
<script src="../phone-sms/providers/registry.js"></script>
|
||||||
<script src="../icloud-utils.js"></script>
|
<script src="../icloud-utils.js"></script>
|
||||||
<script src="../mail-provider-utils.js"></script>
|
<script src="../mail-provider-utils.js"></script>
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ const rowNexSmsApiKey = document.getElementById('row-nex-sms-api-key');
|
|||||||
const rowNexSmsCountry = document.getElementById('row-nex-sms-country');
|
const rowNexSmsCountry = document.getElementById('row-nex-sms-country');
|
||||||
const rowNexSmsCountryFallback = document.getElementById('row-nex-sms-country-fallback');
|
const rowNexSmsCountryFallback = document.getElementById('row-nex-sms-country-fallback');
|
||||||
const rowNexSmsServiceCode = document.getElementById('row-nex-sms-service-code');
|
const rowNexSmsServiceCode = document.getElementById('row-nex-sms-service-code');
|
||||||
|
const rowSmsBowerApiKey = document.getElementById('row-sms-bower-api-key');
|
||||||
const rowMaDaoBaseUrl = document.getElementById('row-madao-base-url');
|
const rowMaDaoBaseUrl = document.getElementById('row-madao-base-url');
|
||||||
const rowMaDaoHttpSecret = document.getElementById('row-madao-http-secret');
|
const rowMaDaoHttpSecret = document.getElementById('row-madao-http-secret');
|
||||||
const rowMaDaoMode = document.getElementById('row-madao-mode');
|
const rowMaDaoMode = document.getElementById('row-madao-mode');
|
||||||
@@ -491,6 +492,8 @@ const inputFiveSimProduct = document.getElementById('input-five-sim-product');
|
|||||||
const inputNexSmsApiKey = document.getElementById('input-nex-sms-api-key');
|
const inputNexSmsApiKey = document.getElementById('input-nex-sms-api-key');
|
||||||
const btnToggleNexSmsApiKey = document.getElementById('btn-toggle-nex-sms-api-key');
|
const btnToggleNexSmsApiKey = document.getElementById('btn-toggle-nex-sms-api-key');
|
||||||
const inputNexSmsServiceCode = document.getElementById('input-nex-sms-service-code');
|
const inputNexSmsServiceCode = document.getElementById('input-nex-sms-service-code');
|
||||||
|
const inputSmsBowerApiKey = document.getElementById('input-sms-bower-api-key');
|
||||||
|
const btnToggleSmsBowerApiKey = document.getElementById('btn-toggle-sms-bower-api-key');
|
||||||
const inputMaDaoBaseUrl = document.getElementById('input-madao-base-url');
|
const inputMaDaoBaseUrl = document.getElementById('input-madao-base-url');
|
||||||
const inputMaDaoHttpSecret = document.getElementById('input-madao-http-secret');
|
const inputMaDaoHttpSecret = document.getElementById('input-madao-http-secret');
|
||||||
const btnToggleMaDaoHttpSecret = document.getElementById('btn-toggle-madao-http-secret');
|
const btnToggleMaDaoHttpSecret = document.getElementById('btn-toggle-madao-http-secret');
|
||||||
@@ -679,18 +682,22 @@ const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
|||||||
const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO;
|
const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO;
|
||||||
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
||||||
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
||||||
|
const PHONE_SMS_PROVIDER_SMS_BOWER = 'sms-bower';
|
||||||
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO;
|
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO;
|
||||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([
|
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([
|
||||||
PHONE_SMS_PROVIDER_HERO,
|
PHONE_SMS_PROVIDER_HERO,
|
||||||
PHONE_SMS_PROVIDER_FIVE_SIM,
|
PHONE_SMS_PROVIDER_FIVE_SIM,
|
||||||
PHONE_SMS_PROVIDER_NEXSMS,
|
PHONE_SMS_PROVIDER_NEXSMS,
|
||||||
PHONE_SMS_PROVIDER_MADAO,
|
PHONE_SMS_PROVIDER_MADAO,
|
||||||
|
PHONE_SMS_PROVIDER_SMS_BOWER,
|
||||||
]);
|
]);
|
||||||
const DEFAULT_FIVE_SIM_COUNTRY_ORDER = Object.freeze(['thailand']);
|
const DEFAULT_FIVE_SIM_COUNTRY_ORDER = Object.freeze(['thailand']);
|
||||||
const DEFAULT_FIVE_SIM_OPERATOR = 'any';
|
const DEFAULT_FIVE_SIM_OPERATOR = 'any';
|
||||||
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
|
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
|
||||||
const DEFAULT_NEX_SMS_COUNTRY_ORDER = Object.freeze([1]);
|
const DEFAULT_NEX_SMS_COUNTRY_ORDER = Object.freeze([1]);
|
||||||
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
|
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
|
||||||
|
const DEFAULT_SMS_BOWER_COUNTRY_ORDER = Object.freeze([52]);
|
||||||
|
const DEFAULT_SMS_BOWER_SERVICE_CODE = 'ot';
|
||||||
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||||
const MADAO_MODE_ROUTING_PLAN = 'routing_plan';
|
const MADAO_MODE_ROUTING_PLAN = 'routing_plan';
|
||||||
const MADAO_MODE_DIRECT = 'direct';
|
const MADAO_MODE_DIRECT = 'direct';
|
||||||
@@ -732,6 +739,11 @@ const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = Object.freeze({
|
|||||||
'rowNexSmsServiceCode',
|
'rowNexSmsServiceCode',
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
|
[PHONE_SMS_PROVIDER_SMS_BOWER]: Object.freeze({
|
||||||
|
rowKeys: Object.freeze([
|
||||||
|
'rowSmsBowerApiKey',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
[PHONE_SMS_PROVIDER_MADAO]: Object.freeze({
|
[PHONE_SMS_PROVIDER_MADAO]: Object.freeze({
|
||||||
rowKeys: Object.freeze([
|
rowKeys: Object.freeze([
|
||||||
'rowMaDaoBaseUrl',
|
'rowMaDaoBaseUrl',
|
||||||
@@ -768,6 +780,7 @@ function getPhoneSmsProviderUiRowMap() {
|
|||||||
rowNexSmsCountry,
|
rowNexSmsCountry,
|
||||||
rowNexSmsCountryFallback,
|
rowNexSmsCountryFallback,
|
||||||
rowNexSmsServiceCode,
|
rowNexSmsServiceCode,
|
||||||
|
rowSmsBowerApiKey,
|
||||||
rowMaDaoBaseUrl,
|
rowMaDaoBaseUrl,
|
||||||
rowMaDaoHttpSecret,
|
rowMaDaoHttpSecret,
|
||||||
rowMaDaoMode,
|
rowMaDaoMode,
|
||||||
@@ -4642,6 +4655,9 @@ function collectSettingsPayload() {
|
|||||||
const nexSmsApiKeyValue = typeof inputNexSmsApiKey !== 'undefined' && inputNexSmsApiKey
|
const nexSmsApiKeyValue = typeof inputNexSmsApiKey !== 'undefined' && inputNexSmsApiKey
|
||||||
? String(inputNexSmsApiKey.value || '')
|
? String(inputNexSmsApiKey.value || '')
|
||||||
: String(latestState?.nexSmsApiKey || '');
|
: String(latestState?.nexSmsApiKey || '');
|
||||||
|
const smsBowerApiKeyValue = typeof inputSmsBowerApiKey !== 'undefined' && inputSmsBowerApiKey
|
||||||
|
? String(inputSmsBowerApiKey.value || '')
|
||||||
|
: String(latestState?.smsBowerApiKey || '');
|
||||||
const maDaoBaseUrlValue = typeof inputMaDaoBaseUrl !== 'undefined' && inputMaDaoBaseUrl
|
const maDaoBaseUrlValue = typeof inputMaDaoBaseUrl !== 'undefined' && inputMaDaoBaseUrl
|
||||||
? normalizeMaDaoBaseUrlSafe(inputMaDaoBaseUrl.value || latestState?.madaoBaseUrl)
|
? normalizeMaDaoBaseUrlSafe(inputMaDaoBaseUrl.value || latestState?.madaoBaseUrl)
|
||||||
: normalizeMaDaoBaseUrlSafe(latestState?.madaoBaseUrl);
|
: normalizeMaDaoBaseUrlSafe(latestState?.madaoBaseUrl);
|
||||||
@@ -5287,6 +5303,7 @@ function collectSettingsPayload() {
|
|||||||
nexSmsApiKey: nexSmsApiKeyValue,
|
nexSmsApiKey: nexSmsApiKeyValue,
|
||||||
nexSmsCountryOrder: nexSmsCountryOrderValue,
|
nexSmsCountryOrder: nexSmsCountryOrderValue,
|
||||||
nexSmsServiceCode: nexSmsServiceCodeValue,
|
nexSmsServiceCode: nexSmsServiceCodeValue,
|
||||||
|
smsBowerApiKey: smsBowerApiKeyValue,
|
||||||
madaoBaseUrl: maDaoBaseUrlValue,
|
madaoBaseUrl: maDaoBaseUrlValue,
|
||||||
madaoHttpSecret: maDaoHttpSecretValue,
|
madaoHttpSecret: maDaoHttpSecretValue,
|
||||||
madaoMode: maDaoModeValue,
|
madaoMode: maDaoModeValue,
|
||||||
@@ -5426,6 +5443,9 @@ function normalizePhoneSmsProvider(value = '') {
|
|||||||
const maDaoProvider = typeof PHONE_SMS_PROVIDER_MADAO !== 'undefined'
|
const maDaoProvider = typeof PHONE_SMS_PROVIDER_MADAO !== 'undefined'
|
||||||
? PHONE_SMS_PROVIDER_MADAO
|
? PHONE_SMS_PROVIDER_MADAO
|
||||||
: 'madao';
|
: 'madao';
|
||||||
|
const smsBowerProvider = typeof PHONE_SMS_PROVIDER_SMS_BOWER !== 'undefined'
|
||||||
|
? PHONE_SMS_PROVIDER_SMS_BOWER
|
||||||
|
: 'sms-bower';
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) {
|
if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) {
|
||||||
return PHONE_SMS_PROVIDER_FIVE_SIM;
|
return PHONE_SMS_PROVIDER_FIVE_SIM;
|
||||||
@@ -5436,6 +5456,9 @@ function normalizePhoneSmsProvider(value = '') {
|
|||||||
if (normalized === maDaoProvider) {
|
if (normalized === maDaoProvider) {
|
||||||
return maDaoProvider;
|
return maDaoProvider;
|
||||||
}
|
}
|
||||||
|
if (normalized === smsBowerProvider) {
|
||||||
|
return smsBowerProvider;
|
||||||
|
}
|
||||||
return PHONE_SMS_PROVIDER_HERO_SMS;
|
return PHONE_SMS_PROVIDER_HERO_SMS;
|
||||||
}
|
}
|
||||||
function setPhoneSmsProviderSelectValue(provider) {
|
function setPhoneSmsProviderSelectValue(provider) {
|
||||||
@@ -5475,6 +5498,9 @@ function getPhoneSmsProviderLabel(provider = getSelectedPhoneSmsProvider()) {
|
|||||||
if (normalizedProvider === PHONE_SMS_PROVIDER_MADAO) {
|
if (normalizedProvider === PHONE_SMS_PROVIDER_MADAO) {
|
||||||
return 'MaDao';
|
return 'MaDao';
|
||||||
}
|
}
|
||||||
|
if (normalizedProvider === PHONE_SMS_PROVIDER_SMS_BOWER) {
|
||||||
|
return 'SMS Bower';
|
||||||
|
}
|
||||||
return 'HeroSMS';
|
return 'HeroSMS';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17772,6 +17798,9 @@ function applyPhoneSmsProviderFieldsToInputs(provider = getSelectedPhoneSmsProvi
|
|||||||
if (typeof inputNexSmsApiKey !== 'undefined' && inputNexSmsApiKey) {
|
if (typeof inputNexSmsApiKey !== 'undefined' && inputNexSmsApiKey) {
|
||||||
inputNexSmsApiKey.value = String(state?.nexSmsApiKey || '');
|
inputNexSmsApiKey.value = String(state?.nexSmsApiKey || '');
|
||||||
}
|
}
|
||||||
|
if (typeof inputSmsBowerApiKey !== 'undefined' && inputSmsBowerApiKey) {
|
||||||
|
inputSmsBowerApiKey.value = String(state?.smsBowerApiKey || '');
|
||||||
|
}
|
||||||
if (typeof inputMaDaoBaseUrl !== 'undefined' && inputMaDaoBaseUrl) {
|
if (typeof inputMaDaoBaseUrl !== 'undefined' && inputMaDaoBaseUrl) {
|
||||||
inputMaDaoBaseUrl.value = normalizeMaDaoBaseUrlValue(state?.madaoBaseUrl);
|
inputMaDaoBaseUrl.value = normalizeMaDaoBaseUrlValue(state?.madaoBaseUrl);
|
||||||
}
|
}
|
||||||
@@ -19202,6 +19231,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (message.payload.nexSmsApiKey !== undefined && inputNexSmsApiKey) {
|
if (message.payload.nexSmsApiKey !== undefined && inputNexSmsApiKey) {
|
||||||
inputNexSmsApiKey.value = String(message.payload.nexSmsApiKey || '').trim();
|
inputNexSmsApiKey.value = String(message.payload.nexSmsApiKey || '').trim();
|
||||||
}
|
}
|
||||||
|
if (message.payload.smsBowerApiKey !== undefined && inputSmsBowerApiKey) {
|
||||||
|
inputSmsBowerApiKey.value = String(message.payload.smsBowerApiKey || '').trim();
|
||||||
|
}
|
||||||
if (message.payload.fiveSimCountryOrder !== undefined && typeof applyFiveSimCountrySelection === 'function') {
|
if (message.payload.fiveSimCountryOrder !== undefined && typeof applyFiveSimCountrySelection === 'function') {
|
||||||
applyFiveSimCountrySelection(
|
applyFiveSimCountrySelection(
|
||||||
Array.isArray(message.payload.fiveSimCountryOrder) ? message.payload.fiveSimCountryOrder : []
|
Array.isArray(message.payload.fiveSimCountryOrder) ? message.payload.fiveSimCountryOrder : []
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
|||||||
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
|
||||||
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
|
||||||
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
const PHONE_SMS_PROVIDER_MADAO = 'madao';
|
||||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms', 'madao'];
|
const PHONE_SMS_PROVIDER_SMS_BOWER = 'sms-bower';
|
||||||
|
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms', 'madao', 'sms-bower'];
|
||||||
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS;
|
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS;
|
||||||
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||||
const DEFAULT_MADAO_MODE = 'routing_plan';
|
const DEFAULT_MADAO_MODE = 'routing_plan';
|
||||||
|
|||||||
@@ -24,30 +24,36 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
|||||||
PhoneSmsMaDaoProvider: {
|
PhoneSmsMaDaoProvider: {
|
||||||
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
|
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
|
||||||
},
|
},
|
||||||
|
PhoneSmsBowerProvider: {
|
||||||
|
createProvider: (deps = {}) => ({ provider: 'sms-bower', deps }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registry.getProviderIds(), ['hero-sms', '5sim', 'nexsms', 'madao']);
|
assert.deepStrictEqual(registry.getProviderIds(), ['hero-sms', '5sim', 'nexsms', 'madao', 'sms-bower']);
|
||||||
assert.equal(registry.normalizeProviderId(' NEXSMS '), 'nexsms');
|
assert.equal(registry.normalizeProviderId(' NEXSMS '), 'nexsms');
|
||||||
assert.equal(registry.normalizeProviderId(' MaDao '), 'madao');
|
assert.equal(registry.normalizeProviderId(' MaDao '), 'madao');
|
||||||
assert.equal(registry.normalizeProviderId('unknown-provider'), 'hero-sms');
|
assert.equal(registry.normalizeProviderId('unknown-provider'), 'hero-sms');
|
||||||
assert.equal(registry.getProviderLabel('nexsms'), 'NexSMS');
|
assert.equal(registry.getProviderLabel('nexsms'), 'NexSMS');
|
||||||
assert.equal(registry.getProviderLabel('madao'), 'MaDao');
|
assert.equal(registry.getProviderLabel('madao'), 'MaDao');
|
||||||
|
assert.equal(registry.getProviderLabel('sms-bower'), 'SMS Bower');
|
||||||
assert.equal(registry.getProviderDefinition('nexsms').moduleKey, 'PhoneSmsNexSmsProvider');
|
assert.equal(registry.getProviderDefinition('nexsms').moduleKey, 'PhoneSmsNexSmsProvider');
|
||||||
assert.equal(registry.getProviderDefinition('madao').moduleKey, 'PhoneSmsMaDaoProvider');
|
assert.equal(registry.getProviderDefinition('madao').moduleKey, 'PhoneSmsMaDaoProvider');
|
||||||
|
assert.equal(registry.getProviderDefinition('sms-bower').moduleKey, 'PhoneSmsBowerProvider');
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
registry.normalizeProviderOrder([
|
registry.normalizeProviderOrder([
|
||||||
{ provider: 'madao' },
|
{ provider: 'madao' },
|
||||||
|
{ provider: 'sms-bower' },
|
||||||
{ provider: 'nexsms' },
|
{ provider: 'nexsms' },
|
||||||
{ id: '5sim' },
|
{ id: '5sim' },
|
||||||
{ value: 'hero-sms' },
|
{ value: 'hero-sms' },
|
||||||
'MADAO',
|
'MADAO',
|
||||||
'NEXSMS',
|
'NEXSMS',
|
||||||
]),
|
]),
|
||||||
['madao', 'nexsms', '5sim', 'hero-sms']
|
['madao', 'sms-bower', 'nexsms', '5sim', 'hero-sms']
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
registry.normalizeProviderOrder([], ['madao', 'nexsms', '5sim', 'nexsms']),
|
registry.normalizeProviderOrder([], ['madao', 'sms-bower', 'nexsms', '5sim', 'nexsms']),
|
||||||
['madao', 'nexsms', '5sim']
|
['madao', 'sms-bower', 'nexsms', '5sim']
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
registry.createProvider('5sim', { foo: 1 }),
|
registry.createProvider('5sim', { foo: 1 }),
|
||||||
@@ -61,6 +67,10 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
|||||||
registry.createProvider('nexsms', { foo: 3 }),
|
registry.createProvider('nexsms', { foo: 3 }),
|
||||||
{ provider: 'nexsms', deps: { foo: 3 } }
|
{ provider: 'nexsms', deps: { foo: 3 } }
|
||||||
);
|
);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
registry.createProvider('sms-bower', { foo: 4 }),
|
||||||
|
{ provider: 'sms-bower', deps: { foo: 4 } }
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone sms provider registry can create the real MaDao provider module', () => {
|
test('phone sms provider registry can create the real MaDao provider module', () => {
|
||||||
|
|||||||
@@ -933,6 +933,7 @@ const rowNexSmsApiKey = { style: { display: 'none' } };
|
|||||||
const rowNexSmsCountry = { style: { display: 'none' } };
|
const rowNexSmsCountry = { style: { display: 'none' } };
|
||||||
const rowNexSmsCountryFallback = { style: { display: 'none' } };
|
const rowNexSmsCountryFallback = { style: { display: 'none' } };
|
||||||
const rowNexSmsServiceCode = { style: { display: 'none' } };
|
const rowNexSmsServiceCode = { style: { display: 'none' } };
|
||||||
|
const rowSmsBowerApiKey = { style: { display: 'none' } };
|
||||||
const rowMaDaoBaseUrl = { style: { display: 'none' } };
|
const rowMaDaoBaseUrl = { style: { display: 'none' } };
|
||||||
const rowMaDaoHttpSecret = { style: { display: 'none' } };
|
const rowMaDaoHttpSecret = { style: { display: 'none' } };
|
||||||
const rowMaDaoMode = { style: { display: 'none' } };
|
const rowMaDaoMode = { style: { display: 'none' } };
|
||||||
@@ -1009,6 +1010,11 @@ const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = ${JSON.stringify({
|
|||||||
'rowNexSmsServiceCode',
|
'rowNexSmsServiceCode',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
'sms-bower': {
|
||||||
|
rowKeys: [
|
||||||
|
'rowSmsBowerApiKey',
|
||||||
|
],
|
||||||
|
},
|
||||||
madao: {
|
madao: {
|
||||||
rowKeys: [
|
rowKeys: [
|
||||||
'rowMaDaoBaseUrl',
|
'rowMaDaoBaseUrl',
|
||||||
@@ -1091,6 +1097,7 @@ return {
|
|||||||
rowNexSmsCountry,
|
rowNexSmsCountry,
|
||||||
rowNexSmsCountryFallback,
|
rowNexSmsCountryFallback,
|
||||||
rowNexSmsServiceCode,
|
rowNexSmsServiceCode,
|
||||||
|
rowSmsBowerApiKey,
|
||||||
rowMaDaoBaseUrl,
|
rowMaDaoBaseUrl,
|
||||||
rowMaDaoHttpSecret,
|
rowMaDaoHttpSecret,
|
||||||
rowMaDaoMode,
|
rowMaDaoMode,
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('phone-sms/providers/sms-bower.js', 'utf8');
|
||||||
|
const api = new Function('self', `${source}; return self.PhoneSmsBowerProvider;`)({});
|
||||||
|
|
||||||
|
function createTextResponse(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('SMS Bower provider uses handler API key query for balance and price catalog', async () => {
|
||||||
|
const requests = [];
|
||||||
|
const provider = api.createProvider({
|
||||||
|
fetchImpl: async (url, options = {}) => {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
requests.push({ url: parsed, options });
|
||||||
|
if (parsed.searchParams.get('action') === 'getBalance') {
|
||||||
|
return createTextResponse('ACCESS_BALANCE:12.34');
|
||||||
|
}
|
||||||
|
if (parsed.searchParams.get('action') === 'getPrices') {
|
||||||
|
return createTextResponse({ 52: { ot: { cost: 0.21, count: 7 } } });
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected ${parsed.toString()}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const balance = await provider.fetchBalance({ smsBowerApiKey: 'demo-key' });
|
||||||
|
const prices = await provider.fetchPrices({ smsBowerApiKey: 'demo-key', smsBowerCountryId: 52 });
|
||||||
|
const entries = provider.collectPriceEntries(prices, []);
|
||||||
|
|
||||||
|
assert.equal(requests[0].url.origin + requests[0].url.pathname, 'https://smsbower.page/stubs/handler_api.php');
|
||||||
|
assert.equal(requests[0].url.searchParams.get('api_key'), 'demo-key');
|
||||||
|
assert.equal(requests[0].url.searchParams.get('action'), 'getBalance');
|
||||||
|
assert.equal(balance.balance, 12.34);
|
||||||
|
assert.equal(requests[1].url.searchParams.get('action'), 'getPrices');
|
||||||
|
assert.equal(requests[1].url.searchParams.get('service'), 'ot');
|
||||||
|
assert.equal(requests[1].url.searchParams.get('country'), '52');
|
||||||
|
assert.deepStrictEqual(entries, [{ cost: 0.21, count: 7, inStock: true }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SMS Bower provider acquires number, polls code, and updates activation statuses', async () => {
|
||||||
|
const requests = [];
|
||||||
|
const provider = api.createProvider({
|
||||||
|
fetchImpl: async (url, options = {}) => {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const action = parsed.searchParams.get('action');
|
||||||
|
requests.push({ url: parsed, options });
|
||||||
|
if (action === 'getNumber') {
|
||||||
|
return createTextResponse('ACCESS_NUMBER:12345:447700900123');
|
||||||
|
}
|
||||||
|
if (action === 'getStatus') {
|
||||||
|
return createTextResponse('STATUS_OK:Your OpenAI code is 654321');
|
||||||
|
}
|
||||||
|
if (action === 'setStatus') {
|
||||||
|
return createTextResponse(parsed.searchParams.get('status') === '6' ? 'ACCESS_ACTIVATION' : 'ACCESS_CANCEL');
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected ${parsed.toString()}`);
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
smsBowerApiKey: 'demo-key',
|
||||||
|
smsBowerServiceCode: 'ot',
|
||||||
|
smsBowerCountryId: 16,
|
||||||
|
smsBowerCountryLabel: 'United Kingdom',
|
||||||
|
smsBowerMaxPrice: '0.3',
|
||||||
|
smsBowerMinPrice: '0.1',
|
||||||
|
smsBowerProviderIds: '3170,3180',
|
||||||
|
};
|
||||||
|
|
||||||
|
const activation = await provider.requestActivation(state);
|
||||||
|
const code = await provider.pollActivationCode(state, activation, { timeoutMs: 1000, intervalMs: 1, maxRounds: 1 });
|
||||||
|
await provider.finishActivation(state, activation);
|
||||||
|
await provider.cancelActivation(state, activation);
|
||||||
|
await provider.banActivation(state, activation);
|
||||||
|
const reused = await provider.reuseActivation(state, activation);
|
||||||
|
|
||||||
|
assert.equal(activation.provider, 'sms-bower');
|
||||||
|
assert.equal(activation.activationId, '12345');
|
||||||
|
assert.equal(activation.phoneNumber, '447700900123');
|
||||||
|
assert.equal(activation.countryId, 16);
|
||||||
|
assert.equal(code, '654321');
|
||||||
|
assert.equal(reused.activationId, '12345');
|
||||||
|
|
||||||
|
const acquireUrl = requests[0].url;
|
||||||
|
assert.equal(acquireUrl.searchParams.get('action'), 'getNumber');
|
||||||
|
assert.equal(acquireUrl.searchParams.get('service'), 'ot');
|
||||||
|
assert.equal(acquireUrl.searchParams.get('country'), '16');
|
||||||
|
assert.equal(acquireUrl.searchParams.get('maxPrice'), '0.3');
|
||||||
|
assert.equal(acquireUrl.searchParams.get('minPrice'), '0.1');
|
||||||
|
assert.equal(acquireUrl.searchParams.get('providerIds'), '3170,3180');
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
requests.map((entry) => [entry.url.searchParams.get('action'), entry.url.searchParams.get('status')]),
|
||||||
|
[
|
||||||
|
['getNumber', null],
|
||||||
|
['getStatus', null],
|
||||||
|
['setStatus', '6'],
|
||||||
|
['setStatus', '8'],
|
||||||
|
['setStatus', '8'],
|
||||||
|
['setStatus', '3'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
|
||||||
|
function loadRegistry() {
|
||||||
|
const sandbox = { globalThis: {} };
|
||||||
|
sandbox.self = sandbox.globalThis;
|
||||||
|
vm.runInNewContext(fs.readFileSync('phone-sms/providers/sms-bower.js', 'utf8'), sandbox);
|
||||||
|
vm.runInNewContext(fs.readFileSync('phone-sms/providers/registry.js', 'utf8'), sandbox);
|
||||||
|
return sandbox.globalThis.PhoneSmsProviderRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('SMS Bower is registered as a known phone SMS provider', () => {
|
||||||
|
const registry = loadRegistry();
|
||||||
|
assert.equal(registry.normalizeProviderId('sms-bower'), 'sms-bower');
|
||||||
|
assert.equal(registry.getProviderLabel('sms-bower'), 'SMS Bower');
|
||||||
|
assert.ok(registry.getProviderIds().includes('sms-bower'));
|
||||||
|
assert.deepEqual(
|
||||||
|
Array.from(registry.normalizeProviderOrder(['sms-bower', 'hero-sms'])),
|
||||||
|
['sms-bower', 'hero-sms']
|
||||||
|
);
|
||||||
|
const provider = registry.createProvider('sms-bower', { fetchImpl: async () => ({ ok: true, text: async () => 'ACCESS_BALANCE:1.23' }) });
|
||||||
|
assert.equal(provider.id, 'sms-bower');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user