diff --git a/lib/__tests__/lite-tokens.test.ts b/lib/__tests__/lite-tokens.test.ts index 382842744..5913319f2 100644 --- a/lib/__tests__/lite-tokens.test.ts +++ b/lib/__tests__/lite-tokens.test.ts @@ -233,6 +233,13 @@ describe('liteRefreshTokens', () => { expect(readLiteRefreshToken(0)?.refreshToken).toBe('RT'); }); + it.each([429, 408])('keeps the token on a %i, which is an outage and not a sign-out', async (status) => { + saveLiteRefreshToken(0, { serverUrl: SERVER, username: 'a', refreshToken: 'RT' }, true); + fetchMock.mockResolvedValue(new Response('', { status })); + await expect(liteRefreshTokens(0)).rejects.toMatchObject({ code: 'token_exchange_failed', status }); + expect(readLiteRefreshToken(0)?.refreshToken).toBe('RT'); + }); + it('propagates network errors and keeps the token', async () => { saveLiteRefreshToken(0, { serverUrl: SERVER, username: 'a', refreshToken: 'RT' }, true); fetchMock.mockRejectedValue(new TypeError('offline')); diff --git a/lib/auth/lite-tokens.ts b/lib/auth/lite-tokens.ts index c31575ea3..b6610ba82 100644 --- a/lib/auth/lite-tokens.ts +++ b/lib/auth/lite-tokens.ts @@ -356,7 +356,12 @@ export async function liteRefreshTokens(slot: number, clientIdOverride?: string) }).toString(), }); - if (response.status >= 500) { + // Only a definitive refusal (400/401/403) ends the session, as in the + // server build. A rate limit (429), a request timeout (408) or a 5xx is an + // outage: the refresh token stays, so the session resumes once the server + // answers again. + const refused = response.status === 400 || response.status === 401 || response.status === 403; + if (!response.ok && !refused) { throw new LiteLoginError('token_exchange_failed', response.status); } const tokens = (await response.json().catch(() => ({}))) as TokenResponse;