feat(flow): unify sms multi-provider fallback and stabilize proxy/auth retries

This commit is contained in:
daniellee2015
2026-05-03 01:20:10 +08:00
parent 99313843c1
commit 2437e6e67d
37 changed files with 12254 additions and 1142 deletions
+85 -4
View File
@@ -13,7 +13,10 @@ function normalizeIpProxyService(value = '') {
const ipProxyActionState = {
busy: false,
action: '',
startedAt: 0,
};
const IP_PROXY_ACTION_LOCK_TIMEOUT_MS = 25000;
let ipProxyDeferredProbeTimer = 0;
const IP_PROXY_SECTION_EXPANDED_STORAGE_KEY = 'multipage-ip-proxy-section-expanded';
let ipProxySectionExpanded = false;
@@ -78,12 +81,14 @@ function getIpProxyActionState() {
return {
busy: Boolean(ipProxyActionState.busy),
action: normalizeIpProxyActionType(ipProxyActionState.action),
startedAt: Number(ipProxyActionState.startedAt) || 0,
};
}
function setIpProxyActionBusy(action = '', busy = false) {
ipProxyActionState.busy = Boolean(busy);
ipProxyActionState.action = ipProxyActionState.busy ? normalizeIpProxyActionType(action) : '';
ipProxyActionState.startedAt = ipProxyActionState.busy ? Date.now() : 0;
}
async function runIpProxyActionWithLock(action = '', runner) {
@@ -104,10 +109,23 @@ async function runIpProxyActionWithLock(action = '', runner) {
updateIpProxyUI(latestState);
}
const actionLabel = getIpProxyActionLabel(nextAction);
const timeoutMs = Math.max(5000, Number(IP_PROXY_ACTION_LOCK_TIMEOUT_MS) || 25000);
let timeoutId = 0;
try {
const value = await runner();
const value = await Promise.race([
Promise.resolve().then(() => runner()),
new Promise((_, reject) => {
timeoutId = globalThis.setTimeout(() => {
reject(new Error(`${actionLabel}超时(${Math.round(timeoutMs / 1000)} 秒),已自动解锁,请重试。`));
}, timeoutMs);
}),
]);
return { skipped: false, value };
} finally {
if (timeoutId) {
globalThis.clearTimeout(timeoutId);
}
setIpProxyActionBusy(nextAction, false);
if (typeof updateIpProxyUI === 'function') {
updateIpProxyUI(latestState);
@@ -115,6 +133,24 @@ async function runIpProxyActionWithLock(action = '', runner) {
}
}
function scheduleIpProxyExitProbe(options = {}) {
const {
silent = true,
delayMs = 80,
} = options;
const delay = Math.max(0, Number(delayMs) || 0);
if (ipProxyDeferredProbeTimer) {
globalThis.clearTimeout(ipProxyDeferredProbeTimer);
ipProxyDeferredProbeTimer = 0;
}
ipProxyDeferredProbeTimer = globalThis.setTimeout(() => {
ipProxyDeferredProbeTimer = 0;
Promise.resolve()
.then(() => probeIpProxyExit({ silent }))
.catch(() => {});
}, delay);
}
function normalizeIpProxyMode(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return SUPPORTED_IP_PROXY_MODES.includes(normalized) ? normalized : DEFAULT_IP_PROXY_MODE;
@@ -210,6 +246,8 @@ function normalizeIpProxyServiceProfile(rawValue = {}) {
accountSessionPrefix: normalizeIpProxyAccountSessionPrefix(raw.accountSessionPrefix || ''),
accountLifeMinutes: normalizeIpProxyAccountLifeMinutes(raw.accountLifeMinutes || ''),
poolTargetCount: normalizeIpProxyPoolTargetCount(raw.poolTargetCount || '', 20),
autoSyncEnabled: Boolean(raw.autoSyncEnabled),
autoSyncIntervalMinutes: String(Math.max(1, Math.min(1440, Number.parseInt(String(raw.autoSyncIntervalMinutes ?? '').trim(), 10) || 15))),
host: String(raw.host || '').trim(),
port: String(normalizeIpProxyPort(raw.port || '') || ''),
protocol: normalizeIpProxyProtocol(raw.protocol),
@@ -227,6 +265,8 @@ function buildIpProxyServiceProfileFromFlatState(state = {}) {
accountSessionPrefix: state?.ipProxyAccountSessionPrefix,
accountLifeMinutes: state?.ipProxyAccountLifeMinutes,
poolTargetCount: state?.ipProxyPoolTargetCount,
autoSyncEnabled: state?.ipProxyAutoSyncEnabled,
autoSyncIntervalMinutes: state?.ipProxyAutoSyncIntervalMinutes,
host: state?.ipProxyHost,
port: state?.ipProxyPort,
protocol: state?.ipProxyProtocol,
@@ -387,6 +427,8 @@ function buildCurrentIpProxyServiceProfileFromInputs() {
accountSessionPrefix: inputIpProxyAccountSessionPrefix?.value || '',
accountLifeMinutes: inputIpProxyAccountLifeMinutes?.value || '',
poolTargetCount: inputIpProxyPoolTargetCount?.value || '',
autoSyncEnabled: Boolean(inputIpProxyAutoSyncEnabled?.checked),
autoSyncIntervalMinutes: inputIpProxyAutoSyncIntervalMinutes?.value || '',
host: inputIpProxyHost?.value || '',
port: inputIpProxyPort?.value || '',
protocol: selectIpProxyProtocol?.value || '',
@@ -419,6 +461,8 @@ function buildIpProxyStatePatchFromServiceProfile(service = '', profile = {}) {
ipProxyAccountSessionPrefix: normalizedProfile.accountSessionPrefix,
ipProxyAccountLifeMinutes: normalizedProfile.accountLifeMinutes,
ipProxyPoolTargetCount: normalizedProfile.poolTargetCount,
ipProxyAutoSyncEnabled: normalizedProfile.autoSyncEnabled,
ipProxyAutoSyncIntervalMinutes: Number.parseInt(String(normalizedProfile.autoSyncIntervalMinutes || '15').trim(), 10) || 15,
ipProxyHost: normalizedProfile.host,
ipProxyPort: normalizedProfile.port,
ipProxyProtocol: normalizedProfile.protocol,
@@ -449,6 +493,12 @@ function applyIpProxyServiceProfileToInputs(profile = {}, options = {}) {
if (inputIpProxyPoolTargetCount) {
inputIpProxyPoolTargetCount.value = normalizedProfile.poolTargetCount;
}
if (inputIpProxyAutoSyncEnabled) {
inputIpProxyAutoSyncEnabled.checked = Boolean(normalizedProfile.autoSyncEnabled);
}
if (inputIpProxyAutoSyncIntervalMinutes) {
inputIpProxyAutoSyncIntervalMinutes.value = String(normalizedProfile.autoSyncIntervalMinutes || '15');
}
if (inputIpProxyHost) {
inputIpProxyHost.value = normalizedProfile.host;
}
@@ -1058,6 +1108,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
stateClass: 'state-idle',
text: '未启用,沿用浏览器默认/全局代理。',
details: '',
hideCurrentDisplay: false,
};
}
@@ -1073,6 +1124,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
stateClass: warningText ? 'state-warning' : 'state-applied',
text: `${statusText}${briefWarning}`,
details,
hideCurrentDisplay: true,
};
}
@@ -1082,6 +1134,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
stateClass: 'state-warning',
text: '已启用,但当前没有可用代理(已阻断直连)。',
details: errorText,
hideCurrentDisplay: false,
};
}
return {
@@ -1090,6 +1143,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
? '已启用,但账号模式没有可用代理。请先填写代理列表,或填写 Host/Port。已阻断所有网站直连。'
: '已启用,但当前没有可用代理。请先点击“拉取”获取 IP 列表。已阻断所有网站直连。',
details: '',
hideCurrentDisplay: false,
};
}
if (reason === 'proxy_api_unavailable') {
@@ -1097,6 +1151,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
stateClass: 'state-error',
text: '已启用,但当前浏览器不支持扩展代理 API,无法应用。',
details,
hideCurrentDisplay: false,
};
}
if (reason === 'apply_failed') {
@@ -1104,6 +1159,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
stateClass: 'state-error',
text: '已启用,但代理应用失败(已回退默认代理)。',
details: errorText || details,
hideCurrentDisplay: false,
};
}
if (reason === 'connectivity_failed') {
@@ -1112,6 +1168,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
stateClass: 'state-error',
text: `${prefix};连通性失败,请切换节点或重试。`,
details: errorText || details,
hideCurrentDisplay: true,
};
}
@@ -1121,6 +1178,7 @@ function formatIpProxyRuntimeStatus(state = latestState) {
? `已启用,等待生效:${endpointWithRegion}${authSuffix}`
: '已启用,等待拉取并应用代理。',
details,
hideCurrentDisplay: false,
};
}
@@ -1137,6 +1195,10 @@ function setIpProxyRuntimeStatusDisplay(status = {}) {
if (ipProxyRuntimeDot) {
ipProxyRuntimeDot.title = text;
}
const runtimeMeta = ipProxyCurrent?.closest?.('.ip-proxy-runtime-meta') || null;
if (runtimeMeta) {
runtimeMeta.style.display = status?.hideCurrentDisplay ? 'none' : '';
}
const detailsText = String(status?.details || '').trim();
if (ipProxyRuntimeDetails && ipProxyRuntimeDetailsText) {
if (detailsText) {
@@ -1241,6 +1303,12 @@ function updateIpProxyUI(state = latestState) {
if (rowIpProxyPoolTargetCount) {
rowIpProxyPoolTargetCount.style.display = showSettings ? '' : 'none';
}
if (rowIpProxyAutoSyncEnabled) {
rowIpProxyAutoSyncEnabled.style.display = showSettings ? '' : 'none';
}
if (rowIpProxyAutoSyncInterval) {
rowIpProxyAutoSyncInterval.style.display = showSettings ? '' : 'none';
}
if (rowIpProxyHost) {
rowIpProxyHost.style.display = showSettings && isAccountMode ? '' : 'none';
}
@@ -1334,6 +1402,16 @@ function updateIpProxyUI(state = latestState) {
if (inputIpProxyAccountList) {
inputIpProxyAccountList.disabled = !enabled || !isAccountMode || !accountListAvailable;
}
if (inputIpProxyAutoSyncEnabled) {
inputIpProxyAutoSyncEnabled.disabled = !enabled;
}
if (inputIpProxyAutoSyncIntervalMinutes) {
const autoSyncEnabled = Boolean(inputIpProxyAutoSyncEnabled?.checked);
if (!Number.isFinite(Number.parseInt(String(inputIpProxyAutoSyncIntervalMinutes.value || '').trim(), 10))) {
inputIpProxyAutoSyncIntervalMinutes.value = '15';
}
inputIpProxyAutoSyncIntervalMinutes.disabled = !enabled || !autoSyncEnabled;
}
const runtimeStatus = formatIpProxyRuntimeStatus(runtimeState);
setIpProxyRuntimeStatusDisplay(runtimeStatus);
@@ -1395,6 +1473,7 @@ async function refreshIpProxyPoolByApi(options = {}) {
source: 'sidepanel',
payload: {
mode,
skipExitProbe: true,
},
});
if (response?.error) {
@@ -1425,7 +1504,7 @@ async function refreshIpProxyPoolByApi(options = {}) {
syncLatestState(patch);
}
updateIpProxyUI(latestState);
await probeIpProxyExit({ silent: true }).catch(() => {});
scheduleIpProxyExitProbe({ silent: true });
if (!silent) {
if (mode === 'account') {
@@ -1447,6 +1526,7 @@ async function switchIpProxyToNext(options = {}) {
direction: 'next',
mode,
forceRefresh: false,
skipExitProbe: true,
},
});
if (response?.error) {
@@ -1476,7 +1556,7 @@ async function switchIpProxyToNext(options = {}) {
syncLatestState(patch);
}
updateIpProxyUI(latestState);
await probeIpProxyExit({ silent: true }).catch(() => {});
scheduleIpProxyExitProbe({ silent: true });
if (!silent) {
showToast(`已切换代理:${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800);
}
@@ -1491,6 +1571,7 @@ async function changeIpProxyExitBySession(options = {}) {
source: 'sidepanel',
payload: {
mode,
skipExitProbe: true,
},
});
if (response?.error) {
@@ -1520,7 +1601,7 @@ async function changeIpProxyExitBySession(options = {}) {
syncLatestState(patch);
}
updateIpProxyUI(latestState);
await probeIpProxyExit({ silent: true }).catch(() => {});
scheduleIpProxyExitProbe({ silent: true });
if (!silent) {
showToast(`已执行 Change${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800);
}