Skip to content

Commit 7dc7320

Browse files
committed
🚸(frontend) redirect on current url tab after 401
When multiple tabs were opened and a 401 error occurred, the user was redirected to the login page, then after login, the user was redirected to the page where the last 401 error occurred. We improved this behavior by saving the url per tab, and after login, the user is redirected to the last url of the current tab.
1 parent d933435 commit 7dc7320

5 files changed

Lines changed: 85 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to
88

99
### Fixed
1010

11+
- 🚸(frontend) redirect on current url tab after 401 #2197
1112
- 🐛(frontend) abort check media status unmount #2194
1213
- ✨(backend) order pinned documents by last updated at #2028
1314

src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
import crypto from 'crypto';
2-
31
import { expect, test } from '@playwright/test';
42

5-
import {
6-
createDoc,
7-
getCurrentConfig,
8-
mockedDocument,
9-
verifyDocName,
10-
} from './utils-common';
3+
import { createDoc, getCurrentConfig, verifyDocName } from './utils-common';
114
import { writeInEditor } from './utils-editor';
125
import { SignIn, expectLoginPage } from './utils-signin';
136
import { createRootSubPage } from './utils-sub-pages';
@@ -119,13 +112,53 @@ test.describe('Doc Routing: Not logged', () => {
119112
page,
120113
browserName,
121114
}) => {
122-
const uuid = crypto.randomUUID();
123-
await mockedDocument(page, { link_reach: 'public', id: uuid });
124-
await page.goto(`/docs/${uuid}/`);
125-
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
126-
await page.getByRole('button', { name: 'Login' }).click();
115+
await page.goto('/');
116+
await SignIn(page, browserName);
117+
118+
const [docTitle1] = await createDoc(page, 'doc-login-1', browserName, 1);
119+
await verifyDocName(page, docTitle1);
120+
121+
const page2 = await page.context().newPage();
122+
await page2.goto('/');
123+
const [docTitle2] = await createDoc(page2, 'doc-login-2', browserName, 1);
124+
await verifyDocName(page2, docTitle2);
125+
126+
// Remove cookies `docs_sessionid` to simulate the user being logged out
127+
await page2.context().clearCookies();
128+
await page2.reload();
129+
130+
// Tab 2 - 401 triggered, user should be redirected to login page
131+
await expect(
132+
page2
133+
.getByRole('main', { name: 'Main content' })
134+
.getByRole('button', { name: 'Login' }),
135+
).toBeVisible({
136+
timeout: 10000,
137+
});
138+
139+
// Tab 1 - 401 triggered, user should be redirected to login page
140+
await page.reload();
141+
await expect(
142+
page
143+
.getByRole('main', { name: 'Main content' })
144+
.getByRole('button', { name: 'Login' }),
145+
).toBeVisible({
146+
timeout: 10000,
147+
});
148+
149+
// Reconnected
150+
await page
151+
.getByRole('main', { name: 'Main content' })
152+
.getByRole('button', { name: 'Login' })
153+
.click();
127154
await SignIn(page, browserName, false);
128-
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
155+
156+
// Tab 1 - Should be on its doc
157+
await verifyDocName(page, docTitle1);
158+
159+
// Tab 2 - Should be on its doc
160+
await page2.reload();
161+
await verifyDocName(page2, docTitle2);
129162
});
130163

131164
// eslint-disable-next-line playwright/expect-expect

src/frontend/apps/impress/src/features/auth/conf.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { baseApiUrl } from '@/api';
33
export const HOME_URL = '/home/';
44
export const LOGIN_URL = `${baseApiUrl()}authenticate/`;
55
export const LOGOUT_URL = `${baseApiUrl()}logout/`;
6-
export const PATH_AUTH_LOCAL_STORAGE = 'docs-path-auth';
6+
export const PATH_AUTH_SESSION_STORAGE = 'docs-path-auth';
77
export const SILENT_LOGIN_RETRY = 'silent-login-retry';

src/frontend/apps/impress/src/features/auth/utils.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
import { terminateCrispSession } from '@/services/Crisp';
2-
import { safeLocalStorage } from '@/utils/storages';
2+
import { safeLocalStorage, safeSessionStorage } from '@/utils/storages';
33

44
import {
55
HOME_URL,
66
LOGIN_URL,
77
LOGOUT_URL,
8-
PATH_AUTH_LOCAL_STORAGE,
8+
PATH_AUTH_SESSION_STORAGE,
99
SILENT_LOGIN_RETRY,
1010
} from './conf';
1111

1212
/**
13-
* Get the stored auth URL from local storage
13+
* Get the stored auth URL from session storage (per-tab)
1414
*/
1515
export const getAuthUrl = () => {
16-
const path_auth = safeLocalStorage.getItem(PATH_AUTH_LOCAL_STORAGE);
16+
const path_auth = safeSessionStorage.getItem(PATH_AUTH_SESSION_STORAGE);
1717
if (path_auth) {
18-
safeLocalStorage.removeItem(PATH_AUTH_LOCAL_STORAGE);
18+
safeSessionStorage.removeItem(PATH_AUTH_SESSION_STORAGE);
1919
return path_auth;
2020
}
2121
};
2222

2323
/**
24-
* Store the current path in local storage if it's not the homepage or root
25-
* so we can redirect the user to this path after login
24+
* Store the current path in session storage (per-tab) if it's not the
25+
* homepage or root, so we can redirect the user to this path after login.
26+
* Using sessionStorage ensures each tab independently tracks its own URL.
2627
*/
2728
export const setAuthUrl = () => {
2829
if (
2930
window.location.pathname !== '/' &&
3031
window.location.pathname !== `${HOME_URL}/`
3132
) {
32-
safeLocalStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.href);
33+
safeSessionStorage.setItem(PATH_AUTH_SESSION_STORAGE, window.location.href);
3334
}
3435
};
3536

src/frontend/apps/impress/src/utils/storages.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,30 @@ export const safeLocalStorage: SyncStorage = {
5050
localStorage.removeItem(key);
5151
},
5252
};
53+
54+
/**
55+
* @namespace safeSessionStorage
56+
* @description A utility for safely interacting with sessionStorage.
57+
* sessionStorage is scoped to the current browser tab, making it suitable
58+
* for per-tab state that should not be shared across tabs.
59+
*/
60+
export const safeSessionStorage: SyncStorage = {
61+
getItem: (key: string): string | null => {
62+
if (typeof window === 'undefined') {
63+
return null;
64+
}
65+
return sessionStorage.getItem(key);
66+
},
67+
setItem: (key: string, value: string): void => {
68+
if (typeof window === 'undefined') {
69+
return;
70+
}
71+
sessionStorage.setItem(key, value);
72+
},
73+
removeItem: (key: string): void => {
74+
if (typeof window === 'undefined') {
75+
return;
76+
}
77+
sessionStorage.removeItem(key);
78+
},
79+
};

0 commit comments

Comments
 (0)