fix(sidepanel): restore plus settings before auto run

This commit is contained in:
QLHazycoder
2026-05-03 02:57:34 +08:00
parent 2437e6e67d
commit aa2337f9e5
4 changed files with 391 additions and 29 deletions
+9 -4
View File
@@ -284,11 +284,16 @@
}
if (generator === 'icloud') {
const stateFetchMode = String(mergedState.icloudFetchMode || '').trim().toLowerCase();
return fetchIcloudHideMyEmail({
const icloudOptions = {
generateNew: Boolean(options.generateNew) || stateFetchMode === 'always_new',
hostPreference: mergedState.icloudHostPreference,
preferredHost: mergedState.preferredIcloudHost,
});
};
if (mergedState.icloudHostPreference !== undefined) {
icloudOptions.hostPreference = mergedState.icloudHostPreference;
}
if (mergedState.preferredIcloudHost !== undefined) {
icloudOptions.preferredHost = mergedState.preferredIcloudHost;
}
return fetchIcloudHideMyEmail(icloudOptions);
}
if (generator === 'cloudflare') {
return fetchCloudflareEmail(mergedState, options);
+6 -7
View File
@@ -45,22 +45,22 @@
## 4. 错误分类优先级(当前实现)
1) 先做当步恢复(能在步骤内闭环就不升级)
2) 当步失败后,再判断是否走小循环(回步骤 7)
1) 先做当步恢复(能在步骤内闭环就不升级)
2) 当步失败后,再判断是否走小循环(回步骤 7)
3) 小循环仍失败,才交给大循环(attempt / round / 线程间隔)
## 5. 已落实的关键规则(与近期问题对应)
- `Failed to fetch / network error / fetch failed`
- `Failed to fetch / network error / fetch failed`
步骤 4/8/9 进入有上限的网络重试(当前默认 3 次,冷却 12 秒)。
- `Step 9: 5sim check activation failed: order not found`
- `Step 9: 5sim check activation failed: order not found`
归类为“号码失效”,在步骤 9 内立即换号(当步重试),不应直接触发线程间隔。
- `Route Error 405`(手机号验证码页):
- `Route Error 405`(手机号验证码页):
优先当步恢复;若落入 route-405 循环,步骤 9 内换号。
- `add-phone / phone-verification` 页面状态:
- `add-phone / phone-verification` 页面状态:
表示流程已进入手机链路,自动运行层默认不再盲目回步骤 7,避免破坏当前链路。
## 6. 关于“第几轮尝试 + 暂停”含义
@@ -70,4 +70,3 @@
- 用户手动停止会中断当前计划;恢复时是否回到步骤 7,取决于当前页面状态与回调是否过期:
- 回调未过期且仍在可恢复链路:优先当前链路继续;
- 回调已过期或链路断裂:回步骤 7 重拉授权。
+208 -18
View File
@@ -162,6 +162,7 @@ const inputCodex2ApiAdminKey = document.getElementById('input-codex2api-admin-ke
const rowCustomPassword = document.getElementById('row-custom-password');
const rowPlusMode = document.getElementById('row-plus-mode');
const inputPlusModeEnabled = document.getElementById('input-plus-mode-enabled');
const selectPlusPaymentMethod = document.getElementById('select-plus-payment-method');
const rowPayPalAccount = document.getElementById('row-paypal-account');
const selectPayPalAccount = document.getElementById('select-paypal-account');
const btnAddPayPalAccount = document.getElementById('btn-add-paypal-account');
@@ -291,6 +292,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('inpu
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds');
const inputOAuthFlowTimeoutEnabled = document.getElementById('input-oauth-flow-timeout-enabled');
const inputVerificationResendCount = document.getElementById('input-verification-resend-count');
const rowPhoneVerificationEnabled = document.getElementById('row-phone-verification-enabled');
const btnTogglePhoneVerificationSection = document.getElementById('btn-toggle-phone-verification-section');
@@ -402,6 +404,7 @@ const btnAutoStartContinue = document.getElementById('btn-auto-start-continue');
const autoHintText = document.querySelector('.auto-hint');
const stepsList = document.querySelector('.steps-list');
let currentPlusModeEnabled = false;
let currentPlusPaymentMethod = 'paypal';
let heroSmsCountrySelectionOrder = [];
let phoneSmsProviderOrderSelection = [];
let heroSmsCountryMenuSearchKeyword = '';
@@ -417,7 +420,7 @@ const fiveSimCountrySearchTextByCode = new Map();
let nexSmsCountrySelectionOrder = [];
let nexSmsCountryMenuSearchKeyword = '';
const nexSmsCountrySearchTextById = new Map();
let stepDefinitions = getStepDefinitionsForMode(false);
let stepDefinitions = getStepDefinitionsForMode(false, currentPlusPaymentMethod);
let STEP_IDS = stepDefinitions.map((step) => Number(step.id)).filter(Number.isFinite);
let STEP_DEFAULT_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending']));
let SKIPPABLE_STEPS = new Set(STEP_IDS);
@@ -538,8 +541,22 @@ const AUTO_RUN_PLUS_RISK_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-run-plus
const PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY = 'multipage-plus-contribution-prompt-ledger';
const PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY = 'multipage-phone-verification-section-expanded';
function getStepDefinitionsForMode(plusModeEnabled = false) {
return (window.MultiPageStepDefinitions?.getSteps?.({ plusModeEnabled }) || [])
function normalizePlusPaymentMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal';
}
function getSelectedPlusPaymentMethod() {
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
return normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
}
return normalizePlusPaymentMethod(latestState?.plusPaymentMethod || currentPlusPaymentMethod);
}
function getStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethod = 'paypal') {
return (window.MultiPageStepDefinitions?.getSteps?.({
plusModeEnabled,
plusPaymentMethod: normalizePlusPaymentMethod(plusPaymentMethod),
}) || [])
.sort((left, right) => {
const leftOrder = Number.isFinite(left.order) ? left.order : left.id;
const rightOrder = Number.isFinite(right.order) ? right.order : right.id;
@@ -548,9 +565,10 @@ function getStepDefinitionsForMode(plusModeEnabled = false) {
});
}
function rebuildStepDefinitionState(plusModeEnabled = false) {
function rebuildStepDefinitionState(plusModeEnabled = false, plusPaymentMethod = 'paypal') {
currentPlusModeEnabled = Boolean(plusModeEnabled);
stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled);
currentPlusPaymentMethod = normalizePlusPaymentMethod(plusPaymentMethod);
stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, currentPlusPaymentMethod);
STEP_IDS = stepDefinitions.map((step) => Number(step.id)).filter(Number.isFinite);
STEP_DEFAULT_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending']));
SKIPPABLE_STEPS = new Set(STEP_IDS);
@@ -843,6 +861,8 @@ let cloudflareTempEmailDomainEditMode = false;
let modalChoiceResolver = null;
let currentModalActions = [];
let modalResultBuilder = null;
let activePlusManualConfirmationRequestId = '';
let plusManualConfirmationDialogInFlight = false;
let scheduledCountdownTimer = null;
let configMenuOpen = false;
let configActionInFlight = false;
@@ -1279,6 +1299,95 @@ async function openConfirmModalWithOption({
};
}
async function openPlusManualConfirmationDialog(options = {}) {
const method = String(options.method || '').trim().toLowerCase();
const title = String(options.title || '').trim() || (method === 'gopay' ? 'GoPay 订阅确认' : '手动确认');
const message = String(options.message || '').trim()
|| (method === 'gopay'
? '请在当前订阅页中手动完成 GoPay 订阅,完成后点击“我已完成订阅”继续。'
: '请先在页面中完成当前手动操作,完成后点击确认继续。');
return openActionModal({
title,
message,
actions: [
{ id: 'cancel', label: '取消等待', variant: 'btn-ghost' },
{ id: 'confirm', label: '我已完成订阅', variant: 'btn-primary' },
],
alert: method === 'gopay'
? { text: '确认后流程会直接继续到 Plus 模式第 10 步 OAuth 登录。', tone: 'info' }
: null,
});
}
async function syncPlusManualConfirmationDialog() {
const requestId = String(latestState?.plusManualConfirmationRequestId || '').trim();
const pending = Boolean(latestState?.plusManualConfirmationPending);
if (!pending || !requestId || plusManualConfirmationDialogInFlight || activePlusManualConfirmationRequestId === requestId) {
return;
}
const step = Number(latestState?.plusManualConfirmationStep) || 0;
const method = String(latestState?.plusManualConfirmationMethod || '').trim().toLowerCase();
const title = latestState?.plusManualConfirmationTitle;
const message = latestState?.plusManualConfirmationMessage;
activePlusManualConfirmationRequestId = requestId;
plusManualConfirmationDialogInFlight = true;
let shouldReopenDialog = false;
try {
const choice = await openPlusManualConfirmationDialog({
method,
title,
message,
});
const currentRequestId = String(latestState?.plusManualConfirmationRequestId || '').trim();
const stillPending = Boolean(latestState?.plusManualConfirmationPending);
if (!stillPending || currentRequestId !== requestId) {
return;
}
if (choice == null) {
shouldReopenDialog = true;
showToast('当前订阅确认仍在等待中,将重新弹出确认窗口。', 'info', 1800);
return;
}
const confirmed = choice === 'confirm';
const response = await chrome.runtime.sendMessage({
type: 'RESOLVE_PLUS_MANUAL_CONFIRMATION',
source: 'sidepanel',
payload: {
step,
requestId,
confirmed,
},
});
if (response?.error) {
throw new Error(response.error);
}
if (confirmed) {
showToast(method === 'gopay' ? 'GoPay 订阅已确认,正在继续 OAuth 登录...' : '已确认,流程继续执行中...', 'info', 2200);
} else {
showToast(method === 'gopay' ? '已取消 GoPay 订阅等待。' : '已取消当前手动确认。', 'warn', 2200);
}
} catch (error) {
showToast(error?.message || String(error || '未知错误'), 'error');
} finally {
if (activePlusManualConfirmationRequestId === requestId) {
activePlusManualConfirmationRequestId = '';
}
plusManualConfirmationDialogInFlight = false;
if (
shouldReopenDialog
&& latestState?.plusManualConfirmationPending
&& String(latestState?.plusManualConfirmationRequestId || '').trim() === requestId
) {
setTimeout(() => {
void syncPlusManualConfirmationDialog();
}, 0);
}
}
}
function isPromptDismissed(storageKey) {
return localStorage.getItem(storageKey) === '1';
}
@@ -2707,6 +2816,13 @@ function collectSettingsPayload() {
const currentPayPalAccount = typeof getCurrentPayPalAccount === 'function'
? getCurrentPayPalAccount(latestState)
: payPalAccounts.find((account) => account?.id === String(latestState?.currentPayPalAccountId || '').trim()) || null;
const plusPaymentMethod = typeof getSelectedPlusPaymentMethod === 'function'
? getSelectedPlusPaymentMethod()
: (String(
(typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod
? selectPlusPaymentMethod.value
: latestState?.plusPaymentMethod) || ''
).trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal');
return {
...(contributionModeEnabled ? {} : {
panelMode: selectPanelMode.value,
@@ -2744,6 +2860,7 @@ function collectSettingsPayload() {
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
? Boolean(inputPlusModeEnabled.checked)
: Boolean(latestState?.plusModeEnabled),
plusPaymentMethod,
paypalEmail: String(currentPayPalAccount?.email || latestState?.paypalEmail || '').trim(),
paypalPassword: String(currentPayPalAccount?.password || latestState?.paypalPassword || ''),
currentPayPalAccountId: String(latestState?.currentPayPalAccountId || '').trim(),
@@ -2797,6 +2914,9 @@ function collectSettingsPayload() {
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
oauthFlowTimeoutEnabled: typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
: true,
phoneVerificationEnabled: Boolean(inputPhoneVerificationEnabled?.checked),
phoneSmsProvider: phoneSmsProviderValue,
phoneSmsProviderOrder: phoneSmsProviderOrderValue,
@@ -6361,13 +6481,18 @@ function updatePlusModeUI() {
const enabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
? Boolean(inputPlusModeEnabled.checked)
: false;
const paymentMethod = getSelectedPlusPaymentMethod();
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
selectPlusPaymentMethod.value = paymentMethod;
selectPlusPaymentMethod.style.display = enabled ? '' : 'none';
}
[
typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null,
].forEach((row) => {
if (!row) {
return;
}
row.style.display = enabled ? '' : 'none';
row.style.display = enabled && paymentMethod === 'paypal' ? '' : 'none';
});
}
@@ -6435,10 +6560,10 @@ function scheduleSettingsAutoSave() {
}
async function saveSettings(options = {}) {
const { silent = false } = options;
const { silent = false, force = false } = options;
clearTimeout(settingsAutoSaveTimer);
if (!settingsDirty && !settingsSaveInFlight && silent) {
if (!force && !settingsDirty && !settingsSaveInFlight && silent) {
return;
}
@@ -6484,6 +6609,12 @@ async function saveSettings(options = {}) {
}
}
async function persistCurrentSettingsForAction() {
clearTimeout(settingsAutoSaveTimer);
await waitForSettingsSaveIdle();
await saveSettings({ silent: true, force: true });
}
function applyAutoRunStatus(payload = currentAutoRun) {
syncAutoRunState(payload);
const runLabel = getAutoRunLabel(currentAutoRun);
@@ -6494,7 +6625,13 @@ function applyAutoRunStatus(payload = currentAutoRun) {
setSettingsCardLocked(settingsCardLocked);
inputRunCount.disabled = currentAutoRun.autoRunning || shouldLockRunCountToEmailPool();
const lockedRunCount = getLockedRunCountFromEmailPool();
const shouldSyncAutoRunTotalRuns = currentAutoRun.autoRunning
|| locked
|| paused
|| scheduled;
inputRunCount.disabled = currentAutoRun.autoRunning || lockedRunCount > 0;
btnAutoRun.disabled = currentAutoRun.autoRunning;
btnFetchEmail.disabled = locked
|| isCustomMailProvider()
@@ -6502,7 +6639,9 @@ function applyAutoRunStatus(payload = currentAutoRun) {
inputEmail.disabled = locked;
inputAutoSkipFailures.disabled = scheduled;
if (currentAutoRun.totalRuns > 0) {
if (lockedRunCount > 0) {
inputRunCount.value = String(lockedRunCount);
} else if (shouldSyncAutoRunTotalRuns && currentAutoRun.totalRuns > 0) {
inputRunCount.value = String(currentAutoRun.totalRuns);
}
@@ -6603,14 +6742,17 @@ function renderStepsList() {
updateButtonStates();
}
function syncStepDefinitionsForMode(plusModeEnabled = false, options = {}) {
function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethod = 'paypal', options = {}) {
const nextPlusModeEnabled = Boolean(plusModeEnabled);
const shouldRender = Boolean(options.render) || nextPlusModeEnabled !== currentPlusModeEnabled;
const nextPlusPaymentMethod = normalizePlusPaymentMethod(plusPaymentMethod);
const shouldRender = Boolean(options.render)
|| nextPlusModeEnabled !== currentPlusModeEnabled
|| nextPlusPaymentMethod !== currentPlusPaymentMethod;
if (!shouldRender) {
return;
}
rebuildStepDefinitionState(nextPlusModeEnabled);
rebuildStepDefinitionState(nextPlusModeEnabled, nextPlusPaymentMethod);
renderStepsList();
}
@@ -6620,7 +6762,7 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, options = {}) {
function applySettingsState(state) {
if (typeof syncStepDefinitionsForMode === 'function') {
syncStepDefinitionsForMode(Boolean(state?.plusModeEnabled));
syncStepDefinitionsForMode(Boolean(state?.plusModeEnabled), state?.plusPaymentMethod);
}
const fallbackIpProxyService = '711proxy';
const fallbackIpProxyMode = 'account';
@@ -6677,6 +6819,9 @@ function applySettingsState(state) {
if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled) {
inputPlusModeEnabled.checked = Boolean(state?.plusModeEnabled);
}
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(state?.plusPaymentMethod);
}
inputVpsUrl.value = state?.vpsUrl || '';
inputVpsPassword.value = state?.vpsPassword || '';
setLocalCpaStep9Mode(state?.localCpaStep9Mode);
@@ -6853,6 +6998,11 @@ function applySettingsState(state) {
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
if (typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
inputOAuthFlowTimeoutEnabled.checked = state?.oauthFlowTimeoutEnabled !== undefined
? Boolean(state.oauthFlowTimeoutEnabled)
: true;
}
if (inputVerificationResendCount) {
const restoredVerificationResendCount = state?.verificationResendCount !== undefined
? state.verificationResendCount
@@ -6950,7 +7100,12 @@ function applySettingsState(state) {
if (normalized === 'nexsms') return 'nexsms';
return 'hero-sms';
});
const providerOrderForRestore = resolveNormalizedProviderOrderForRuntime(state || {});
const providerOrderForRestore = typeof resolveNormalizedProviderOrderForRuntime === 'function'
? resolveNormalizedProviderOrderForRuntime(state || {})
: [normalizeProvider(
state?.phoneSmsProvider
|| (typeof DEFAULT_PHONE_SMS_PROVIDER !== 'undefined' ? DEFAULT_PHONE_SMS_PROVIDER : 'hero-sms')
)];
const activeProvider = providerOrderForRestore[0] || normalizeProvider(
state?.phoneSmsProvider
|| (typeof DEFAULT_PHONE_SMS_PROVIDER !== 'undefined' ? DEFAULT_PHONE_SMS_PROVIDER : 'hero-sms')
@@ -7028,6 +7183,9 @@ function applySettingsState(state) {
queueLuckmailPurchaseRefresh();
}
updateButtonStates();
if (typeof syncPlusManualConfirmationDialog === 'function') {
void syncPlusManualConfirmationDialog();
}
}
async function restoreState() {
@@ -9122,6 +9280,7 @@ stepsList?.addEventListener('click', async (event) => {
if (!(await maybeTakeoverAutoRun(`执行步骤 ${step}`))) {
return;
}
await persistCurrentSettingsForAction();
if (step === 3) {
if (inputPassword.value !== (latestState?.customPassword || '')) {
await chrome.runtime.sendMessage({
@@ -9363,8 +9522,8 @@ async function startAutoRunFromCurrentSettings() {
console.warn('Failed to refresh contribution content hint before auto run:', error);
}
if (typeof settingsDirty !== 'undefined' && settingsDirty && typeof saveSettings === 'function') {
await saveSettings({ silent: true });
if (typeof persistCurrentSettingsForAction === 'function') {
await persistCurrentSettingsForAction();
}
const customEmailPoolEnabled = typeof usesCustomEmailPoolGenerator === 'function'
@@ -9646,7 +9805,15 @@ inputPassword.addEventListener('blur', () => {
inputPlusModeEnabled?.addEventListener('change', () => {
updatePlusModeUI();
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled.checked), { render: true });
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled.checked), getSelectedPlusPaymentMethod(), { render: true });
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
selectPlusPaymentMethod?.addEventListener('change', () => {
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
updatePlusModeUI();
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled?.checked), selectPlusPaymentMethod.value, { render: true });
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
@@ -10504,6 +10671,11 @@ inputAutoDelayMinutes.addEventListener('blur', () => {
saveSettings({ silent: true }).catch(() => { });
});
inputOAuthFlowTimeoutEnabled?.addEventListener('change', () => {
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
inputPhoneVerificationEnabled?.addEventListener('change', () => {
if (inputPhoneVerificationEnabled.checked) {
setPhoneVerificationSectionExpanded(true);
@@ -11224,6 +11396,20 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
) {
updateMailProviderUI();
}
if (message.payload.plusModeEnabled !== undefined && inputPlusModeEnabled) {
inputPlusModeEnabled.checked = Boolean(message.payload.plusModeEnabled);
}
if (message.payload.plusPaymentMethod !== undefined && selectPlusPaymentMethod) {
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(message.payload.plusPaymentMethod);
}
if (message.payload.plusModeEnabled !== undefined || message.payload.plusPaymentMethod !== undefined) {
syncStepDefinitionsForMode(
Boolean(latestState?.plusModeEnabled),
latestState?.plusPaymentMethod,
{ render: true }
);
updatePlusModeUI();
}
if (message.payload.currentHotmailAccountId !== undefined || message.payload.hotmailAccounts !== undefined) {
renderHotmailAccounts();
if (selectMailProvider.value === 'hotmail-api') {
@@ -11303,6 +11489,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.payload.autoStepDelaySeconds !== undefined) {
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
}
if (message.payload.oauthFlowTimeoutEnabled !== undefined && typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
inputOAuthFlowTimeoutEnabled.checked = Boolean(message.payload.oauthFlowTimeoutEnabled);
}
if (
(
message.payload.verificationResendCount !== undefined
@@ -11509,6 +11698,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
}
updateAccountRunHistorySettingsUI();
renderContributionMode();
void syncPlusManualConfirmationDialog();
break;
}
+168
View File
@@ -0,0 +1,168 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
function extractFunction(name) {
const asyncStart = sidepanelSource.indexOf(`async function ${name}`);
const normalStart = sidepanelSource.indexOf(`function ${name}`);
const start = asyncStart !== -1
? asyncStart
: normalStart;
if (start === -1) {
throw new Error(`Function ${name} not found`);
}
const signatureEnd = sidepanelSource.indexOf(')', start);
const bodyStart = sidepanelSource.indexOf('{', signatureEnd);
let depth = 0;
let end = bodyStart;
for (; end < sidepanelSource.length; end += 1) {
const char = sidepanelSource[end];
if (char === '{') {
depth += 1;
} else if (char === '}') {
depth -= 1;
if (depth === 0) {
end += 1;
break;
}
}
}
return sidepanelSource.slice(start, end);
}
test('sidepanel step definitions keep the selected Plus payment method', () => {
const bundle = [
extractFunction('normalizePlusPaymentMethod'),
extractFunction('getStepDefinitionsForMode'),
extractFunction('rebuildStepDefinitionState'),
extractFunction('syncStepDefinitionsForMode'),
].join('\n');
const api = new Function(`
const calls = [];
const window = {
MultiPageStepDefinitions: {
getSteps(options) {
calls.push({ type: 'getSteps', options });
return [{ id: options.plusPaymentMethod === 'gopay' ? 7 : 6, order: 1 }];
},
},
};
let currentPlusModeEnabled = false;
let currentPlusPaymentMethod = 'paypal';
let stepDefinitions = [];
let STEP_IDS = [];
let STEP_DEFAULT_STATUSES = {};
let SKIPPABLE_STEPS = new Set();
function renderStepsList() {
calls.push({ type: 'render', stepIds: [...STEP_IDS] });
}
${bundle}
return {
calls,
syncStepDefinitionsForMode,
getCurrentPlusPaymentMethod: () => currentPlusPaymentMethod,
getStepIds: () => [...STEP_IDS],
};
`)();
api.syncStepDefinitionsForMode(true, 'gopay', { render: true });
assert.equal(api.getCurrentPlusPaymentMethod(), 'gopay');
assert.deepEqual(api.getStepIds(), [7]);
assert.deepEqual(api.calls[0], {
type: 'getSteps',
options: { plusModeEnabled: true, plusPaymentMethod: 'gopay' },
});
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [7] });
});
test('sidepanel Plus UI hides PayPal account selector while GoPay is selected', () => {
const bundle = [
extractFunction('normalizePlusPaymentMethod'),
extractFunction('getSelectedPlusPaymentMethod'),
extractFunction('updatePlusModeUI'),
].join('\n');
const api = new Function(`
let latestState = { plusPaymentMethod: 'gopay' };
let currentPlusPaymentMethod = 'paypal';
const inputPlusModeEnabled = { checked: true };
const selectPlusPaymentMethod = { value: 'gopay', style: { display: 'none' } };
const rowPayPalAccount = { style: { display: '' } };
${bundle}
return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
`)();
api.updatePlusModeUI();
assert.equal(api.selectPlusPaymentMethod.style.display, '');
assert.equal(api.rowPayPalAccount.style.display, 'none');
api.selectPlusPaymentMethod.value = 'paypal';
api.updatePlusModeUI();
assert.equal(api.rowPayPalAccount.style.display, '');
});
test('sidepanel resolves pending GoPay manual confirmation from DATA_UPDATED state', async () => {
const bundle = [
extractFunction('openPlusManualConfirmationDialog'),
extractFunction('syncPlusManualConfirmationDialog'),
].join('\n');
const api = new Function(`
const events = [];
let latestState = {
plusManualConfirmationPending: true,
plusManualConfirmationRequestId: 'gopay-request-1',
plusManualConfirmationStep: 7,
plusManualConfirmationMethod: 'gopay',
plusManualConfirmationTitle: 'GoPay 订阅确认',
plusManualConfirmationMessage: '请确认订阅。',
};
let activePlusManualConfirmationRequestId = '';
let plusManualConfirmationDialogInFlight = false;
function openActionModal(options) {
events.push({ type: 'modal', options });
return Promise.resolve('confirm');
}
function showToast(message, tone) {
events.push({ type: 'toast', message, tone });
}
const chrome = {
runtime: {
async sendMessage(message) {
events.push({ type: 'send', message });
latestState = {
...latestState,
plusManualConfirmationPending: false,
};
return { ok: true };
},
},
};
${bundle}
return { events, syncPlusManualConfirmationDialog };
`)();
await api.syncPlusManualConfirmationDialog();
assert.equal(api.events[0].type, 'modal');
assert.equal(api.events[0].options.title, 'GoPay 订阅确认');
assert.deepEqual(api.events[1], {
type: 'send',
message: {
type: 'RESOLVE_PLUS_MANUAL_CONFIRMATION',
source: 'sidepanel',
payload: {
step: 7,
requestId: 'gopay-request-1',
confirmed: true,
},
},
});
assert.match(api.events[2].message, /GoPay/);
assert.equal(api.events[2].tone, 'info');
});