From 7aae6d608271500ef6417292d95a87f2533d326b Mon Sep 17 00:00:00 2001 From: Isulew <224964+netcookies@users.noreply.github.com> Date: Mon, 25 May 2026 01:10:13 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5=20MaDao=20?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=8E=A5=E7=A0=81=E5=B9=B6=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=9B=BD=E5=AE=B6=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 MaDao 接码 provider,并将 acquire/poll/release 接入手机号验证链路。 侧边栏新增 MaDao 互斥配置模型,支持路由方案与直连参数两种模式, 同时保留 HeroSMS、5sim、NexSMS 的原有分散式直连能力。 同步适配 MaDao 最新国家 canonical 规则: - madaoCountry 改为保留 ISO alpha-2 与 any/local sentinel - 运行态国家展示按 canonical 值还原可读国家名 - 更新持久化、测试夹具与文档说明 --- background.js | 53 ++ background/phone-verification-flow.js | 128 ++++ phone-sms/providers/madao.js | 277 +++++++ phone-sms/providers/registry.js | 8 + sidepanel/sidepanel.html | 70 ++ sidepanel/sidepanel.js | 598 ++++++++++++++- ...ackground-account-history-settings.test.js | 13 + tests/phone-sms-provider-registry.test.js | 19 +- tests/phone-verification-flow.test.js | 189 +++++ tests/sidepanel-contribution-mode.test.js | 37 + tests/sidepanel-icloud-provider.test.js | 38 + tests/sidepanel-mail2925-base-email.test.js | 49 ++ ...epanel-phone-verification-settings.test.js | 698 +++++++++++++++++- ...23\346\236\204\350\257\264\346\230\216.md" | 6 +- 14 files changed, 2134 insertions(+), 49 deletions(-) create mode 100644 phone-sms/providers/madao.js diff --git a/background.js b/background.js index ce206b3c..0c50acbd 100644 --- a/background.js +++ b/background.js @@ -21,6 +21,7 @@ importScripts( 'gopay-utils.js', 'phone-sms/providers/hero-sms.js', 'phone-sms/providers/five-sim.js', + 'phone-sms/providers/madao.js', 'phone-sms/providers/registry.js', 'background/phone-verification-flow.js', 'background/account-run-history.js', @@ -694,11 +695,13 @@ const PHONE_SMS_PROVIDER_5SIM = '5sim'; const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO; const PHONE_SMS_PROVIDER_FIVE_SIM = PHONE_SMS_PROVIDER_5SIM; const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms'; +const PHONE_SMS_PROVIDER_MADAO = 'madao'; const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO; const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([ PHONE_SMS_PROVIDER_HERO, PHONE_SMS_PROVIDER_5SIM, PHONE_SMS_PROVIDER_NEXSMS, + PHONE_SMS_PROVIDER_MADAO, ]); const DEFAULT_FIVE_SIM_BASE_URL = 'https://5sim.net/v1'; const DEFAULT_FIVE_SIM_PRODUCT = 'openai'; @@ -1448,6 +1451,17 @@ const PERSISTED_SETTING_DEFAULTS = { nexSmsApiKey: '', nexSmsCountryOrder: [...DEFAULT_NEX_SMS_COUNTRY_ORDER], nexSmsServiceCode: DEFAULT_NEX_SMS_SERVICE_CODE, + madaoMode: 'routing_plan', + madaoBaseUrl: 'http://127.0.0.1:7822', + madaoHttpSecret: '', + madaoProviderId: '', + madaoRoutingPlanId: '', + madaoServiceName: 'openai', + madaoCountry: '', + madaoAutoPickCountry: true, + madaoReusePhone: true, + madaoMinPrice: '', + madaoMaxPrice: '', phonePreferredActivation: null, }; @@ -1823,6 +1837,9 @@ function normalizePhoneSmsProvider(value = '') { if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderId) { return rootScope.PhoneSmsProviderRegistry.normalizeProviderId(value); } + const madaoProvider = typeof PHONE_SMS_PROVIDER_MADAO !== 'undefined' + ? PHONE_SMS_PROVIDER_MADAO + : 'madao'; const normalized = String(value || '').trim().toLowerCase(); if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) { return PHONE_SMS_PROVIDER_FIVE_SIM; @@ -1830,6 +1847,9 @@ function normalizePhoneSmsProvider(value = '') { if (normalized === PHONE_SMS_PROVIDER_NEXSMS) { return PHONE_SMS_PROVIDER_NEXSMS; } + if (normalized === madaoProvider) { + return madaoProvider; + } return PHONE_SMS_PROVIDER_HERO_SMS; } function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) { @@ -3567,6 +3587,39 @@ function normalizePersistentSettingValue(key, value) { return normalizeNexSmsCountryOrder(value); case 'nexSmsServiceCode': return normalizeNexSmsServiceCode(value); + case 'madaoBaseUrl': + return String(value || '').trim() || 'http://127.0.0.1:7822'; + case 'madaoMode': + return String(value || '').trim().toLowerCase() === 'direct' ? 'direct' : 'routing_plan'; + case 'madaoHttpSecret': + return String(value || '').trim(); + case 'madaoProviderId': + return String(value || '').trim().toLowerCase(); + case 'madaoRoutingPlanId': + return String(value || '').trim(); + case 'madaoServiceName': + return String(value || '').trim().toLowerCase() || 'openai'; + case 'madaoCountry': + { + 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; + } + case 'madaoAutoPickCountry': + case 'madaoReusePhone': + return Boolean(value); + case 'madaoMinPrice': + case 'madaoMaxPrice': + return normalizeHeroSmsMaxPrice(value); case 'phonePreferredActivation': return normalizePhonePreferredActivation(value); default: diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index 08141efd..88e2dbd1 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -83,11 +83,13 @@ const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO; const PHONE_SMS_PROVIDER_FIVE_SIM = PHONE_SMS_PROVIDER_5SIM; const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms'; + const PHONE_SMS_PROVIDER_MADAO = 'madao'; const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO; const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([ PHONE_SMS_PROVIDER_HERO, PHONE_SMS_PROVIDER_5SIM, PHONE_SMS_PROVIDER_NEXSMS, + PHONE_SMS_PROVIDER_MADAO, ]); const MAX_PHONE_REUSABLE_POOL = 12; const PHONE_CODE_TIMEOUT_ERROR_PREFIX = 'PHONE_CODE_TIMEOUT::'; @@ -191,6 +193,9 @@ if (normalized === PHONE_SMS_PROVIDER_NEXSMS) { return PHONE_SMS_PROVIDER_NEXSMS; } + if (normalized === PHONE_SMS_PROVIDER_MADAO) { + return PHONE_SMS_PROVIDER_MADAO; + } return PHONE_SMS_PROVIDER_HERO; } function isFiveSimProvider(state = {}) { @@ -935,9 +940,56 @@ if (provider === PHONE_SMS_PROVIDER_NEXSMS) { return 'NexSMS'; } + if (provider === PHONE_SMS_PROVIDER_MADAO) { + return 'MaDao'; + } return 'HeroSMS'; } + function getMaDaoProviderForState() { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + return rootScope.PhoneSmsMaDaoProvider || null; + } + + function resolveMaDaoPriceRange(state = {}) { + const minPrice = normalizeHeroSmsPriceLimit(state?.madaoMinPrice); + const maxPrice = normalizeHeroSmsPriceLimit(state?.madaoMaxPrice); + return { + minPrice, + maxPrice, + invalidRange: minPrice !== null && maxPrice !== null && minPrice > maxPrice, + }; + } + + async function requestMaDaoActivation(state = {}, options = {}) { + const provider = getMaDaoProviderForState(); + if (!provider?.acquireActivation) { + throw new Error('MaDao 接码模块未加载。'); + } + const madaoMode = String(state?.madaoMode || '').trim().toLowerCase() === 'direct' + ? 'direct' + : 'routing_plan'; + const priceRange = resolveMaDaoPriceRange(state); + if (priceRange.invalidRange) { + throw new Error( + `MaDao 价格区间无效:最低购买价 ${priceRange.minPrice} 高于价格上限 ${priceRange.maxPrice}。` + ); + } + return provider.acquireActivation(state, { + providerId: madaoMode === 'direct' ? state?.madaoProviderId : '', + routingPlanId: madaoMode === 'routing_plan' ? state?.madaoRoutingPlanId : '', + service: state?.madaoServiceName || DEFAULT_FIVE_SIM_PRODUCT, + country: madaoMode === 'direct' ? (state?.madaoCountry || '') : '', + autoPickCountry: madaoMode === 'direct' ? state?.madaoAutoPickCountry : true, + reusePhone: madaoMode === 'direct' ? state?.madaoReusePhone : true, + minPrice: madaoMode === 'direct' ? priceRange.minPrice : null, + maxPrice: madaoMode === 'direct' ? priceRange.maxPrice : null, + }, { + fetchImpl, + requestTimeoutMs: DEFAULT_PHONE_REQUEST_TIMEOUT_MS, + }); + } + function formatStep9Reason(reason = '') { const text = String(reason || '').trim(); if (!text) { @@ -2056,6 +2108,13 @@ function resolvePhoneConfig(state = {}) { const provider = normalizePhoneSmsProvider(state?.phoneSmsProvider || DEFAULT_PHONE_SMS_PROVIDER); + if (provider === PHONE_SMS_PROVIDER_MADAO) { + return { + provider, + baseUrl: String(state?.madaoBaseUrl || 'http://127.0.0.1:7822').trim() || 'http://127.0.0.1:7822', + countryCandidates: [], + }; + } if (provider === PHONE_SMS_PROVIDER_5SIM) { const apiKey = normalizeApiKey(state.fiveSimApiKey || state.heroSmsApiKey); if (!apiKey) { @@ -3584,6 +3643,9 @@ } async function requestPhoneActivation(state = {}, options = {}) { + if (normalizePhoneSmsProvider(state?.phoneSmsProvider) === PHONE_SMS_PROVIDER_MADAO) { + return requestMaDaoActivation(state, options); + } if (normalizePhoneSmsProvider(state?.phoneSmsProvider) === PHONE_SMS_PROVIDER_FIVE_SIM) { const provider = getFiveSimProviderForState(state); if (provider) { @@ -4008,6 +4070,18 @@ } return describeNexSmsPayload(payload); } + if (config.provider === PHONE_SMS_PROVIDER_MADAO) { + const provider = getMaDaoProviderForState(); + if (!provider?.releaseActivation) { + throw new Error('MaDao 接码模块未加载。'); + } + const action = normalizedStatus === 6 ? 'finish' : 'cancel'; + const payload = await provider.releaseActivation(state, normalizedActivation, action, { + fetchImpl, + requestTimeoutMs: DEFAULT_PHONE_REQUEST_TIMEOUT_MS, + }); + return String(payload?.message || action).trim() || action; + } const payload = await fetchHeroSmsPayload(config, { action: 'setStatus', id: normalizedActivation.activationId, @@ -4500,6 +4574,60 @@ throw buildPhoneCodeTimeoutError(lastResponse); } + if (config.provider === PHONE_SMS_PROVIDER_MADAO) { + const provider = getMaDaoProviderForState(); + if (!provider?.pollActivation) { + throw new Error('MaDao 接码模块未加载。'); + } + while (Date.now() - start < timeoutMs) { + if (maxRounds > 0 && pollCount >= maxRounds) { + break; + } + throwIfStopped(); + const payload = await provider.pollActivation(state, normalizedActivation, { + fetchImpl, + requestTimeoutMs: DEFAULT_PHONE_REQUEST_TIMEOUT_MS, + }); + const statusText = String(payload?.status || '').trim(); + const mappedStatus = provider.mapTicketStatus + ? provider.mapTicketStatus(statusText) + : String(statusText || '').trim().toLowerCase(); + lastResponse = String(payload?.message || statusText || '').trim(); + pollCount += 1; + + if (mappedStatus === 'code_received') { + const directCode = extractVerificationCode(payload?.code || payload?.message || ''); + if (directCode) { + return directCode; + } + throw new Error('MaDao 返回 code_received,但未提供验证码。'); + } + + if (mappedStatus === 'waiting_code' || mappedStatus === 'pending') { + if (typeof options.onStatus === 'function') { + await options.onStatus({ + activation: normalizedActivation, + elapsedMs: Date.now() - start, + pollCount, + statusText: statusText || 'WAITING_CODE', + timeoutMs, + }); + } + await emitWaitingForCode(statusText || 'WAITING_CODE'); + await sleepWithStop(intervalMs); + continue; + } + + if (mappedStatus === 'cancelled' || mappedStatus === 'failed' || mappedStatus === 'finished') { + throw new Error(`MaDao 订单在收到短信前已结束:${statusText || mappedStatus}`); + } + + throw new Error(`MaDao 返回未知状态:${statusText || mappedStatus || 'unknown'}`); + } + + throw buildPhoneCodeTimeoutError(lastResponse); + } + while (Date.now() - start < timeoutMs) { if (maxRounds > 0 && pollCount >= maxRounds) { break; diff --git a/phone-sms/providers/madao.js b/phone-sms/providers/madao.js new file mode 100644 index 00000000..b2ec6e55 --- /dev/null +++ b/phone-sms/providers/madao.js @@ -0,0 +1,277 @@ +// phone-sms/providers/madao.js — MaDao 统一接码后端适配层 +(function attachMaDaoProvider(root, factory) { + root.PhoneSmsMaDaoProvider = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createMaDaoProviderModule() { + const PROVIDER_ID = 'madao'; + const DEFAULT_BASE_URL = 'http://127.0.0.1:7822'; + const DEFAULT_SERVICE = 'openai'; + const DEFAULT_REQUEST_TIMEOUT_MS = 20000; + + function normalizeBaseUrl(value = '', fallback = DEFAULT_BASE_URL) { + const trimmed = String(value || '').trim() || fallback; + try { + return new URL(trimmed).toString().replace(/\/+$/, ''); + } catch { + return fallback; + } + } + + function normalizeText(value = '', fallback = '') { + return String(value || '').trim() || fallback; + } + + function normalizeProviderId(value = '') { + return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); + } + + function normalizeCountry(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; + } + + function normalizeBoolean(value, fallback = false) { + if (value === undefined || value === null) { + return Boolean(fallback); + } + return Boolean(value); + } + + function normalizePrice(value) { + const numeric = Number(value); + if (!Number.isFinite(numeric) || numeric <= 0) { + return null; + } + return Math.round(numeric * 10000) / 10000; + } + + function buildHeaders(config = {}, extraHeaders = {}) { + const headers = { + Accept: 'application/json', + ...extraHeaders, + }; + const secret = normalizeText(config.httpSecret); + if (secret) { + headers.Authorization = `Bearer ${secret}`; + } + return headers; + } + + async function requestJson(config, path, options = {}) { + const fetchImpl = config.fetchImpl || (typeof fetch === 'function' ? fetch.bind(globalThis) : null); + if (!fetchImpl) { + throw new Error('MaDao 网络请求实现不可用。'); + } + const controller = typeof AbortController === 'function' ? new AbortController() : null; + const timeoutId = controller + ? setTimeout(() => controller.abort(), Number(config.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS) + : null; + + try { + const url = new URL(path.replace(/^\/+/, ''), `${config.baseUrl.replace(/\/+$/, '')}/`); + const method = String(options.method || 'GET').trim().toUpperCase() || 'GET'; + const init = { + method, + headers: buildHeaders(config, options.headers || {}), + signal: controller?.signal, + }; + if (options.body !== undefined) { + init.body = JSON.stringify(options.body); + init.headers['Content-Type'] = 'application/json'; + } + const response = await fetchImpl(url.toString(), init); + const rawText = await response.text(); + let payload = null; + try { + payload = rawText ? JSON.parse(rawText) : null; + } catch { + payload = rawText; + } + if (!response.ok) { + const detail = ( + payload && typeof payload === 'object' + ? String(payload.message || payload.error || response.statusText || response.status).trim() + : String(payload || response.statusText || response.status).trim() + ) || `HTTP ${response.status}`; + const error = new Error(`MaDao 请求失败:${detail}`); + error.status = response.status; + error.payload = payload; + throw error; + } + return payload; + } catch (error) { + if (error?.name === 'AbortError') { + throw new Error('MaDao 请求超时。'); + } + throw error; + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } + } + } + + function resolveConfig(state = {}, deps = {}) { + return { + baseUrl: normalizeBaseUrl(state?.madaoBaseUrl || DEFAULT_BASE_URL), + httpSecret: normalizeText(state?.madaoHttpSecret), + fetchImpl: deps.fetchImpl || (typeof fetch === 'function' ? fetch.bind(globalThis) : null), + requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS, + }; + } + + function mapAcquirePath(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'same_activation_retry') { + return 'same_activation_retry'; + } + if (normalized === 'exact_reuse') { + return 'exact_reuse'; + } + if (normalized === 'intent_reuse') { + return 'intent_reuse'; + } + return 'fresh_acquire'; + } + + function mapTicketStatus(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'waiting_code') { + return 'waiting_code'; + } + if (normalized === 'code_received') { + return 'code_received'; + } + if (normalized === 'finished') { + return 'finished'; + } + if (normalized === 'cancelled') { + return 'cancelled'; + } + if (normalized === 'failed') { + return 'failed'; + } + return 'pending'; + } + + function buildAcquireRequest(state = {}, options = {}) { + const routingPlanId = normalizeText(options?.routingPlanId || state?.madaoRoutingPlanId); + const directProvider = normalizeProviderId(options?.providerId || state?.madaoProviderId); + const request = { + provider: routingPlanId ? 'auto' : (directProvider || 'auto'), + service: normalizeText(options?.service || state?.madaoServiceName, DEFAULT_SERVICE), + }; + const country = normalizeCountry(options?.country || state?.madaoCountry); + const minPrice = normalizePrice(options?.minPrice ?? state?.madaoMinPrice); + const maxPrice = normalizePrice(options?.maxPrice ?? state?.madaoMaxPrice); + + if (routingPlanId) { + request.routing_plan_id = routingPlanId; + } else { + request.auto_pick_country = normalizeBoolean(options?.autoPickCountry ?? state?.madaoAutoPickCountry, true); + request.reuse_phone = normalizeBoolean(options?.reusePhone ?? state?.madaoReusePhone, true); + if (country) { + request.country = country; + } + if (minPrice !== null) { + request.min_price = minPrice; + } + if (maxPrice !== null) { + request.max_price = maxPrice; + } + } + + return request; + } + + function normalizeActivationFromAcquire(payload = {}, fallback = {}) { + const ticketId = normalizeText(payload?.ticket_id || payload?.id); + const phoneNumber = normalizeText(payload?.phone_number || payload?.phone); + if (!ticketId || !phoneNumber) { + return null; + } + return { + activationId: ticketId, + phoneNumber, + provider: PROVIDER_ID, + serviceCode: normalizeText(payload?.service || fallback.service, DEFAULT_SERVICE), + countryId: normalizeCountry(payload?.country || fallback.country), + countryLabel: '', + maxUses: 1, + successfulUses: 0, + madaoProviderId: normalizeProviderId(payload?.provider || fallback.provider), + madaoRoutingPlanId: normalizeText(payload?.routing_plan_id || fallback.routing_plan_id), + madaoRoutingPlanName: normalizeText(payload?.routing_plan_name || fallback.routing_plan_name), + madaoAcquirePath: mapAcquirePath(payload?.acquire_path), + madaoStatus: mapTicketStatus(payload?.status), + ...(payload?.price !== undefined && payload?.price !== null + ? { madaoPrice: normalizePrice(payload.price) } + : {}), + }; + } + + async function acquireActivation(state = {}, options = {}, deps = {}) { + const config = resolveConfig(state, deps); + const requestBody = buildAcquireRequest(state, options); + const payload = await requestJson(config, '/api/acquire', { + method: 'POST', + body: requestBody, + }); + const activation = normalizeActivationFromAcquire(payload, requestBody); + if (!activation) { + throw new Error('MaDao 返回的激活记录无效。'); + } + return activation; + } + + async function pollActivation(state = {}, activation, deps = {}) { + const config = resolveConfig(state, deps); + const ticketId = normalizeText(activation?.activationId || activation?.ticketId); + if (!ticketId) { + throw new Error('MaDao 激活记录缺少 ticket_id。'); + } + return requestJson(config, '/api/poll', { + method: 'POST', + body: { + ticket_id: ticketId, + }, + }); + } + + async function releaseActivation(state = {}, activation, action = 'cancel', deps = {}) { + const config = resolveConfig(state, deps); + const ticketId = normalizeText(activation?.activationId || activation?.ticketId); + if (!ticketId) { + throw new Error('MaDao 激活记录缺少 ticket_id。'); + } + return requestJson(config, '/api/release', { + method: 'POST', + body: { + ticket_id: ticketId, + action: normalizeText(action, 'cancel'), + }, + }); + } + + return { + PROVIDER_ID, + DEFAULT_BASE_URL, + DEFAULT_SERVICE, + acquireActivation, + mapAcquirePath, + mapTicketStatus, + normalizeActivationFromAcquire, + pollActivation, + releaseActivation, + resolveConfig, + }; +}); diff --git a/phone-sms/providers/registry.js b/phone-sms/providers/registry.js index 070da152..d46cd286 100644 --- a/phone-sms/providers/registry.js +++ b/phone-sms/providers/registry.js @@ -5,11 +5,13 @@ const PROVIDER_HERO_SMS = 'hero-sms'; const PROVIDER_FIVE_SIM = '5sim'; const PROVIDER_NEXSMS = 'nexsms'; + const PROVIDER_MADAO = 'madao'; const DEFAULT_PROVIDER = PROVIDER_HERO_SMS; const DEFAULT_PROVIDER_ORDER = Object.freeze([ PROVIDER_HERO_SMS, PROVIDER_FIVE_SIM, PROVIDER_NEXSMS, + PROVIDER_MADAO, ]); const PROVIDER_DEFINITIONS = Object.freeze({ [PROVIDER_HERO_SMS]: Object.freeze({ @@ -27,6 +29,11 @@ label: 'NexSMS', moduleKey: 'PhoneSmsNexSmsProvider', }), + [PROVIDER_MADAO]: Object.freeze({ + id: PROVIDER_MADAO, + label: 'MaDao', + moduleKey: 'PhoneSmsMaDaoProvider', + }), }); function resolveProviderKey(value = '') { @@ -125,6 +132,7 @@ PROVIDER_HERO_SMS, PROVIDER_FIVE_SIM, PROVIDER_NEXSMS, + PROVIDER_MADAO, DEFAULT_PROVIDER, DEFAULT_PROVIDER_ORDER, PROVIDER_DEFINITIONS, diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 1b7cb691..16054483 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -1473,6 +1473,7 @@ +