🛂(frontend) invalidate doc query when lost connection

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.
This commit is contained in:
Anthony LC
2025-09-17 14:59:22 +02:00
parent b773f09792
commit fbdeb90113
4 changed files with 44 additions and 3 deletions

View File

@@ -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();
});

View File

@@ -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.

View File

@@ -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<UseCollaborationStore>((set, get) => ({
@@ -36,8 +39,15 @@ export const useProviderStore = create<UseCollaborationStore>((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<UseCollaborationStore>((set, get) => ({
set(defaultValues);
},
resetLostConnection: () => set({ hasLostConnection: false }),
}));

View File

@@ -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;