From 7a5f64e5ca1db1c33f7b476298eb2fa88731fbe0 Mon Sep 17 00:00:00 2001 From: Jens Date: Sun, 23 Aug 2026 15:04:45 +0200 Subject: [PATCH] Deel geen antwoorden meer over een identiteitswissel heen De verzoekdeler uit de vorige commit houdt een antwoord driehonderd milliseconde vast om de opstartcascade op te vangen. Uitloggen herlaadt de pagina echter niet, dus die tabel overleeft een sessiewissel: binnen dat venster zou de volgende gebruiker het antwoord van de vorige kunnen krijgen. Met de hand is dat nauwelijks te bereiken, maar een verzoekcache die een identiteitswissel overleeft is hoe dan ook fout. De tabel wordt nu gewist bij uitloggen, bij aanmelden en zodra de sessie verloopt. Co-Authored-By: Claude Opus 5 --- frontend/src/hooks/useOperatorSession.ts | 4 +++ frontend/src/services/api/client.test.ts | 31 +++++++++++++++++++++++- frontend/src/services/api/client.ts | 16 ++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/useOperatorSession.ts b/frontend/src/hooks/useOperatorSession.ts index 61277bff..4cfcf609 100644 --- a/frontend/src/hooks/useOperatorSession.ts +++ b/frontend/src/hooks/useOperatorSession.ts @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react' import { formatAuthError } from '../lib/authError' import { getAuthSession, logout, type AuthSession } from '../services/api/auth' +import { vergeetGedeeldeVerzoeken } from '../services/api/client' const signedOutSession: AuthSession = { authentication_required: true, @@ -53,6 +54,8 @@ export function useOperatorSession() { setLoggingOut(true) try { setSession(await logout()) + // Geen gedeelde antwoorden meenemen naar de volgende gebruiker. + vergeetGedeeldeVerzoeken() setSessionError(null) } catch (error) { setSessionError(formatAuthError(error, 'Uitloggen is niet gelukt. Vernieuw de pagina en probeer opnieuw.')) @@ -62,6 +65,7 @@ export function useOperatorSession() { } const handleAuthenticated = (authenticatedSession: AuthSession) => { + vergeetGedeeldeVerzoeken() setSession(authenticatedSession) setSessionError(null) } diff --git a/frontend/src/services/api/client.test.ts b/frontend/src/services/api/client.test.ts index 7589a6d1..fe3ad563 100644 --- a/frontend/src/services/api/client.test.ts +++ b/frontend/src/services/api/client.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { apiGet, apiPost } from './client' +import { apiGet, apiPost, vergeetGedeeldeVerzoeken } from './client' function antwoord(data: unknown) { return { @@ -85,6 +85,35 @@ describe('apiGet deelt verzoeken', () => { expect(fetchSpy).toHaveBeenCalledTimes(2) }) + it('deelt niets meer over een identiteitswissel heen', async () => { + // Uitloggen herlaadt de pagina niet, dus de tabel leeft door. Zonder deze + // reset zou de volgende gebruiker binnen het deelvenster het antwoord van + // de vorige kunnen krijgen. + const fetchSpy = vi.fn() + .mockResolvedValueOnce(antwoord({ items: ['werkruimte van A'] })) + .mockResolvedValueOnce(antwoord({ items: ['werkruimte van B'] })) + vi.stubGlobal('fetch', fetchSpy) + + const vanA = await apiGet<{ items: string[] }>('/api/v1/projects') + vergeetGedeeldeVerzoeken() + const vanB = await apiGet<{ items: string[] }>('/api/v1/projects') + + expect(fetchSpy).toHaveBeenCalledTimes(2) + expect(vanA.items).toEqual(['werkruimte van A']) + expect(vanB.items).toEqual(['werkruimte van B']) + }) + + it('wist de tabel ook wanneer de sessie verloopt', async () => { + const fetchSpy = vi.fn().mockResolvedValue(antwoord({ items: [] })) + vi.stubGlobal('fetch', fetchSpy) + + await apiGet('/api/v1/projects') + window.dispatchEvent(new CustomEvent('geointel:session-expired')) + await apiGet('/api/v1/projects') + + expect(fetchSpy).toHaveBeenCalledTimes(2) + }) + it('laat een mislukt verzoek niet in de tabel achter', async () => { const fetchSpy = vi.fn() .mockRejectedValueOnce(new Error('netwerk weg')) diff --git a/frontend/src/services/api/client.ts b/frontend/src/services/api/client.ts index 90de6526..dd3bc270 100644 --- a/frontend/src/services/api/client.ts +++ b/frontend/src/services/api/client.ts @@ -57,6 +57,22 @@ const lopendeGets = new Map>(); */ const DEEL_VENSTER_MS = 300; +/** + * Vergeet alle gedeelde antwoorden. + * + * Uitloggen herlaadt de pagina niet, dus zonder dit zou een antwoord van de ene + * gebruiker binnen het deelvenster nog aan de volgende geserveerd kunnen worden. + * Dat is binnen driehonderd milliseconde met de hand nauwelijks te bereiken, + * maar een verzoekcache die een identiteitswissel overleeft is hoe dan ook fout. + */ +export function vergeetGedeeldeVerzoeken(): void { + lopendeGets.clear(); +} + +if (typeof window !== "undefined") { + window.addEventListener("geointel:session-expired", vergeetGedeeldeVerzoeken); +} + export async function apiGet(path: string): Promise { const lopend = lopendeGets.get(path); if (lopend) {