feat: 统一验证码重发次数配置,优化相关逻辑和界面
This commit is contained in:
+18
-9
@@ -153,9 +153,9 @@ const AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0;
|
|||||||
const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;
|
const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;
|
||||||
const VERIFICATION_RESEND_COUNT_MIN = 0;
|
const VERIFICATION_RESEND_COUNT_MIN = 0;
|
||||||
const VERIFICATION_RESEND_COUNT_MAX = 20;
|
const VERIFICATION_RESEND_COUNT_MAX = 20;
|
||||||
const DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT = 5;
|
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||||
const DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT = 4;
|
|
||||||
const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds'];
|
const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds'];
|
||||||
|
const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = ['signupVerificationResendCount', 'loginVerificationResendCount'];
|
||||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||||
const DEFAULT_CPA_CALLBACK_MODE = 'step9';
|
const DEFAULT_CPA_CALLBACK_MODE = 'step9';
|
||||||
const MAIL_2925_MODE_PROVIDE = 'provide';
|
const MAIL_2925_MODE_PROVIDE = 'provide';
|
||||||
@@ -222,8 +222,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
autoRunDelayEnabled: false,
|
autoRunDelayEnabled: false,
|
||||||
autoRunDelayMinutes: 30,
|
autoRunDelayMinutes: 30,
|
||||||
autoStepDelaySeconds: null,
|
autoStepDelaySeconds: null,
|
||||||
signupVerificationResendCount: DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT,
|
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
||||||
loginVerificationResendCount: DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT,
|
|
||||||
mailProvider: '163',
|
mailProvider: '163',
|
||||||
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
||||||
emailGenerator: 'duck',
|
emailGenerator: 'duck',
|
||||||
@@ -804,10 +803,8 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizeAutoRunDelayMinutes(value);
|
return normalizeAutoRunDelayMinutes(value);
|
||||||
case 'autoStepDelaySeconds':
|
case 'autoStepDelaySeconds':
|
||||||
return normalizeAutoStepDelaySeconds(value, PERSISTED_SETTING_DEFAULTS.autoStepDelaySeconds);
|
return normalizeAutoStepDelaySeconds(value, PERSISTED_SETTING_DEFAULTS.autoStepDelaySeconds);
|
||||||
case 'signupVerificationResendCount':
|
case 'verificationResendCount':
|
||||||
return normalizeVerificationResendCount(value, DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT);
|
return normalizeVerificationResendCount(value, DEFAULT_VERIFICATION_RESEND_COUNT);
|
||||||
case 'loginVerificationResendCount':
|
|
||||||
return normalizeVerificationResendCount(value, DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT);
|
|
||||||
case 'mailProvider':
|
case 'mailProvider':
|
||||||
return normalizeMailProvider(value);
|
return normalizeMailProvider(value);
|
||||||
case 'mail2925Mode':
|
case 'mail2925Mode':
|
||||||
@@ -870,6 +867,14 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
normalizedInput.autoStepDelaySeconds = legacyAutoStepDelaySeconds;
|
normalizedInput.autoStepDelaySeconds = legacyAutoStepDelaySeconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (normalizedInput.verificationResendCount === undefined) {
|
||||||
|
const legacyVerificationResendCount = normalizedInput.signupVerificationResendCount !== undefined
|
||||||
|
? normalizedInput.signupVerificationResendCount
|
||||||
|
: normalizedInput.loginVerificationResendCount;
|
||||||
|
if (legacyVerificationResendCount !== undefined) {
|
||||||
|
normalizedInput.verificationResendCount = legacyVerificationResendCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {};
|
const payload = {};
|
||||||
let matchedKeyCount = 0;
|
let matchedKeyCount = 0;
|
||||||
@@ -905,7 +910,11 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getPersistedSettings() {
|
async function getPersistedSettings() {
|
||||||
const stored = await chrome.storage.local.get([...PERSISTED_SETTING_KEYS, ...LEGACY_AUTO_STEP_DELAY_KEYS]);
|
const stored = await chrome.storage.local.get([
|
||||||
|
...PERSISTED_SETTING_KEYS,
|
||||||
|
...LEGACY_AUTO_STEP_DELAY_KEYS,
|
||||||
|
...LEGACY_VERIFICATION_RESEND_COUNT_KEYS,
|
||||||
|
]);
|
||||||
return buildPersistentSettingsPayload(stored, { fillDefaults: true });
|
return buildPersistentSettingsPayload(stored, { fillDefaults: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+178
-93
@@ -1,4 +1,4 @@
|
|||||||
(function attachBackgroundVerificationFlow(root, factory) {
|
(function attachBackgroundVerificationFlow(root, factory) {
|
||||||
root.MultiPageBackgroundVerificationFlow = factory();
|
root.MultiPageBackgroundVerificationFlow = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundVerificationFlowModule() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundVerificationFlowModule() {
|
||||||
function createVerificationFlowHelpers(deps = {}) {
|
function createVerificationFlowHelpers(deps = {}) {
|
||||||
@@ -36,6 +36,52 @@
|
|||||||
return step === 4 ? '注册' : '登录';
|
return step === 4 ? '注册' : '登录';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getVerificationResendStateKey() {
|
||||||
|
return 'verificationResendCount';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeVerificationResendCount(value, fallback = 0) {
|
||||||
|
const numeric = Number(value);
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return Math.max(0, Math.floor(Number(fallback) || 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(20, Math.max(0, Math.floor(numeric)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLegacyVerificationResendCountDefault(step, options = {}) {
|
||||||
|
const requestFreshCodeFirst = Boolean(options.requestFreshCodeFirst);
|
||||||
|
const legacyMaxRounds = Math.max(1, Math.floor(Number(VERIFICATION_POLL_MAX_ROUNDS) || 1));
|
||||||
|
if (step === 4 && requestFreshCodeFirst) {
|
||||||
|
return legacyMaxRounds;
|
||||||
|
}
|
||||||
|
return Math.max(0, legacyMaxRounds - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfiguredVerificationResendCount(step, state, options = {}) {
|
||||||
|
const stateKey = getVerificationResendStateKey(step);
|
||||||
|
const configuredValue = state?.[stateKey] !== undefined
|
||||||
|
? state[stateKey]
|
||||||
|
: (state?.signupVerificationResendCount ?? state?.loginVerificationResendCount);
|
||||||
|
return normalizeVerificationResendCount(
|
||||||
|
configuredValue,
|
||||||
|
getLegacyVerificationResendCountDefault(step, options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMaxResendRequests(pollOverrides = {}) {
|
||||||
|
if (pollOverrides.maxResendRequests !== undefined) {
|
||||||
|
return normalizeVerificationResendCount(pollOverrides.maxResendRequests, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyMaxRounds = Number(pollOverrides.maxRounds);
|
||||||
|
if (Number.isFinite(legacyMaxRounds)) {
|
||||||
|
return Math.max(0, Math.floor(legacyMaxRounds) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(0, Math.floor(Number(VERIFICATION_POLL_MAX_ROUNDS) || 1) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
async function confirmCustomVerificationStepBypass(step) {
|
async function confirmCustomVerificationStepBypass(step) {
|
||||||
const verificationLabel = getVerificationCodeLabel(step);
|
const verificationLabel = getVerificationCodeLabel(step);
|
||||||
await addLog(`步骤 ${step}:当前为自定义邮箱模式,请手动在页面中输入${verificationLabel}验证码并进入下一页面。`, 'warn');
|
await addLog(`步骤 ${step}:当前为自定义邮箱模式,请手动在页面中输入${verificationLabel}验证码并进入下一页面。`, 'warn');
|
||||||
@@ -144,6 +190,7 @@
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
maxRounds: _ignoredMaxRounds,
|
maxRounds: _ignoredMaxRounds,
|
||||||
|
maxResendRequests: _ignoredMaxResendRequests,
|
||||||
resendIntervalMs: _ignoredResendIntervalMs,
|
resendIntervalMs: _ignoredResendIntervalMs,
|
||||||
lastResendAt: _ignoredLastResendAt,
|
lastResendAt: _ignoredLastResendAt,
|
||||||
onResendRequestedAt: _ignoredOnResendRequestedAt,
|
onResendRequestedAt: _ignoredOnResendRequestedAt,
|
||||||
@@ -154,14 +201,18 @@
|
|||||||
: null;
|
: null;
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
let filterAfterTimestamp = payloadOverrides.filterAfterTimestamp ?? getVerificationPollPayload(step, state).filterAfterTimestamp;
|
let filterAfterTimestamp = payloadOverrides.filterAfterTimestamp ?? getVerificationPollPayload(step, state).filterAfterTimestamp;
|
||||||
const maxRounds = pollOverrides.maxRounds || VERIFICATION_POLL_MAX_ROUNDS;
|
const maxResendRequests = resolveMaxResendRequests(pollOverrides);
|
||||||
|
const totalRounds = maxResendRequests + 1;
|
||||||
|
const maxRounds = totalRounds;
|
||||||
const resendIntervalMs = Math.max(0, Number(pollOverrides.resendIntervalMs) || 0);
|
const resendIntervalMs = Math.max(0, Number(pollOverrides.resendIntervalMs) || 0);
|
||||||
let lastResendAt = Number(pollOverrides.lastResendAt) || 0;
|
let lastResendAt = Number(pollOverrides.lastResendAt) || 0;
|
||||||
|
let usedResendRequests = 0;
|
||||||
|
|
||||||
for (let round = 1; round <= maxRounds; round++) {
|
for (let round = 1; round <= totalRounds; round++) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (round > 1) {
|
if (round > 1) {
|
||||||
lastResendAt = await requestVerificationCodeResend(step);
|
lastResendAt = await requestVerificationCodeResend(step);
|
||||||
|
usedResendRequests += 1;
|
||||||
if (onResendRequestedAt) {
|
if (onResendRequestedAt) {
|
||||||
const nextFilterAfterTimestamp = await onResendRequestedAt(lastResendAt);
|
const nextFilterAfterTimestamp = await onResendRequestedAt(lastResendAt);
|
||||||
if (nextFilterAfterTimestamp !== undefined) {
|
if (nextFilterAfterTimestamp !== undefined) {
|
||||||
@@ -215,6 +266,7 @@
|
|||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
lastResendAt,
|
lastResendAt,
|
||||||
|
remainingResendRequests: Math.max(0, maxResendRequests - usedResendRequests),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
@@ -246,7 +298,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pollFreshVerificationCode(step, state, mail, pollOverrides = {}) {
|
async function pollFreshVerificationCode(step, state, mail, pollOverrides = {}) {
|
||||||
const { onResendRequestedAt, ...cleanPollOverrides } = pollOverrides;
|
const {
|
||||||
|
onResendRequestedAt,
|
||||||
|
maxRounds: _ignoredMaxRounds,
|
||||||
|
maxResendRequests: _ignoredMaxResendRequests,
|
||||||
|
...cleanPollOverrides
|
||||||
|
} = pollOverrides;
|
||||||
|
|
||||||
if (mail.provider === HOTMAIL_PROVIDER) {
|
if (mail.provider === HOTMAIL_PROVIDER) {
|
||||||
const hotmailPollConfig = getHotmailVerificationPollConfig(step);
|
const hotmailPollConfig = getHotmailVerificationPollConfig(step);
|
||||||
@@ -259,13 +316,13 @@
|
|||||||
if (mail.provider === LUCKMAIL_PROVIDER) {
|
if (mail.provider === LUCKMAIL_PROVIDER) {
|
||||||
return pollLuckmailVerificationCode(step, state, {
|
return pollLuckmailVerificationCode(step, state, {
|
||||||
...getVerificationPollPayload(step, state),
|
...getVerificationPollPayload(step, state),
|
||||||
...pollOverrides,
|
...cleanPollOverrides,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
if (mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
||||||
return pollCloudflareTempEmailVerificationCode(step, state, {
|
return pollCloudflareTempEmailVerificationCode(step, state, {
|
||||||
...getVerificationPollPayload(step, state),
|
...getVerificationPollPayload(step, state),
|
||||||
...pollOverrides,
|
...cleanPollOverrides,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,12 +341,15 @@
|
|||||||
|
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
let filterAfterTimestamp = cleanPollOverrides.filterAfterTimestamp ?? getVerificationPollPayload(step, state).filterAfterTimestamp;
|
let filterAfterTimestamp = cleanPollOverrides.filterAfterTimestamp ?? getVerificationPollPayload(step, state).filterAfterTimestamp;
|
||||||
const maxRounds = pollOverrides.maxRounds || VERIFICATION_POLL_MAX_ROUNDS;
|
const maxResendRequests = resolveMaxResendRequests(pollOverrides);
|
||||||
|
const maxRounds = maxResendRequests + 1;
|
||||||
|
let usedResendRequests = 0;
|
||||||
|
|
||||||
for (let round = 1; round <= maxRounds; round++) {
|
for (let round = 1; round <= maxRounds; round++) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (round > 1) {
|
if (round > 1) {
|
||||||
const requestedAt = await requestVerificationCodeResend(step);
|
const requestedAt = await requestVerificationCodeResend(step);
|
||||||
|
usedResendRequests += 1;
|
||||||
if (typeof onResendRequestedAt === 'function') {
|
if (typeof onResendRequestedAt === 'function') {
|
||||||
const nextFilterAfterTimestamp = await onResendRequestedAt(requestedAt);
|
const nextFilterAfterTimestamp = await onResendRequestedAt(requestedAt);
|
||||||
if (nextFilterAfterTimestamp !== undefined) {
|
if (nextFilterAfterTimestamp !== undefined) {
|
||||||
@@ -331,7 +391,10 @@
|
|||||||
throw new Error(`步骤 ${step}:再次收到了相同的${getVerificationCodeLabel(step)}验证码:${result.code}`);
|
throw new Error(`步骤 ${step}:再次收到了相同的${getVerificationCodeLabel(step)}验证码:${result.code}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return {
|
||||||
|
...result,
|
||||||
|
remainingResendRequests: Math.max(0, maxResendRequests - usedResendRequests),
|
||||||
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -386,6 +449,12 @@
|
|||||||
const requestFreshCodeFirst = options.requestFreshCodeFirst !== undefined
|
const requestFreshCodeFirst = options.requestFreshCodeFirst !== undefined
|
||||||
? Boolean(options.requestFreshCodeFirst)
|
? Boolean(options.requestFreshCodeFirst)
|
||||||
: (hotmailPollConfig?.requestFreshCodeFirst ?? false);
|
: (hotmailPollConfig?.requestFreshCodeFirst ?? false);
|
||||||
|
let remainingAutomaticResendCount = options.maxResendRequests !== undefined
|
||||||
|
? normalizeVerificationResendCount(
|
||||||
|
options.maxResendRequests,
|
||||||
|
getLegacyVerificationResendCountDefault(step, { requestFreshCodeFirst })
|
||||||
|
)
|
||||||
|
: getConfiguredVerificationResendCount(step, state, { requestFreshCodeFirst });
|
||||||
const maxSubmitAttempts = 3;
|
const maxSubmitAttempts = 3;
|
||||||
const resendIntervalMs = Math.max(0, Number(options.resendIntervalMs) || 0);
|
const resendIntervalMs = Math.max(0, Number(options.resendIntervalMs) || 0);
|
||||||
let lastResendAt = Number(options.lastResendAt) || 0;
|
let lastResendAt = Number(options.lastResendAt) || 0;
|
||||||
@@ -410,104 +479,120 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (requestFreshCodeFirst) {
|
if (requestFreshCodeFirst) {
|
||||||
try {
|
if (remainingAutomaticResendCount <= 0) {
|
||||||
lastResendAt = await requestVerificationCodeResend(step);
|
await addLog(`步骤 ${step}:当前自动重新发送验证码次数为 0,将直接使用当前时间窗口轮询邮箱。`, 'info');
|
||||||
await updateFilterAfterTimestampForVerificationStep(lastResendAt);
|
} else {
|
||||||
await addLog(`步骤 ${step}:已先请求一封新的${getVerificationCodeLabel(step)}验证码,再开始轮询邮箱。`, 'warn');
|
try {
|
||||||
} catch (err) {
|
lastResendAt = await requestVerificationCodeResend(step);
|
||||||
if (isStopError(err)) {
|
remainingAutomaticResendCount -= 1;
|
||||||
throw err;
|
await updateFilterAfterTimestampForVerificationStep(lastResendAt);
|
||||||
|
await addLog(`步骤 ${step}:已先请求一封新的${getVerificationCodeLabel(step)}验证码,再开始轮询邮箱。`, 'warn');
|
||||||
|
} catch (err) {
|
||||||
|
if (isStopError(err)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
await addLog(`步骤 ${step}:首次重新获取验证码失败:${err.message},将继续使用当前时间窗口轮询。`, 'warn');
|
||||||
}
|
}
|
||||||
await addLog(`步骤 ${step}:首次重新获取验证码失败:${err.message},将继续使用当前时间窗口轮询。`, 'warn');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mail.provider === HOTMAIL_PROVIDER) {
|
if (mail.provider === HOTMAIL_PROVIDER) {
|
||||||
const initialDelayMs = Number(options.initialDelayMs ?? hotmailPollConfig.initialDelayMs) || 0;
|
const initialDelayMs = Number(options.initialDelayMs ?? hotmailPollConfig.initialDelayMs) || 0;
|
||||||
if (initialDelayMs > 0) {
|
if (initialDelayMs > 0) {
|
||||||
await addLog(`步骤 ${step}:等待 ${Math.round(initialDelayMs / 1000)} 秒,让 Hotmail 验证码邮件先到达...`, 'info');
|
await addLog(`步骤 ${step}:等待 ${Math.round(initialDelayMs / 1000)} 秒,让 Hotmail 验证码邮件先到达...`, 'info');
|
||||||
await sleepWithStop(initialDelayMs);
|
await sleepWithStop(initialDelayMs);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxSubmitAttempts; attempt++) {
|
|
||||||
const pollOptions = {
|
|
||||||
excludeCodes: [...rejectedCodes],
|
|
||||||
resendIntervalMs,
|
|
||||||
lastResendAt,
|
|
||||||
onResendRequestedAt: updateFilterAfterTimestampForVerificationStep,
|
|
||||||
};
|
|
||||||
if (nextFilterAfterTimestamp !== null && nextFilterAfterTimestamp !== undefined) {
|
|
||||||
pollOptions.filterAfterTimestamp = nextFilterAfterTimestamp;
|
|
||||||
}
|
|
||||||
const result = await pollFreshVerificationCode(step, state, mail, pollOptions);
|
|
||||||
lastResendAt = Number(result?.lastResendAt) || lastResendAt;
|
|
||||||
|
|
||||||
throwIfStopped();
|
|
||||||
await addLog(`步骤 ${step}:已获取${getVerificationCodeLabel(step)}验证码:${result.code}`);
|
|
||||||
if (beforeSubmit) {
|
|
||||||
await beforeSubmit(result, {
|
|
||||||
attempt,
|
|
||||||
rejectedCodes: new Set(rejectedCodes),
|
|
||||||
filterAfterTimestamp: nextFilterAfterTimestamp ?? undefined,
|
|
||||||
lastResendAt,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throwIfStopped();
|
|
||||||
const submitResult = await submitVerificationCode(step, result.code);
|
|
||||||
|
|
||||||
if (submitResult.invalidCode) {
|
|
||||||
rejectedCodes.add(result.code);
|
|
||||||
await addLog(`步骤 ${step}:验证码被页面拒绝:${submitResult.errorText || result.code}`, 'warn');
|
|
||||||
|
|
||||||
if (attempt >= maxSubmitAttempts) {
|
|
||||||
throw new Error(`步骤 ${step}:验证码连续失败,已达到 ${maxSubmitAttempts} 次重试上限。`);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const remainingBeforeResendMs = resendIntervalMs > 0 && lastResendAt > 0
|
for (let attempt = 1; attempt <= maxSubmitAttempts; attempt++) {
|
||||||
? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
|
const pollOptions = {
|
||||||
: 0;
|
excludeCodes: [...rejectedCodes],
|
||||||
if (remainingBeforeResendMs > 0) {
|
maxResendRequests: remainingAutomaticResendCount,
|
||||||
await addLog(
|
resendIntervalMs,
|
||||||
`步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts})...`,
|
lastResendAt,
|
||||||
'warn'
|
onResendRequestedAt: updateFilterAfterTimestampForVerificationStep,
|
||||||
);
|
};
|
||||||
|
if (nextFilterAfterTimestamp !== null && nextFilterAfterTimestamp !== undefined) {
|
||||||
|
pollOptions.filterAfterTimestamp = nextFilterAfterTimestamp;
|
||||||
|
}
|
||||||
|
const result = await pollFreshVerificationCode(step, state, mail, pollOptions);
|
||||||
|
lastResendAt = Number(result?.lastResendAt) || lastResendAt;
|
||||||
|
remainingAutomaticResendCount = normalizeVerificationResendCount(
|
||||||
|
result?.remainingResendRequests,
|
||||||
|
remainingAutomaticResendCount
|
||||||
|
);
|
||||||
|
|
||||||
|
throwIfStopped();
|
||||||
|
await addLog(`步骤 ${step}:已获取${getVerificationCodeLabel(step)}验证码:${result.code}`);
|
||||||
|
if (beforeSubmit) {
|
||||||
|
await beforeSubmit(result, {
|
||||||
|
attempt,
|
||||||
|
rejectedCodes: new Set(rejectedCodes),
|
||||||
|
filterAfterTimestamp: nextFilterAfterTimestamp ?? undefined,
|
||||||
|
lastResendAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throwIfStopped();
|
||||||
|
const submitResult = await submitVerificationCode(step, result.code);
|
||||||
|
|
||||||
|
if (submitResult.invalidCode) {
|
||||||
|
rejectedCodes.add(result.code);
|
||||||
|
await addLog(`步骤 ${step}:验证码被页面拒绝:${submitResult.errorText || result.code}`, 'warn');
|
||||||
|
|
||||||
|
if (attempt >= maxSubmitAttempts) {
|
||||||
|
throw new Error(`步骤 ${step}:验证码连续失败,已达到 ${maxSubmitAttempts} 次重试上限。`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingBeforeResendMs = resendIntervalMs > 0 && lastResendAt > 0
|
||||||
|
? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
|
||||||
|
: 0;
|
||||||
|
if (remainingBeforeResendMs > 0) {
|
||||||
|
await addLog(
|
||||||
|
`步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts})...`,
|
||||||
|
'warn'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingAutomaticResendCount <= 0) {
|
||||||
|
await addLog(`步骤 ${step}:已达到自动重新发送验证码次数上限,将直接使用当前时间窗口继续重试。`, 'warn');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastResendAt = await requestVerificationCodeResend(step);
|
||||||
|
remainingAutomaticResendCount -= 1;
|
||||||
|
await updateFilterAfterTimestampForVerificationStep(lastResendAt);
|
||||||
|
await addLog(`步骤 ${step}:提交失败后已请求新验证码(${attempt + 1}/${maxSubmitAttempts})...`, 'warn');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastResendAt = await requestVerificationCodeResend(step);
|
await setState({
|
||||||
await updateFilterAfterTimestampForVerificationStep(lastResendAt);
|
lastEmailTimestamp: result.emailTimestamp,
|
||||||
await addLog(`步骤 ${step}:提交失败后已请求新验证码(${attempt + 1}/${maxSubmitAttempts})...`, 'warn');
|
[stateKey]: result.code,
|
||||||
continue;
|
});
|
||||||
|
|
||||||
|
await completeStepFromBackground(step, {
|
||||||
|
emailTimestamp: result.emailTimestamp,
|
||||||
|
code: result.code,
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await setState({
|
|
||||||
lastEmailTimestamp: result.emailTimestamp,
|
|
||||||
[stateKey]: result.code,
|
|
||||||
});
|
|
||||||
|
|
||||||
await completeStepFromBackground(step, {
|
|
||||||
emailTimestamp: result.emailTimestamp,
|
|
||||||
code: result.code,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
confirmCustomVerificationStepBypass,
|
||||||
|
getVerificationCodeLabel,
|
||||||
|
getVerificationCodeStateKey,
|
||||||
|
getVerificationPollPayload,
|
||||||
|
pollFreshVerificationCode,
|
||||||
|
pollFreshVerificationCodeWithResendInterval,
|
||||||
|
requestVerificationCodeResend,
|
||||||
|
resolveVerificationStep,
|
||||||
|
submitVerificationCode,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
confirmCustomVerificationStepBypass,
|
createVerificationFlowHelpers,
|
||||||
getVerificationCodeLabel,
|
|
||||||
getVerificationCodeStateKey,
|
|
||||||
getVerificationPollPayload,
|
|
||||||
pollFreshVerificationCode,
|
|
||||||
pollFreshVerificationCodeWithResendInterval,
|
|
||||||
requestVerificationCodeResend,
|
|
||||||
resolveVerificationStep,
|
|
||||||
submitVerificationCode,
|
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
createVerificationFlowHelpers,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1259,6 +1259,11 @@ header {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-inline-right {
|
||||||
|
min-width: 196px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-switch {
|
.toggle-switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
+29
-22
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
@@ -243,25 +243,6 @@
|
|||||||
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
|
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row">
|
|
||||||
<span class="data-label">自动重试</span>
|
|
||||||
<div class="data-inline fallback-inline">
|
|
||||||
<label class="toggle-switch" for="input-auto-skip-failures">
|
|
||||||
<input type="checkbox" id="input-auto-skip-failures" />
|
|
||||||
<span class="toggle-switch-track" aria-hidden="true">
|
|
||||||
<span class="toggle-switch-thumb"></span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="timing-field timing-field-labeled timing-field-right fallback-thread-interval">
|
|
||||||
<span class="auto-delay-caption">线程间隔</span>
|
|
||||||
<div class="auto-delay-controls">
|
|
||||||
<input type="number" id="input-auto-skip-failures-thread-interval-minutes" class="data-input auto-delay-input" value="0"
|
|
||||||
min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
|
|
||||||
<span class="data-unit">分钟</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">延迟</span>
|
<span class="data-label">延迟</span>
|
||||||
<div class="data-inline auto-delay-inline">
|
<div class="data-inline auto-delay-inline">
|
||||||
@@ -288,16 +269,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">自动重试</span>
|
||||||
|
<div class="data-inline fallback-inline">
|
||||||
|
<label class="toggle-switch" for="input-auto-skip-failures">
|
||||||
|
<input type="checkbox" id="input-auto-skip-failures" />
|
||||||
|
<span class="toggle-switch-track" aria-hidden="true">
|
||||||
|
<span class="toggle-switch-thumb"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="timing-field timing-field-labeled timing-field-right fallback-thread-interval">
|
||||||
|
<span class="auto-delay-caption">线程间隔</span>
|
||||||
|
<div class="auto-delay-controls">
|
||||||
|
<input type="number" id="input-auto-skip-failures-thread-interval-minutes" class="data-input auto-delay-input" value="0"
|
||||||
|
min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
|
||||||
|
<span class="data-unit">分钟</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">本地留档</span>
|
<span class="data-label">本地留档</span>
|
||||||
<div class="data-inline">
|
<div class="data-inline auto-delay-inline">
|
||||||
<label class="toggle-switch" for="input-account-run-history-text-enabled">
|
<label class="toggle-switch" for="input-account-run-history-text-enabled">
|
||||||
<input type="checkbox" id="input-account-run-history-text-enabled" />
|
<input type="checkbox" id="input-account-run-history-text-enabled" />
|
||||||
<span class="toggle-switch-track" aria-hidden="true">
|
<span class="toggle-switch-track" aria-hidden="true">
|
||||||
<span class="toggle-switch-thumb"></span>
|
<span class="toggle-switch-thumb"></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<span class="data-value">同步写入账号运行历史 txt</span>
|
<div class="timing-field timing-field-right setting-inline-right">
|
||||||
|
<span class="auto-delay-caption">验证码重发</span>
|
||||||
|
<div class="auto-delay-controls">
|
||||||
|
<input type="number" id="input-verification-resend-count" class="data-input auto-delay-input" value="4"
|
||||||
|
min="0" max="20" step="1" title="自动点击重新发送验证码的次数" />
|
||||||
|
<span class="data-unit">次</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-account-run-history-helper-base-url" style="display:none;">
|
<div class="data-row" id="row-account-run-history-helper-base-url" style="display:none;">
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('inpu
|
|||||||
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
|
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
|
||||||
const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
|
const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
|
||||||
const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds');
|
const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds');
|
||||||
|
const inputVerificationResendCount = document.getElementById('input-verification-resend-count');
|
||||||
const inputAccountRunHistoryTextEnabled = document.getElementById('input-account-run-history-text-enabled');
|
const inputAccountRunHistoryTextEnabled = document.getElementById('input-account-run-history-text-enabled');
|
||||||
const rowAccountRunHistoryHelperBaseUrl = document.getElementById('row-account-run-history-helper-base-url');
|
const rowAccountRunHistoryHelperBaseUrl = document.getElementById('row-account-run-history-helper-base-url');
|
||||||
const inputAccountRunHistoryHelperBaseUrl = document.getElementById('input-account-run-history-helper-base-url');
|
const inputAccountRunHistoryHelperBaseUrl = document.getElementById('input-account-run-history-helper-base-url');
|
||||||
@@ -193,6 +194,9 @@ const AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES = 0;
|
|||||||
const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
|
const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
|
||||||
const AUTO_STEP_DELAY_MIN_SECONDS = 0;
|
const AUTO_STEP_DELAY_MIN_SECONDS = 0;
|
||||||
const AUTO_STEP_DELAY_MAX_SECONDS = 600;
|
const AUTO_STEP_DELAY_MAX_SECONDS = 600;
|
||||||
|
const VERIFICATION_RESEND_COUNT_MIN = 0;
|
||||||
|
const VERIFICATION_RESEND_COUNT_MAX = 20;
|
||||||
|
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||||
const DEFAULT_CPA_CALLBACK_MODE = 'step9';
|
const DEFAULT_CPA_CALLBACK_MODE = 'step9';
|
||||||
const MAIL_2925_MODE_PROVIDE = 'provide';
|
const MAIL_2925_MODE_PROVIDE = 'provide';
|
||||||
@@ -976,6 +980,23 @@ function normalizeAutoStepDelaySeconds(value) {
|
|||||||
return Math.min(AUTO_STEP_DELAY_MAX_SECONDS, Math.max(AUTO_STEP_DELAY_MIN_SECONDS, Math.floor(numeric)));
|
return Math.min(AUTO_STEP_DELAY_MAX_SECONDS, Math.max(AUTO_STEP_DELAY_MIN_SECONDS, Math.floor(numeric)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeVerificationResendCount(value, fallback) {
|
||||||
|
const rawValue = String(value ?? '').trim();
|
||||||
|
if (!rawValue) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numeric = Number(rawValue);
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(
|
||||||
|
VERIFICATION_RESEND_COUNT_MAX,
|
||||||
|
Math.max(VERIFICATION_RESEND_COUNT_MIN, Math.floor(numeric))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function formatAutoStepDelayInputValue(value) {
|
function formatAutoStepDelayInputValue(value) {
|
||||||
const normalized = normalizeAutoStepDelaySeconds(value);
|
const normalized = normalizeAutoStepDelaySeconds(value);
|
||||||
return normalized === null ? '' : String(normalized);
|
return normalized === null ? '' : String(normalized);
|
||||||
@@ -1310,6 +1331,10 @@ function collectSettingsPayload() {
|
|||||||
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
|
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
|
||||||
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
|
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
|
||||||
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
|
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
|
||||||
|
verificationResendCount: normalizeVerificationResendCount(
|
||||||
|
inputVerificationResendCount?.value,
|
||||||
|
DEFAULT_VERIFICATION_RESEND_COUNT
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1729,6 +1754,14 @@ function applySettingsState(state) {
|
|||||||
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
||||||
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
|
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
|
||||||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
|
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
|
||||||
|
if (inputVerificationResendCount) {
|
||||||
|
const restoredVerificationResendCount = state?.verificationResendCount !== undefined
|
||||||
|
? state.verificationResendCount
|
||||||
|
: (state?.signupVerificationResendCount ?? state?.loginVerificationResendCount);
|
||||||
|
inputVerificationResendCount.value = String(
|
||||||
|
normalizeVerificationResendCount(restoredVerificationResendCount, DEFAULT_VERIFICATION_RESEND_COUNT)
|
||||||
|
);
|
||||||
|
}
|
||||||
if (state?.autoRunTotalRuns) {
|
if (state?.autoRunTotalRuns) {
|
||||||
inputRunCount.value = String(state.autoRunTotalRuns);
|
inputRunCount.value = String(state.autoRunTotalRuns);
|
||||||
}
|
}
|
||||||
@@ -3992,6 +4025,20 @@ inputAutoStepDelaySeconds.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
inputVerificationResendCount?.addEventListener('input', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
scheduleSettingsAutoSave();
|
||||||
|
});
|
||||||
|
inputVerificationResendCount?.addEventListener('blur', () => {
|
||||||
|
inputVerificationResendCount.value = String(
|
||||||
|
normalizeVerificationResendCount(
|
||||||
|
inputVerificationResendCount.value,
|
||||||
|
DEFAULT_VERIFICATION_RESEND_COUNT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Listen for Background broadcasts
|
// Listen for Background broadcasts
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -4192,6 +4239,24 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (message.payload.autoStepDelaySeconds !== undefined) {
|
if (message.payload.autoStepDelaySeconds !== undefined) {
|
||||||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
|
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
message.payload.verificationResendCount !== undefined
|
||||||
|
|| message.payload.signupVerificationResendCount !== undefined
|
||||||
|
|| message.payload.loginVerificationResendCount !== undefined
|
||||||
|
)
|
||||||
|
&& inputVerificationResendCount
|
||||||
|
) {
|
||||||
|
const nextVerificationResendCount = message.payload.verificationResendCount !== undefined
|
||||||
|
? message.payload.verificationResendCount
|
||||||
|
: (message.payload.signupVerificationResendCount ?? message.payload.loginVerificationResendCount);
|
||||||
|
inputVerificationResendCount.value = String(
|
||||||
|
normalizeVerificationResendCount(
|
||||||
|
nextVerificationResendCount,
|
||||||
|
DEFAULT_VERIFICATION_RESEND_COUNT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ test('background account history settings are normalized independently from hotm
|
|||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizeHotmailLocalBaseUrl'),
|
extractFunction('normalizeHotmailLocalBaseUrl'),
|
||||||
extractFunction('normalizeAccountRunHistoryHelperBaseUrl'),
|
extractFunction('normalizeAccountRunHistoryHelperBaseUrl'),
|
||||||
|
extractFunction('normalizeVerificationResendCount'),
|
||||||
extractFunction('normalizePersistentSettingValue'),
|
extractFunction('normalizePersistentSettingValue'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
@@ -59,8 +60,11 @@ test('background account history settings are normalized independently from hotm
|
|||||||
const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373';
|
const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373';
|
||||||
const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = DEFAULT_HOTMAIL_LOCAL_BASE_URL;
|
const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = DEFAULT_HOTMAIL_LOCAL_BASE_URL;
|
||||||
const DEFAULT_HOTMAIL_REMOTE_BASE_URL = '';
|
const DEFAULT_HOTMAIL_REMOTE_BASE_URL = '';
|
||||||
|
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||||
const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
|
const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
|
||||||
const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
|
const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
|
||||||
|
const VERIFICATION_RESEND_COUNT_MIN = 0;
|
||||||
|
const VERIFICATION_RESEND_COUNT_MAX = 20;
|
||||||
const PERSISTED_SETTING_DEFAULTS = {
|
const PERSISTED_SETTING_DEFAULTS = {
|
||||||
autoStepDelaySeconds: null,
|
autoStepDelaySeconds: null,
|
||||||
mailProvider: '163',
|
mailProvider: '163',
|
||||||
@@ -95,6 +99,8 @@ return {
|
|||||||
`)();
|
`)();
|
||||||
|
|
||||||
assert.equal(api.normalizePersistentSettingValue('accountRunHistoryTextEnabled', 1), true);
|
assert.equal(api.normalizePersistentSettingValue('accountRunHistoryTextEnabled', 1), true);
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('verificationResendCount', '7'), 7);
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('verificationResendCount', '-1'), 0);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
api.normalizePersistentSettingValue('accountRunHistoryHelperBaseUrl', 'http://127.0.0.1:17373/append-account-log'),
|
api.normalizePersistentSettingValue('accountRunHistoryHelperBaseUrl', 'http://127.0.0.1:17373/append-account-log'),
|
||||||
'http://127.0.0.1:17373'
|
'http://127.0.0.1:17373'
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ function extractFunction(name) {
|
|||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizeEmailGenerator'),
|
extractFunction('normalizeEmailGenerator'),
|
||||||
extractFunction('getEmailGeneratorLabel'),
|
extractFunction('getEmailGeneratorLabel'),
|
||||||
|
extractFunction('normalizeVerificationResendCount'),
|
||||||
extractFunction('normalizePersistentSettingValue'),
|
extractFunction('normalizePersistentSettingValue'),
|
||||||
extractFunction('finalizeIcloudAliasAfterSuccessfulFlow'),
|
extractFunction('finalizeIcloudAliasAfterSuccessfulFlow'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -66,6 +67,9 @@ const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
|
|||||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||||
const DEFAULT_HOTMAIL_REMOTE_BASE_URL = '';
|
const DEFAULT_HOTMAIL_REMOTE_BASE_URL = '';
|
||||||
const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373';
|
const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373';
|
||||||
|
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||||
|
const VERIFICATION_RESEND_COUNT_MIN = 0;
|
||||||
|
const VERIFICATION_RESEND_COUNT_MAX = 20;
|
||||||
const PERSISTED_SETTING_DEFAULTS = {
|
const PERSISTED_SETTING_DEFAULTS = {
|
||||||
mailProvider: '163',
|
mailProvider: '163',
|
||||||
autoStepDelaySeconds: null,
|
autoStepDelaySeconds: null,
|
||||||
@@ -175,6 +179,8 @@ test('normalizePersistentSettingValue handles icloud settings', () => {
|
|||||||
assert.equal(api.normalizePersistentSettingValue('icloudHostPreference', 'icloud.com'), 'icloud.com');
|
assert.equal(api.normalizePersistentSettingValue('icloudHostPreference', 'icloud.com'), 'icloud.com');
|
||||||
assert.equal(api.normalizePersistentSettingValue('icloudHostPreference', 'bad-host'), 'auto');
|
assert.equal(api.normalizePersistentSettingValue('icloudHostPreference', 'bad-host'), 'auto');
|
||||||
assert.equal(api.normalizePersistentSettingValue('autoDeleteUsedIcloudAlias', 1), true);
|
assert.equal(api.normalizePersistentSettingValue('autoDeleteUsedIcloudAlias', 1), true);
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('verificationResendCount', '6'), 6);
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('verificationResendCount', 99), 20);
|
||||||
assert.equal(api.normalizePersistentSettingValue('cloudflareTempEmailReceiveMailbox', ' Forward@Example.com '), 'forward@example.com');
|
assert.equal(api.normalizePersistentSettingValue('cloudflareTempEmailReceiveMailbox', ' Forward@Example.com '), 'forward@example.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -232,3 +232,121 @@ test('verification flow refreshes Hotmail signup filter timestamp after step 4 r
|
|||||||
assert.equal(pollPayloads[0].filterAfterTimestamp, 85000);
|
assert.equal(pollPayloads[0].filterAfterTimestamp, 85000);
|
||||||
assert.equal(pollPayloads[1].filterAfterTimestamp, 185000);
|
assert.equal(pollPayloads[1].filterAfterTimestamp, 185000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('verification flow uses configured signup resend count for step 4', async () => {
|
||||||
|
const resendSteps = [];
|
||||||
|
let pollCalls = 0;
|
||||||
|
|
||||||
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||||
|
getHotmailVerificationPollConfig: () => ({}),
|
||||||
|
getHotmailVerificationRequestTimestamp: () => 0,
|
||||||
|
getState: async () => ({}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isStopError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||||
|
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||||
|
pollHotmailVerificationCode: async () => ({}),
|
||||||
|
pollLuckmailVerificationCode: async () => ({}),
|
||||||
|
sendToContentScript: async (_source, message) => {
|
||||||
|
if (message.type === 'RESEND_VERIFICATION_CODE') {
|
||||||
|
resendSteps.push(message.step);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
sendToMailContentScriptResilient: async () => {
|
||||||
|
pollCalls += 1;
|
||||||
|
return pollCalls === 2
|
||||||
|
? { code: '654321', emailTimestamp: 123 }
|
||||||
|
: {};
|
||||||
|
},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
await helpers.resolveVerificationStep(
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
email: 'user@example.com',
|
||||||
|
verificationResendCount: 2,
|
||||||
|
lastSignupCode: null,
|
||||||
|
},
|
||||||
|
{ provider: 'qq', label: 'QQ 邮箱' },
|
||||||
|
{
|
||||||
|
requestFreshCodeFirst: true,
|
||||||
|
resendIntervalMs: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(resendSteps, [4, 4]);
|
||||||
|
assert.equal(pollCalls, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verification flow uses configured login resend count for step 8', async () => {
|
||||||
|
const resendSteps = [];
|
||||||
|
let pollCalls = 0;
|
||||||
|
|
||||||
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||||
|
getHotmailVerificationPollConfig: () => ({}),
|
||||||
|
getHotmailVerificationRequestTimestamp: () => 0,
|
||||||
|
getState: async () => ({}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isStopError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||||
|
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||||
|
pollHotmailVerificationCode: async () => ({}),
|
||||||
|
pollLuckmailVerificationCode: async () => ({}),
|
||||||
|
sendToContentScript: async (_source, message) => {
|
||||||
|
if (message.type === 'RESEND_VERIFICATION_CODE') {
|
||||||
|
resendSteps.push(message.step);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
sendToMailContentScriptResilient: async () => {
|
||||||
|
pollCalls += 1;
|
||||||
|
return pollCalls === 3
|
||||||
|
? { code: '654321', emailTimestamp: 123 }
|
||||||
|
: {};
|
||||||
|
},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
await helpers.resolveVerificationStep(
|
||||||
|
8,
|
||||||
|
{
|
||||||
|
email: 'user@example.com',
|
||||||
|
verificationResendCount: 2,
|
||||||
|
lastLoginCode: null,
|
||||||
|
},
|
||||||
|
{ provider: 'qq', label: 'QQ 邮箱' },
|
||||||
|
{
|
||||||
|
requestFreshCodeFirst: false,
|
||||||
|
resendIntervalMs: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(resendSteps, [8, 8]);
|
||||||
|
assert.equal(pollCalls, 3);
|
||||||
|
});
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
# 项目完整链路说明
|
# 项目完整链路说明
|
||||||
|
|
||||||
本文档面向 AI 与开发者,目标是让阅读者在最短时间内理解“项目做什么、怎么跑、数据怎么流、功能链路怎么串”,从而在新增功能时不漏逻辑、不误改边界。
|
本文档面向 AI 与开发者,目标是让阅读者在最短时间内理解“项目做什么、怎么跑、数据怎么流、功能链路怎么串”,从而在新增功能时不漏逻辑、不误改边界。
|
||||||
|
|
||||||
@@ -247,6 +247,7 @@
|
|||||||
|
|
||||||
- `2925` provider 会拉长单轮轮询窗口,并关闭 Step 4 / 8 的自动重发间隔,减少因邮件延迟过早判负。
|
- `2925` provider 会拉长单轮轮询窗口,并关闭 Step 4 / 8 的自动重发间隔,减少因邮件延迟过早判负。
|
||||||
- Hotmail 在 Step 4 / 8 首轮轮询时会优先使用最近一次验证码请求时间作为时间窗口;如果中途触发重发,轮询窗口也会同步前移,避免旧邮件反复命中。
|
- Hotmail 在 Step 4 / 8 首轮轮询时会优先使用最近一次验证码请求时间作为时间窗口;如果中途触发重发,轮询窗口也会同步前移,避免旧邮件反复命中。
|
||||||
|
- 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。
|
||||||
- CPA 模式下,Step 8 在真正回填登录验证码前,会先刷新最新 OAuth 地址并快速重走一次 Step 7,降低验证码等待过久导致 OAuth 过期的概率。
|
- CPA 模式下,Step 8 在真正回填登录验证码前,会先刷新最新 OAuth 地址并快速重走一次 Step 7,降低验证码等待过久导致 OAuth 过期的概率。
|
||||||
|
|
||||||
- Auto 模式下,如果 Step 4 当前轮失败,后台会沿用当前邮箱回到 Step 1 重新开始当前轮,而不是立刻换邮箱开新尝试。
|
- Auto 模式下,如果 Step 4 当前轮失败,后台会沿用当前邮箱回到 Step 1 重新开始当前轮,而不是立刻换邮箱开新尝试。
|
||||||
|
|||||||
+3
-3
@@ -49,7 +49,7 @@
|
|||||||
- `background/panel-bridge.js`:CPA / SUB2API 面板桥接层,封装 OAuth 地址获取所需的页面打开、脚本注入和通信。
|
- `background/panel-bridge.js`:CPA / SUB2API 面板桥接层,封装 OAuth 地址获取所需的页面打开、脚本注入和通信。
|
||||||
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、等待密码页以及解析当前流程所用邮箱;在 Gmail / 2925 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成。
|
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、等待密码页以及解析当前流程所用邮箱;在 Gmail / 2925 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成。
|
||||||
- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列。
|
- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列。
|
||||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过以及 2925 长轮询参数。
|
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置以及 2925 长轮询参数。
|
||||||
|
|
||||||
## `background/steps/`
|
## `background/steps/`
|
||||||
|
|
||||||
@@ -113,8 +113,8 @@
|
|||||||
- `sidepanel/icloud-manager.js`:侧边栏 iCloud 隐私邮箱管理器,负责列表、筛选、保留、删除和批量操作。
|
- `sidepanel/icloud-manager.js`:侧边栏 iCloud 隐私邮箱管理器,负责列表、筛选、保留、删除和批量操作。
|
||||||
- `sidepanel/luckmail-manager.js`:侧边栏 LuckMail 管理器,负责邮箱列表、筛选、启停、保留与批量操作。
|
- `sidepanel/luckmail-manager.js`:侧边栏 LuckMail 管理器,负责邮箱列表、筛选、启停、保留与批量操作。
|
||||||
- `sidepanel/sidepanel.css`:侧边栏样式文件。
|
- `sidepanel/sidepanel.css`:侧边栏样式文件。
|
||||||
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区标题下方预留账号运行历史摘要条带,且会加载 `managed-alias-utils.js`,把旧的“邮箱前缀”字段语义改为“别名基邮箱”。
|
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区标题下方预留账号运行历史摘要条带,并新增共享“验证码重发”次数输入,同时会加载 `managed-alias-utils.js`,把旧的“邮箱前缀”字段语义改为“别名基邮箱”。
|
||||||
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、账号运行历史摘要渲染、独立 txt 留档配置与广播接收;同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接到共享别名逻辑上。
|
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、账号运行历史摘要渲染、独立 txt 留档配置、共享验证码自动重发次数配置与广播接收;同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接到共享别名逻辑上。
|
||||||
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询与版本展示。
|
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询与版本展示。
|
||||||
|
|
||||||
## `tests/`
|
## `tests/`
|
||||||
|
|||||||
Reference in New Issue
Block a user