Skip to content

Commit eb32130

Browse files
committed
frontend: fix users not getting logged out when service worker timeout occurs
1 parent 4ce5ad8 commit eb32130

File tree

2 files changed

+130
-2
lines changed

2 files changed

+130
-2
lines changed

frontend/src/__tests__/utils.test.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,134 @@ describe('utils', () => {
287287
// Should not have transitioned to LoggedIn
288288
expect(utils.loggedIn.value).not.toBe(utils.AppState.LoggedIn);
289289
});
290+
291+
it('catch block logs error and maintains logged in state when tokens exist', async () => {
292+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { /* no-op */ });
293+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { /* no-op */ });
294+
295+
// Mock localStorage to return loginSalt
296+
(window.localStorage.getItem as any).mockImplementation((key: string) =>
297+
key === 'loginSalt' ? 'test-salt' : null
298+
);
299+
300+
// Mock service worker to return secret
301+
const postMessage = vi.fn((msg: any) => {
302+
if (msg.type === MessageType.RequestSecret) {
303+
triggerSWMessage({ type: MessageType.RequestSecret, data: 'test-secret' });
304+
}
305+
});
306+
withServiceWorker({ postMessage } as any);
307+
308+
// Mock fetchClient to throw an error
309+
const testError = new Error('Network timeout');
310+
(utils.fetchClient as any).GET = vi.fn(async (path: string) => {
311+
if (path === '/auth/jwt_refresh') {
312+
throw testError;
313+
}
314+
return { error: null, response: { status: 200 } };
315+
});
316+
317+
// Set initial state
318+
utils.loggedIn.value = utils.AppState.Loading;
319+
320+
await utils.refresh_access_token();
321+
322+
// Verify logging
323+
expect(consoleLogSpy).toHaveBeenCalledWith('Failed to refresh access token:', testError);
324+
expect(consoleErrorSpy).toHaveBeenCalledWith(testError);
325+
326+
// Verify state is set to LoggedIn when both tokens exist
327+
expect(utils.loggedIn.value).toBe(utils.AppState.LoggedIn);
328+
329+
consoleLogSpy.mockRestore();
330+
consoleErrorSpy.mockRestore();
331+
});
332+
333+
it('catch block calls logout when loginSalt is missing', async () => {
334+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { /* no-op */ });
335+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { /* no-op */ });
336+
337+
// Mock localStorage to return no loginSalt
338+
(window.localStorage.getItem as any).mockImplementation(() => null);
339+
340+
// Mock service worker to return secret (but loginSalt is missing)
341+
const postMessage = vi.fn((msg: any) => {
342+
if (msg.type === MessageType.RequestSecret) {
343+
triggerSWMessage({ type: MessageType.RequestSecret, data: 'test-secret' });
344+
}
345+
});
346+
withServiceWorker({ postMessage } as any);
347+
348+
// Mock fetchClient to throw an error
349+
const testError = new Error('Network timeout');
350+
(utils.fetchClient as any).GET = vi.fn(async (path: string) => {
351+
if (path === '/auth/jwt_refresh') {
352+
throw testError;
353+
}
354+
return { error: null, response: { status: 200 } };
355+
});
356+
357+
// Mock logout function
358+
const logoutModule = await import('../components/Navbar');
359+
const logoutSpy = vi.spyOn(logoutModule, 'logout').mockImplementation(async () => { /* no-op */ });
360+
361+
await utils.refresh_access_token();
362+
363+
// Verify logging
364+
expect(consoleLogSpy).toHaveBeenCalledWith('Failed to refresh access token:', testError);
365+
expect(consoleErrorSpy).toHaveBeenCalledWith(testError);
366+
367+
// Verify logout was called
368+
expect(logoutSpy).toHaveBeenCalledWith(false);
369+
370+
consoleLogSpy.mockRestore();
371+
consoleErrorSpy.mockRestore();
372+
logoutSpy.mockRestore();
373+
});
374+
375+
it('catch block calls logout when secret is missing', async () => {
376+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { /* no-op */ });
377+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { /* no-op */ });
378+
379+
// Mock localStorage to return loginSalt
380+
(window.localStorage.getItem as any).mockImplementation((key: string) =>
381+
key === 'loginSalt' ? 'test-salt' : null
382+
);
383+
384+
// Mock service worker to return no secret
385+
const postMessage = vi.fn((msg: any) => {
386+
if (msg.type === MessageType.RequestSecret) {
387+
triggerSWMessage({ type: MessageType.RequestSecret, data: '' });
388+
}
389+
});
390+
withServiceWorker({ postMessage } as any);
391+
392+
// Mock fetchClient to throw an error
393+
const testError = new Error('Network timeout');
394+
(utils.fetchClient as any).GET = vi.fn(async (path: string) => {
395+
if (path === '/auth/jwt_refresh') {
396+
throw testError;
397+
}
398+
return { error: null, response: { status: 200 } };
399+
});
400+
401+
// Mock logout function
402+
const logoutModule = await import('../components/Navbar');
403+
const logoutSpy = vi.spyOn(logoutModule, 'logout').mockImplementation(async () => { /* no-op */ });
404+
405+
await utils.refresh_access_token();
406+
407+
// Verify logging
408+
expect(consoleLogSpy).toHaveBeenCalledWith('Failed to refresh access token:', testError);
409+
expect(consoleErrorSpy).toHaveBeenCalledWith(testError);
410+
411+
// Verify logout was called
412+
expect(logoutSpy).toHaveBeenCalledWith(false);
413+
414+
consoleLogSpy.mockRestore();
415+
consoleErrorSpy.mockRestore();
416+
logoutSpy.mockRestore();
417+
});
290418
});
291419

292420
describe('get_salt & get_salt_for_user', () => {

frontend/src/utils.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export async function refresh_access_token() {
123123
if (hasLoginSalt && hasSecret) {
124124
loggedIn.value = AppState.LoggedIn;
125125
} else {
126-
resetSecret();
126+
logout(false);
127127
}
128128
console.error(e);
129129
}
@@ -190,7 +190,7 @@ export async function getSecretKeyFromServiceWorker(): Promise<string> {
190190
const controller = await navigator.serviceWorker.ready;
191191

192192
const timeout = setTimeout(async () => {
193-
console.error("Service Worker: Failed to get secretKey within timeout. Retrying...");
193+
console.error("Service Worker: Failed to get secretKey within timeout.");
194194
if (!appSleeps || !Median.isNativeApp()) {
195195
reject("Timeout waiting for secretKey from Service Worker");
196196
}

0 commit comments

Comments
 (0)