From 3445485ebc21f8912b95397d0d68e32f2e4c154e Mon Sep 17 00:00:00 2001 From: wucm667 Date: Fri, 21 Aug 2026 19:14:04 +0800 Subject: [PATCH] fix(frontend): prevent token refresh lock loop --- frontend/src/api/__tests__/tokenRefresh.spec.ts | 17 +++++++++++++++++ frontend/src/api/tokenRefresh.ts | 12 ------------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/frontend/src/api/__tests__/tokenRefresh.spec.ts b/frontend/src/api/__tests__/tokenRefresh.spec.ts index a7fee8c64..80b8c8a36 100644 --- a/frontend/src/api/__tests__/tokenRefresh.spec.ts +++ b/frontend/src/api/__tests__/tokenRefresh.spec.ts @@ -91,6 +91,23 @@ describe('refreshAuthTokens', () => { }) }) + it('does not mistake boundary timer jitter for a completed peer refresh', async () => { + vi.useFakeTimers() + seedSession({ token_expires_at: String(Date.now() + 120_001) }) + mockedPost.mockResolvedValueOnce(refreshedResponse()) + const request = vi.fn(async (_name: string, callback: () => Promise) => callback()) + Object.defineProperty(navigator, 'locks', { + configurable: true, + value: { request } + }) + const { refreshAuthTokens } = await import('@/api/tokenRefresh') + + await expect(refreshAuthTokens()).resolves.toMatchObject({ access_token: 'new-access' }) + + expect(request).toHaveBeenCalledTimes(1) + expect(mockedPost).toHaveBeenCalledTimes(1) + }) + it('recovers when a peer publishes the rotated token just after this request fails', async () => { seedSession() mockedPost.mockRejectedValueOnce(new Error('refresh token already used')) diff --git a/frontend/src/api/tokenRefresh.ts b/frontend/src/api/tokenRefresh.ts index 142dbba04..95747627b 100644 --- a/frontend/src/api/tokenRefresh.ts +++ b/frontend/src/api/tokenRefresh.ts @@ -8,7 +8,6 @@ const REFRESH_TOKEN_KEY = 'refresh_token' const TOKEN_EXPIRES_AT_KEY = 'token_expires_at' const TOKEN_REFRESH_LOCK_NAME = 'sub2api-auth-token-refresh' const TOKEN_REFRESH_TIMEOUT_MS = 30_000 -const TOKEN_REFRESH_BUFFER_MS = 120_000 const PEER_REFRESH_WAIT_MS = 1_000 const PEER_REFRESH_GRACE_MS = 1_000 const PEER_REFRESH_POLL_MS = 25 @@ -106,17 +105,6 @@ function readPeerRefreshResult( return storedPair } - if (!failedAccessToken) { - const expiresAt = Number(localStorage.getItem(TOKEN_EXPIRES_AT_KEY)) - if ( - expiresAt === snapshot.expiresAt && - storedPair.access_token === snapshot.accessToken && - expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS - ) { - return storedPair - } - } - return null }