Skip to content

Commit e21752a

Browse files
committed
frontend: fix the fix of users not getting logged out when ServiceWorker times out
1 parent a51a735 commit e21752a

File tree

2 files changed

+100
-3
lines changed

2 files changed

+100
-3
lines changed

frontend/src/__tests__/utils.test.ts

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ describe('utils', () => {
372372
logoutSpy.mockRestore();
373373
});
374374

375-
it('catch block calls logout when secret is missing', async () => {
375+
it('catch block calls logout when secret is missing (empty string)', async () => {
376376
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { /* no-op */ });
377377
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { /* no-op */ });
378378

@@ -381,7 +381,7 @@ describe('utils', () => {
381381
key === 'loginSalt' ? 'test-salt' : null
382382
);
383383

384-
// Mock service worker to return no secret
384+
// Mock service worker to return empty secret
385385
const postMessage = vi.fn((msg: any) => {
386386
if (msg.type === MessageType.RequestSecret) {
387387
triggerSWMessage({ type: MessageType.RequestSecret, data: '' });
@@ -415,6 +415,98 @@ describe('utils', () => {
415415
consoleErrorSpy.mockRestore();
416416
logoutSpy.mockRestore();
417417
});
418+
419+
it('catch block calls logout when secret is null', async () => {
420+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { /* no-op */ });
421+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { /* no-op */ });
422+
423+
// Mock localStorage to return loginSalt
424+
(window.localStorage.getItem as any).mockImplementation((key: string) =>
425+
key === 'loginSalt' ? 'test-salt' : null
426+
);
427+
428+
// Mock service worker to return null secret
429+
const postMessage = vi.fn((msg: any) => {
430+
if (msg.type === MessageType.RequestSecret) {
431+
triggerSWMessage({ type: MessageType.RequestSecret, data: null });
432+
}
433+
});
434+
withServiceWorker({ postMessage } as any);
435+
436+
// Mock fetchClient to throw an error
437+
const testError = new Error('Network timeout');
438+
(utils.fetchClient as any).GET = vi.fn(async (path: string) => {
439+
if (path === '/auth/jwt_refresh') {
440+
throw testError;
441+
}
442+
return { error: null, response: { status: 200 } };
443+
});
444+
445+
// Mock logout function
446+
const logoutModule = await import('../components/Navbar');
447+
const logoutSpy = vi.spyOn(logoutModule, 'logout').mockImplementation(async () => { /* no-op */ });
448+
449+
await utils.refresh_access_token();
450+
451+
// Verify logging
452+
expect(consoleLogSpy).toHaveBeenCalledWith('Failed to refresh access token:', testError);
453+
expect(consoleErrorSpy).toHaveBeenCalledWith(testError);
454+
455+
// Verify logout was called
456+
expect(logoutSpy).toHaveBeenCalledWith(false);
457+
458+
consoleLogSpy.mockRestore();
459+
consoleErrorSpy.mockRestore();
460+
logoutSpy.mockRestore();
461+
});
462+
463+
it('catch block calls logout when getSecretKeyFromServiceWorker times out', async () => {
464+
// Use fake timers to test timeout scenario
465+
vi.useFakeTimers();
466+
467+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { /* no-op */ });
468+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { /* no-op */ });
469+
470+
// Mock localStorage to return loginSalt
471+
(window.localStorage.getItem as any).mockImplementation((key: string) =>
472+
key === 'loginSalt' ? 'test-salt' : null
473+
);
474+
475+
// Mock service worker that never responds
476+
const postMessage = vi.fn(); // No response triggered
477+
withServiceWorker({ postMessage } as any);
478+
479+
// Mock fetchClient to throw an error (triggers outer catch block)
480+
const testError = new Error('Network timeout');
481+
(utils.fetchClient as any).GET = vi.fn(async (path: string) => {
482+
if (path === '/auth/jwt_refresh') {
483+
throw testError;
484+
}
485+
return { error: null, response: { status: 200 } };
486+
});
487+
488+
// Mock logout function
489+
const logoutModule = await import('../components/Navbar');
490+
const logoutSpy = vi.spyOn(logoutModule, 'logout').mockImplementation(async () => { /* no-op */ });
491+
492+
// Start refresh_access_token without awaiting
493+
const refreshPromise = utils.refresh_access_token();
494+
495+
// Fast-forward time by 5000ms to trigger timeout in getSecretKeyFromServiceWorker
496+
await vi.advanceTimersByTimeAsync(5000);
497+
498+
// Now await the refresh to complete
499+
await refreshPromise;
500+
501+
// Verify logout was called when getSecretKeyFromServiceWorker timed out
502+
expect(logoutSpy).toHaveBeenCalledWith(false);
503+
504+
consoleLogSpy.mockRestore();
505+
consoleErrorSpy.mockRestore();
506+
logoutSpy.mockRestore();
507+
508+
vi.useRealTimers();
509+
});
418510
});
419511

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

frontend/src/utils.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,12 @@ export async function refresh_access_token() {
119119
//This means we are logged in but the refresh failed
120120
console.log("Failed to refresh access token:", e);
121121
const hasLoginSalt = localStorage.getItem("loginSalt");
122-
const hasSecret = await getSecretKeyFromServiceWorker();
122+
let hasSecret = null;
123+
try {
124+
hasSecret = await getSecretKeyFromServiceWorker();
125+
} catch (e) {
126+
logout(false);
127+
}
123128
if (hasLoginSalt && hasSecret) {
124129
loggedIn.value = AppState.LoggedIn;
125130
} else {

0 commit comments

Comments
 (0)