🐛(frontend) redirection 401 overridden

To capture a 401 we were using "onError" in the
queryClient default mutation options. The problem
is this way does not capture globally the onError,
if a mutation uses as well is own "onError", it will
override the default one, causing the 401 to
not be captured anymore.
We now use MutationCache, which allows us to
capture globally the onError, even if a mutation
has its own "onError" defined, this global one will
still be called.
This commit is contained in:
Anthony LC
2025-07-24 11:45:09 +02:00
parent 0b301b95c8
commit 04273c3b3e
4 changed files with 45 additions and 28 deletions

View File

@@ -23,6 +23,7 @@ and this project adheres to
- 🐛(service-worker) Fix useOffline Maximum update depth exceeded #1196
- 🐛(helm) charts generate invalid YAML for collaboration API / WS #890
- 🐛(frontend) 401 redirection overridden #1214
## [3.4.2] - 2025-07-18

View File

@@ -9,6 +9,7 @@ import {
mockedDocument,
verifyDocName,
} from './utils-common';
import { createRootSubPage } from './utils-sub-pages';
test.describe('Doc Routing', () => {
test.beforeEach(async ({ page }) => {
@@ -60,16 +61,20 @@ test.describe('Doc Routing', () => {
});
test('checks 401 on docs/[id] page', async ({ page, browserName }) => {
const [docTitle] = await createDoc(page, '401-doc', browserName, 1);
const [docTitle] = await createDoc(page, '401-doc-parent', browserName, 1);
await verifyDocName(page, docTitle);
await createRootSubPage(page, browserName, '401-doc-child');
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
const responsePromise = page.route(
/.*\/link-configuration\/$|users\/me\/$/,
/.*\/documents\/.*\/$|users\/me\/$/,
async (route) => {
const request = route.request();
if (
request.method().includes('PUT') ||
request.method().includes('PATCH') ||
request.method().includes('GET')
) {
await route.fulfill({
@@ -84,11 +89,7 @@ test.describe('Doc Routing', () => {
},
);
await page.getByRole('button', { name: 'Share' }).click();
const selectVisibility = page.getByLabel('Visibility', { exact: true });
await selectVisibility.click();
await page.getByLabel('Connected').click();
await page.getByRole('link', { name: '401-doc-parent' }).click();
await responsePromise;

View File

@@ -1,5 +1,9 @@
import { CunninghamProvider } from '@openfun/cunningham-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
MutationCache,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
@@ -24,8 +28,24 @@ const defaultOptions = {
retry: DEFAULT_QUERY_RETRY,
},
};
let globalRouterReplace: ((url: string) => void) | null = null;
const queryClient = new QueryClient({
defaultOptions,
mutationCache: new MutationCache({
onError: (error) => {
if (error instanceof Error && 'status' in error && error.status === 401) {
void queryClient.resetQueries({
queryKey: [KEY_AUTH],
});
setAuthUrl();
if (globalRouterReplace) {
void globalRouterReplace('/401');
}
}
},
}),
});
export function AppProvider({ children }: { children: React.ReactNode }) {
@@ -40,25 +60,14 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
return initializeResizeListener();
}, [initializeResizeListener]);
/**
* Update the global router replace function
* This allows us to use the router replace function globally
*/
useEffect(() => {
queryClient.setDefaultOptions({
...defaultOptions,
mutations: {
onError: (error) => {
if (
error instanceof Error &&
'status' in error &&
error.status === 401
) {
void queryClient.resetQueries({
queryKey: [KEY_AUTH],
});
setAuthUrl();
void replace(`/401`);
}
},
},
});
globalRouterReplace = (url: string) => {
void replace(url);
};
}, [replace]);
return (

View File

@@ -52,10 +52,16 @@ export function useUpdateDoc(queryConfig?: UseUpdateDoc) {
void queryConfig.onSuccess(data, variables, context);
}
},
onError: () => {
onError: (error, variables, context) => {
// If error it means the user is probably not allowed to edit the doc
// so we invalidate the canEdit query to update the UI accordingly
void queryClient.invalidateQueries({
queryKey: [KEY_CAN_EDIT],
});
if (queryConfig?.onError) {
queryConfig.onError(error, variables, context);
}
},
});
}