完善手机接码逻辑

This commit is contained in:
QLHazyCoder
2026-05-15 05:07:34 +08:00
parent a7ea261093
commit 22fc865f19
10 changed files with 475 additions and 61 deletions
+30 -5
View File
@@ -809,6 +809,7 @@ const PERSISTED_SETTING_DEFAULTS = {
autoStepDelaySeconds: null, autoStepDelaySeconds: null,
step6CookieCleanupEnabled: false, step6CookieCleanupEnabled: false,
phoneVerificationEnabled: false, phoneVerificationEnabled: false,
phoneSmsReuseEnabled: DEFAULT_HERO_SMS_REUSE_ENABLED,
freePhoneReuseEnabled: true, freePhoneReuseEnabled: true,
freePhoneReuseAutoEnabled: true, freePhoneReuseAutoEnabled: true,
signupMethod: DEFAULT_SIGNUP_METHOD, signupMethod: DEFAULT_SIGNUP_METHOD,
@@ -1329,12 +1330,15 @@ function normalizePhoneSmsProvider(value = '') {
} }
return PHONE_SMS_PROVIDER_HERO_SMS; return PHONE_SMS_PROVIDER_HERO_SMS;
} }
function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) { function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderOrder) {
return rootScope.PhoneSmsProviderRegistry.normalizeProviderOrder(value, fallbackOrder);
}
const source = Array.isArray(value) const source = Array.isArray(value)
? value ? value
: String(value || '') : String(value || '')
.split(/[\r\n,;]+/) .split(/[\r\n,]+/)
.map((entry) => String(entry || '').trim()) .map((entry) => String(entry || '').trim())
.filter(Boolean); .filter(Boolean);
const normalized = []; const normalized = [];
@@ -1373,7 +1377,6 @@ function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) {
return normalized.slice(0, DEFAULT_PHONE_SMS_PROVIDER_ORDER.length); return normalized.slice(0, DEFAULT_PHONE_SMS_PROVIDER_ORDER.length);
} }
function normalizeSignupMethod(value = '') { function normalizeSignupMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'phone' return String(value || '').trim().toLowerCase() === 'phone'
? 'phone' ? 'phone'
@@ -2668,6 +2671,7 @@ function normalizePersistentSettingValue(key, value) {
return typeof value === 'boolean' ? value : true; return typeof value === 'boolean' ? value : true;
case 'step6CookieCleanupEnabled': case 'step6CookieCleanupEnabled':
case 'phoneVerificationEnabled': case 'phoneVerificationEnabled':
case 'phoneSmsReuseEnabled':
case 'freePhoneReuseEnabled': case 'freePhoneReuseEnabled':
case 'freePhoneReuseAutoEnabled': case 'freePhoneReuseAutoEnabled':
case 'plusModeEnabled': case 'plusModeEnabled':
@@ -2842,6 +2846,13 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
throw new Error('\u914d\u7f6e\u5185\u5bb9\u683c\u5f0f\u65e0\u6548\u3002'); throw new Error('\u914d\u7f6e\u5185\u5bb9\u683c\u5f0f\u65e0\u6548\u3002');
} }
const persistedSettingDefaults = typeof PERSISTED_SETTING_DEFAULTS !== 'undefined' && PERSISTED_SETTING_DEFAULTS
? PERSISTED_SETTING_DEFAULTS
: {};
const persistedSettingKeys = Array.isArray(typeof PERSISTED_SETTING_KEYS !== 'undefined' ? PERSISTED_SETTING_KEYS : null)
? PERSISTED_SETTING_KEYS
: Object.keys(persistedSettingDefaults);
const normalizedInput = { ...input }; const normalizedInput = { ...input };
if (normalizedInput.autoStepDelaySeconds === undefined) { if (normalizedInput.autoStepDelaySeconds === undefined) {
const legacyAutoStepDelaySeconds = resolveLegacyAutoStepDelaySeconds(normalizedInput); const legacyAutoStepDelaySeconds = resolveLegacyAutoStepDelaySeconds(normalizedInput);
@@ -2860,15 +2871,29 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
const payload = {}; const payload = {};
let matchedKeyCount = 0; let matchedKeyCount = 0;
for (const key of PERSISTED_SETTING_KEYS) { for (const key of persistedSettingKeys) {
if (normalizedInput[key] !== undefined) { if (normalizedInput[key] !== undefined) {
payload[key] = normalizePersistentSettingValue(key, normalizedInput[key]); payload[key] = normalizePersistentSettingValue(key, normalizedInput[key]);
matchedKeyCount += 1; matchedKeyCount += 1;
} else if (fillDefaults) { } else if (fillDefaults) {
payload[key] = normalizePersistentSettingValue(key, PERSISTED_SETTING_DEFAULTS[key]); payload[key] = normalizePersistentSettingValue(key, persistedSettingDefaults[key]);
} }
} }
const hasPhoneSmsReuseEnabled = Object.prototype.hasOwnProperty.call(normalizedInput, 'phoneSmsReuseEnabled');
const hasHeroSmsReuseEnabled = Object.prototype.hasOwnProperty.call(normalizedInput, 'heroSmsReuseEnabled');
const hasFiveSimReuseEnabled = Object.prototype.hasOwnProperty.call(normalizedInput, 'fiveSimReuseEnabled');
if (hasPhoneSmsReuseEnabled || hasHeroSmsReuseEnabled || hasFiveSimReuseEnabled) {
const reuseSource = hasPhoneSmsReuseEnabled
? normalizedInput.phoneSmsReuseEnabled
: (hasHeroSmsReuseEnabled
? normalizedInput.heroSmsReuseEnabled
: normalizedInput.fiveSimReuseEnabled);
const normalizedReuseEnabled = normalizePersistentSettingValue('phoneSmsReuseEnabled', reuseSource);
payload.phoneSmsReuseEnabled = normalizedReuseEnabled;
payload.heroSmsReuseEnabled = normalizedReuseEnabled;
}
if (requireKnownKeys && matchedKeyCount === 0) { if (requireKnownKeys && matchedKeyCount === 0) {
throw new Error('\u914d\u7f6e\u6587\u4ef6\u4e2d\u6ca1\u6709\u53ef\u8bc6\u522b\u7684\u914d\u7f6e\u5185\u5bb9\u3002'); throw new Error('\u914d\u7f6e\u6587\u4ef6\u4e2d\u6ca1\u6709\u53ef\u8bc6\u522b\u7684\u914d\u7f6e\u5185\u5bb9\u3002');
} }
+19 -9
View File
@@ -180,6 +180,10 @@
} }
function normalizePhoneSmsProvider(value = '') { function normalizePhoneSmsProvider(value = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderId) {
return rootScope.PhoneSmsProviderRegistry.normalizeProviderId(value);
}
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
if (normalized === PHONE_SMS_PROVIDER_5SIM) { if (normalized === PHONE_SMS_PROVIDER_5SIM) {
return PHONE_SMS_PROVIDER_5SIM; return PHONE_SMS_PROVIDER_5SIM;
@@ -189,7 +193,6 @@
} }
return PHONE_SMS_PROVIDER_HERO; return PHONE_SMS_PROVIDER_HERO;
} }
function isFiveSimProvider(state = {}) { function isFiveSimProvider(state = {}) {
return normalizePhoneSmsProvider(state?.phoneSmsProvider || DEFAULT_PHONE_SMS_PROVIDER) === PHONE_SMS_PROVIDER_5SIM; return normalizePhoneSmsProvider(state?.phoneSmsProvider || DEFAULT_PHONE_SMS_PROVIDER) === PHONE_SMS_PROVIDER_5SIM;
} }
@@ -515,6 +518,13 @@
return Boolean(value); return Boolean(value);
} }
function normalizePhoneSmsReuseEnabled(state = {}) {
if (Object.prototype.hasOwnProperty.call(state, 'phoneSmsReuseEnabled')) {
return Boolean(state.phoneSmsReuseEnabled);
}
return normalizeHeroSmsReuseEnabled(state?.heroSmsReuseEnabled);
}
function normalizeFreePhoneReuseEnabled(value) { function normalizeFreePhoneReuseEnabled(value) {
return Boolean(value); return Boolean(value);
} }
@@ -536,10 +546,14 @@
} }
function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) { function normalizePhoneSmsProviderOrder(value = [], fallbackOrder = []) {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderOrder) {
return rootScope.PhoneSmsProviderRegistry.normalizeProviderOrder(value, fallbackOrder);
}
const source = Array.isArray(value) const source = Array.isArray(value)
? value ? value
: String(value || '') : String(value || '')
.split(/[\r\n,;|/]+/) .split(/[\r\n,]+/)
.map((entry) => String(entry || '').trim()) .map((entry) => String(entry || '').trim())
.filter(Boolean); .filter(Boolean);
const normalized = []; const normalized = [];
@@ -573,7 +587,6 @@
return fallbackNormalized.slice(0, 3); return fallbackNormalized.slice(0, 3);
} }
function resolvePhoneProviderOrder(state = {}, preferredProvider = '') { function resolvePhoneProviderOrder(state = {}, preferredProvider = '') {
const currentProvider = normalizePhoneSmsProvider( const currentProvider = normalizePhoneSmsProvider(
preferredProvider || state?.phoneSmsProvider || DEFAULT_PHONE_SMS_PROVIDER preferredProvider || state?.phoneSmsProvider || DEFAULT_PHONE_SMS_PROVIDER
@@ -856,10 +869,7 @@
} }
function isPhoneSmsReuseEnabled(state = {}) { function isPhoneSmsReuseEnabled(state = {}) {
if (normalizePhoneSmsProvider(state?.phoneSmsProvider) === PHONE_SMS_PROVIDER_FIVE_SIM) { return normalizePhoneSmsReuseEnabled(state);
return state?.fiveSimReuseEnabled !== false;
}
return normalizeHeroSmsReuseEnabled(state?.heroSmsReuseEnabled);
} }
function createResolvedFiveSimProvider() { function createResolvedFiveSimProvider() {
@@ -2658,7 +2668,7 @@
{ {
query: { query: {
...(candidatePrice !== null && candidatePrice !== undefined ? { maxPrice: candidatePrice } : {}), ...(candidatePrice !== null && candidatePrice !== undefined ? { maxPrice: candidatePrice } : {}),
...(normalizeHeroSmsReuseEnabled(state.heroSmsReuseEnabled) ? { reuse: 1 } : {}), ...(normalizePhoneSmsReuseEnabled(state) ? { reuse: 1 } : {}),
}, },
} }
); );
@@ -4850,7 +4860,7 @@
delete nextReusableActivation.phoneCodeReceived; delete nextReusableActivation.phoneCodeReceived;
delete nextReusableActivation.phoneCodeReceivedAt; delete nextReusableActivation.phoneCodeReceivedAt;
await upsertReusableActivationPool(nextReusableActivation, { state }); await upsertReusableActivationPool(nextReusableActivation, { state });
if (!normalizeHeroSmsReuseEnabled(state?.heroSmsReuseEnabled)) { if (!normalizePhoneSmsReuseEnabled(state)) {
await clearReusableActivation(); await clearReusableActivation();
return; return;
} }
+22 -12
View File
@@ -574,22 +574,32 @@
return /not\s+enough\s+(?:user\s+)?balance|not\s+enough\s+rating|unauthorized|invalid\s+token|banned|bad\s+(?:country|operator)|no\s+product|server\s+offline/i.test(text); return /not\s+enough\s+(?:user\s+)?balance|not\s+enough\s+rating|unauthorized|invalid\s+token|banned|bad\s+(?:country|operator)|no\s+product|server\s+offline/i.test(text);
} }
function assertMaxPriceCompatibleWithOperator(state = {}) { function assertMaxPriceCompatibleWithOperator(state = {}) {
const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice); const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
const operator = normalizeFiveSimOperator(state.fiveSimOperator); const operator = normalizeFiveSimOperator(state.fiveSimOperator);
if (maxPrice && operator !== DEFAULT_OPERATOR) { if (maxPrice && operator !== DEFAULT_OPERATOR) {
throw new Error('5sim maxPrice only works when operator is "any"; clear the price limit or switch operator to any before buying a number.'); throw new Error('5sim maxPrice only works when operator is "any"; clear the price limit or switch operator to any before buying a number.');
} }
} }
async function buyActivationWithPrice(state = {}, countryConfig, maxPrice, deps = {}) { function normalizeReuseEnabled(state = {}) {
const config = resolveConfig(state, deps); if (Object.prototype.hasOwnProperty.call(state, 'phoneSmsReuseEnabled')) {
const operator = normalizeFiveSimOperator(state.fiveSimOperator); return state.phoneSmsReuseEnabled !== false;
}
if (Object.prototype.hasOwnProperty.call(state, 'heroSmsReuseEnabled')) {
return state.heroSmsReuseEnabled !== false;
}
return true;
}
async function buyActivationWithPrice(state = {}, countryConfig, maxPrice, deps = {}) {
const config = resolveConfig(state, deps);
const operator = normalizeFiveSimOperator(state.fiveSimOperator);
const query = {}; const query = {};
if (maxPrice !== null && maxPrice !== undefined) { if (maxPrice !== null && maxPrice !== undefined) {
query.maxPrice = maxPrice; query.maxPrice = maxPrice;
} }
if (state.fiveSimReuseEnabled !== false) { if (normalizeReuseEnabled(state)) {
query.reuse = 1; query.reuse = 1;
} }
const payload = await fetchJson( const payload = await fetchJson(
+106 -10
View File
@@ -1,41 +1,137 @@
// phone-sms/providers/registry.js — 接码平台注册表 // phone-sms/providers/registry.js - phone SMS provider registry
(function attachPhoneSmsProviderRegistry(root, factory) { (function attachPhoneSmsProviderRegistry(root, factory) {
root.PhoneSmsProviderRegistry = factory(root); root.PhoneSmsProviderRegistry = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createPhoneSmsProviderRegistry(root) { })(typeof self !== 'undefined' ? self : globalThis, function createPhoneSmsProviderRegistry(root) {
const PROVIDER_HERO_SMS = 'hero-sms'; const PROVIDER_HERO_SMS = 'hero-sms';
const PROVIDER_FIVE_SIM = '5sim'; const PROVIDER_FIVE_SIM = '5sim';
const PROVIDER_NEXSMS = 'nexsms';
const DEFAULT_PROVIDER = PROVIDER_HERO_SMS; const DEFAULT_PROVIDER = PROVIDER_HERO_SMS;
const DEFAULT_PROVIDER_ORDER = Object.freeze([
PROVIDER_HERO_SMS,
PROVIDER_FIVE_SIM,
PROVIDER_NEXSMS,
]);
const PROVIDER_DEFINITIONS = Object.freeze({
[PROVIDER_HERO_SMS]: Object.freeze({
id: PROVIDER_HERO_SMS,
label: 'HeroSMS',
moduleKey: 'PhoneSmsHeroSmsProvider',
}),
[PROVIDER_FIVE_SIM]: Object.freeze({
id: PROVIDER_FIVE_SIM,
label: '5sim',
moduleKey: 'PhoneSmsFiveSimProvider',
}),
[PROVIDER_NEXSMS]: Object.freeze({
id: PROVIDER_NEXSMS,
label: 'NexSMS',
moduleKey: 'PhoneSmsNexSmsProvider',
}),
});
function normalizeProviderId(value = '') { function resolveProviderKey(value = '') {
const normalized = String(value || '').trim().toLowerCase(); if (value && typeof value === 'object' && !Array.isArray(value)) {
return normalized === PROVIDER_FIVE_SIM ? PROVIDER_FIVE_SIM : PROVIDER_HERO_SMS; return String(value.provider || value.id || value.value || '').trim().toLowerCase();
}
return String(value || '').trim().toLowerCase();
}
function isKnownProviderId(providerId = '') {
return Boolean(PROVIDER_DEFINITIONS[providerId]);
}
function normalizeProviderId(value = '', fallback = DEFAULT_PROVIDER) {
const normalized = resolveProviderKey(value);
if (isKnownProviderId(normalized)) {
return normalized;
}
const fallbackNormalized = resolveProviderKey(fallback);
if (isKnownProviderId(fallbackNormalized)) {
return fallbackNormalized;
}
return DEFAULT_PROVIDER;
}
function normalizeProviderOrder(value = [], fallbackOrder = DEFAULT_PROVIDER_ORDER) {
const source = Array.isArray(value)
? value
: String(value || '')
.split(/[\r\n,;]+/)
.map((entry) => String(entry || '').trim())
.filter(Boolean);
const normalized = [];
const seen = new Set();
const pushProvider = (entry) => {
const provider = normalizeProviderId(entry, DEFAULT_PROVIDER);
if (!provider || seen.has(provider)) {
return;
}
seen.add(provider);
normalized.push(provider);
};
source.forEach((entry) => {
pushProvider(entry && typeof entry === 'object' && !Array.isArray(entry)
? (entry.provider || entry.id || entry.value || '')
: entry);
});
if (normalized.length) {
return normalized.slice(0, DEFAULT_PROVIDER_ORDER.length);
}
const fallback = Array.isArray(fallbackOrder) ? fallbackOrder : [];
fallback.forEach((entry) => {
pushProvider(entry && typeof entry === 'object' && !Array.isArray(entry)
? (entry.provider || entry.id || entry.value || '')
: entry);
});
return normalized.slice(0, DEFAULT_PROVIDER_ORDER.length);
}
function getProviderIds() {
return DEFAULT_PROVIDER_ORDER.slice();
}
function getProviderDefinition(providerId = DEFAULT_PROVIDER) {
return PROVIDER_DEFINITIONS[normalizeProviderId(providerId)] || PROVIDER_DEFINITIONS[DEFAULT_PROVIDER];
} }
function getProviderModule(providerId = DEFAULT_PROVIDER) { function getProviderModule(providerId = DEFAULT_PROVIDER) {
const normalized = normalizeProviderId(providerId); const definition = getProviderDefinition(providerId);
if (normalized === PROVIDER_FIVE_SIM) { const moduleKey = definition?.moduleKey;
return root.PhoneSmsFiveSimProvider || null; if (!moduleKey) {
return null;
} }
return root.PhoneSmsHeroSmsProvider || null; return root?.[moduleKey] || null;
} }
function createProvider(providerId = DEFAULT_PROVIDER, deps = {}) { function createProvider(providerId = DEFAULT_PROVIDER, deps = {}) {
const definition = getProviderDefinition(providerId);
const module = getProviderModule(providerId); const module = getProviderModule(providerId);
if (!module || typeof module.createProvider !== 'function') { if (!module || typeof module.createProvider !== 'function') {
throw new Error(`Phone SMS provider is not loaded: ${normalizeProviderId(providerId)}`); throw new Error(`Phone SMS provider module is not loaded: ${definition.id}`);
} }
return module.createProvider(deps); return module.createProvider(deps);
} }
function getProviderLabel(providerId = DEFAULT_PROVIDER) { function getProviderLabel(providerId = DEFAULT_PROVIDER) {
return normalizeProviderId(providerId) === PROVIDER_FIVE_SIM ? '5sim' : 'HeroSMS'; return getProviderDefinition(providerId)?.label || 'HeroSMS';
} }
return { return {
PROVIDER_HERO_SMS, PROVIDER_HERO_SMS,
PROVIDER_FIVE_SIM, PROVIDER_FIVE_SIM,
PROVIDER_NEXSMS,
DEFAULT_PROVIDER, DEFAULT_PROVIDER,
DEFAULT_PROVIDER_ORDER,
PROVIDER_DEFINITIONS,
normalizeProviderId, normalizeProviderId,
normalizeProviderOrder,
getProviderIds,
getProviderDefinition,
getProviderModule, getProviderModule,
createProvider, createProvider,
getProviderLabel, getProviderLabel,
+46 -22
View File
@@ -3333,9 +3333,13 @@ function collectSettingsPayload() {
const defaultPhoneCodePollMaxRounds = typeof DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS !== 'undefined' const defaultPhoneCodePollMaxRounds = typeof DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS !== 'undefined'
? DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS ? DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS
: 12; : 12;
const heroSmsReuseEnabledValue = typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled const phoneSmsReuseEnabledValue = typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled
? normalizeHeroSmsReuseEnabledValue(inputHeroSmsReuseEnabled.checked) ? normalizeHeroSmsReuseEnabledValue(inputHeroSmsReuseEnabled.checked)
: defaultHeroSmsReuseEnabled; : normalizeHeroSmsReuseEnabledValue(
latestState?.phoneSmsReuseEnabled,
latestState?.heroSmsReuseEnabled
);
const heroSmsReuseEnabledValue = phoneSmsReuseEnabledValue;
const freePhoneReuseEnabledValue = typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled const freePhoneReuseEnabledValue = typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled
? Boolean(inputFreePhoneReuseEnabled.checked) ? Boolean(inputFreePhoneReuseEnabled.checked)
: Boolean(latestState?.freePhoneReuseEnabled); : Boolean(latestState?.freePhoneReuseEnabled);
@@ -3782,6 +3786,7 @@ function collectSettingsPayload() {
nexSmsApiKey: nexSmsApiKeyValue, nexSmsApiKey: nexSmsApiKeyValue,
nexSmsCountryOrder: nexSmsCountryOrderValue, nexSmsCountryOrder: nexSmsCountryOrderValue,
nexSmsServiceCode: nexSmsServiceCodeValue, nexSmsServiceCode: nexSmsServiceCodeValue,
phoneSmsReuseEnabled: phoneSmsReuseEnabledValue,
heroSmsReuseEnabled: heroSmsReuseEnabledValue, heroSmsReuseEnabled: heroSmsReuseEnabledValue,
freePhoneReuseEnabled: freePhoneReuseEnabledValue, freePhoneReuseEnabled: freePhoneReuseEnabledValue,
freePhoneReuseAutoEnabled: freePhoneReuseAutoEnabledValue, freePhoneReuseAutoEnabled: freePhoneReuseAutoEnabledValue,
@@ -3851,15 +3856,22 @@ function normalizeAccountRunHistoryHelperBaseUrlValue(value = '') {
function normalizePhoneSmsProvider(value = '') { function normalizePhoneSmsProvider(value = '') {
if (typeof window !== 'undefined' && window.PhoneSmsProviderRegistry?.normalizeProviderId) { const rootScope = typeof window !== 'undefined' ? window : globalThis;
return window.PhoneSmsProviderRegistry.normalizeProviderId(value); if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderId) {
return rootScope.PhoneSmsProviderRegistry.normalizeProviderId(value);
} }
const nexSmsProvider = typeof PHONE_SMS_PROVIDER_NEXSMS !== 'undefined'
? PHONE_SMS_PROVIDER_NEXSMS
: 'nexsms';
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
return normalized === PHONE_SMS_PROVIDER_FIVE_SIM if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) {
? PHONE_SMS_PROVIDER_FIVE_SIM return PHONE_SMS_PROVIDER_FIVE_SIM;
: PHONE_SMS_PROVIDER_HERO_SMS; }
if (normalized === nexSmsProvider) {
return nexSmsProvider;
}
return PHONE_SMS_PROVIDER_HERO_SMS;
} }
function setPhoneSmsProviderSelectValue(provider) { function setPhoneSmsProviderSelectValue(provider) {
const normalizedProvider = normalizePhoneSmsProvider(provider); const normalizedProvider = normalizePhoneSmsProvider(provider);
if (selectPhoneSmsProvider) { if (selectPhoneSmsProvider) {
@@ -4042,21 +4054,21 @@ function normalizeHeroSmsMaxPriceValue(value = '') {
} }
function normalizePhoneSmsProviderValue(value = '') { function normalizePhoneSmsProviderValue(value = '') {
const normalized = String(value || '').trim().toLowerCase(); const rootScope = typeof window !== 'undefined' ? window : globalThis;
if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) { if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderId) {
return PHONE_SMS_PROVIDER_FIVE_SIM; return rootScope.PhoneSmsProviderRegistry.normalizeProviderId(value);
} }
if (normalized === PHONE_SMS_PROVIDER_NEXSMS) { return normalizePhoneSmsProvider(value);
return PHONE_SMS_PROVIDER_NEXSMS;
}
return PHONE_SMS_PROVIDER_HERO;
} }
function normalizePhoneSmsProviderOrderValue(value = [], fallbackOrder = []) { function normalizePhoneSmsProviderOrderValue(value = [], fallbackOrder = []) {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderOrder) {
return rootScope.PhoneSmsProviderRegistry.normalizeProviderOrder(value, fallbackOrder);
}
const source = Array.isArray(value) const source = Array.isArray(value)
? value ? value
: String(value || '') : String(value || '')
.split(/[\r\n,;|/]+/) .split(/[\r\n,]+/)
.map((entry) => String(entry || '').trim()) .map((entry) => String(entry || '').trim())
.filter(Boolean); .filter(Boolean);
const normalized = []; const normalized = [];
@@ -4089,7 +4101,6 @@ function normalizePhoneSmsProviderOrderValue(value = [], fallbackOrder = []) {
}); });
return fallbackNormalized.slice(0, 3); return fallbackNormalized.slice(0, 3);
} }
function formatPhoneSmsProviderOrderSummary(order = []) { function formatPhoneSmsProviderOrderSummary(order = []) {
const normalized = normalizePhoneSmsProviderOrderValue(order, []); const normalized = normalizePhoneSmsProviderOrderValue(order, []);
if (!normalized.length) { if (!normalized.length) {
@@ -4593,8 +4604,11 @@ function normalizePhoneCodePollMaxRoundsValue(value, fallback = DEFAULT_PHONE_CO
return Math.max(PHONE_CODE_POLL_MAX_ROUNDS_MIN, Math.min(PHONE_CODE_POLL_MAX_ROUNDS_MAX, parsed)); return Math.max(PHONE_CODE_POLL_MAX_ROUNDS_MIN, Math.min(PHONE_CODE_POLL_MAX_ROUNDS_MAX, parsed));
} }
function normalizeHeroSmsReuseEnabledValue(value) { function normalizeHeroSmsReuseEnabledValue(value, fallbackValue = undefined) {
if (value === undefined || value === null) { if (value === undefined || value === null) {
if (fallbackValue !== undefined && fallbackValue !== null) {
return Boolean(fallbackValue);
}
return DEFAULT_HERO_SMS_REUSE_ENABLED; return DEFAULT_HERO_SMS_REUSE_ENABLED;
} }
return Boolean(value); return Boolean(value);
@@ -8983,7 +8997,10 @@ function applySettingsState(state) {
: String(state?.nexSmsServiceCode || defaultNexSmsServiceCode).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || defaultNexSmsServiceCode; : String(state?.nexSmsServiceCode || defaultNexSmsServiceCode).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || defaultNexSmsServiceCode;
} }
if (typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled) { if (typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled) {
inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(state?.heroSmsReuseEnabled); inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(
state?.phoneSmsReuseEnabled,
state?.heroSmsReuseEnabled
);
} }
if (typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled) { if (typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled) {
inputFreePhoneReuseEnabled.checked = Boolean(state?.freePhoneReuseEnabled); inputFreePhoneReuseEnabled.checked = Boolean(state?.freePhoneReuseEnabled);
@@ -14582,8 +14599,15 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.payload.nexSmsServiceCode !== undefined && inputNexSmsServiceCode) { if (message.payload.nexSmsServiceCode !== undefined && inputNexSmsServiceCode) {
inputNexSmsServiceCode.value = normalizeNexSmsServiceCodeValue(message.payload.nexSmsServiceCode); inputNexSmsServiceCode.value = normalizeNexSmsServiceCodeValue(message.payload.nexSmsServiceCode);
} }
if (message.payload.heroSmsReuseEnabled !== undefined && inputHeroSmsReuseEnabled) { if (
inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(message.payload.heroSmsReuseEnabled); (message.payload.phoneSmsReuseEnabled !== undefined
|| message.payload.heroSmsReuseEnabled !== undefined)
&& inputHeroSmsReuseEnabled
) {
inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(
message.payload.phoneSmsReuseEnabled,
message.payload.heroSmsReuseEnabled
);
} }
if (message.payload.freePhoneReuseEnabled !== undefined && inputFreePhoneReuseEnabled) { if (message.payload.freePhoneReuseEnabled !== undefined && inputFreePhoneReuseEnabled) {
inputFreePhoneReuseEnabled.checked = Boolean(message.payload.freePhoneReuseEnabled); inputFreePhoneReuseEnabled.checked = Boolean(message.payload.freePhoneReuseEnabled);
@@ -81,6 +81,7 @@ test('background account history settings are normalized independently from hotm
extractFunction('normalizeSub2ApiGroupNames'), extractFunction('normalizeSub2ApiGroupNames'),
extractFunction('normalizeBoundedIntegerSetting'), extractFunction('normalizeBoundedIntegerSetting'),
extractFunction('normalizeLocalHttpBaseUrl'), extractFunction('normalizeLocalHttpBaseUrl'),
extractFunction('buildPersistentSettingsPayload'),
extractFunction('normalizePersistentSettingValue'), extractFunction('normalizePersistentSettingValue'),
].join('\n'); ].join('\n');
@@ -181,10 +182,16 @@ function normalizeCloudflareTempEmailReceiveMailbox(value) { return String(value
function normalizeCloudflareTempEmailDomain(value) { return String(value || '').trim(); } function normalizeCloudflareTempEmailDomain(value) { return String(value || '').trim(); }
function normalizeCloudflareTempEmailDomains(value) { return Array.isArray(value) ? value : []; } function normalizeCloudflareTempEmailDomains(value) { return Array.isArray(value) ? value : []; }
function normalizeHotmailAccounts(value) { return Array.isArray(value) ? value : []; } function normalizeHotmailAccounts(value) { return Array.isArray(value) ? value : []; }
function resolveLegacyAutoStepDelaySeconds(value) {
return value && Object.prototype.hasOwnProperty.call(value, 'autoStepDelaySeconds')
? value.autoStepDelaySeconds
: undefined;
}
${bundle} ${bundle}
return { return {
normalizeAccountRunHistoryHelperBaseUrl, normalizeAccountRunHistoryHelperBaseUrl,
normalizePersistentSettingValue, normalizePersistentSettingValue,
buildPersistentSettingsPayload,
}; };
`)(); `)();
@@ -243,6 +250,8 @@ return {
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'NEXSMS'), 'nexsms'); assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'NEXSMS'), 'nexsms');
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'unknown'), 'hero-sms'); assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'unknown'), 'hero-sms');
assert.deepStrictEqual(api.normalizePersistentSettingValue('phoneSmsProviderOrder', ['nexsms', '5sim', 'nexsms']), ['nexsms', '5sim']); assert.deepStrictEqual(api.normalizePersistentSettingValue('phoneSmsProviderOrder', ['nexsms', '5sim', 'nexsms']), ['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 '); assert.equal(api.normalizePersistentSettingValue('fiveSimApiKey', ' demo-five '), ' demo-five ');
assert.equal(api.normalizePersistentSettingValue('fiveSimProduct', ' OpenAI! '), 'openai'); assert.equal(api.normalizePersistentSettingValue('fiveSimProduct', ' OpenAI! '), 'openai');
assert.equal(api.normalizePersistentSettingValue('fiveSimCountryId', ' England! '), 'england'); assert.equal(api.normalizePersistentSettingValue('fiveSimCountryId', ' England! '), 'england');
@@ -335,4 +344,26 @@ return {
countryLabel: '', countryLabel: '',
} }
); );
const legacyReusePayload = api.buildPersistentSettingsPayload({
heroSmsReuseEnabled: false,
});
assert.equal(legacyReusePayload.phoneSmsReuseEnabled, false);
assert.equal(legacyReusePayload.heroSmsReuseEnabled, false);
const fiveSimReusePayload = api.buildPersistentSettingsPayload({
fiveSimReuseEnabled: false,
});
assert.equal(fiveSimReusePayload.phoneSmsReuseEnabled, false);
assert.equal(fiveSimReusePayload.heroSmsReuseEnabled, false);
const conflictingReusePayload = api.buildPersistentSettingsPayload({
heroSmsReuseEnabled: true,
fiveSimReuseEnabled: false,
});
assert.equal(conflictingReusePayload.phoneSmsReuseEnabled, true);
assert.equal(conflictingReusePayload.heroSmsReuseEnabled, true);
const newReusePayload = api.buildPersistentSettingsPayload({
phoneSmsReuseEnabled: false,
heroSmsReuseEnabled: true,
});
assert.equal(newReusePayload.phoneSmsReuseEnabled, false);
assert.equal(newReusePayload.heroSmsReuseEnabled, false);
}); });
@@ -15,6 +15,7 @@ test('background defaults enable free phone reuse switches', () => {
assert.match(defaultsBlock, /freePhoneReuseEnabled:\s*true/); assert.match(defaultsBlock, /freePhoneReuseEnabled:\s*true/);
assert.match(defaultsBlock, /freePhoneReuseAutoEnabled:\s*true/); assert.match(defaultsBlock, /freePhoneReuseAutoEnabled:\s*true/);
assert.match(defaultsBlock, /phoneSmsReuseEnabled:\s*DEFAULT_HERO_SMS_REUSE_ENABLED/);
}); });
test('background free reusable phone setter does not depend on module-scoped phone flow constants', () => { test('background free reusable phone setter does not depend on module-scoped phone flow constants', () => {
+44
View File
@@ -0,0 +1,44 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('phone-sms/providers/registry.js', 'utf8');
function loadRegistry(root = {}) {
return new Function('self', `${source}; return self.PhoneSmsProviderRegistry;`)(root);
}
test('phone sms provider registry normalizes ids, order and labels consistently', () => {
const registry = loadRegistry({
PhoneSmsHeroSmsProvider: {
createProvider: (deps = {}) => ({ provider: 'hero-sms', deps }),
},
PhoneSmsFiveSimProvider: {
createProvider: (deps = {}) => ({ provider: '5sim', deps }),
},
});
assert.deepStrictEqual(registry.getProviderIds(), ['hero-sms', '5sim', 'nexsms']);
assert.equal(registry.normalizeProviderId(' NEXSMS '), 'nexsms');
assert.equal(registry.normalizeProviderId('unknown-provider'), 'hero-sms');
assert.equal(registry.getProviderLabel('nexsms'), 'NexSMS');
assert.equal(registry.getProviderDefinition('nexsms').moduleKey, 'PhoneSmsNexSmsProvider');
assert.deepStrictEqual(
registry.normalizeProviderOrder([
{ provider: 'nexsms' },
{ id: '5sim' },
{ value: 'hero-sms' },
'NEXSMS',
]),
['nexsms', '5sim', 'hero-sms']
);
assert.deepStrictEqual(
registry.normalizeProviderOrder([], ['nexsms', '5sim', 'nexsms']),
['nexsms', '5sim']
);
assert.deepStrictEqual(
registry.createProvider('5sim', { foo: 1 }),
{ provider: '5sim', deps: { foo: 1 } }
);
assert.throws(() => registry.createProvider('nexsms'), /Phone SMS provider module is not loaded: nexsms/);
});
+175 -3
View File
@@ -1948,9 +1948,181 @@ test('phone verification helper acquires a number from 5sim with fallback countr
assert.equal(requests[3].pathname, '/v1/user/buy/activation/england/any/openai'); assert.equal(requests[3].pathname, '/v1/user/buy/activation/england/any/openai');
}); });
test('phone verification helper rejects 5sim maxPrice with custom operator before buying', async () => { test('phone verification helper prefers phoneSmsReuseEnabled over legacy heroSmsReuseEnabled for 5sim acquisition', async () => {
const requests = []; const requests = [];
const helpers = api.createPhoneVerificationHelpers({ const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
requests.push({
pathname: parsedUrl.pathname,
search: parsedUrl.searchParams,
headers: options?.headers || {},
});
if (parsedUrl.pathname === '/v1/guest/prices') {
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
openai: {
thailand: {
any: {
cost: 0.08,
count: 12,
},
},
},
}),
};
}
if (parsedUrl.pathname === '/v1/user/buy/activation/thailand/any/openai') {
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
id: 1234567,
phone: '+66880000000',
country: 'thailand',
country_name: 'Thailand',
product: 'openai',
}),
};
}
throw new Error(`Unexpected 5sim request: ${parsedUrl.pathname}`);
},
getState: async () => ({
phoneSmsProvider: '5sim',
fiveSimApiKey: 'five-token',
fiveSimCountryOrder: ['thailand'],
fiveSimOperator: 'any',
fiveSimProduct: 'openai',
phoneSmsReuseEnabled: false,
heroSmsReuseEnabled: true,
heroSmsActivationRetryRounds: 1,
}),
sendToContentScriptResilient: async () => ({}),
setState: async () => {},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
const activation = await helpers.requestPhoneActivation({
phoneSmsProvider: '5sim',
fiveSimApiKey: 'five-token',
fiveSimCountryOrder: ['thailand'],
fiveSimOperator: 'any',
fiveSimProduct: 'openai',
phoneSmsReuseEnabled: false,
heroSmsReuseEnabled: true,
heroSmsActivationRetryRounds: 1,
});
assert.deepStrictEqual(activation, {
activationId: '1234567',
phoneNumber: '+66880000000',
provider: '5sim',
serviceCode: 'openai',
countryId: 'thailand',
countryCode: 'thailand',
countryLabel: 'Thailand',
successfulUses: 0,
maxUses: 3,
});
assert.equal(requests[0].pathname, '/v1/guest/prices');
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[1].search.get('reuse'), null);
});
test('phone verification helper treats fiveSimReuseEnabled as legacy-only when phoneSmsReuseEnabled is absent', async () => {
const requests = [];
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
requests.push({
pathname: parsedUrl.pathname,
search: parsedUrl.searchParams,
headers: options?.headers || {},
});
if (parsedUrl.pathname === '/v1/guest/prices') {
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
openai: {
thailand: {
any: {
cost: 0.08,
count: 12,
},
},
},
}),
};
}
if (parsedUrl.pathname === '/v1/user/buy/activation/thailand/any/openai') {
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
id: 1234568,
phone: '+66880000001',
country: 'thailand',
country_name: 'Thailand',
product: 'openai',
}),
};
}
throw new Error(`Unexpected 5sim request: ${parsedUrl.pathname}`);
},
getState: async () => ({
phoneSmsProvider: '5sim',
fiveSimApiKey: 'five-token',
fiveSimCountryOrder: ['thailand'],
fiveSimOperator: 'any',
fiveSimProduct: 'openai',
heroSmsReuseEnabled: true,
fiveSimReuseEnabled: false,
heroSmsActivationRetryRounds: 1,
}),
sendToContentScriptResilient: async () => ({}),
setState: async () => {},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
const activation = await helpers.requestPhoneActivation({
phoneSmsProvider: '5sim',
fiveSimApiKey: 'five-token',
fiveSimCountryOrder: ['thailand'],
fiveSimOperator: 'any',
fiveSimProduct: 'openai',
heroSmsReuseEnabled: true,
fiveSimReuseEnabled: false,
heroSmsActivationRetryRounds: 1,
});
assert.deepStrictEqual(activation, {
activationId: '1234568',
phoneNumber: '+66880000001',
provider: '5sim',
serviceCode: 'openai',
countryId: 'thailand',
countryCode: 'thailand',
countryLabel: 'Thailand',
successfulUses: 0,
maxUses: 3,
});
assert.equal(requests[0].pathname, '/v1/guest/prices');
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[1].search.get('reuse'), '1');
});
test('phone verification helper rejects 5sim maxPrice with custom operator before buying', async () => {
const requests = [];
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {}, addLog: async () => {},
ensureStep8SignupPageReady: async () => {}, ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url) => { fetchImpl: async (url) => {
@@ -943,6 +943,7 @@ return { collectSettingsPayload };
assert.equal(payload.nexSmsApiKey, 'nex-key'); assert.equal(payload.nexSmsApiKey, 'nex-key');
assert.deepStrictEqual(payload.nexSmsCountryOrder, [1]); assert.deepStrictEqual(payload.nexSmsCountryOrder, [1]);
assert.equal(payload.nexSmsServiceCode, 'ot'); assert.equal(payload.nexSmsServiceCode, 'ot');
assert.equal(payload.phoneSmsReuseEnabled, true);
assert.equal(payload.heroSmsReuseEnabled, true); assert.equal(payload.heroSmsReuseEnabled, true);
assert.equal(payload.freePhoneReuseEnabled, true); assert.equal(payload.freePhoneReuseEnabled, true);
assert.equal(payload.freePhoneReuseAutoEnabled, true); assert.equal(payload.freePhoneReuseAutoEnabled, true);