@@ -21,7 +21,6 @@ importScripts(
|
|||||||
'gopay-utils.js',
|
'gopay-utils.js',
|
||||||
'phone-sms/providers/hero-sms.js',
|
'phone-sms/providers/hero-sms.js',
|
||||||
'phone-sms/providers/five-sim.js',
|
'phone-sms/providers/five-sim.js',
|
||||||
'phone-sms/providers/nexsms.js',
|
|
||||||
'phone-sms/providers/madao.js',
|
'phone-sms/providers/madao.js',
|
||||||
'phone-sms/providers/registry.js',
|
'phone-sms/providers/registry.js',
|
||||||
'background/phone-verification-flow.js',
|
'background/phone-verification-flow.js',
|
||||||
|
|||||||
@@ -6963,8 +6963,8 @@
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rotateCurrentActivation = async (activationCandidate, reason = '', releaseAction = 'cancel') => {
|
const rotateCurrentActivation = async (reason = '', releaseAction = 'cancel') => {
|
||||||
const normalizedActivation = normalizeActivation(activationCandidate);
|
const normalizedActivation = normalizeActivation(activation);
|
||||||
if (!normalizedActivation || getActivationProviderId(normalizedActivation, state) !== PHONE_SMS_PROVIDER_MADAO) {
|
if (!normalizedActivation || getActivationProviderId(normalizedActivation, state) !== PHONE_SMS_PROVIDER_MADAO) {
|
||||||
return { handled: false, nextActivation: null };
|
return { handled: false, nextActivation: null };
|
||||||
}
|
}
|
||||||
@@ -6984,6 +6984,9 @@
|
|||||||
if (!nextActivation) {
|
if (!nextActivation) {
|
||||||
return { handled: true, nextActivation: null };
|
return { handled: true, nextActivation: null };
|
||||||
}
|
}
|
||||||
|
activation = nextActivation;
|
||||||
|
shouldCancelActivation = true;
|
||||||
|
await persistCurrentActivation(nextActivation);
|
||||||
await addLog(
|
await addLog(
|
||||||
`步骤 9:MaDao 已通过路由替换切换到新号码 ${nextActivation.phoneNumber}。`,
|
`步骤 9:MaDao 已通过路由替换切换到新号码 ${nextActivation.phoneNumber}。`,
|
||||||
'info'
|
'info'
|
||||||
@@ -7001,19 +7004,13 @@
|
|||||||
`步骤 9:添加手机号失败后正在更换号码(${formatStep9Reason(failureReason)},${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`,
|
`步骤 9:添加手机号失败后正在更换号码(${formatStep9Reason(failureReason)},${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`,
|
||||||
'warn'
|
'warn'
|
||||||
);
|
);
|
||||||
const failedActivation = activation;
|
const rotated = shouldCancelActivation && activation
|
||||||
const rotated = shouldCancelActivation && failedActivation
|
|
||||||
? await rotateCurrentActivation(
|
? await rotateCurrentActivation(
|
||||||
failedActivation,
|
|
||||||
failureCode || failureReason,
|
failureCode || failureReason,
|
||||||
getPhoneReplacementReleaseAction(failureCode || failureReason)
|
getPhoneReplacementReleaseAction(failureCode || failureReason)
|
||||||
)
|
)
|
||||||
: { handled: false, nextActivation: null };
|
: { handled: false, nextActivation: null };
|
||||||
if (rotated.nextActivation) {
|
if (rotated.nextActivation) {
|
||||||
activation = normalizeActivation(rotated.nextActivation);
|
|
||||||
shouldCancelActivation = true;
|
|
||||||
await clearCurrentActivation();
|
|
||||||
await persistCurrentActivation(activation);
|
|
||||||
preferReuseExistingActivationOnAddPhone = false;
|
preferReuseExistingActivationOnAddPhone = false;
|
||||||
addPhoneReentryWithSameActivation = 0;
|
addPhoneReentryWithSameActivation = 0;
|
||||||
pageState = {
|
pageState = {
|
||||||
@@ -7129,14 +7126,9 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const rotated = shouldCancelActivation && activation
|
const rotated = shouldCancelActivation && activation
|
||||||
? await rotateCurrentActivation(activation, 'returned_to_add_phone_loop', 'cancel')
|
? await rotateCurrentActivation('returned_to_add_phone_loop', 'cancel')
|
||||||
: { handled: false, nextActivation: null };
|
: { handled: false, nextActivation: null };
|
||||||
if (rotated.nextActivation) {
|
if (!rotated.nextActivation) {
|
||||||
activation = normalizeActivation(rotated.nextActivation);
|
|
||||||
shouldCancelActivation = true;
|
|
||||||
await clearCurrentActivation();
|
|
||||||
await persistCurrentActivation(activation);
|
|
||||||
} else {
|
|
||||||
if (!rotated.handled && shouldCancelActivation && activation) {
|
if (!rotated.handled && shouldCancelActivation && activation) {
|
||||||
await cancelPhoneActivation(state, activation);
|
await cancelPhoneActivation(state, activation);
|
||||||
}
|
}
|
||||||
@@ -7199,14 +7191,9 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const rotated = shouldCancelActivation && activation
|
const rotated = shouldCancelActivation && activation
|
||||||
? await rotateCurrentActivation(activation, 'phone_number_used', 'ban')
|
? await rotateCurrentActivation('phone_number_used', 'ban')
|
||||||
: { handled: false, nextActivation: null };
|
: { handled: false, nextActivation: null };
|
||||||
if (rotated.nextActivation) {
|
if (!rotated.nextActivation) {
|
||||||
activation = normalizeActivation(rotated.nextActivation);
|
|
||||||
shouldCancelActivation = true;
|
|
||||||
await clearCurrentActivation();
|
|
||||||
await persistCurrentActivation(activation);
|
|
||||||
} else {
|
|
||||||
if (!rotated.handled && shouldCancelActivation && activation) {
|
if (!rotated.handled && shouldCancelActivation && activation) {
|
||||||
await banPhoneActivation(state, activation);
|
await banPhoneActivation(state, activation);
|
||||||
}
|
}
|
||||||
@@ -7295,8 +7282,6 @@
|
|||||||
|
|
||||||
let shouldReplaceNumber = false;
|
let shouldReplaceNumber = false;
|
||||||
let replaceReason = '';
|
let replaceReason = '';
|
||||||
let failedActivationForReplacement = null;
|
|
||||||
let replacementPreparedInAdvance = false;
|
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= DEFAULT_PHONE_SUBMIT_ATTEMPTS; attempt += 1) {
|
for (let attempt = 1; attempt <= DEFAULT_PHONE_SUBMIT_ATTEMPTS; attempt += 1) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
@@ -7306,7 +7291,6 @@
|
|||||||
await markPreferredActivationExhausted(codeResult.reason || 'sms_timeout');
|
await markPreferredActivationExhausted(codeResult.reason || 'sms_timeout');
|
||||||
shouldReplaceNumber = true;
|
shouldReplaceNumber = true;
|
||||||
replaceReason = codeResult.reason || 'sms_not_received';
|
replaceReason = codeResult.reason || 'sms_not_received';
|
||||||
failedActivationForReplacement = activation;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7342,7 +7326,6 @@
|
|||||||
if (isPhoneNumberUsedError(invalidErrorText)) {
|
if (isPhoneNumberUsedError(invalidErrorText)) {
|
||||||
shouldReplaceNumber = true;
|
shouldReplaceNumber = true;
|
||||||
replaceReason = 'phone_number_used';
|
replaceReason = 'phone_number_used';
|
||||||
failedActivationForReplacement = activation;
|
|
||||||
await discardPhoneActivationFromReuse(
|
await discardPhoneActivationFromReuse(
|
||||||
`目标站拒绝该号码(${invalidErrorText})。`,
|
`目标站拒绝该号码(${invalidErrorText})。`,
|
||||||
activation,
|
activation,
|
||||||
@@ -7353,19 +7336,13 @@
|
|||||||
`自动白嫖复用号码 ${activation.phoneNumber} 被目标站拒绝。`
|
`自动白嫖复用号码 ${activation.phoneNumber} 被目标站拒绝。`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (shouldCancelActivation && activation) {
|
if (
|
||||||
const rotated = await rotateCurrentActivation(activation, 'phone_number_used', 'ban');
|
shouldCancelActivation
|
||||||
if (rotated.nextActivation) {
|
&& activation
|
||||||
activation = normalizeActivation(rotated.nextActivation);
|
&& getActivationProviderId(activation, state) !== PHONE_SMS_PROVIDER_MADAO
|
||||||
shouldCancelActivation = true;
|
) {
|
||||||
replacementPreparedInAdvance = true;
|
await banPhoneActivation(state, activation);
|
||||||
} else if (rotated.handled) {
|
shouldCancelActivation = false;
|
||||||
activation = null;
|
|
||||||
shouldCancelActivation = false;
|
|
||||||
} else {
|
|
||||||
await banPhoneActivation(state, activation);
|
|
||||||
shouldCancelActivation = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await addLog(
|
await addLog(
|
||||||
`步骤 9:手机号被提示已使用(${invalidErrorText}),立即更换新号码。`,
|
`步骤 9:手机号被提示已使用(${invalidErrorText}),立即更换新号码。`,
|
||||||
@@ -7377,20 +7354,13 @@
|
|||||||
if (attempt >= DEFAULT_PHONE_SUBMIT_ATTEMPTS) {
|
if (attempt >= DEFAULT_PHONE_SUBMIT_ATTEMPTS) {
|
||||||
shouldReplaceNumber = true;
|
shouldReplaceNumber = true;
|
||||||
replaceReason = 'code_rejected';
|
replaceReason = 'code_rejected';
|
||||||
failedActivationForReplacement = activation;
|
if (
|
||||||
if (shouldCancelActivation && activation) {
|
shouldCancelActivation
|
||||||
const rotated = await rotateCurrentActivation(activation, 'code_rejected', 'ban');
|
&& activation
|
||||||
if (rotated.nextActivation) {
|
&& getActivationProviderId(activation, state) !== PHONE_SMS_PROVIDER_MADAO
|
||||||
activation = normalizeActivation(rotated.nextActivation);
|
) {
|
||||||
shouldCancelActivation = true;
|
await banPhoneActivation(state, activation);
|
||||||
replacementPreparedInAdvance = true;
|
shouldCancelActivation = false;
|
||||||
} else if (rotated.handled) {
|
|
||||||
activation = null;
|
|
||||||
shouldCancelActivation = false;
|
|
||||||
} else {
|
|
||||||
await banPhoneActivation(state, activation);
|
|
||||||
shouldCancelActivation = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await addLog(
|
await addLog(
|
||||||
`步骤 9:手机验证码连续 ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} 次被拒(${invalidErrorText}),将更换号码。`,
|
`步骤 9:手机验证码连续 ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} 次被拒(${invalidErrorText}),将更换号码。`,
|
||||||
@@ -7496,47 +7466,33 @@
|
|||||||
throw buildPhoneReplacementLimitError(maxNumberReplacementAttempts, replaceReason || 'unknown');
|
throw buildPhoneReplacementLimitError(maxNumberReplacementAttempts, replaceReason || 'unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
const failedActivation = failedActivationForReplacement || activation;
|
const rotated = shouldCancelActivation && activation
|
||||||
const rotated = replacementPreparedInAdvance
|
? await rotateCurrentActivation(
|
||||||
? { handled: true, nextActivation: activation }
|
replaceReason || 'replace_number',
|
||||||
: (
|
getPhoneReplacementReleaseAction(replaceReason || 'replace_number')
|
||||||
shouldCancelActivation && failedActivation
|
)
|
||||||
? await rotateCurrentActivation(
|
: { handled: false, nextActivation: null };
|
||||||
failedActivation,
|
if (shouldRetireFreeReusableActivationOnFailure(await getState(), activation)) {
|
||||||
replaceReason || 'replace_number',
|
|
||||||
getPhoneReplacementReleaseAction(replaceReason || 'replace_number')
|
|
||||||
)
|
|
||||||
: { handled: false, nextActivation: null }
|
|
||||||
);
|
|
||||||
if (shouldRetireFreeReusableActivationOnFailure(await getState(), failedActivation)) {
|
|
||||||
await retireFreeReusableActivation(
|
await retireFreeReusableActivation(
|
||||||
`自动白嫖复用号码 ${failedActivation.phoneNumber} 在失败后被更换。`
|
`自动白嫖复用号码 ${activation.phoneNumber} 在失败后被更换。`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isPhoneNumberUsedFailureReason(replaceReason) && failedActivation) {
|
if (isPhoneNumberUsedFailureReason(replaceReason)) {
|
||||||
await discardPhoneActivationFromReuse(
|
await discardPhoneActivationFromReuse(
|
||||||
`目标站拒绝该号码(${replaceReason})。`,
|
`目标站拒绝该号码(${replaceReason})。`,
|
||||||
failedActivation,
|
activation,
|
||||||
await getState()
|
await getState()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await clearCurrentActivation();
|
if (!rotated.nextActivation) {
|
||||||
if (rotated.nextActivation) {
|
if (!rotated.handled && shouldCancelActivation && activation) {
|
||||||
activation = normalizeActivation(rotated.nextActivation);
|
await cancelPhoneActivation(state, activation);
|
||||||
shouldCancelActivation = Boolean(activation);
|
|
||||||
if (activation) {
|
|
||||||
await persistCurrentActivation(activation);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!rotated.handled && shouldCancelActivation && failedActivation) {
|
|
||||||
await cancelPhoneActivation(state, failedActivation);
|
|
||||||
}
|
}
|
||||||
|
await clearCurrentActivation();
|
||||||
activation = null;
|
activation = null;
|
||||||
shouldCancelActivation = false;
|
shouldCancelActivation = false;
|
||||||
}
|
}
|
||||||
addPhoneReentryWithSameActivation = 0;
|
addPhoneReentryWithSameActivation = 0;
|
||||||
failedActivationForReplacement = null;
|
|
||||||
replacementPreparedInAdvance = false;
|
|
||||||
|
|
||||||
let returnResult = null;
|
let returnResult = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,265 +0,0 @@
|
|||||||
// phone-sms/providers/nexsms.js - NexSMS provider registry adapter
|
|
||||||
(function attachNexSmsProvider(root, factory) {
|
|
||||||
root.PhoneSmsNexSmsProvider = factory();
|
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createNexSmsProviderModule() {
|
|
||||||
const PROVIDER_ID = 'nexsms';
|
|
||||||
const DEFAULT_BASE_URL = 'https://api.nexsms.net';
|
|
||||||
const DEFAULT_SERVICE_CODE = 'ot';
|
|
||||||
const DEFAULT_SERVICE_LABEL = 'OpenAI';
|
|
||||||
const DEFAULT_COUNTRY_ID = 1;
|
|
||||||
const DEFAULT_COUNTRY_LABEL = 'Country #1';
|
|
||||||
const DEFAULT_REQUEST_TIMEOUT_MS = 20000;
|
|
||||||
|
|
||||||
function normalizeBaseUrl(value = '') {
|
|
||||||
const trimmed = String(value || '').trim() || DEFAULT_BASE_URL;
|
|
||||||
try {
|
|
||||||
return new URL(trimmed).toString().replace(/\/+$/, '');
|
|
||||||
} catch {
|
|
||||||
return DEFAULT_BASE_URL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeText(value = '', fallback = '') {
|
|
||||||
return String(value || '').trim() || fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeNexSmsCountryId(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));
|
|
||||||
if (Number.isFinite(fallbackParsed) && fallbackParsed >= 0) {
|
|
||||||
return fallbackParsed;
|
|
||||||
}
|
|
||||||
return DEFAULT_COUNTRY_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeNexSmsCountryLabel(value = '', fallback = DEFAULT_COUNTRY_LABEL) {
|
|
||||||
return normalizeText(value, fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeNexSmsServiceCode(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 normalizeNexSmsCountryOrder(value = []) {
|
|
||||||
const source = Array.isArray(value)
|
|
||||||
? value
|
|
||||||
: String(value || '')
|
|
||||||
.split(/[\r\n,,;;]+/)
|
|
||||||
.map((entry) => String(entry || '').trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
const normalized = [];
|
|
||||||
const seen = new Set();
|
|
||||||
|
|
||||||
source.forEach((entry) => {
|
|
||||||
let id = -1;
|
|
||||||
let label = '';
|
|
||||||
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
||||||
id = normalizeNexSmsCountryId(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 = normalizeNexSmsCountryId(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 resolveCountryCandidates(state = {}) {
|
|
||||||
const candidates = normalizeNexSmsCountryOrder(state?.nexSmsCountryOrder);
|
|
||||||
if (candidates.length) {
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
return [{
|
|
||||||
id: normalizeNexSmsCountryId(state?.nexSmsCountryId, DEFAULT_COUNTRY_ID),
|
|
||||||
label: normalizeNexSmsCountryLabel(state?.nexSmsCountryLabel, DEFAULT_COUNTRY_LABEL),
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePayload(text) {
|
|
||||||
const trimmed = String(text || '').trim();
|
|
||||||
if (!trimmed) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.parse(trimmed);
|
|
||||||
} catch {
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function describePayload(raw) {
|
|
||||||
if (typeof raw === 'string') {
|
|
||||||
return raw.trim();
|
|
||||||
}
|
|
||||||
if (raw && typeof raw === 'object') {
|
|
||||||
const message = normalizeText(raw.message || raw.error || raw.msg || raw.statusText, '');
|
|
||||||
if (message) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.stringify(raw);
|
|
||||||
} catch {
|
|
||||||
return String(raw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return String(raw || '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSuccessPayload(payload) {
|
|
||||||
return Boolean(payload && typeof payload === 'object' && !Array.isArray(payload) && Number(payload.code) === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveConfig(state = {}, deps = {}) {
|
|
||||||
return {
|
|
||||||
apiKey: normalizeText(state?.nexSmsApiKey),
|
|
||||||
baseUrl: normalizeBaseUrl(state?.nexSmsBaseUrl || DEFAULT_BASE_URL),
|
|
||||||
serviceCode: normalizeNexSmsServiceCode(state?.nexSmsServiceCode, DEFAULT_SERVICE_CODE),
|
|
||||||
fetchImpl: deps.fetchImpl || (typeof fetch === 'function' ? fetch.bind(globalThis) : null),
|
|
||||||
requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchPayload(config, path, actionLabel, options = {}) {
|
|
||||||
if (!config.fetchImpl) {
|
|
||||||
throw new Error('NexSMS 网络请求实现不可用。');
|
|
||||||
}
|
|
||||||
if (!config.apiKey) {
|
|
||||||
throw new Error('NexSMS 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 method = String(options.method || 'GET').trim().toUpperCase() || 'GET';
|
|
||||||
const requestUrl = new URL(path.replace(/^\/+/, ''), `${config.baseUrl.replace(/\/+$/, '')}/`);
|
|
||||||
requestUrl.searchParams.set('apiKey', config.apiKey);
|
|
||||||
Object.entries(options.query || {}).forEach(([key, value]) => {
|
|
||||||
if (value === undefined || value === null || value === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
requestUrl.searchParams.set(key, String(value));
|
|
||||||
});
|
|
||||||
const headers = {
|
|
||||||
Accept: 'application/json',
|
|
||||||
...(options.headers && typeof options.headers === 'object' ? options.headers : {}),
|
|
||||||
};
|
|
||||||
const requestInit = {
|
|
||||||
method,
|
|
||||||
headers,
|
|
||||||
signal: controller?.signal,
|
|
||||||
};
|
|
||||||
if (method !== 'GET' && method !== 'HEAD' && options.body !== undefined) {
|
|
||||||
requestInit.body = typeof options.body === 'string'
|
|
||||||
? options.body
|
|
||||||
: JSON.stringify(options.body);
|
|
||||||
if (!requestInit.headers['Content-Type']) {
|
|
||||||
requestInit.headers['Content-Type'] = 'application/json';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const response = await config.fetchImpl(requestUrl.toString(), requestInit);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return payload;
|
|
||||||
} catch (error) {
|
|
||||||
if (error?.name === 'AbortError') {
|
|
||||||
throw new Error(`${actionLabel}超时。`);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
if (timeoutId) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchBalance(state = {}, deps = {}) {
|
|
||||||
const config = resolveConfig(state, deps);
|
|
||||||
const payload = await fetchPayload(config, '/api/user/getBalance', 'NexSMS get balance');
|
|
||||||
if (!isSuccessPayload(payload)) {
|
|
||||||
throw new Error(`NexSMS get balance 失败:${describePayload(payload) || 'empty response'}`);
|
|
||||||
}
|
|
||||||
const balance = Number(payload?.data?.balance);
|
|
||||||
return {
|
|
||||||
balance: Number.isFinite(balance) ? balance : null,
|
|
||||||
raw: payload,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchPrices(state = {}, countryConfig = {}, deps = {}) {
|
|
||||||
const config = resolveConfig(state, deps);
|
|
||||||
const countryId = normalizeNexSmsCountryId(countryConfig?.id, DEFAULT_COUNTRY_ID);
|
|
||||||
return fetchPayload(config, '/api/getCountryByService', 'NexSMS getCountryByService', {
|
|
||||||
query: {
|
|
||||||
serviceCode: config.serviceCode,
|
|
||||||
countryId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createProvider(deps = {}) {
|
|
||||||
const providerDeps = {
|
|
||||||
fetchImpl: deps.fetchImpl,
|
|
||||||
requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
id: PROVIDER_ID,
|
|
||||||
label: 'NexSMS',
|
|
||||||
defaultCountryId: DEFAULT_COUNTRY_ID,
|
|
||||||
defaultCountryLabel: DEFAULT_COUNTRY_LABEL,
|
|
||||||
defaultProduct: DEFAULT_SERVICE_LABEL,
|
|
||||||
defaultServiceCode: DEFAULT_SERVICE_CODE,
|
|
||||||
normalizeCountryId: normalizeNexSmsCountryId,
|
|
||||||
normalizeCountryLabel: normalizeNexSmsCountryLabel,
|
|
||||||
normalizeCountryOrder: normalizeNexSmsCountryOrder,
|
|
||||||
normalizeServiceCode: normalizeNexSmsServiceCode,
|
|
||||||
resolveCountryCandidates,
|
|
||||||
fetchBalance: (state) => fetchBalance(state, providerDeps),
|
|
||||||
fetchPrices: (state, countryConfig) => fetchPrices(state, countryConfig, providerDeps),
|
|
||||||
describePayload,
|
|
||||||
isSuccessPayload,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
PROVIDER_ID,
|
|
||||||
DEFAULT_BASE_URL,
|
|
||||||
DEFAULT_COUNTRY_ID,
|
|
||||||
DEFAULT_COUNTRY_LABEL,
|
|
||||||
DEFAULT_SERVICE_CODE,
|
|
||||||
DEFAULT_SERVICE_LABEL,
|
|
||||||
createProvider,
|
|
||||||
describePayload,
|
|
||||||
isSuccessPayload,
|
|
||||||
normalizeNexSmsCountryId,
|
|
||||||
normalizeNexSmsCountryLabel,
|
|
||||||
normalizeNexSmsCountryOrder,
|
|
||||||
normalizeNexSmsServiceCode,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1896,7 +1896,6 @@
|
|||||||
<script src="../gopay-utils.js"></script>
|
<script src="../gopay-utils.js"></script>
|
||||||
<script src="../phone-sms/providers/hero-sms.js"></script>
|
<script src="../phone-sms/providers/hero-sms.js"></script>
|
||||||
<script src="../phone-sms/providers/five-sim.js"></script>
|
<script src="../phone-sms/providers/five-sim.js"></script>
|
||||||
<script src="../phone-sms/providers/nexsms.js"></script>
|
|
||||||
<script src="../phone-sms/providers/madao.js"></script>
|
<script src="../phone-sms/providers/madao.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>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ const assert = require('node:assert/strict');
|
|||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
|
|
||||||
const source = fs.readFileSync('phone-sms/providers/registry.js', 'utf8');
|
const source = fs.readFileSync('phone-sms/providers/registry.js', 'utf8');
|
||||||
const nexSmsSource = fs.readFileSync('phone-sms/providers/nexsms.js', 'utf8');
|
|
||||||
|
|
||||||
function loadRegistry(root = {}) {
|
function loadRegistry(root = {}) {
|
||||||
return new Function('self', `${source}; return self.PhoneSmsProviderRegistry;`)(root);
|
return new Function('self', `${source}; return self.PhoneSmsProviderRegistry;`)(root);
|
||||||
@@ -17,9 +16,6 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
|||||||
PhoneSmsFiveSimProvider: {
|
PhoneSmsFiveSimProvider: {
|
||||||
createProvider: (deps = {}) => ({ provider: '5sim', deps }),
|
createProvider: (deps = {}) => ({ provider: '5sim', deps }),
|
||||||
},
|
},
|
||||||
PhoneSmsNexSmsProvider: {
|
|
||||||
createProvider: (deps = {}) => ({ provider: 'nexsms', deps }),
|
|
||||||
},
|
|
||||||
PhoneSmsMaDaoProvider: {
|
PhoneSmsMaDaoProvider: {
|
||||||
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
|
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
|
||||||
},
|
},
|
||||||
@@ -56,29 +52,5 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
|||||||
registry.createProvider('madao', { foo: 2 }),
|
registry.createProvider('madao', { foo: 2 }),
|
||||||
{ provider: 'madao', deps: { foo: 2 } }
|
{ provider: 'madao', deps: { foo: 2 } }
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(
|
assert.throws(() => registry.createProvider('nexsms'), /接码平台模块未加载:nexsms/);
|
||||||
registry.createProvider('nexsms', { foo: 3 }),
|
|
||||||
{ provider: 'nexsms', deps: { foo: 3 } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('phone sms provider registry creates the real NexSMS provider module', () => {
|
|
||||||
const root = {};
|
|
||||||
const nexSmsModule = new Function('self', `${nexSmsSource}; return self.PhoneSmsNexSmsProvider;`)(root);
|
|
||||||
const registry = loadRegistry({
|
|
||||||
PhoneSmsNexSmsProvider: nexSmsModule,
|
|
||||||
});
|
|
||||||
|
|
||||||
const provider = registry.createProvider('nexsms', { fetchImpl: async () => ({ ok: true, text: async () => '{}' }) });
|
|
||||||
|
|
||||||
assert.equal(provider.id, 'nexsms');
|
|
||||||
assert.equal(provider.label, 'NexSMS');
|
|
||||||
assert.equal(provider.defaultServiceCode, 'ot');
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
provider.normalizeCountryOrder(['1:Thailand', { id: 2, label: 'United States' }, '1:Duplicate']),
|
|
||||||
[
|
|
||||||
{ id: 1, label: 'Thailand' },
|
|
||||||
{ id: 2, label: 'United States' },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8955,148 +8955,6 @@ test('phone verification helper uses MaDao routing replace when add-phone reject
|
|||||||
assert.equal(currentState.currentPhoneActivation, null);
|
assert.equal(currentState.currentPhoneActivation, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper keeps failed MaDao activation separate after routing replace on invalid code', async () => {
|
|
||||||
const requests = [];
|
|
||||||
const submittedPhones = [];
|
|
||||||
const removedReusePhones = [];
|
|
||||||
let verificationSubmitCount = 0;
|
|
||||||
let currentState = {
|
|
||||||
phoneSmsProvider: 'madao',
|
|
||||||
madaoBaseUrl: 'http://127.0.0.1:7822',
|
|
||||||
madaoMode: 'routing_plan',
|
|
||||||
madaoRoutingPlanId: 'rp-openai',
|
|
||||||
verificationResendCount: 0,
|
|
||||||
phoneVerificationReplacementLimit: 2,
|
|
||||||
phoneCodeWaitSeconds: 15,
|
|
||||||
phoneCodeTimeoutWindows: 1,
|
|
||||||
phoneCodePollIntervalSeconds: 1,
|
|
||||||
phoneCodePollMaxRounds: 1,
|
|
||||||
currentPhoneActivation: null,
|
|
||||||
reusablePhoneActivation: null,
|
|
||||||
};
|
|
||||||
const helpers = api.createPhoneVerificationHelpers({
|
|
||||||
addLog: async () => {},
|
|
||||||
createMaDaoProvider: maDaoModule.createProvider,
|
|
||||||
ensureStep8SignupPageReady: async () => {},
|
|
||||||
fetchImpl: async (url, options = {}) => {
|
|
||||||
const parsedUrl = new URL(url);
|
|
||||||
requests.push({ url: parsedUrl, body: options.body ? JSON.parse(options.body) : null });
|
|
||||||
if (parsedUrl.pathname === '/api/acquire') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
text: async () => JSON.stringify({
|
|
||||||
ticket_id: 'madao-old',
|
|
||||||
phone_number: '+441111111111',
|
|
||||||
service: 'openai',
|
|
||||||
country: 'GB',
|
|
||||||
routing_plan_id: 'rp-openai',
|
|
||||||
routing_item_id: 'route-old',
|
|
||||||
status: 'waiting_code',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (parsedUrl.pathname === '/api/routing/replace') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
text: async () => JSON.stringify({
|
|
||||||
current_ticket_id: 'madao-old',
|
|
||||||
current_ticket_release: { ticket_id: 'madao-old', status: 'banned' },
|
|
||||||
next_ticket: {
|
|
||||||
ticket_id: 'madao-new',
|
|
||||||
phone_number: '+442222222222',
|
|
||||||
service: 'openai',
|
|
||||||
country: 'GB',
|
|
||||||
routing_plan_id: 'rp-openai',
|
|
||||||
routing_item_id: 'route-new',
|
|
||||||
status: 'waiting_code',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (parsedUrl.pathname === '/api/poll') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
text: async () => JSON.stringify({
|
|
||||||
ticket_id: parsedUrl.pathname,
|
|
||||||
status: 'code_received',
|
|
||||||
code: requests.filter((entry) => entry.url.pathname === '/api/poll').length === 1 ? '111111' : '222222',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (parsedUrl.pathname === '/api/release') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
text: async () => JSON.stringify({ ok: true }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`);
|
|
||||||
},
|
|
||||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
|
||||||
getState: async () => ({ ...currentState }),
|
|
||||||
sendToContentScriptResilient: async (_source, message) => {
|
|
||||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
|
||||||
submittedPhones.push(message.payload.phoneNumber);
|
|
||||||
return { phoneVerificationPage: true, url: 'https://auth.openai.com/phone-verification' };
|
|
||||||
}
|
|
||||||
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
|
|
||||||
verificationSubmitCount += 1;
|
|
||||||
if (verificationSubmitCount === 1) {
|
|
||||||
return {
|
|
||||||
invalidCode: true,
|
|
||||||
errorText: 'This phone number is already linked to another account',
|
|
||||||
phoneVerificationPage: true,
|
|
||||||
url: 'https://auth.openai.com/phone-verification',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
assert.equal(message.payload.code, '222222');
|
|
||||||
return { success: true, consentReady: true, url: 'https://auth.openai.com/authorize' };
|
|
||||||
}
|
|
||||||
if (message.type === 'RETURN_TO_ADD_PHONE' || message.type === 'STEP8_GET_STATE') {
|
|
||||||
return { addPhonePage: true, phoneVerificationPage: false, url: 'https://auth.openai.com/add-phone' };
|
|
||||||
}
|
|
||||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
|
||||||
},
|
|
||||||
setState: async (updates) => {
|
|
||||||
if (Array.isArray(updates.reusablePhoneActivationPool)) {
|
|
||||||
updates.reusablePhoneActivationPool.forEach((entry) => removedReusePhones.push(entry.phoneNumber));
|
|
||||||
}
|
|
||||||
currentState = { ...currentState, ...updates };
|
|
||||||
},
|
|
||||||
sleepWithStop: async () => {},
|
|
||||||
throwIfStopped: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await helpers.completePhoneVerificationFlow(1, {
|
|
||||||
addPhonePage: true,
|
|
||||||
phoneVerificationPage: false,
|
|
||||||
url: 'https://auth.openai.com/add-phone',
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.deepStrictEqual(result, {
|
|
||||||
success: true,
|
|
||||||
consentReady: true,
|
|
||||||
url: 'https://auth.openai.com/authorize',
|
|
||||||
});
|
|
||||||
assert.deepStrictEqual(submittedPhones, ['+441111111111', '+442222222222']);
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
requests.map((entry) => entry.url.pathname),
|
|
||||||
['/api/acquire', '/api/poll', '/api/routing/replace', '/api/poll', '/api/release']
|
|
||||||
);
|
|
||||||
assert.deepStrictEqual(requests[2].body, {
|
|
||||||
ticket_id: 'madao-old',
|
|
||||||
release_action: 'ban',
|
|
||||||
failed_item_id: 'route-old',
|
|
||||||
reason: 'phone_number_used',
|
|
||||||
});
|
|
||||||
assert.deepStrictEqual(requests[4].body, { ticket_id: 'madao-new', action: 'finish' });
|
|
||||||
assert.equal(currentState.currentPhoneActivation, null);
|
|
||||||
assert.equal(removedReusePhones.includes('+442222222222'), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('phone verification helper reacquires MaDao direct numbers after releasing rejected number', async () => {
|
test('phone verification helper reacquires MaDao direct numbers after releasing rejected number', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const submittedPhones = [];
|
const submittedPhones = [];
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
|||||||
assert.match(html, /id="select-phone-sms-provider"/);
|
assert.match(html, /id="select-phone-sms-provider"/);
|
||||||
assert.match(html, /\.\.\/phone-sms\/providers\/hero-sms\.js/);
|
assert.match(html, /\.\.\/phone-sms\/providers\/hero-sms\.js/);
|
||||||
assert.match(html, /\.\.\/phone-sms\/providers\/five-sim\.js/);
|
assert.match(html, /\.\.\/phone-sms\/providers\/five-sim\.js/);
|
||||||
assert.match(html, /\.\.\/phone-sms\/providers\/nexsms\.js/);
|
|
||||||
assert.match(html, /\.\.\/phone-sms\/providers\/madao\.js/);
|
assert.match(html, /\.\.\/phone-sms\/providers\/madao\.js/);
|
||||||
assert.match(html, /\.\.\/phone-sms\/providers\/registry\.js/);
|
assert.match(html, /\.\.\/phone-sms\/providers\/registry\.js/);
|
||||||
assert.match(html, /<option value="hero-sms">HeroSMS<\/option>/);
|
assert.match(html, /<option value="hero-sms">HeroSMS<\/option>/);
|
||||||
|
|||||||
Reference in New Issue
Block a user