From 01849505e4cd9223ccc1e0c31ac96a0c9cdfcb84 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Sun, 19 Apr 2026 23:35:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E6=94=AF=E6=8C=81=EF=BC=8C=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=E5=92=8C=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 13 +++- background/contribution-oauth.js | 1 + background/message-router.js | 6 ++ sidepanel/sidepanel.js | 1 + tests/background-contribution-mode.test.js | 75 ++++++++++++++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/background.js b/background.js index 779312e..ea7dfe3 100644 --- a/background.js +++ b/background.js @@ -179,7 +179,8 @@ const MICROSOFT_TOKEN_DNR_RULE_ID = 1001; const PERSISTENT_ALIAS_STATE_KEYS = ['manualAliasUsage', 'preservedAliases']; const ACCOUNT_RUN_HISTORY_STORAGE_KEY = 'accountRunHistory'; const CONTRIBUTION_RUNTIME_DEFAULTS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_DEFAULTS || { - ...CONTRIBUTION_RUNTIME_DEFAULTS, + contributionMode: false, + contributionModeExpected: false, contributionSessionId: '', contributionAuthUrl: '', contributionAuthState: '', @@ -1149,6 +1150,7 @@ function buildContributionModeState(enabled, persistedSettings = {}, currentStat return { ...currentContributionState, contributionMode: true, + contributionModeExpected: true, panelMode: 'cpa', customPassword: '', accountRunHistoryTextEnabled: false, @@ -1158,6 +1160,7 @@ function buildContributionModeState(enabled, persistedSettings = {}, currentStat return { ...CONTRIBUTION_RUNTIME_DEFAULTS, contributionMode: false, + contributionModeExpected: false, panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode, customPassword: persistedSettings.customPassword || '', accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled), @@ -6454,7 +6457,11 @@ async function runPreStep6CookieCleanup() { // ============================================================ async function refreshOAuthUrlBeforeStep6(state) { + if (state?.contributionModeExpected && !state?.contributionMode) { + throw new Error('步骤 7:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA 面板。请重新进入贡献模式后再点击自动。'); + } if (state?.contributionMode && contributionOAuthManager?.startContributionFlow) { + await addLog('步骤 7:contributionMode=true,正在通过公开贡献接口申请 OAuth 链接...', 'info'); await addLog('步骤 7:贡献模式正在申请贡献登录地址...'); const contributionState = await contributionOAuthManager.startContributionFlow({ nickname: state.email, @@ -6469,6 +6476,7 @@ async function refreshOAuthUrlBeforeStep6(state) { return oauthUrl; } await addLog(`步骤 7:正在刷新登录用的 ${getPanelModeLabel(state)} OAuth 链接...`); + await addLog(`步骤 7:contributionMode=${Boolean(state?.contributionMode)},当前将回退到 ${getPanelModeLabel(state)} 面板刷新 OAuth。`, 'warn'); console.log(LOG_PREFIX, '[refreshOAuthUrlBeforeStep6] requesting fresh OAuth directly from panel'); const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: '步骤 7' }); await handleStepData(1, refreshResult); @@ -7145,6 +7153,9 @@ async function executeContributionStep10(state) { } async function executeStep10(state) { + if (state?.contributionModeExpected && !state?.contributionMode) { + throw new Error('步骤 10:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API 提交。请重新进入贡献模式后再点击自动。'); + } if (state?.contributionMode) { return executeContributionStep10(state); } diff --git a/background/contribution-oauth.js b/background/contribution-oauth.js index 9eb2033..1a59f7f 100644 --- a/background/contribution-oauth.js +++ b/background/contribution-oauth.js @@ -9,6 +9,7 @@ const RUNTIME_DEFAULTS = { contributionMode: false, + contributionModeExpected: false, contributionSessionId: '', contributionAuthUrl: '', contributionAuthState: '', diff --git a/background/message-router.js b/background/message-router.js index fb93f4d..d478806 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -372,6 +372,9 @@ case 'AUTO_RUN': { clearStopRequest(); + if (Boolean(message.payload?.contributionMode) && typeof setContributionMode === 'function') { + await setContributionMode(true); + } const state = await getState(); if (getPendingAutoRunTimerPlan(state)) { throw new Error('已有自动运行倒计时计划,请先取消或立即开始。'); @@ -386,6 +389,9 @@ case 'SCHEDULE_AUTO_RUN': { clearStopRequest(); + if (Boolean(message.payload?.contributionMode) && typeof setContributionMode === 'function') { + await setContributionMode(true); + } const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1); return await scheduleAutoRun(totalRuns, { delayMinutes: message.payload?.delayMinutes, diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index de5c20c..edc44bb 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -3423,6 +3423,7 @@ async function startAutoRunFromCurrentSettings() { totalRuns, delayMinutes, autoRunSkipFailures, + contributionMode: Boolean(latestState?.contributionMode), mode, }, }); diff --git a/tests/background-contribution-mode.test.js b/tests/background-contribution-mode.test.js index bc34e6c..85054eb 100644 --- a/tests/background-contribution-mode.test.js +++ b/tests/background-contribution-mode.test.js @@ -91,6 +91,7 @@ test('buildContributionModeState preserves active contribution runtime while for const DEFAULT_STATE = { panelMode: 'cpa' }; const CONTRIBUTION_RUNTIME_DEFAULTS = { contributionMode: false, + contributionModeExpected: false, contributionSessionId: '', contributionAuthUrl: '', contributionAuthState: '', @@ -121,6 +122,7 @@ return { buildContributionModeState }; }), { contributionMode: true, + contributionModeExpected: true, contributionSessionId: 'session-001', contributionAuthUrl: 'https://auth.example.com', contributionAuthState: '', @@ -150,6 +152,7 @@ return { buildContributionModeState }; }), { contributionMode: false, + contributionModeExpected: false, contributionSessionId: '', contributionAuthUrl: '', contributionAuthState: '', @@ -229,6 +232,50 @@ test('message router handles contribution mode, start flow, and status polling m ]); }); +test('message router re-syncs contribution mode before AUTO_RUN when sidepanel payload marks contributionMode=true', async () => { + const source = fs.readFileSync('background/message-router.js', 'utf8'); + const globalScope = {}; + const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope); + + const calls = []; + const router = api.createMessageRouter({ + clearStopRequest: () => {}, + getPendingAutoRunTimerPlan: () => null, + getState: async () => ({ + contributionMode: false, + stepStatuses: {}, + }), + normalizeRunCount: (value) => Number(value) || 1, + setContributionMode: async (enabled) => { + calls.push({ type: 'toggle', enabled }); + return { contributionMode: true }; + }, + setState: async (updates) => { + calls.push({ type: 'setState', updates }); + }, + startAutoRunLoop: (totalRuns, options) => { + calls.push({ type: 'startAutoRunLoop', totalRuns, options }); + }, + }); + + const response = await router.handleMessage({ + type: 'AUTO_RUN', + payload: { + totalRuns: 2, + autoRunSkipFailures: true, + mode: 'restart', + contributionMode: true, + }, + }); + + assert.equal(response.ok, true); + assert.deepStrictEqual(calls, [ + { type: 'toggle', enabled: true }, + { type: 'setState', updates: { autoRunSkipFailures: true } }, + { type: 'startAutoRunLoop', totalRuns: 2, options: { autoRunSkipFailures: true, mode: 'restart' } }, + ]); +}); + test('account run history snapshot sync is disabled in contribution mode', () => { const source = fs.readFileSync('background/account-run-history.js', 'utf8'); const globalScope = {}; @@ -411,6 +458,7 @@ return { refreshOAuthUrlBeforeStep6 }; assert.equal(oauthUrl, 'https://auth.example.com/oauth?state=oauth-state-001'); assert.deepStrictEqual(calls, [ + { type: 'log', message: '步骤 7:contributionMode=true,正在通过公开贡献接口申请 OAuth 链接...' }, { type: 'log', message: '步骤 7:贡献模式正在申请贡献登录地址...' }, { type: 'contribution', @@ -439,3 +487,30 @@ return { refreshOAuthUrlBeforeStep6 }; delete globalThis.requestOAuthUrlFromPanel; delete globalThis.LOG_PREFIX; }); + +test('executeStep10 blocks silent fallback when contributionModeExpected=true but contributionMode=false', async () => { + const bundle = extractFunction(backgroundSource, 'executeStep10'); + + const api = new Function(` +${bundle} +return { executeStep10 }; +`)(); + + globalThis.executeContributionStep10 = async () => ({ ok: true }); + globalThis.step10Executor = { + async executeStep10() { + return { ok: true }; + }, + }; + + await assert.rejects( + () => api.executeStep10({ + contributionModeExpected: true, + contributionMode: false, + }), + /步骤 10:当前自动流程预期使用贡献模式/ + ); + + delete globalThis.executeContributionStep10; + delete globalThis.step10Executor; +});