From fbdeb901139c0390e3950d2df35e563b6de6b168 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 17 Sep 2025 14:59:22 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=82(frontend)=20invalidate=20doc=20que?= =?UTF-8?q?ry=20when=20lost=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the provider reports a lost connection, we invalidate the doc query to refetch the document data. This ensures that if a user has lost is rights to access a document, he will be redirected to a 403 page without needing to refresh the page. --- .../app-impress/doc-member-create.spec.ts | 8 +++++++- .../apps/e2e/__tests__/app-impress/utils-share.ts | 14 ++++++++++++++ .../doc-management/stores/useProviderStore.tsx | 15 +++++++++++++-- .../apps/impress/src/pages/docs/[id]/index.tsx | 10 ++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts index 846d7642..12d1b83b 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts @@ -7,7 +7,7 @@ import { randomName, verifyDocName, } from './utils-common'; -import { connectOtherUserToDoc } from './utils-share'; +import { connectOtherUserToDoc, updateRoleUser } from './utils-share'; import { createRootSubPage } from './utils-sub-pages'; test.describe('Document create member', () => { @@ -274,6 +274,12 @@ test.describe('Document create member', () => { await verifyDocName(otherPage, docTitle); await expect(otherPage.getByText('Hello World')).toBeVisible(); + // Revoke access + await updateRoleUser(page, 'Remove access', emailRequest); + await expect( + otherPage.getByText('Insufficient access rights to view the document.'), + ).toBeVisible(); + // Cleanup: other user logout await cleanup(); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts index 522bb164..5fe90a48 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts @@ -68,6 +68,20 @@ export const updateShareLink = async ( } }; +export const updateRoleUser = async ( + page: Page, + role: Role | 'Remove access', + email: string, +) => { + const list = page.getByTestId('doc-share-quick-search'); + + const currentUser = list.getByTestId(`doc-share-member-row-${email}`); + const currentUserRole = currentUser.getByLabel('doc-role-dropdown'); + await currentUserRole.click(); + await page.getByLabel(role).click(); + await list.click(); +}; + /** * Connects another user to a document. * Useful to test real-time collaboration features. diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/stores/useProviderStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/stores/useProviderStore.tsx index ae7ed151..d6779f21 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/stores/useProviderStore.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/stores/useProviderStore.tsx @@ -13,11 +13,14 @@ export interface UseCollaborationStore { destroyProvider: () => void; provider: HocuspocusProvider | undefined; isConnected: boolean; + hasLostConnection: boolean; + resetLostConnection: () => void; } const defaultValues = { provider: undefined, isConnected: false, + hasLostConnection: false, }; export const useProviderStore = create((set, get) => ({ @@ -36,8 +39,15 @@ export const useProviderStore = create((set, get) => ({ name: storeId, document: doc, onStatus: ({ status }) => { - set({ - isConnected: status === WebSocketStatus.Connected, + set((state) => { + const nextConnected = status === WebSocketStatus.Connected; + return { + isConnected: nextConnected, + hasLostConnection: + state.isConnected && !nextConnected + ? true + : state.hasLostConnection, + }; }); }, }); @@ -56,4 +66,5 @@ export const useProviderStore = create((set, get) => ({ set(defaultValues); }, + resetLostConnection: () => set({ hasLostConnection: false }), })); diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx index a3e3bf41..86ce81e1 100644 --- a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx +++ b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx @@ -15,6 +15,7 @@ import { useCollaboration, useDoc, useDocStore, + useProviderStore, } from '@/docs/doc-management/'; import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth'; import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/'; @@ -57,6 +58,7 @@ interface DocProps { } const DocPage = ({ id }: DocProps) => { + const { hasLostConnection, resetLostConnection } = useProviderStore(); const { data: docQuery, isError, @@ -87,6 +89,14 @@ const DocPage = ({ id }: DocProps) => { const { t } = useTranslation(); const { authenticated } = useAuth(); + // Invalidate when provider store reports a lost connection + useEffect(() => { + if (hasLostConnection && doc?.id) { + queryClient.invalidateQueries({ queryKey: [KEY_DOC, { id: doc.id }] }); + resetLostConnection(); + } + }, [hasLostConnection, doc?.id, queryClient, resetLostConnection]); + useEffect(() => { if (!docQuery || isFetching) { return;