feat: support manual HeroSMS maxPrice and defer phone reuse confirmation until final flow success
- 合并 PR #170 的核心改动:新增 HeroSMS 手动 maxPrice 配置,并调整手机号验证链路直接按手填价格取号 - 本地补充修复:吸收延后确认手机号复用计数、仅在整轮成功后提交 pending activation,以及保留 fresh reset 后的可复用号码状态 - 影响范围:background phone verification flow、platform-verify success cleanup、auto-run fresh reset、sidepanel phone verification settings
This commit is contained in:
@@ -481,6 +481,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
mail2925Accounts: [],
|
mail2925Accounts: [],
|
||||||
paypalAccounts: [],
|
paypalAccounts: [],
|
||||||
heroSmsApiKey: '',
|
heroSmsApiKey: '',
|
||||||
|
heroSmsMaxPrice: '',
|
||||||
heroSmsCountryId: HERO_SMS_COUNTRY_ID,
|
heroSmsCountryId: HERO_SMS_COUNTRY_ID,
|
||||||
heroSmsCountryLabel: HERO_SMS_COUNTRY_LABEL,
|
heroSmsCountryLabel: HERO_SMS_COUNTRY_LABEL,
|
||||||
};
|
};
|
||||||
@@ -563,6 +564,7 @@ const DEFAULT_STATE = {
|
|||||||
currentLuckmailMailCursor: null,
|
currentLuckmailMailCursor: null,
|
||||||
currentPhoneActivation: null,
|
currentPhoneActivation: null,
|
||||||
reusablePhoneActivation: null,
|
reusablePhoneActivation: null,
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
autoRunning: false, // 当前是否处于自动运行中。
|
autoRunning: false, // 当前是否处于自动运行中。
|
||||||
autoRunPhase: 'idle', // 当前自动运行阶段。
|
autoRunPhase: 'idle', // 当前自动运行阶段。
|
||||||
autoRunCurrentRun: 0, // 自动运行当前执行到第几轮。
|
autoRunCurrentRun: 0, // 自动运行当前执行到第几轮。
|
||||||
@@ -1325,6 +1327,8 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizePayPalAccounts(value);
|
return normalizePayPalAccounts(value);
|
||||||
case 'heroSmsApiKey':
|
case 'heroSmsApiKey':
|
||||||
return String(value || '');
|
return String(value || '');
|
||||||
|
case 'heroSmsMaxPrice':
|
||||||
|
return String(value || '').trim();
|
||||||
case 'heroSmsCountryId':
|
case 'heroSmsCountryId':
|
||||||
return Math.max(1, Math.floor(Number(value) || HERO_SMS_COUNTRY_ID));
|
return Math.max(1, Math.floor(Number(value) || HERO_SMS_COUNTRY_ID));
|
||||||
case 'heroSmsCountryLabel':
|
case 'heroSmsCountryLabel':
|
||||||
@@ -5292,6 +5296,13 @@ async function finalizeIcloudAliasAfterSuccessfulFlow(state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function finalizePhoneActivationAfterSuccessfulFlow(state) {
|
||||||
|
if (typeof phoneVerificationHelpers?.finalizePendingPhoneActivationConfirmation !== 'function') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return phoneVerificationHelpers.finalizePendingPhoneActivationConfirmation(state);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Tab Registry
|
// Tab Registry
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -6027,6 +6038,7 @@ function getDownstreamStateResets(step, state = {}) {
|
|||||||
loginVerificationRequestedAt: null,
|
loginVerificationRequestedAt: null,
|
||||||
oauthFlowDeadlineAt: null,
|
oauthFlowDeadlineAt: null,
|
||||||
oauthFlowDeadlineSourceUrl: null,
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
lastSignupCode: null,
|
lastSignupCode: null,
|
||||||
lastLoginCode: null,
|
lastLoginCode: null,
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
@@ -6041,6 +6053,7 @@ function getDownstreamStateResets(step, state = {}) {
|
|||||||
loginVerificationRequestedAt: null,
|
loginVerificationRequestedAt: null,
|
||||||
oauthFlowDeadlineAt: null,
|
oauthFlowDeadlineAt: null,
|
||||||
oauthFlowDeadlineSourceUrl: null,
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
lastSignupCode: null,
|
lastSignupCode: null,
|
||||||
lastLoginCode: null,
|
lastLoginCode: null,
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
@@ -6054,6 +6067,7 @@ function getDownstreamStateResets(step, state = {}) {
|
|||||||
loginVerificationRequestedAt: null,
|
loginVerificationRequestedAt: null,
|
||||||
oauthFlowDeadlineAt: null,
|
oauthFlowDeadlineAt: null,
|
||||||
oauthFlowDeadlineSourceUrl: null,
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
lastSignupCode: null,
|
lastSignupCode: null,
|
||||||
lastLoginCode: null,
|
lastLoginCode: null,
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
@@ -6076,11 +6090,13 @@ function getDownstreamStateResets(step, state = {}) {
|
|||||||
loginVerificationRequestedAt: null,
|
loginVerificationRequestedAt: null,
|
||||||
oauthFlowDeadlineAt: null,
|
oauthFlowDeadlineAt: null,
|
||||||
oauthFlowDeadlineSourceUrl: null,
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (step === 9) {
|
if (step === 9) {
|
||||||
return {
|
return {
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
plusReturnUrl: '',
|
plusReturnUrl: '',
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
};
|
};
|
||||||
@@ -6091,11 +6107,13 @@ function getDownstreamStateResets(step, state = {}) {
|
|||||||
loginVerificationRequestedAt: null,
|
loginVerificationRequestedAt: null,
|
||||||
oauthFlowDeadlineAt: null,
|
oauthFlowDeadlineAt: null,
|
||||||
oauthFlowDeadlineSourceUrl: null,
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (stepKey === 'confirm-oauth') {
|
if (stepKey === 'confirm-oauth') {
|
||||||
return {
|
return {
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6946,6 +6964,7 @@ async function handleStepData(step, payload) {
|
|||||||
if ((shouldUseCustomRegistrationEmail(latestState) || shouldClearCustomPoolEmail) && latestState.email) {
|
if ((shouldUseCustomRegistrationEmail(latestState) || shouldClearCustomPoolEmail) && latestState.email) {
|
||||||
await setEmailStateSilently(null);
|
await setEmailStateSilently(null);
|
||||||
}
|
}
|
||||||
|
await finalizePhoneActivationAfterSuccessfulFlow(latestState);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8824,6 +8843,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
executeStepViaCompletionSignal,
|
executeStepViaCompletionSignal,
|
||||||
exportSettingsBundle,
|
exportSettingsBundle,
|
||||||
fetchGeneratedEmail,
|
fetchGeneratedEmail,
|
||||||
|
finalizePhoneActivationAfterSuccessfulFlow,
|
||||||
finalizeStep3Completion: async () => {
|
finalizeStep3Completion: async () => {
|
||||||
const currentState = await getState();
|
const currentState = await getState();
|
||||||
const signupTabId = await getTabId('signup-page');
|
const signupTabId = await getTabId('signup-page');
|
||||||
|
|||||||
@@ -392,6 +392,7 @@
|
|||||||
inbucketMailbox: prevState.inbucketMailbox,
|
inbucketMailbox: prevState.inbucketMailbox,
|
||||||
cloudflareDomain: prevState.cloudflareDomain,
|
cloudflareDomain: prevState.cloudflareDomain,
|
||||||
cloudflareDomains: prevState.cloudflareDomains,
|
cloudflareDomains: prevState.cloudflareDomains,
|
||||||
|
reusablePhoneActivation: prevState.reusablePhoneActivation,
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
autoRunSessionId: sessionId,
|
autoRunSessionId: sessionId,
|
||||||
tabRegistry: {},
|
tabRegistry: {},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
executeStepViaCompletionSignal,
|
executeStepViaCompletionSignal,
|
||||||
exportSettingsBundle,
|
exportSettingsBundle,
|
||||||
fetchGeneratedEmail,
|
fetchGeneratedEmail,
|
||||||
|
finalizePhoneActivationAfterSuccessfulFlow,
|
||||||
finalizeStep3Completion,
|
finalizeStep3Completion,
|
||||||
finalizeIcloudAliasAfterSuccessfulFlow,
|
finalizeIcloudAliasAfterSuccessfulFlow,
|
||||||
findHotmailAccount,
|
findHotmailAccount,
|
||||||
@@ -209,6 +210,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
|
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
|
||||||
|
if (typeof finalizePhoneActivationAfterSuccessfulFlow === 'function') {
|
||||||
|
await finalizePhoneActivationAfterSuccessfulFlow(latestState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleStepData(step, payload) {
|
async function handleStepData(step, payload) {
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
|
|
||||||
const PHONE_ACTIVATION_STATE_KEY = 'currentPhoneActivation';
|
const PHONE_ACTIVATION_STATE_KEY = 'currentPhoneActivation';
|
||||||
const REUSABLE_PHONE_ACTIVATION_STATE_KEY = 'reusablePhoneActivation';
|
const REUSABLE_PHONE_ACTIVATION_STATE_KEY = 'reusablePhoneActivation';
|
||||||
|
const PENDING_PHONE_ACTIVATION_CONFIRMATION_STATE_KEY = 'pendingPhoneActivationConfirmation';
|
||||||
const DEFAULT_PHONE_POLL_INTERVAL_MS = 5000;
|
const DEFAULT_PHONE_POLL_INTERVAL_MS = 5000;
|
||||||
const DEFAULT_PHONE_POLL_TIMEOUT_MS = 180000;
|
const DEFAULT_PHONE_POLL_TIMEOUT_MS = 180000;
|
||||||
const DEFAULT_PHONE_REQUEST_TIMEOUT_MS = 20000;
|
const DEFAULT_PHONE_REQUEST_TIMEOUT_MS = 20000;
|
||||||
const DEFAULT_PHONE_SUBMIT_ATTEMPTS = 3;
|
const DEFAULT_PHONE_SUBMIT_ATTEMPTS = 3;
|
||||||
const DEFAULT_PHONE_CODE_WAIT_WINDOW_MS = 60000;
|
const DEFAULT_PHONE_CODE_WAIT_WINDOW_MS = 60000;
|
||||||
const DEFAULT_PHONE_NUMBER_MAX_USES = 3;
|
const DEFAULT_PHONE_NUMBER_MAX_USES = 3;
|
||||||
const DEFAULT_PHONE_PRICE_LOOKUP_ATTEMPTS = 3;
|
|
||||||
const PHONE_CODE_TIMEOUT_ERROR_PREFIX = 'PHONE_CODE_TIMEOUT::';
|
const PHONE_CODE_TIMEOUT_ERROR_PREFIX = 'PHONE_CODE_TIMEOUT::';
|
||||||
const PHONE_RESTART_STEP7_ERROR_PREFIX = 'PHONE_RESTART_STEP7::';
|
const PHONE_RESTART_STEP7_ERROR_PREFIX = 'PHONE_RESTART_STEP7::';
|
||||||
|
|
||||||
@@ -47,6 +47,18 @@
|
|||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeManualHeroSmsMaxPrice(value) {
|
||||||
|
const trimmed = String(value ?? '').trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const price = Number(trimmed);
|
||||||
|
if (!Number.isFinite(price) || price <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return String(price);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeUseCount(value) {
|
function normalizeUseCount(value) {
|
||||||
return Math.max(0, Math.floor(Number(value) || 0));
|
return Math.max(0, Math.floor(Number(value) || 0));
|
||||||
}
|
}
|
||||||
@@ -236,13 +248,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePhoneConfig(state = {}) {
|
function resolvePhoneConfig(state = {}, options = {}) {
|
||||||
const apiKey = normalizeApiKey(state.heroSmsApiKey);
|
const apiKey = normalizeApiKey(state.heroSmsApiKey);
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new Error('HeroSMS API key is missing. Save it in the side panel before running the phone flow.');
|
throw new Error('HeroSMS API key is missing. Save it in the side panel before running the phone flow.');
|
||||||
}
|
}
|
||||||
|
const requireMaxPrice = Boolean(options.requireMaxPrice);
|
||||||
|
const maxPrice = normalizeManualHeroSmsMaxPrice(state.heroSmsMaxPrice);
|
||||||
|
if (requireMaxPrice && !maxPrice) {
|
||||||
|
throw new Error('HeroSMS maxPrice is missing. Fill it in below the country selector before running the phone flow.');
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
apiKey,
|
apiKey,
|
||||||
|
...(maxPrice ? { maxPrice } : {}),
|
||||||
baseUrl: normalizeUrl(state.heroSmsBaseUrl, DEFAULT_HERO_SMS_BASE_URL),
|
baseUrl: normalizeUrl(state.heroSmsBaseUrl, DEFAULT_HERO_SMS_BASE_URL),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -289,91 +307,10 @@
|
|||||||
return activation?.statusAction === 'getStatusV2' ? 'getStatusV2' : 'getStatus';
|
return activation?.statusAction === 'getStatusV2' ? 'getStatusV2' : 'getStatus';
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeHeroSmsPrice(value) {
|
|
||||||
const price = Number(value);
|
|
||||||
if (!Number.isFinite(price) || price < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectHeroSmsPriceCandidates(payload, candidates = []) {
|
|
||||||
if (Array.isArray(payload)) {
|
|
||||||
payload.forEach((entry) => collectHeroSmsPriceCandidates(entry, candidates));
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
if (!payload || typeof payload !== 'object') {
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cost = normalizeHeroSmsPrice(payload.cost);
|
|
||||||
if (cost !== null) {
|
|
||||||
const count = Number(payload.count);
|
|
||||||
const physicalCount = Number(payload.physicalCount);
|
|
||||||
const hasCount = Number.isFinite(count);
|
|
||||||
const hasPhysicalCount = Number.isFinite(physicalCount);
|
|
||||||
if ((!hasCount && !hasPhysicalCount) || count > 0 || physicalCount > 0) {
|
|
||||||
candidates.push(cost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(payload).forEach((value) => collectHeroSmsPriceCandidates(value, candidates));
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findLowestHeroSmsPrice(payload) {
|
|
||||||
const candidates = collectHeroSmsPriceCandidates(payload, []);
|
|
||||||
if (!candidates.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Math.min(...candidates);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isHeroSmsNoNumbersPayload(payload) {
|
function isHeroSmsNoNumbersPayload(payload) {
|
||||||
return /\bNO_NUMBERS\b/i.test(describeHeroSmsPayload(payload));
|
return /\bNO_NUMBERS\b/i.test(describeHeroSmsPayload(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractHeroSmsWrongMaxPrice(payload) {
|
|
||||||
if (payload && typeof payload === 'object') {
|
|
||||||
const title = String(payload.title || '').trim();
|
|
||||||
const minPrice = normalizeHeroSmsPrice(payload.info?.min);
|
|
||||||
if (/^WRONG_MAX_PRICE$/i.test(title) && minPrice !== null) {
|
|
||||||
return minPrice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = describeHeroSmsPayload(payload);
|
|
||||||
const match = text.match(/\bWRONG_MAX_PRICE:(\d+(?:\.\d+)?)\b/i);
|
|
||||||
if (!match) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return normalizeHeroSmsPrice(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNetworkFetchFailure(error) {
|
|
||||||
const message = String(error?.message || '').trim();
|
|
||||||
return /failed to fetch|networkerror|load failed/i.test(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveCheapestPhoneActivationPrice(config, countryConfig) {
|
|
||||||
for (let attempt = 1; attempt <= DEFAULT_PHONE_PRICE_LOOKUP_ATTEMPTS; attempt += 1) {
|
|
||||||
try {
|
|
||||||
const payload = await fetchHeroSmsPayload(config, {
|
|
||||||
action: 'getPrices',
|
|
||||||
service: HERO_SMS_SERVICE_CODE,
|
|
||||||
country: countryConfig.id,
|
|
||||||
}, 'HeroSMS getPrices');
|
|
||||||
const price = findLowestHeroSmsPrice(payload);
|
|
||||||
if (price !== null) {
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
// Best-effort lookup only.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchPhoneActivationPayload(config, countryConfig, action, options = {}) {
|
async function fetchPhoneActivationPayload(config, countryConfig, action, options = {}) {
|
||||||
const query = {
|
const query = {
|
||||||
action,
|
action,
|
||||||
@@ -387,49 +324,10 @@
|
|||||||
return fetchHeroSmsPayload(config, query, `HeroSMS ${action}`);
|
return fetchHeroSmsPayload(config, query, `HeroSMS ${action}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestPhoneActivationWithPrice(config, countryConfig, action, maxPrice) {
|
|
||||||
let nextMaxPrice = maxPrice;
|
|
||||||
let retriedWithUpdatedPrice = false;
|
|
||||||
let retriedWithoutPrice = false;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
return await fetchPhoneActivationPayload(config, countryConfig, action, {
|
|
||||||
maxPrice: nextMaxPrice,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const updatedMaxPrice = extractHeroSmsWrongMaxPrice(error?.payload || error?.message);
|
|
||||||
if (
|
|
||||||
nextMaxPrice !== null
|
|
||||||
&& nextMaxPrice !== undefined
|
|
||||||
&& !retriedWithUpdatedPrice
|
|
||||||
&& updatedMaxPrice !== null
|
|
||||||
) {
|
|
||||||
nextMaxPrice = updatedMaxPrice;
|
|
||||||
retriedWithUpdatedPrice = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
nextMaxPrice !== null
|
|
||||||
&& nextMaxPrice !== undefined
|
|
||||||
&& !retriedWithoutPrice
|
|
||||||
&& isNetworkFetchFailure(error)
|
|
||||||
) {
|
|
||||||
nextMaxPrice = null;
|
|
||||||
retriedWithoutPrice = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function requestPhoneActivation(state = {}) {
|
async function requestPhoneActivation(state = {}) {
|
||||||
const config = resolvePhoneConfig(state);
|
const config = resolvePhoneConfig(state, { requireMaxPrice: true });
|
||||||
const countryConfig = resolveCountryConfig(state);
|
const countryConfig = resolveCountryConfig(state);
|
||||||
const maxPrice = await resolveCheapestPhoneActivationPrice(config, countryConfig);
|
const maxPrice = config.maxPrice;
|
||||||
const buildFallbackActivation = (requestAction) => ({
|
const buildFallbackActivation = (requestAction) => ({
|
||||||
countryId: countryConfig.id,
|
countryId: countryConfig.id,
|
||||||
...(requestAction === 'getNumberV2' ? { statusAction: 'getStatusV2' } : {}),
|
...(requestAction === 'getNumberV2' ? { statusAction: 'getStatusV2' } : {}),
|
||||||
@@ -438,19 +336,19 @@
|
|||||||
let payload;
|
let payload;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload = await requestPhoneActivationWithPrice(config, countryConfig, requestAction, maxPrice);
|
payload = await fetchPhoneActivationPayload(config, countryConfig, requestAction, { maxPrice });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!isHeroSmsNoNumbersPayload(error?.payload || error?.message)) {
|
if (!isHeroSmsNoNumbersPayload(error?.payload || error?.message)) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
requestAction = 'getNumberV2';
|
requestAction = 'getNumberV2';
|
||||||
payload = await requestPhoneActivationWithPrice(config, countryConfig, requestAction, maxPrice);
|
payload = await fetchPhoneActivationPayload(config, countryConfig, requestAction, { maxPrice });
|
||||||
}
|
}
|
||||||
|
|
||||||
let activation = parseActivationPayload(payload, buildFallbackActivation(requestAction));
|
let activation = parseActivationPayload(payload, buildFallbackActivation(requestAction));
|
||||||
if (!activation && requestAction === 'getNumber' && isHeroSmsNoNumbersPayload(payload)) {
|
if (!activation && requestAction === 'getNumber' && isHeroSmsNoNumbersPayload(payload)) {
|
||||||
requestAction = 'getNumberV2';
|
requestAction = 'getNumberV2';
|
||||||
payload = await requestPhoneActivationWithPrice(config, countryConfig, requestAction, maxPrice);
|
payload = await fetchPhoneActivationPayload(config, countryConfig, requestAction, { maxPrice });
|
||||||
activation = parseActivationPayload(payload, buildFallbackActivation(requestAction));
|
activation = parseActivationPayload(payload, buildFallbackActivation(requestAction));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +394,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function completePhoneActivation(state = {}, activation) {
|
async function completePhoneActivation(state = {}, activation) {
|
||||||
await setPhoneActivationStatus(state, activation, 6, 'HeroSMS setStatus(6)');
|
await setPhoneActivationStatus(state, activation, 3, 'HeroSMS setStatus(3)');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cancelPhoneActivation(state = {}, activation) {
|
async function cancelPhoneActivation(state = {}, activation) {
|
||||||
@@ -738,6 +636,18 @@
|
|||||||
await persistReusableActivation(null);
|
await persistReusableActivation(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function incrementActivationUseCount(activation) {
|
||||||
|
const normalizedActivation = normalizeActivation(activation);
|
||||||
|
if (!normalizedActivation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...normalizedActivation,
|
||||||
|
successfulUses: Math.min(normalizedActivation.successfulUses + 1, normalizedActivation.maxUses),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function acquirePhoneActivation(state = {}) {
|
async function acquirePhoneActivation(state = {}) {
|
||||||
const countryConfig = resolveCountryConfig(state);
|
const countryConfig = resolveCountryConfig(state);
|
||||||
const reusableActivation = normalizeActivation(state[REUSABLE_PHONE_ACTIVATION_STATE_KEY]);
|
const reusableActivation = normalizeActivation(state[REUSABLE_PHONE_ACTIVATION_STATE_KEY]);
|
||||||
@@ -769,28 +679,53 @@
|
|||||||
return activation;
|
return activation;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function markActivationReusableAfterSuccess(activation) {
|
async function syncReusableActivationAfterUse(activation) {
|
||||||
const normalizedActivation = normalizeActivation(activation);
|
const normalizedActivation = normalizeActivation(activation);
|
||||||
if (!normalizedActivation) {
|
if (!normalizedActivation) {
|
||||||
await clearReusableActivation();
|
await clearReusableActivation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const successfulUses = normalizedActivation.successfulUses + 1;
|
if (normalizedActivation.successfulUses >= normalizedActivation.maxUses) {
|
||||||
if (successfulUses >= normalizedActivation.maxUses) {
|
|
||||||
await clearReusableActivation();
|
await clearReusableActivation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await persistReusableActivation({
|
await persistReusableActivation(normalizedActivation);
|
||||||
...normalizedActivation,
|
}
|
||||||
successfulUses,
|
|
||||||
|
async function persistPendingPhoneActivationConfirmation(activation) {
|
||||||
|
await setState({
|
||||||
|
[PENDING_PHONE_ACTIVATION_CONFIRMATION_STATE_KEY]: activation || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function clearPendingPhoneActivationConfirmation() {
|
||||||
|
await persistPendingPhoneActivationConfirmation(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finalizePendingPhoneActivationConfirmation(stateOverride = null) {
|
||||||
|
const state = stateOverride || await getState();
|
||||||
|
const pendingActivation = normalizeActivation(state[PENDING_PHONE_ACTIVATION_CONFIRMATION_STATE_KEY]);
|
||||||
|
if (!pendingActivation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const committedActivation = incrementActivationUseCount(pendingActivation);
|
||||||
|
if (!committedActivation) {
|
||||||
|
await clearPendingPhoneActivationConfirmation();
|
||||||
|
await clearReusableActivation();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await syncReusableActivationAfterUse(committedActivation);
|
||||||
|
await clearPendingPhoneActivationConfirmation();
|
||||||
|
return committedActivation;
|
||||||
|
}
|
||||||
|
|
||||||
async function waitForPhoneCodeOrRotateNumber(tabId, state, activation) {
|
async function waitForPhoneCodeOrRotateNumber(tabId, state, activation) {
|
||||||
const normalizedActivation = normalizeActivation(activation);
|
let currentActivation = normalizeActivation(activation);
|
||||||
if (!normalizedActivation) {
|
if (!currentActivation) {
|
||||||
throw new Error('Phone activation is missing.');
|
throw new Error('Phone activation is missing.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,11 +734,11 @@
|
|||||||
|
|
||||||
for (let windowIndex = 1; windowIndex <= 2; windowIndex += 1) {
|
for (let windowIndex = 1; windowIndex <= 2; windowIndex += 1) {
|
||||||
await addLog(
|
await addLog(
|
||||||
`Step 9: waiting up to 60 seconds for SMS on ${normalizedActivation.phoneNumber} (${windowIndex}/2).`,
|
`Step 9: waiting up to 60 seconds for SMS on ${currentActivation.phoneNumber} (${windowIndex}/2).`,
|
||||||
'info'
|
'info'
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const code = await pollPhoneActivationCode(state, normalizedActivation, {
|
const code = await pollPhoneActivationCode(state, currentActivation, {
|
||||||
actionLabel: windowIndex === 1
|
actionLabel: windowIndex === 1
|
||||||
? 'poll phone verification code from HeroSMS'
|
? 'poll phone verification code from HeroSMS'
|
||||||
: 'poll resent phone verification code from HeroSMS',
|
: 'poll resent phone verification code from HeroSMS',
|
||||||
@@ -820,7 +755,7 @@
|
|||||||
lastLoggedStatus = statusText;
|
lastLoggedStatus = statusText;
|
||||||
lastLoggedPollCount = pollCount;
|
lastLoggedPollCount = pollCount;
|
||||||
await addLog(
|
await addLog(
|
||||||
`Step 9: HeroSMS status for ${normalizedActivation.phoneNumber}: ${statusText} (${Math.ceil(elapsedMs / 1000)}s elapsed).`,
|
`Step 9: HeroSMS status for ${currentActivation.phoneNumber}: ${statusText} (${Math.ceil(elapsedMs / 1000)}s elapsed).`,
|
||||||
'info'
|
'info'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -836,10 +771,10 @@
|
|||||||
|
|
||||||
if (windowIndex === 1) {
|
if (windowIndex === 1) {
|
||||||
await addLog(
|
await addLog(
|
||||||
`Step 9: no SMS arrived for ${normalizedActivation.phoneNumber} within 60 seconds, requesting another SMS.`,
|
`Step 9: no SMS arrived for ${currentActivation.phoneNumber} within 60 seconds, requesting another SMS.`,
|
||||||
'warn'
|
'warn'
|
||||||
);
|
);
|
||||||
await requestAdditionalPhoneSms(state, normalizedActivation);
|
await requestAdditionalPhoneSms(state, currentActivation);
|
||||||
try {
|
try {
|
||||||
await resendPhoneVerificationCode(tabId);
|
await resendPhoneVerificationCode(tabId);
|
||||||
await addLog('Step 9: clicked "Resend text message" on the phone verification page.', 'info');
|
await addLog('Step 9: clicked "Resend text message" on the phone verification page.', 'info');
|
||||||
@@ -850,10 +785,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await addLog(
|
await addLog(
|
||||||
`Step 9: still no SMS for ${normalizedActivation.phoneNumber} 60 seconds after resend, restarting from step 7 with a new number.`,
|
`Step 9: still no SMS for ${currentActivation.phoneNumber} 60 seconds after resend, restarting from step 7 with a new number.`,
|
||||||
'warn'
|
'warn'
|
||||||
);
|
);
|
||||||
throw buildPhoneRestartStep7Error(normalizedActivation.phoneNumber);
|
throw buildPhoneRestartStep7Error(currentActivation.phoneNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,6 +810,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pageState?.addPhonePage) {
|
if (pageState?.addPhonePage) {
|
||||||
|
if (normalizeActivation(state[PENDING_PHONE_ACTIVATION_CONFIRMATION_STATE_KEY])) {
|
||||||
|
await clearPendingPhoneActivationConfirmation();
|
||||||
|
}
|
||||||
if (activation) {
|
if (activation) {
|
||||||
await cancelPhoneActivation(state, activation);
|
await cancelPhoneActivation(state, activation);
|
||||||
await clearCurrentActivation();
|
await clearCurrentActivation();
|
||||||
@@ -965,8 +903,18 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await completePhoneActivation(state, activation);
|
try {
|
||||||
await markActivationReusableAfterSuccess(activation);
|
await completePhoneActivation(state, activation);
|
||||||
|
await persistReusableActivation(activation);
|
||||||
|
await persistPendingPhoneActivationConfirmation(activation);
|
||||||
|
} catch (activationStatusError) {
|
||||||
|
await clearReusableActivation();
|
||||||
|
await clearPendingPhoneActivationConfirmation();
|
||||||
|
await addLog(
|
||||||
|
`Step 9: phone verification succeeded, but HeroSMS setStatus(3) failed. The next flow will request a new number. ${activationStatusError.message}`,
|
||||||
|
'warn'
|
||||||
|
);
|
||||||
|
}
|
||||||
shouldCancelActivation = false;
|
shouldCancelActivation = false;
|
||||||
await clearCurrentActivation();
|
await clearCurrentActivation();
|
||||||
await addLog('Step 9: phone verification finished, waiting for OAuth consent.', 'ok');
|
await addLog('Step 9: phone verification finished, waiting for OAuth consent.', 'ok');
|
||||||
@@ -988,6 +936,7 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
completePhoneVerificationFlow,
|
completePhoneVerificationFlow,
|
||||||
|
finalizePendingPhoneActivationConfirmation,
|
||||||
normalizeActivation,
|
normalizeActivation,
|
||||||
pollPhoneActivationCode,
|
pollPhoneActivationCode,
|
||||||
reactivatePhoneActivation,
|
reactivatePhoneActivation,
|
||||||
|
|||||||
@@ -425,6 +425,11 @@
|
|||||||
<option value="52" selected>Thailand</option>
|
<option value="52" selected>Thailand</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row" id="row-hero-sms-max-price" style="display:none;">
|
||||||
|
<span class="data-label">最高价格</span>
|
||||||
|
<input type="number" id="input-hero-sms-max-price" class="data-input mono" min="0.0001" step="0.0001" required
|
||||||
|
placeholder="必填,例如 0.08" />
|
||||||
|
</div>
|
||||||
<div class="data-row" id="row-hero-sms-api-key" style="display:none;">
|
<div class="data-row" id="row-hero-sms-api-key" style="display:none;">
|
||||||
<span class="data-label">接码 API</span>
|
<span class="data-label">接码 API</span>
|
||||||
<div class="input-with-icon">
|
<div class="input-with-icon">
|
||||||
|
|||||||
+34
-1
@@ -292,8 +292,10 @@ const rowPhoneVerificationEnabled = document.getElementById('row-phone-verificat
|
|||||||
const inputPhoneVerificationEnabled = document.getElementById('input-phone-verification-enabled');
|
const inputPhoneVerificationEnabled = document.getElementById('input-phone-verification-enabled');
|
||||||
const rowHeroSmsPlatform = document.getElementById('row-hero-sms-platform');
|
const rowHeroSmsPlatform = document.getElementById('row-hero-sms-platform');
|
||||||
const rowHeroSmsCountry = document.getElementById('row-hero-sms-country');
|
const rowHeroSmsCountry = document.getElementById('row-hero-sms-country');
|
||||||
|
const rowHeroSmsMaxPrice = document.getElementById('row-hero-sms-max-price');
|
||||||
const rowHeroSmsApiKey = document.getElementById('row-hero-sms-api-key');
|
const rowHeroSmsApiKey = document.getElementById('row-hero-sms-api-key');
|
||||||
const inputHeroSmsApiKey = document.getElementById('input-hero-sms-api-key');
|
const inputHeroSmsApiKey = document.getElementById('input-hero-sms-api-key');
|
||||||
|
const inputHeroSmsMaxPrice = document.getElementById('input-hero-sms-max-price');
|
||||||
const selectHeroSmsCountry = document.getElementById('select-hero-sms-country');
|
const selectHeroSmsCountry = document.getElementById('select-hero-sms-country');
|
||||||
const displayHeroSmsPlatform = document.getElementById('display-hero-sms-platform');
|
const displayHeroSmsPlatform = document.getElementById('display-hero-sms-platform');
|
||||||
const rowAccountRunHistoryHelperBaseUrl = document.getElementById('row-account-run-history-helper-base-url');
|
const rowAccountRunHistoryHelperBaseUrl = document.getElementById('row-account-run-history-helper-base-url');
|
||||||
@@ -2220,6 +2222,9 @@ function collectSettingsPayload() {
|
|||||||
const heroSmsApiKeyValue = typeof inputHeroSmsApiKey !== 'undefined' && inputHeroSmsApiKey
|
const heroSmsApiKeyValue = typeof inputHeroSmsApiKey !== 'undefined' && inputHeroSmsApiKey
|
||||||
? (inputHeroSmsApiKey.value || '')
|
? (inputHeroSmsApiKey.value || '')
|
||||||
: '';
|
: '';
|
||||||
|
const heroSmsMaxPriceValue = typeof inputHeroSmsMaxPrice !== 'undefined' && inputHeroSmsMaxPrice
|
||||||
|
? normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice.value)
|
||||||
|
: '';
|
||||||
const heroSmsCountry = typeof getSelectedHeroSmsCountryOption === 'function'
|
const heroSmsCountry = typeof getSelectedHeroSmsCountryOption === 'function'
|
||||||
? getSelectedHeroSmsCountryOption()
|
? getSelectedHeroSmsCountryOption()
|
||||||
: {
|
: {
|
||||||
@@ -2323,6 +2328,7 @@ function collectSettingsPayload() {
|
|||||||
DEFAULT_VERIFICATION_RESEND_COUNT
|
DEFAULT_VERIFICATION_RESEND_COUNT
|
||||||
),
|
),
|
||||||
heroSmsApiKey: heroSmsApiKeyValue,
|
heroSmsApiKey: heroSmsApiKeyValue,
|
||||||
|
heroSmsMaxPrice: heroSmsMaxPriceValue,
|
||||||
heroSmsCountryId: heroSmsCountry.id,
|
heroSmsCountryId: heroSmsCountry.id,
|
||||||
heroSmsCountryLabel: heroSmsCountry.label,
|
heroSmsCountryLabel: heroSmsCountry.label,
|
||||||
};
|
};
|
||||||
@@ -2381,6 +2387,18 @@ function normalizeHeroSmsCountryLabel(value = '') {
|
|||||||
return String(value || '').trim() || DEFAULT_HERO_SMS_COUNTRY_LABEL;
|
return String(value || '').trim() || DEFAULT_HERO_SMS_COUNTRY_LABEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeHeroSmsMaxPriceValue(value, fallback = '') {
|
||||||
|
const trimmed = String(value ?? '').trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return String(fallback || '').trim();
|
||||||
|
}
|
||||||
|
const numeric = Number(trimmed);
|
||||||
|
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||||||
|
return String(fallback || '').trim();
|
||||||
|
}
|
||||||
|
return String(numeric);
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedHeroSmsCountryOption() {
|
function getSelectedHeroSmsCountryOption() {
|
||||||
if (!selectHeroSmsCountry) {
|
if (!selectHeroSmsCountry) {
|
||||||
return {
|
return {
|
||||||
@@ -2496,7 +2514,7 @@ function updateAccountRunHistorySettingsUI() {
|
|||||||
|
|
||||||
function updatePhoneVerificationSettingsUI() {
|
function updatePhoneVerificationSettingsUI() {
|
||||||
const enabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
const enabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
||||||
[rowHeroSmsPlatform, rowHeroSmsCountry, rowHeroSmsApiKey].forEach((row) => {
|
[rowHeroSmsPlatform, rowHeroSmsCountry, rowHeroSmsMaxPrice, rowHeroSmsApiKey].forEach((row) => {
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3000,6 +3018,9 @@ function applySettingsState(state) {
|
|||||||
if (inputHeroSmsApiKey) {
|
if (inputHeroSmsApiKey) {
|
||||||
inputHeroSmsApiKey.value = state?.heroSmsApiKey || '';
|
inputHeroSmsApiKey.value = state?.heroSmsApiKey || '';
|
||||||
}
|
}
|
||||||
|
if (inputHeroSmsMaxPrice) {
|
||||||
|
inputHeroSmsMaxPrice.value = normalizeHeroSmsMaxPriceValue(state?.heroSmsMaxPrice);
|
||||||
|
}
|
||||||
if (selectHeroSmsCountry) {
|
if (selectHeroSmsCountry) {
|
||||||
const restoredCountryId = String(normalizeHeroSmsCountryId(state?.heroSmsCountryId));
|
const restoredCountryId = String(normalizeHeroSmsCountryId(state?.heroSmsCountryId));
|
||||||
if (Array.from(selectHeroSmsCountry.options).some((option) => option.value === restoredCountryId)) {
|
if (Array.from(selectHeroSmsCountry.options).some((option) => option.value === restoredCountryId)) {
|
||||||
@@ -6520,6 +6541,15 @@ inputHeroSmsApiKey?.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
inputHeroSmsMaxPrice?.addEventListener('input', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
scheduleSettingsAutoSave();
|
||||||
|
});
|
||||||
|
inputHeroSmsMaxPrice?.addEventListener('blur', () => {
|
||||||
|
inputHeroSmsMaxPrice.value = normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice.value);
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
selectHeroSmsCountry?.addEventListener('change', () => {
|
selectHeroSmsCountry?.addEventListener('change', () => {
|
||||||
updateHeroSmsPlatformDisplay(getSelectedHeroSmsCountryOption().label);
|
updateHeroSmsPlatformDisplay(getSelectedHeroSmsCountryOption().label);
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
@@ -6922,6 +6952,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (message.payload.heroSmsApiKey !== undefined && inputHeroSmsApiKey) {
|
if (message.payload.heroSmsApiKey !== undefined && inputHeroSmsApiKey) {
|
||||||
inputHeroSmsApiKey.value = message.payload.heroSmsApiKey || '';
|
inputHeroSmsApiKey.value = message.payload.heroSmsApiKey || '';
|
||||||
}
|
}
|
||||||
|
if (message.payload.heroSmsMaxPrice !== undefined && inputHeroSmsMaxPrice) {
|
||||||
|
inputHeroSmsMaxPrice.value = normalizeHeroSmsMaxPriceValue(message.payload.heroSmsMaxPrice);
|
||||||
|
}
|
||||||
if (message.payload.phoneVerificationEnabled !== undefined && inputPhoneVerificationEnabled) {
|
if (message.payload.phoneVerificationEnabled !== undefined && inputPhoneVerificationEnabled) {
|
||||||
inputPhoneVerificationEnabled.checked = Boolean(message.payload.phoneVerificationEnabled);
|
inputPhoneVerificationEnabled.checked = Boolean(message.payload.phoneVerificationEnabled);
|
||||||
updatePhoneVerificationSettingsUI();
|
updatePhoneVerificationSettingsUI();
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ let currentState = {
|
|||||||
inbucketMailbox: '',
|
inbucketMailbox: '',
|
||||||
cloudflareDomain: '',
|
cloudflareDomain: '',
|
||||||
cloudflareDomains: [],
|
cloudflareDomains: [],
|
||||||
|
reusablePhoneActivation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
successfulUses: 1,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
tabRegistry: {},
|
tabRegistry: {},
|
||||||
sourceLastUrls: {},
|
sourceLastUrls: {},
|
||||||
};
|
};
|
||||||
@@ -329,6 +335,16 @@ return {
|
|||||||
assert.strictEqual(snapshot.currentState.autoRunSessionId, 0, 'session id should be cleared after completion');
|
assert.strictEqual(snapshot.currentState.autoRunSessionId, 0, 'session id should be cleared after completion');
|
||||||
assert.strictEqual(snapshot.currentState.gmailBaseEmail, 'demo@gmail.com', 'gmail base email should survive fresh-attempt reset');
|
assert.strictEqual(snapshot.currentState.gmailBaseEmail, 'demo@gmail.com', 'gmail base email should survive fresh-attempt reset');
|
||||||
assert.strictEqual(snapshot.currentState.mail2925BaseEmail, 'demo@2925.com', '2925 base email should survive fresh-attempt reset');
|
assert.strictEqual(snapshot.currentState.mail2925BaseEmail, 'demo@2925.com', '2925 base email should survive fresh-attempt reset');
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
snapshot.currentState.reusablePhoneActivation,
|
||||||
|
{
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
successfulUses: 1,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
'reusable phone activation should survive fresh-attempt reset'
|
||||||
|
);
|
||||||
|
|
||||||
console.log('auto-run fresh attempt reset tests passed');
|
console.log('auto-run fresh attempt reset tests passed');
|
||||||
})().catch((error) => {
|
})().catch((error) => {
|
||||||
|
|||||||
@@ -713,6 +713,7 @@ function broadcastDataUpdate() {}
|
|||||||
function isLocalhostOAuthCallbackUrl() {
|
function isLocalhostOAuthCallbackUrl() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
async function finalizePhoneActivationAfterSuccessfulFlow() {}
|
||||||
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
|
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
|
||||||
|
|
||||||
${bundle}
|
${bundle}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ function createRouter(overrides = {}) {
|
|||||||
stepStatuses: [],
|
stepStatuses: [],
|
||||||
emailStates: [],
|
emailStates: [],
|
||||||
finalizePayloads: [],
|
finalizePayloads: [],
|
||||||
|
phoneFinalizations: [],
|
||||||
notifyCompletions: [],
|
notifyCompletions: [],
|
||||||
notifyErrors: [],
|
notifyErrors: [],
|
||||||
securityBlocks: [],
|
securityBlocks: [],
|
||||||
@@ -49,10 +50,13 @@ function createRouter(overrides = {}) {
|
|||||||
executeStepViaCompletionSignal: async () => {},
|
executeStepViaCompletionSignal: async () => {},
|
||||||
exportSettingsBundle: async () => ({}),
|
exportSettingsBundle: async () => ({}),
|
||||||
fetchGeneratedEmail: async () => '',
|
fetchGeneratedEmail: async () => '',
|
||||||
|
finalizePhoneActivationAfterSuccessfulFlow: overrides.finalizePhoneActivationAfterSuccessfulFlow || (async (state) => {
|
||||||
|
events.phoneFinalizations.push(state);
|
||||||
|
}),
|
||||||
finalizeStep3Completion: overrides.finalizeStep3Completion || (async (payload) => {
|
finalizeStep3Completion: overrides.finalizeStep3Completion || (async (payload) => {
|
||||||
events.finalizePayloads.push(payload);
|
events.finalizePayloads.push(payload);
|
||||||
}),
|
}),
|
||||||
finalizeIcloudAliasAfterSuccessfulFlow: async () => {},
|
finalizeIcloudAliasAfterSuccessfulFlow: overrides.finalizeIcloudAliasAfterSuccessfulFlow || (async () => {}),
|
||||||
findHotmailAccount: async () => null,
|
findHotmailAccount: async () => null,
|
||||||
flushCommand: async () => {},
|
flushCommand: async () => {},
|
||||||
getCurrentLuckmailPurchase: () => null,
|
getCurrentLuckmailPurchase: () => null,
|
||||||
@@ -262,6 +266,68 @@ test('message router finalizes step 3 before marking it completed', async () =>
|
|||||||
assert.deepStrictEqual(response, { ok: true });
|
assert.deepStrictEqual(response, { ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('message router finalizes pending phone activation on platform verify success', async () => {
|
||||||
|
const state = {
|
||||||
|
stepStatuses: { 10: 'pending' },
|
||||||
|
reusablePhoneActivation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
pendingPhoneActivationConfirmation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { router, events } = createRouter({
|
||||||
|
state,
|
||||||
|
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'platform-verify' : '' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.handleStepData(10, {
|
||||||
|
localhostUrl: 'http://localhost:1455/auth/callback?code=ok',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.phoneFinalizations, [state]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('message router does not finalize pending phone activation when icloud finalization fails', async () => {
|
||||||
|
const state = {
|
||||||
|
stepStatuses: { 10: 'pending' },
|
||||||
|
reusablePhoneActivation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
pendingPhoneActivationConfirmation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { router, events } = createRouter({
|
||||||
|
state,
|
||||||
|
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'platform-verify' : '' }),
|
||||||
|
finalizeIcloudAliasAfterSuccessfulFlow: async () => {
|
||||||
|
throw new Error('icloud finalize failed');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => router.handleStepData(10, {
|
||||||
|
localhostUrl: 'http://localhost:1455/auth/callback?code=ok',
|
||||||
|
}),
|
||||||
|
/icloud finalize failed/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.phoneFinalizations, []);
|
||||||
|
});
|
||||||
|
|
||||||
test('message router marks step 3 failed when post-submit finalize fails', async () => {
|
test('message router marks step 3 failed when post-submit finalize fails', async () => {
|
||||||
const { router, events } = createRouter({
|
const { router, events } = createRouter({
|
||||||
finalizeStep3Completion: async () => {
|
finalizeStep3Completion: async () => {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function buildHeroSmsStatusV2Payload({ smsCode = '', smsText = '', callCode = ''
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test('phone verification helper requests HeroSMS numbers with fixed OpenAI and Thailand parameters', async () => {
|
test('phone verification helper requests HeroSMS numbers with manual maxPrice and fixed OpenAI/Thailand parameters', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const helpers = api.createPhoneVerificationHelpers({
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
@@ -40,26 +40,22 @@ test('phone verification helper requests HeroSMS numbers with fixed OpenAI and T
|
|||||||
fetchImpl: async (url) => {
|
fetchImpl: async (url) => {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
requests.push(parsedUrl);
|
||||||
const action = parsedUrl.searchParams.get('action');
|
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08' }),
|
||||||
sendToContentScriptResilient: async () => ({}),
|
sendToContentScriptResilient: async () => ({}),
|
||||||
setState: async () => {},
|
setState: async () => {},
|
||||||
sleepWithStop: async () => {},
|
sleepWithStop: async () => {},
|
||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const activation = await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
const activation = await helpers.requestPhoneActivation({
|
||||||
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(activation, {
|
assert.deepStrictEqual(activation, {
|
||||||
activationId: '123456',
|
activationId: '123456',
|
||||||
@@ -70,111 +66,80 @@ test('phone verification helper requests HeroSMS numbers with fixed OpenAI and T
|
|||||||
successfulUses: 0,
|
successfulUses: 0,
|
||||||
maxUses: 3,
|
maxUses: 3,
|
||||||
});
|
});
|
||||||
assert.equal(requests.length, 2);
|
assert.equal(requests.length, 1);
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
assert.equal(requests[0].searchParams.get('action'), 'getNumber');
|
||||||
assert.equal(requests[0].searchParams.get('service'), 'dr');
|
assert.equal(requests[0].searchParams.get('service'), 'dr');
|
||||||
assert.equal(requests[0].searchParams.get('country'), '52');
|
assert.equal(requests[0].searchParams.get('country'), '52');
|
||||||
assert.equal(requests[0].searchParams.get('api_key'), 'demo-key');
|
assert.equal(requests[0].searchParams.get('api_key'), 'demo-key');
|
||||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
assert.equal(requests[0].searchParams.get('maxPrice'), '0.08');
|
||||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
assert.equal(requests[0].searchParams.get('fixedPrice'), 'true');
|
||||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
|
||||||
assert.equal(requests[1].searchParams.get('service'), 'dr');
|
|
||||||
assert.equal(requests[1].searchParams.get('country'), '52');
|
|
||||||
assert.equal(requests[1].searchParams.get('api_key'), 'demo-key');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper retries HeroSMS getPrices until it receives a usable lowest price', async () => {
|
test('phone verification helper requires manual HeroSMS maxPrice', async () => {
|
||||||
const requests = [];
|
|
||||||
let getPricesAttempt = 0;
|
|
||||||
const helpers = api.createPhoneVerificationHelpers({
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
ensureStep8SignupPageReady: async () => {},
|
ensureStep8SignupPageReady: async () => {},
|
||||||
fetchImpl: async (url) => {
|
fetchImpl: async () => {
|
||||||
const parsedUrl = new URL(url);
|
throw new Error('should not request HeroSMS without maxPrice');
|
||||||
requests.push(parsedUrl);
|
|
||||||
const action = parsedUrl.searchParams.get('action');
|
|
||||||
if (action === 'getPrices') {
|
|
||||||
getPricesAttempt += 1;
|
|
||||||
return getPricesAttempt < 3
|
|
||||||
? {
|
|
||||||
ok: true,
|
|
||||||
text: async () => JSON.stringify({ unavailable: true }),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload({ cost: 0.09 }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
|
||||||
},
|
},
|
||||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '' }),
|
||||||
sendToContentScriptResilient: async () => ({}),
|
sendToContentScriptResilient: async () => ({}),
|
||||||
setState: async () => {},
|
setState: async () => {},
|
||||||
sleepWithStop: async () => {},
|
sleepWithStop: async () => {},
|
||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
await assert.rejects(
|
||||||
|
() => helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '' }),
|
||||||
assert.equal(requests.length, 4);
|
/HeroSMS maxPrice is missing/i
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
);
|
||||||
assert.equal(requests[1].searchParams.get('action'), 'getPrices');
|
|
||||||
assert.equal(requests[2].searchParams.get('action'), 'getPrices');
|
|
||||||
assert.equal(requests[3].searchParams.get('action'), 'getNumber');
|
|
||||||
assert.equal(requests[3].searchParams.get('maxPrice'), '0.09');
|
|
||||||
assert.equal(requests[3].searchParams.get('fixedPrice'), 'true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper falls back to plain getNumber only after HeroSMS getPrices fails three times', async () => {
|
test('phone verification helper still clears existing activation when maxPrice is missing', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
let getPricesAttempt = 0;
|
let currentState = {
|
||||||
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '',
|
||||||
|
currentPhoneActivation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
reusablePhoneActivation: null,
|
||||||
|
pendingPhoneActivationConfirmation: null,
|
||||||
|
};
|
||||||
const helpers = api.createPhoneVerificationHelpers({
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
ensureStep8SignupPageReady: async () => {},
|
ensureStep8SignupPageReady: async () => {},
|
||||||
fetchImpl: async (url) => {
|
fetchImpl: async (url) => {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
requests.push(parsedUrl);
|
||||||
const action = parsedUrl.searchParams.get('action');
|
return {
|
||||||
if (action === 'getPrices') {
|
ok: true,
|
||||||
getPricesAttempt += 1;
|
text: async () => 'ACCESS_CANCEL',
|
||||||
return {
|
};
|
||||||
ok: true,
|
|
||||||
text: async () => JSON.stringify({ unavailable: getPricesAttempt }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
|
||||||
},
|
},
|
||||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
getState: async () => currentState,
|
||||||
sendToContentScriptResilient: async () => ({}),
|
sendToContentScriptResilient: async () => ({}),
|
||||||
setState: async () => {},
|
setState: async (updates) => {
|
||||||
|
currentState = { ...currentState, ...updates };
|
||||||
|
},
|
||||||
sleepWithStop: async () => {},
|
sleepWithStop: async () => {},
|
||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
await assert.rejects(
|
||||||
|
() => helpers.completePhoneVerificationFlow(1, { addPhonePage: true }),
|
||||||
|
/HeroSMS maxPrice is missing/i
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(requests.length, 4);
|
assert.equal(requests.length, 1);
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
assert.equal(requests[0].searchParams.get('action'), 'setStatus');
|
||||||
assert.equal(requests[1].searchParams.get('action'), 'getPrices');
|
assert.equal(requests[0].searchParams.get('id'), '123456');
|
||||||
assert.equal(requests[2].searchParams.get('action'), 'getPrices');
|
assert.equal(requests[0].searchParams.get('status'), '8');
|
||||||
assert.equal(requests[2].searchParams.get('service'), 'dr');
|
assert.equal(requests[0].searchParams.has('maxPrice'), false);
|
||||||
assert.equal(requests[2].searchParams.get('country'), '52');
|
assert.equal(currentState.currentPhoneActivation, null);
|
||||||
assert.equal(requests[2].searchParams.get('api_key'), 'demo-key');
|
|
||||||
assert.equal(requests[3].searchParams.get('action'), 'getNumber');
|
|
||||||
assert.equal(requests[3].searchParams.get('maxPrice'), null);
|
|
||||||
assert.equal(requests[3].searchParams.get('fixedPrice'), null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper retries with HeroSMS getNumberV2 when getNumber reports NO_NUMBERS', async () => {
|
test('phone verification helper retries with HeroSMS getNumberV2 when getNumber reports NO_NUMBERS', async () => {
|
||||||
@@ -186,12 +151,6 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
|||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
requests.push(parsedUrl);
|
||||||
const action = parsedUrl.searchParams.get('action');
|
const action = parsedUrl.searchParams.get('action');
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload({ country: '16' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumber') {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -209,7 +168,7 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
|||||||
}
|
}
|
||||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||||
},
|
},
|
||||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsCountryId: 16 }),
|
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08', heroSmsCountryId: 16 }),
|
||||||
sendToContentScriptResilient: async () => ({}),
|
sendToContentScriptResilient: async () => ({}),
|
||||||
setState: async () => {},
|
setState: async () => {},
|
||||||
sleepWithStop: async () => {},
|
sleepWithStop: async () => {},
|
||||||
@@ -218,6 +177,7 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
|||||||
|
|
||||||
const activation = await helpers.requestPhoneActivation({
|
const activation = await helpers.requestPhoneActivation({
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
heroSmsCountryId: 16,
|
heroSmsCountryId: 16,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -231,17 +191,15 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
|||||||
maxUses: 3,
|
maxUses: 3,
|
||||||
statusAction: 'getStatusV2',
|
statusAction: 'getStatusV2',
|
||||||
});
|
});
|
||||||
assert.equal(requests.length, 3);
|
assert.equal(requests.length, 2);
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
assert.equal(requests[0].searchParams.get('action'), 'getNumber');
|
||||||
assert.equal(requests[0].searchParams.get('country'), '16');
|
assert.equal(requests[0].searchParams.get('country'), '16');
|
||||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
assert.equal(requests[0].searchParams.get('maxPrice'), '0.08');
|
||||||
|
assert.equal(requests[0].searchParams.get('fixedPrice'), 'true');
|
||||||
|
assert.equal(requests[1].searchParams.get('action'), 'getNumberV2');
|
||||||
assert.equal(requests[1].searchParams.get('country'), '16');
|
assert.equal(requests[1].searchParams.get('country'), '16');
|
||||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
||||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
||||||
assert.equal(requests[2].searchParams.get('action'), 'getNumberV2');
|
|
||||||
assert.equal(requests[2].searchParams.get('country'), '16');
|
|
||||||
assert.equal(requests[2].searchParams.get('maxPrice'), '0.08');
|
|
||||||
assert.equal(requests[2].searchParams.get('fixedPrice'), 'true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper uses HeroSMS getStatusV2 after acquiring a number via getNumberV2', async () => {
|
test('phone verification helper uses HeroSMS getStatusV2 after acquiring a number via getNumberV2', async () => {
|
||||||
@@ -249,6 +207,7 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
|||||||
const stateUpdates = [];
|
const stateUpdates = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
heroSmsCountryId: 16,
|
heroSmsCountryId: 16,
|
||||||
heroSmsCountryLabel: 'United Kingdom',
|
heroSmsCountryLabel: 'United Kingdom',
|
||||||
verificationResendCount: 0,
|
verificationResendCount: 0,
|
||||||
@@ -264,12 +223,6 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
|||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
requests.push(parsedUrl);
|
||||||
const action = parsedUrl.searchParams.get('action');
|
const action = parsedUrl.searchParams.get('action');
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload({ country: '16' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumber') {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -361,7 +314,19 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
|||||||
provider: 'hero-sms',
|
provider: 'hero-sms',
|
||||||
serviceCode: 'dr',
|
serviceCode: 'dr',
|
||||||
countryId: 16,
|
countryId: 16,
|
||||||
successfulUses: 1,
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
statusAction: 'getStatusV2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pendingPhoneActivationConfirmation: {
|
||||||
|
activationId: '654321',
|
||||||
|
phoneNumber: '447911123456',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 16,
|
||||||
|
successfulUses: 0,
|
||||||
maxUses: 3,
|
maxUses: 3,
|
||||||
statusAction: 'getStatusV2',
|
statusAction: 'getStatusV2',
|
||||||
},
|
},
|
||||||
@@ -372,7 +337,6 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
|||||||
]);
|
]);
|
||||||
const actions = requests.map((url) => url.searchParams.get('action'));
|
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||||
assert.deepStrictEqual(actions, [
|
assert.deepStrictEqual(actions, [
|
||||||
'getPrices',
|
|
||||||
'getNumber',
|
'getNumber',
|
||||||
'getNumberV2',
|
'getNumberV2',
|
||||||
'getStatusV2',
|
'getStatusV2',
|
||||||
@@ -381,117 +345,32 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper refreshes maxPrice when HeroSMS returns WRONG_MAX_PRICE', async () => {
|
test('phone verification helper keeps the user-provided maxPrice and surfaces HeroSMS price errors', async () => {
|
||||||
const requests = [];
|
|
||||||
let getNumberAttempt = 0;
|
|
||||||
const helpers = api.createPhoneVerificationHelpers({
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
ensureStep8SignupPageReady: async () => {},
|
ensureStep8SignupPageReady: async () => {},
|
||||||
fetchImpl: async (url) => {
|
fetchImpl: async (url) => {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
|
||||||
const action = parsedUrl.searchParams.get('action');
|
const action = parsedUrl.searchParams.get('action');
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumber') {
|
||||||
getNumberAttempt += 1;
|
return {
|
||||||
return getNumberAttempt === 1
|
ok: false,
|
||||||
? {
|
text: async () => 'WRONG_MAX_PRICE:0.09',
|
||||||
ok: false,
|
};
|
||||||
text: async () => 'WRONG_MAX_PRICE:0.09',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
ok: true,
|
|
||||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||||
},
|
},
|
||||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08' }),
|
||||||
sendToContentScriptResilient: async () => ({}),
|
sendToContentScriptResilient: async () => ({}),
|
||||||
setState: async () => {},
|
setState: async () => {},
|
||||||
sleepWithStop: async () => {},
|
sleepWithStop: async () => {},
|
||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const activation = await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
await assert.rejects(
|
||||||
|
() => helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08' }),
|
||||||
assert.deepStrictEqual(activation, {
|
/WRONG_MAX_PRICE:0.09/
|
||||||
activationId: '123456',
|
);
|
||||||
phoneNumber: '66959916439',
|
|
||||||
provider: 'hero-sms',
|
|
||||||
serviceCode: 'dr',
|
|
||||||
countryId: 52,
|
|
||||||
successfulUses: 0,
|
|
||||||
maxUses: 3,
|
|
||||||
});
|
|
||||||
assert.equal(requests.length, 3);
|
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
|
||||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
|
||||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
|
||||||
assert.equal(requests[2].searchParams.get('action'), 'getNumber');
|
|
||||||
assert.equal(requests[2].searchParams.get('maxPrice'), '0.09');
|
|
||||||
assert.equal(requests[2].searchParams.get('fixedPrice'), 'true');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('phone verification helper falls back to plain getNumber when priced request fails to fetch', async () => {
|
|
||||||
const requests = [];
|
|
||||||
let getNumberAttempt = 0;
|
|
||||||
const helpers = api.createPhoneVerificationHelpers({
|
|
||||||
addLog: async () => {},
|
|
||||||
ensureStep8SignupPageReady: async () => {},
|
|
||||||
fetchImpl: async (url) => {
|
|
||||||
const parsedUrl = new URL(url);
|
|
||||||
requests.push(parsedUrl);
|
|
||||||
const action = parsedUrl.searchParams.get('action');
|
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
|
||||||
getNumberAttempt += 1;
|
|
||||||
if (getNumberAttempt === 1) {
|
|
||||||
throw new TypeError('Failed to fetch');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
|
||||||
},
|
|
||||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
|
||||||
sendToContentScriptResilient: async () => ({}),
|
|
||||||
setState: async () => {},
|
|
||||||
sleepWithStop: async () => {},
|
|
||||||
throwIfStopped: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const activation = await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
|
||||||
|
|
||||||
assert.deepStrictEqual(activation, {
|
|
||||||
activationId: '123456',
|
|
||||||
phoneNumber: '66959916439',
|
|
||||||
provider: 'hero-sms',
|
|
||||||
serviceCode: 'dr',
|
|
||||||
countryId: 52,
|
|
||||||
successfulUses: 0,
|
|
||||||
maxUses: 3,
|
|
||||||
});
|
|
||||||
assert.equal(requests.length, 3);
|
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
|
||||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
|
||||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
|
||||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
|
||||||
assert.equal(requests[2].searchParams.get('action'), 'getNumber');
|
|
||||||
assert.equal(requests[2].searchParams.get('maxPrice'), null);
|
|
||||||
assert.equal(requests[2].searchParams.get('fixedPrice'), null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper completes add-phone flow, clears current activation, and stores reusable number state', async () => {
|
test('phone verification helper completes add-phone flow, clears current activation, and stores reusable number state', async () => {
|
||||||
@@ -499,6 +378,7 @@ test('phone verification helper completes add-phone flow, clears current activat
|
|||||||
const stateUpdates = [];
|
const stateUpdates = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
verificationResendCount: 1,
|
verificationResendCount: 1,
|
||||||
currentPhoneActivation: null,
|
currentPhoneActivation: null,
|
||||||
reusablePhoneActivation: null,
|
reusablePhoneActivation: null,
|
||||||
@@ -511,12 +391,6 @@ test('phone verification helper completes add-phone flow, clears current activat
|
|||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
requests.push(parsedUrl);
|
||||||
const action = parsedUrl.searchParams.get('action');
|
const action = parsedUrl.searchParams.get('action');
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumber') {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -593,7 +467,18 @@ test('phone verification helper completes add-phone flow, clears current activat
|
|||||||
provider: 'hero-sms',
|
provider: 'hero-sms',
|
||||||
serviceCode: 'dr',
|
serviceCode: 'dr',
|
||||||
countryId: 52,
|
countryId: 52,
|
||||||
successfulUses: 1,
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pendingPhoneActivationConfirmation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 0,
|
||||||
maxUses: 3,
|
maxUses: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -603,7 +488,87 @@ test('phone verification helper completes add-phone flow, clears current activat
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const actions = requests.map((url) => url.searchParams.get('action'));
|
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||||
assert.deepStrictEqual(actions, ['getPrices', 'getNumber', 'getStatus', 'setStatus']);
|
assert.deepStrictEqual(actions, ['getNumber', 'getStatus', 'setStatus']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone verification helper still succeeds when HeroSMS setStatus(3) fails after a successful submit', async () => {
|
||||||
|
const requests = [];
|
||||||
|
let currentState = {
|
||||||
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
|
verificationResendCount: 1,
|
||||||
|
currentPhoneActivation: null,
|
||||||
|
reusablePhoneActivation: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
ensureStep8SignupPageReady: async () => {},
|
||||||
|
fetchImpl: async (url) => {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
requests.push(parsedUrl);
|
||||||
|
const action = parsedUrl.searchParams.get('action');
|
||||||
|
if (action === 'getNumber') {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action === 'getStatus') {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
text: async () => 'STATUS_OK:654321',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action === 'setStatus') {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
text: async () => 'TEMPORARY_ERROR',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getState: async () => ({ ...currentState }),
|
||||||
|
sendToContentScriptResilient: async (_source, message) => {
|
||||||
|
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||||
|
return {
|
||||||
|
phoneVerificationPage: true,
|
||||||
|
url: 'https://auth.openai.com/phone-verification',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
consentReady: true,
|
||||||
|
url: 'https://auth.openai.com/authorize',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||||
|
},
|
||||||
|
setState: async (updates) => {
|
||||||
|
currentState = { ...currentState, ...updates };
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await helpers.completePhoneVerificationFlow(1, {
|
||||||
|
addPhonePage: true,
|
||||||
|
phoneVerificationPage: false,
|
||||||
|
url: 'https://auth.openai.com/add-phone',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, {
|
||||||
|
success: true,
|
||||||
|
consentReady: true,
|
||||||
|
url: 'https://auth.openai.com/authorize',
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(currentState.currentPhoneActivation, null);
|
||||||
|
assert.deepStrictEqual(currentState.reusablePhoneActivation, null);
|
||||||
|
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, null);
|
||||||
|
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||||
|
assert.deepStrictEqual(actions, ['getNumber', 'getStatus', 'setStatus']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper uses the configured HeroSMS country for both number acquisition and add-phone submission', async () => {
|
test('phone verification helper uses the configured HeroSMS country for both number acquisition and add-phone submission', async () => {
|
||||||
@@ -611,6 +576,7 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
|||||||
const submittedPayloads = [];
|
const submittedPayloads = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
heroSmsCountryId: 16,
|
heroSmsCountryId: 16,
|
||||||
heroSmsCountryLabel: 'United Kingdom',
|
heroSmsCountryLabel: 'United Kingdom',
|
||||||
verificationResendCount: 0,
|
verificationResendCount: 0,
|
||||||
@@ -625,12 +591,6 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
|||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
requests.push(parsedUrl);
|
||||||
const action = parsedUrl.searchParams.get('action');
|
const action = parsedUrl.searchParams.get('action');
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload({ country: '16' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumber') {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -688,12 +648,10 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
|||||||
consentReady: true,
|
consentReady: true,
|
||||||
url: 'https://auth.openai.com/authorize',
|
url: 'https://auth.openai.com/authorize',
|
||||||
});
|
});
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
assert.equal(requests[0].searchParams.get('action'), 'getNumber');
|
||||||
assert.equal(requests[0].searchParams.get('country'), '16');
|
assert.equal(requests[0].searchParams.get('country'), '16');
|
||||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
assert.equal(requests[0].searchParams.get('maxPrice'), '0.08');
|
||||||
assert.equal(requests[1].searchParams.get('country'), '16');
|
assert.equal(requests[0].searchParams.get('fixedPrice'), 'true');
|
||||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
|
||||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
|
||||||
assert.deepStrictEqual(submittedPayloads, [{
|
assert.deepStrictEqual(submittedPayloads, [{
|
||||||
phoneNumber: '447911123456',
|
phoneNumber: '447911123456',
|
||||||
countryId: 16,
|
countryId: 16,
|
||||||
@@ -704,8 +662,10 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
|||||||
test('phone verification helper throws a step-7 restart error after 60 seconds plus one resend window without SMS', async () => {
|
test('phone verification helper throws a step-7 restart error after 60 seconds plus one resend window without SMS', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const messages = [];
|
const messages = [];
|
||||||
|
const stateUpdates = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
verificationResendCount: 0,
|
verificationResendCount: 0,
|
||||||
currentPhoneActivation: null,
|
currentPhoneActivation: null,
|
||||||
reusablePhoneActivation: null,
|
reusablePhoneActivation: null,
|
||||||
@@ -725,13 +685,6 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
|||||||
const action = parsedUrl.searchParams.get('action');
|
const action = parsedUrl.searchParams.get('action');
|
||||||
const id = parsedUrl.searchParams.get('id');
|
const id = parsedUrl.searchParams.get('id');
|
||||||
|
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumber') {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -775,6 +728,7 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
|||||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||||
},
|
},
|
||||||
setState: async (updates) => {
|
setState: async (updates) => {
|
||||||
|
stateUpdates.push(updates);
|
||||||
currentState = { ...currentState, ...updates };
|
currentState = { ...currentState, ...updates };
|
||||||
},
|
},
|
||||||
sleepWithStop: async () => {
|
sleepWithStop: async () => {
|
||||||
@@ -799,7 +753,6 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
|||||||
|
|
||||||
const actions = requests.map((url) => `${url.searchParams.get('action')}:${url.searchParams.get('id') || ''}`);
|
const actions = requests.map((url) => `${url.searchParams.get('action')}:${url.searchParams.get('id') || ''}`);
|
||||||
assert.deepStrictEqual(actions, [
|
assert.deepStrictEqual(actions, [
|
||||||
'getPrices:',
|
|
||||||
'getNumber:',
|
'getNumber:',
|
||||||
'getStatus:123456',
|
'getStatus:123456',
|
||||||
'setStatus:123456',
|
'setStatus:123456',
|
||||||
@@ -807,6 +760,11 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
|||||||
'setStatus:123456',
|
'setStatus:123456',
|
||||||
]);
|
]);
|
||||||
assert.equal(currentState.currentPhoneActivation, null);
|
assert.equal(currentState.currentPhoneActivation, null);
|
||||||
|
assert.equal(
|
||||||
|
stateUpdates.some((updates) => Number(updates.currentPhoneActivation?.successfulUses) > 0),
|
||||||
|
false,
|
||||||
|
'60 seconds without SMS should not increment reuse count'
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
Date.now = realDateNow;
|
Date.now = realDateNow;
|
||||||
}
|
}
|
||||||
@@ -817,6 +775,7 @@ test('phone verification helper replaces the number when code submission returns
|
|||||||
const messages = [];
|
const messages = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
verificationResendCount: 1,
|
verificationResendCount: 1,
|
||||||
currentPhoneActivation: null,
|
currentPhoneActivation: null,
|
||||||
reusablePhoneActivation: null,
|
reusablePhoneActivation: null,
|
||||||
@@ -838,13 +797,6 @@ test('phone verification helper replaces the number when code submission returns
|
|||||||
const action = parsedUrl.searchParams.get('action');
|
const action = parsedUrl.searchParams.get('action');
|
||||||
const id = parsedUrl.searchParams.get('id');
|
const id = parsedUrl.searchParams.get('id');
|
||||||
|
|
||||||
if (action === 'getPrices') {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
text: async () => buildHeroSmsPricesPayload(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumber') {
|
||||||
const nextNumber = numbers[numberIndex];
|
const nextNumber = numbers[numberIndex];
|
||||||
numberIndex += 1;
|
numberIndex += 1;
|
||||||
@@ -925,11 +877,9 @@ test('phone verification helper replaces the number when code submission returns
|
|||||||
|
|
||||||
const actions = requests.map((url) => `${url.searchParams.get('action')}:${url.searchParams.get('id') || ''}`);
|
const actions = requests.map((url) => `${url.searchParams.get('action')}:${url.searchParams.get('id') || ''}`);
|
||||||
assert.deepStrictEqual(actions, [
|
assert.deepStrictEqual(actions, [
|
||||||
'getPrices:',
|
|
||||||
'getNumber:',
|
'getNumber:',
|
||||||
'getStatus:111111',
|
'getStatus:111111',
|
||||||
'setStatus:111111',
|
'setStatus:111111',
|
||||||
'getPrices:',
|
|
||||||
'getNumber:',
|
'getNumber:',
|
||||||
'getStatus:222222',
|
'getStatus:222222',
|
||||||
'setStatus:222222',
|
'setStatus:222222',
|
||||||
@@ -941,15 +891,25 @@ test('phone verification helper replaces the number when code submission returns
|
|||||||
provider: 'hero-sms',
|
provider: 'hero-sms',
|
||||||
serviceCode: 'dr',
|
serviceCode: 'dr',
|
||||||
countryId: 52,
|
countryId: 52,
|
||||||
successfulUses: 1,
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, {
|
||||||
|
activationId: '222222',
|
||||||
|
phoneNumber: '66950000002',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 0,
|
||||||
maxUses: 3,
|
maxUses: 3,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper reuses the same number up to three successful registrations', async () => {
|
test('phone verification helper defers maxUses accounting for reused activations until the full flow succeeds', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
verificationResendCount: 0,
|
verificationResendCount: 0,
|
||||||
currentPhoneActivation: null,
|
currentPhoneActivation: null,
|
||||||
reusablePhoneActivation: {
|
reusablePhoneActivation: {
|
||||||
@@ -1031,13 +991,31 @@ test('phone verification helper reuses the same number up to three successful re
|
|||||||
});
|
});
|
||||||
assert.equal(requests[0].searchParams.get('action'), 'reactivate');
|
assert.equal(requests[0].searchParams.get('action'), 'reactivate');
|
||||||
assert.equal(requests[0].searchParams.get('id'), '123456');
|
assert.equal(requests[0].searchParams.get('id'), '123456');
|
||||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, null);
|
assert.deepStrictEqual(currentState.reusablePhoneActivation, {
|
||||||
|
activationId: '222333',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 2,
|
||||||
|
maxUses: 3,
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, {
|
||||||
|
activationId: '222333',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 2,
|
||||||
|
maxUses: 3,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone verification helper keeps maxUses behavior for reused V2 activations', async () => {
|
test('phone verification helper defers maxUses accounting for reused V2 activations until the full flow succeeds', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
heroSmsApiKey: 'demo-key',
|
heroSmsApiKey: 'demo-key',
|
||||||
|
heroSmsMaxPrice: '0.08',
|
||||||
heroSmsCountryId: 16,
|
heroSmsCountryId: 16,
|
||||||
heroSmsCountryLabel: 'United Kingdom',
|
heroSmsCountryLabel: 'United Kingdom',
|
||||||
verificationResendCount: 0,
|
verificationResendCount: 0,
|
||||||
@@ -1122,5 +1100,131 @@ test('phone verification helper keeps maxUses behavior for reused V2 activations
|
|||||||
});
|
});
|
||||||
const actions = requests.map((url) => url.searchParams.get('action'));
|
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||||
assert.deepStrictEqual(actions, ['reactivate', 'getStatusV2', 'setStatus']);
|
assert.deepStrictEqual(actions, ['reactivate', 'getStatusV2', 'setStatus']);
|
||||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, null);
|
assert.deepStrictEqual(currentState.reusablePhoneActivation, {
|
||||||
|
activationId: '222333',
|
||||||
|
phoneNumber: '447911123456',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 16,
|
||||||
|
successfulUses: 2,
|
||||||
|
maxUses: 3,
|
||||||
|
statusAction: 'getStatusV2',
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, {
|
||||||
|
activationId: '222333',
|
||||||
|
phoneNumber: '447911123456',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 16,
|
||||||
|
successfulUses: 2,
|
||||||
|
maxUses: 3,
|
||||||
|
statusAction: 'getStatusV2',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone verification helper finalizes pending phone activation confirmation after the full flow succeeds', async () => {
|
||||||
|
let currentState = {
|
||||||
|
reusablePhoneActivation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
pendingPhoneActivationConfirmation: {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 0,
|
||||||
|
maxUses: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
getState: async () => ({ ...currentState }),
|
||||||
|
sendToContentScriptResilient: async () => ({}),
|
||||||
|
setState: async (updates) => {
|
||||||
|
currentState = { ...currentState, ...updates };
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const committedActivation = await helpers.finalizePendingPhoneActivationConfirmation();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(committedActivation, {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 1,
|
||||||
|
maxUses: 3,
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(currentState.reusablePhoneActivation, {
|
||||||
|
activationId: '123456',
|
||||||
|
phoneNumber: '66959916439',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 52,
|
||||||
|
successfulUses: 1,
|
||||||
|
maxUses: 3,
|
||||||
|
});
|
||||||
|
assert.equal(currentState.pendingPhoneActivationConfirmation, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone verification helper clears reusable activation when final success exhausts maxUses', async () => {
|
||||||
|
let currentState = {
|
||||||
|
reusablePhoneActivation: {
|
||||||
|
activationId: '222333',
|
||||||
|
phoneNumber: '447911123456',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 16,
|
||||||
|
successfulUses: 2,
|
||||||
|
maxUses: 3,
|
||||||
|
statusAction: 'getStatusV2',
|
||||||
|
},
|
||||||
|
pendingPhoneActivationConfirmation: {
|
||||||
|
activationId: '222333',
|
||||||
|
phoneNumber: '447911123456',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 16,
|
||||||
|
successfulUses: 2,
|
||||||
|
maxUses: 3,
|
||||||
|
statusAction: 'getStatusV2',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
getState: async () => ({ ...currentState }),
|
||||||
|
sendToContentScriptResilient: async () => ({}),
|
||||||
|
setState: async (updates) => {
|
||||||
|
currentState = { ...currentState, ...updates };
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const committedActivation = await helpers.finalizePendingPhoneActivationConfirmation();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(committedActivation, {
|
||||||
|
activationId: '222333',
|
||||||
|
phoneNumber: '447911123456',
|
||||||
|
provider: 'hero-sms',
|
||||||
|
serviceCode: 'dr',
|
||||||
|
countryId: 16,
|
||||||
|
successfulUses: 3,
|
||||||
|
maxUses: 3,
|
||||||
|
statusAction: 'getStatusV2',
|
||||||
|
});
|
||||||
|
assert.equal(currentState.reusablePhoneActivation, null);
|
||||||
|
assert.equal(currentState.pendingPhoneActivationConfirmation, null);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -321,6 +321,7 @@ const inputVerificationResendCount = { value: '' };
|
|||||||
const inputPhoneVerificationEnabled = { checked: false };
|
const inputPhoneVerificationEnabled = { checked: false };
|
||||||
const DEFAULT_PHONE_VERIFICATION_ENABLED = false;
|
const DEFAULT_PHONE_VERIFICATION_ENABLED = false;
|
||||||
const inputHeroSmsApiKey = { value: '' };
|
const inputHeroSmsApiKey = { value: '' };
|
||||||
|
const inputHeroSmsMaxPrice = { value: '' };
|
||||||
const selectHeroSmsCountry = { value: '52', options: [{ value: '52' }] };
|
const selectHeroSmsCountry = { value: '52', options: [{ value: '52' }] };
|
||||||
const inputRunCount = { value: '' };
|
const inputRunCount = { value: '' };
|
||||||
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||||
@@ -347,6 +348,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
|
|||||||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
||||||
function formatAutoStepDelayInputValue(value) { return value == null ? '' : String(value); }
|
function formatAutoStepDelayInputValue(value) { return value == null ? '' : String(value); }
|
||||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||||
|
function normalizeHeroSmsMaxPriceValue(value) { return String(value ?? '').trim(); }
|
||||||
function normalizeHeroSmsCountryId() { return 52; }
|
function normalizeHeroSmsCountryId() { return 52; }
|
||||||
function getSelectedHeroSmsCountryOption() { return { label: 'Thailand' }; }
|
function getSelectedHeroSmsCountryOption() { return { label: 'Thailand' }; }
|
||||||
function updateHeroSmsPlatformDisplay() {}
|
function updateHeroSmsPlatformDisplay() {}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ test('sidepanel html exposes phone verification toggle and dedicated HeroSMS row
|
|||||||
assert.match(html, /id="input-ip-proxy-enabled"/);
|
assert.match(html, /id="input-ip-proxy-enabled"/);
|
||||||
assert.match(html, /id="row-hero-sms-platform"/);
|
assert.match(html, /id="row-hero-sms-platform"/);
|
||||||
assert.match(html, /id="row-hero-sms-country"/);
|
assert.match(html, /id="row-hero-sms-country"/);
|
||||||
|
assert.match(html, /id="row-hero-sms-max-price"/);
|
||||||
assert.match(html, /id="row-hero-sms-api-key"/);
|
assert.match(html, /id="row-hero-sms-api-key"/);
|
||||||
assert.doesNotMatch(html, /id="input-account-run-history-text-enabled"/);
|
assert.doesNotMatch(html, /id="input-account-run-history-text-enabled"/);
|
||||||
});
|
});
|
||||||
@@ -101,6 +102,7 @@ test('updatePhoneVerificationSettingsUI toggles HeroSMS rows from the sms switch
|
|||||||
const inputPhoneVerificationEnabled = { checked: false };
|
const inputPhoneVerificationEnabled = { checked: false };
|
||||||
const rowHeroSmsPlatform = { style: { display: 'none' } };
|
const rowHeroSmsPlatform = { style: { display: 'none' } };
|
||||||
const rowHeroSmsCountry = { style: { display: 'none' } };
|
const rowHeroSmsCountry = { style: { display: 'none' } };
|
||||||
|
const rowHeroSmsMaxPrice = { style: { display: 'none' } };
|
||||||
const rowHeroSmsApiKey = { style: { display: 'none' } };
|
const rowHeroSmsApiKey = { style: { display: 'none' } };
|
||||||
|
|
||||||
${extractFunction('updatePhoneVerificationSettingsUI')}
|
${extractFunction('updatePhoneVerificationSettingsUI')}
|
||||||
@@ -109,6 +111,7 @@ return {
|
|||||||
inputPhoneVerificationEnabled,
|
inputPhoneVerificationEnabled,
|
||||||
rowHeroSmsPlatform,
|
rowHeroSmsPlatform,
|
||||||
rowHeroSmsCountry,
|
rowHeroSmsCountry,
|
||||||
|
rowHeroSmsMaxPrice,
|
||||||
rowHeroSmsApiKey,
|
rowHeroSmsApiKey,
|
||||||
updatePhoneVerificationSettingsUI,
|
updatePhoneVerificationSettingsUI,
|
||||||
};
|
};
|
||||||
@@ -117,12 +120,14 @@ return {
|
|||||||
api.updatePhoneVerificationSettingsUI();
|
api.updatePhoneVerificationSettingsUI();
|
||||||
assert.equal(api.rowHeroSmsPlatform.style.display, 'none');
|
assert.equal(api.rowHeroSmsPlatform.style.display, 'none');
|
||||||
assert.equal(api.rowHeroSmsCountry.style.display, 'none');
|
assert.equal(api.rowHeroSmsCountry.style.display, 'none');
|
||||||
|
assert.equal(api.rowHeroSmsMaxPrice.style.display, 'none');
|
||||||
assert.equal(api.rowHeroSmsApiKey.style.display, 'none');
|
assert.equal(api.rowHeroSmsApiKey.style.display, 'none');
|
||||||
|
|
||||||
api.inputPhoneVerificationEnabled.checked = true;
|
api.inputPhoneVerificationEnabled.checked = true;
|
||||||
api.updatePhoneVerificationSettingsUI();
|
api.updatePhoneVerificationSettingsUI();
|
||||||
assert.equal(api.rowHeroSmsPlatform.style.display, '');
|
assert.equal(api.rowHeroSmsPlatform.style.display, '');
|
||||||
assert.equal(api.rowHeroSmsCountry.style.display, '');
|
assert.equal(api.rowHeroSmsCountry.style.display, '');
|
||||||
|
assert.equal(api.rowHeroSmsMaxPrice.style.display, '');
|
||||||
assert.equal(api.rowHeroSmsApiKey.style.display, '');
|
assert.equal(api.rowHeroSmsApiKey.style.display, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -174,6 +179,7 @@ const inputAutoStepDelaySeconds = { value: '' };
|
|||||||
const inputPhoneVerificationEnabled = { checked: true };
|
const inputPhoneVerificationEnabled = { checked: true };
|
||||||
const inputVerificationResendCount = { value: '4' };
|
const inputVerificationResendCount = { value: '4' };
|
||||||
const inputHeroSmsApiKey = { value: 'demo-key' };
|
const inputHeroSmsApiKey = { value: 'demo-key' };
|
||||||
|
const inputHeroSmsMaxPrice = { value: '0.08' };
|
||||||
const inputAccountRunHistoryHelperBaseUrl = { value: 'http://127.0.0.1:17373' };
|
const inputAccountRunHistoryHelperBaseUrl = { value: 'http://127.0.0.1:17373' };
|
||||||
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||||
const DEFAULT_HERO_SMS_COUNTRY_ID = 52;
|
const DEFAULT_HERO_SMS_COUNTRY_ID = 52;
|
||||||
@@ -202,6 +208,7 @@ function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Num
|
|||||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||||
${extractFunction('normalizeHeroSmsCountryId')}
|
${extractFunction('normalizeHeroSmsCountryId')}
|
||||||
${extractFunction('normalizeHeroSmsCountryLabel')}
|
${extractFunction('normalizeHeroSmsCountryLabel')}
|
||||||
|
${extractFunction('normalizeHeroSmsMaxPriceValue')}
|
||||||
${extractFunction('getSelectedHeroSmsCountryOption')}
|
${extractFunction('getSelectedHeroSmsCountryOption')}
|
||||||
${extractFunction('collectSettingsPayload')}
|
${extractFunction('collectSettingsPayload')}
|
||||||
return { collectSettingsPayload };
|
return { collectSettingsPayload };
|
||||||
@@ -213,6 +220,7 @@ return { collectSettingsPayload };
|
|||||||
assert.equal(payload.accountRunHistoryTextEnabled, true);
|
assert.equal(payload.accountRunHistoryTextEnabled, true);
|
||||||
assert.equal(payload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
assert.equal(payload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
||||||
assert.equal(payload.heroSmsApiKey, 'demo-key');
|
assert.equal(payload.heroSmsApiKey, 'demo-key');
|
||||||
|
assert.equal(payload.heroSmsMaxPrice, '0.08');
|
||||||
assert.equal(payload.heroSmsCountryId, 52);
|
assert.equal(payload.heroSmsCountryId, 52);
|
||||||
assert.equal(payload.heroSmsCountryLabel, 'Thailand');
|
assert.equal(payload.heroSmsCountryLabel, 'Thailand');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ function broadcastDataUpdate() {}
|
|||||||
async function addLog(message) {
|
async function addLog(message) {
|
||||||
logMessages.push(message);
|
logMessages.push(message);
|
||||||
}
|
}
|
||||||
|
async function finalizePhoneActivationAfterSuccessfulFlow() {}
|
||||||
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
|
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
|
||||||
function matchesSourceUrlFamily() {
|
function matchesSourceUrlFamily() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user