🛂(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:
@@ -7,7 +7,7 @@ import {
|
|||||||
randomName,
|
randomName,
|
||||||
verifyDocName,
|
verifyDocName,
|
||||||
} from './utils-common';
|
} from './utils-common';
|
||||||
import { connectOtherUserToDoc } from './utils-share';
|
import { connectOtherUserToDoc, updateRoleUser } from './utils-share';
|
||||||
import { createRootSubPage } from './utils-sub-pages';
|
import { createRootSubPage } from './utils-sub-pages';
|
||||||
|
|
||||||
test.describe('Document create member', () => {
|
test.describe('Document create member', () => {
|
||||||
@@ -274,6 +274,12 @@ test.describe('Document create member', () => {
|
|||||||
await verifyDocName(otherPage, docTitle);
|
await verifyDocName(otherPage, docTitle);
|
||||||
await expect(otherPage.getByText('Hello World')).toBeVisible();
|
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
|
// Cleanup: other user logout
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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.
|
* Connects another user to a document.
|
||||||
* Useful to test real-time collaboration features.
|
* Useful to test real-time collaboration features.
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ export interface UseCollaborationStore {
|
|||||||
destroyProvider: () => void;
|
destroyProvider: () => void;
|
||||||
provider: HocuspocusProvider | undefined;
|
provider: HocuspocusProvider | undefined;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
|
hasLostConnection: boolean;
|
||||||
|
resetLostConnection: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
|
hasLostConnection: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
||||||
@@ -36,8 +39,15 @@ export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
|||||||
name: storeId,
|
name: storeId,
|
||||||
document: doc,
|
document: doc,
|
||||||
onStatus: ({ status }) => {
|
onStatus: ({ status }) => {
|
||||||
set({
|
set((state) => {
|
||||||
isConnected: status === WebSocketStatus.Connected,
|
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);
|
set(defaultValues);
|
||||||
},
|
},
|
||||||
|
resetLostConnection: () => set({ hasLostConnection: false }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
useCollaboration,
|
useCollaboration,
|
||||||
useDoc,
|
useDoc,
|
||||||
useDocStore,
|
useDocStore,
|
||||||
|
useProviderStore,
|
||||||
} from '@/docs/doc-management/';
|
} from '@/docs/doc-management/';
|
||||||
import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth';
|
import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth';
|
||||||
import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/';
|
import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/';
|
||||||
@@ -57,6 +58,7 @@ interface DocProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DocPage = ({ id }: DocProps) => {
|
const DocPage = ({ id }: DocProps) => {
|
||||||
|
const { hasLostConnection, resetLostConnection } = useProviderStore();
|
||||||
const {
|
const {
|
||||||
data: docQuery,
|
data: docQuery,
|
||||||
isError,
|
isError,
|
||||||
@@ -87,6 +89,14 @@ const DocPage = ({ id }: DocProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { authenticated } = useAuth();
|
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(() => {
|
useEffect(() => {
|
||||||
if (!docQuery || isFetching) {
|
if (!docQuery || isFetching) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user