Skip to content

Commit 68fc2d7

Browse files
committed
feat(FR-2630): add useSToken hook with nuqs and stoken deprecation warning
Centralize the URL query parameter lookup for sToken-based SSO entry points. The hook returns [effective, clear] where effective prefers the canonical sToken key over the deprecated lowercase stoken alias, and clear nulls both keys so successful logins strip the token from the URL without leaking either form. A single logger.warn fires per hook instance (via useEffectEvent + ref guard) when only stoken is present, preparing a future hard-deprecation without interfering with the current hybrid acceptance rule. The boundary component (FR-2631) and its route wrappers (FR-2636, FR-2641) consume this hook instead of reading from window.location directly; see spec section URL 파라미터 파싱 규약 (nuqs). Refs FR-2616
1 parent ddbf758 commit 68fc2d7

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

react/src/hooks/useSToken.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
@license
3+
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
4+
*/
5+
import { useBAILogger } from 'backend.ai-ui';
6+
import { parseAsString, useQueryState } from 'nuqs';
7+
import { useCallback, useEffect, useEffectEvent, useRef } from 'react';
8+
9+
/**
10+
* Read and clear the sToken URL query parameter using nuqs.
11+
*
12+
* The `STokenLoginBoundary` component (Epic FR-2616) intentionally does not
13+
* read from `window.location` on its own — callers must source `sToken` via
14+
* nuqs and pass it as a prop. This hook centralizes that lookup so the two
15+
* call sites (LoginView route wrapper, EduAppLauncher route wrapper) and any
16+
* future token URL entry point share the same canonical-vs-deprecated
17+
* handling.
18+
*
19+
* Resolution:
20+
* 1. `?sToken=...` (canonical key) takes precedence.
21+
* 2. `?stoken=...` (deprecated lowercase alias) is accepted as a fallback.
22+
* A single deprecation warning is logged per hook instance when only
23+
* `stoken` is present.
24+
*
25+
* The returned `clear` setter nulls both keys so the tuple is safe to call
26+
* from `STokenLoginBoundary`'s `onSuccess` callback without leaking either
27+
* form of the token into browser history or referers.
28+
*/
29+
30+
export const useSToken = (): [
31+
string | null,
32+
(next: string | null) => Promise<URLSearchParams>,
33+
] => {
34+
'use memo';
35+
const { logger } = useBAILogger();
36+
const [sToken, setSToken] = useQueryState('sToken', parseAsString);
37+
const [stoken, setStoken] = useQueryState('stoken', parseAsString);
38+
const hasWarnedRef = useRef(false);
39+
40+
const logDeprecationIfNeeded = useEffectEvent(() => {
41+
if (stoken && !sToken && !hasWarnedRef.current) {
42+
hasWarnedRef.current = true;
43+
logger.warn(
44+
'Query parameter `stoken` (lowercase) is deprecated; use `sToken`.',
45+
);
46+
}
47+
});
48+
49+
useEffect(() => {
50+
logDeprecationIfNeeded();
51+
}, [sToken, stoken]);
52+
53+
const clear = useCallback(
54+
async (next: string | null) => {
55+
// Clear both keys; callers typically pass `null` after a successful
56+
// login to strip the token from the URL. Return the final search
57+
// params from the last setter to match nuqs' Promise contract.
58+
await setSToken(next);
59+
return setStoken(null);
60+
},
61+
[setSToken, setStoken],
62+
);
63+
64+
const effective = sToken ?? stoken;
65+
return [effective, clear];
66+
};

0 commit comments

Comments
 (0)