diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index a812cf9..46f3370 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -550,8 +550,9 @@ } }, targetEmail: fixedTargetEmail, - maxResendRequests: mail.provider === '2925' ? 1 : undefined, - initialPollMaxAttempts: mail.provider === '2925' ? 2 : undefined, + maxResendRequests: mail.provider === '2925' ? 2 : undefined, + initialPollMaxAttempts: mail.provider === '2925' ? 5 : undefined, + pollAttemptPlan: mail.provider === '2925' ? [2, 3, 15] : undefined, resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER ? 15000 : ((mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925') diff --git a/background/verification-flow.js b/background/verification-flow.js index d3c395f..c6a0ea6 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -875,6 +875,7 @@ maxRounds: _ignoredMaxRounds, maxResendRequests: _ignoredMaxResendRequests, initialPollMaxAttempts: _ignoredInitialPollMaxAttempts, + pollAttemptPlan: _ignoredPollAttemptPlan, ...cleanPollOverrides } = pollOverrides; const basePayload = { @@ -929,6 +930,7 @@ maxRounds: _ignoredMaxRounds, maxResendRequests: _ignoredMaxResendRequests, initialPollMaxAttempts: _ignoredInitialPollMaxAttempts, + pollAttemptPlan: _ignoredPollAttemptPlan, ...cleanPollOverrides } = pollOverrides; @@ -985,6 +987,12 @@ const maxResendRequests = resolveMaxResendRequests(pollOverrides); const maxRounds = maxResendRequests + 1; const initialPollMaxAttempts = Math.max(0, Math.floor(Number(pollOverrides.initialPollMaxAttempts) || 0)); + const configuredPollAttemptPlan = Array.isArray(pollOverrides.pollAttemptPlan) + ? pollOverrides.pollAttemptPlan + .map((value) => Math.floor(Number(value) || 0)) + .filter((value) => value > 0) + : []; + const pollAttemptPlan = rejectedCodes.size > 0 ? [] : configuredPollAttemptPlan; let usedResendRequests = 0; for (let round = 1; round <= maxRounds; round++) { @@ -1005,7 +1013,10 @@ filterAfterTimestamp, excludeCodes: [...rejectedCodes], }); - if (round === 1 && initialPollMaxAttempts > 0) { + const plannedPollMaxAttempts = pollAttemptPlan[round - 1] || 0; + if (plannedPollMaxAttempts > 0) { + payload.maxAttempts = plannedPollMaxAttempts; + } else if (round === 1 && initialPollMaxAttempts > 0) { payload.maxAttempts = initialPollMaxAttempts; } @@ -1316,6 +1327,9 @@ initialPollMaxAttempts: mail.provider === '2925' && rejectedCodes.size > 0 ? undefined : options.initialPollMaxAttempts, + pollAttemptPlan: mail.provider === '2925' && rejectedCodes.size > 0 + ? undefined + : options.pollAttemptPlan, resendIntervalMs, lastResendAt, onResendRequestedAt: updateFilterAfterTimestampForVerificationStep, diff --git a/tests/background-step7-recovery.test.js b/tests/background-step7-recovery.test.js index 9bc9e71..7ab4d5e 100644 --- a/tests/background-step7-recovery.test.js +++ b/tests/background-step7-recovery.test.js @@ -1056,7 +1056,7 @@ test('step 8 completes when polling fails but recovery probe shows oauth consent ]); }); -test('step 8 uses a fixed 10-minute lookback window and delays 2925 resend until after two quick polls', async () => { +test('step 8 uses a fixed 10-minute lookback window and plans 2925 polling as 2/3/15', async () => { let capturedOptions = null; let ensureCalls = 0; let ensureOptions = null; @@ -1148,8 +1148,9 @@ test('step 8 uses a fixed 10-minute lookback window and delays 2925 resend until ]); assert.equal(capturedOptions.filterAfterTimestamp, 300000); assert.equal(capturedOptions.resendIntervalMs, 0); - assert.equal(capturedOptions.maxResendRequests, 1); - assert.equal(capturedOptions.initialPollMaxAttempts, 2); + assert.equal(capturedOptions.maxResendRequests, 2); + assert.equal(capturedOptions.initialPollMaxAttempts, 5); + assert.deepStrictEqual(capturedOptions.pollAttemptPlan, [2, 3, 15]); assert.equal(capturedOptions.targetEmail, ''); assert.equal(capturedOptions.beforeSubmit, undefined); assert.equal(typeof capturedOptions.getRemainingTimeMs, 'function'); diff --git a/tests/verification-flow-polling.test.js b/tests/verification-flow-polling.test.js index f3a4a9f..eb43fcd 100644 --- a/tests/verification-flow-polling.test.js +++ b/tests/verification-flow-polling.test.js @@ -835,7 +835,7 @@ test('verification flow keeps 2925 mailbox polling at 15 refresh attempts even w assert.ok(pollCall.options.timeoutMs >= 250000); }); -test('verification flow delays 2925 login resend until after the first quick mailbox poll fails', async () => { +test('verification flow can run a 2/3/15 2925 resend polling plan', async () => { const events = []; const pollMaxAttempts = []; let pollCalls = 0; @@ -873,7 +873,7 @@ test('verification flow delays 2925 login resend until after the first quick mai events.push('poll'); pollMaxAttempts.push(message.payload.maxAttempts); pollCalls += 1; - return pollCalls === 1 + return pollCalls <= 2 ? { error: '步骤 8:邮箱轮询结束,但未获取到验证码。' } : { code: '654321', emailTimestamp: 123 }; }, @@ -893,17 +893,18 @@ test('verification flow delays 2925 login resend until after the first quick mai }, { provider: '2925', label: '2925 邮箱' }, { - maxResendRequests: 1, - initialPollMaxAttempts: 2, + maxResendRequests: 2, + initialPollMaxAttempts: 5, + pollAttemptPlan: [2, 3, 15], requestFreshCodeFirst: false, filterAfterTimestamp: 123, resendIntervalMs: 0, } ); - assert.deepStrictEqual(events.slice(0, 3), ['poll', 'resend', 'poll']); - assert.deepStrictEqual(pollMaxAttempts.slice(0, 2), [2, 15]); - assert.equal(events.filter((event) => event === 'resend').length, 1); + assert.deepStrictEqual(events.slice(0, 5), ['poll', 'resend', 'poll', 'resend', 'poll']); + assert.deepStrictEqual(pollMaxAttempts.slice(0, 3), [2, 3, 15]); + assert.equal(events.filter((event) => event === 'resend').length, 2); }); test('verification flow uses full 2925 polling window after a rejected login code', async () => { @@ -969,7 +970,8 @@ test('verification flow uses full 2925 polling window after a rejected login cod { provider: '2925', label: '2925 邮箱' }, { maxResendRequests: 0, - initialPollMaxAttempts: 2, + initialPollMaxAttempts: 5, + pollAttemptPlan: [2, 3, 15], requestFreshCodeFirst: false, filterAfterTimestamp: 123, resendIntervalMs: 0,