@imtbl/auth-next-client 2.12.7-alpha.6 → 2.12.7-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/node/index.cjs +49 -1
- package/dist/node/index.js +49 -1
- package/dist/types/idTokenStorage.d.ts +26 -0
- package/package.json +3 -3
- package/src/callback.tsx +7 -0
- package/src/hooks.tsx +35 -1
- package/src/idTokenStorage.ts +56 -0
package/README.md
CHANGED
|
@@ -582,7 +582,7 @@ The session type returned by `useImmutableSession`. Note that `accessToken` is i
|
|
|
582
582
|
interface ImmutableSession {
|
|
583
583
|
// accessToken is NOT exposed -- use getAccessToken() instead
|
|
584
584
|
refreshToken?: string;
|
|
585
|
-
idToken?: string;
|
|
585
|
+
idToken?: string; // Only present transiently after sign-in or token refresh (not stored in cookie)
|
|
586
586
|
accessTokenExpires: number;
|
|
587
587
|
zkEvm?: {
|
|
588
588
|
ethAddress: string;
|
|
@@ -597,6 +597,8 @@ interface ImmutableSession {
|
|
|
597
597
|
}
|
|
598
598
|
```
|
|
599
599
|
|
|
600
|
+
> **Note:** The `idToken` is **not** stored in the session cookie (to avoid CloudFront 413 errors from oversized headers). It is only present in the session response transiently after sign-in or token refresh. `@imtbl/auth-next-client` automatically persists it in `localStorage` so that `getUser()` always returns a valid `idToken` for wallet operations. All data extracted from the idToken (`email`, `nickname`, `zkEvm`) remains in the cookie as separate fields and is always available in the session.
|
|
601
|
+
|
|
600
602
|
### LoginConfig
|
|
601
603
|
|
|
602
604
|
Configuration for the `useLogin` hook's login functions:
|
package/dist/node/index.cjs
CHANGED
|
@@ -41,6 +41,34 @@ var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
|
|
|
41
41
|
var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
|
|
42
42
|
var TOKEN_EXPIRY_BUFFER_MS = 60 * 1e3;
|
|
43
43
|
|
|
44
|
+
// src/idTokenStorage.ts
|
|
45
|
+
var ID_TOKEN_STORAGE_KEY = "imtbl_id_token";
|
|
46
|
+
function storeIdToken(idToken) {
|
|
47
|
+
try {
|
|
48
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
49
|
+
window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function getStoredIdToken() {
|
|
55
|
+
try {
|
|
56
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
57
|
+
return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? void 0;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
function clearStoredIdToken() {
|
|
64
|
+
try {
|
|
65
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
66
|
+
window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
44
72
|
// src/callback.tsx
|
|
45
73
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
46
74
|
function getSearchParams() {
|
|
@@ -90,6 +118,9 @@ function CallbackPage({
|
|
|
90
118
|
window.close();
|
|
91
119
|
} else {
|
|
92
120
|
const tokenData = mapTokensToSignInData(tokens);
|
|
121
|
+
if (tokens.idToken) {
|
|
122
|
+
storeIdToken(tokens.idToken);
|
|
123
|
+
}
|
|
93
124
|
const result = await (0, import_react2.signIn)(IMMUTABLE_PROVIDER_ID, {
|
|
94
125
|
tokens: JSON.stringify(tokenData),
|
|
95
126
|
redirect: false
|
|
@@ -214,6 +245,11 @@ function useImmutableSession() {
|
|
|
214
245
|
deduplicatedUpdate(() => updateRef.current());
|
|
215
246
|
}
|
|
216
247
|
}, [session?.accessTokenExpires]);
|
|
248
|
+
(0, import_react3.useEffect)(() => {
|
|
249
|
+
if (session?.idToken) {
|
|
250
|
+
storeIdToken(session.idToken);
|
|
251
|
+
}
|
|
252
|
+
}, [session?.idToken]);
|
|
217
253
|
const getUser = (0, import_react3.useCallback)(async (forceRefresh) => {
|
|
218
254
|
let currentSession;
|
|
219
255
|
if (forceRefresh) {
|
|
@@ -223,6 +259,9 @@ function useImmutableSession() {
|
|
|
223
259
|
currentSession = updatedSession;
|
|
224
260
|
if (currentSession) {
|
|
225
261
|
sessionRef.current = currentSession;
|
|
262
|
+
if (currentSession.idToken) {
|
|
263
|
+
storeIdToken(currentSession.idToken);
|
|
264
|
+
}
|
|
226
265
|
}
|
|
227
266
|
} catch (error) {
|
|
228
267
|
console.error("[auth-next-client] Force refresh failed:", error);
|
|
@@ -235,6 +274,9 @@ function useImmutableSession() {
|
|
|
235
274
|
if (refreshed) {
|
|
236
275
|
currentSession = refreshed;
|
|
237
276
|
sessionRef.current = currentSession;
|
|
277
|
+
if (currentSession.idToken) {
|
|
278
|
+
storeIdToken(currentSession.idToken);
|
|
279
|
+
}
|
|
238
280
|
} else {
|
|
239
281
|
currentSession = sessionRef.current;
|
|
240
282
|
}
|
|
@@ -251,7 +293,9 @@ function useImmutableSession() {
|
|
|
251
293
|
return {
|
|
252
294
|
accessToken: currentSession.accessToken,
|
|
253
295
|
refreshToken: currentSession.refreshToken,
|
|
254
|
-
idToken
|
|
296
|
+
// Prefer session idToken (fresh after sign-in or refresh, before useEffect
|
|
297
|
+
// stores it), fall back to localStorage for normal reads (cookie has no idToken).
|
|
298
|
+
idToken: currentSession.idToken || getStoredIdToken(),
|
|
255
299
|
profile: {
|
|
256
300
|
sub: currentSession.user?.sub ?? "",
|
|
257
301
|
email: currentSession.user?.email ?? void 0,
|
|
@@ -291,6 +335,9 @@ function useLogin() {
|
|
|
291
335
|
const [isLoggingIn, setIsLoggingIn] = (0, import_react3.useState)(false);
|
|
292
336
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
293
337
|
const signInWithTokens = (0, import_react3.useCallback)(async (tokens) => {
|
|
338
|
+
if (tokens.idToken) {
|
|
339
|
+
storeIdToken(tokens.idToken);
|
|
340
|
+
}
|
|
294
341
|
const result = await (0, import_react4.signIn)(IMMUTABLE_PROVIDER_ID, {
|
|
295
342
|
tokens: JSON.stringify(tokens),
|
|
296
343
|
redirect: false
|
|
@@ -357,6 +404,7 @@ function useLogout() {
|
|
|
357
404
|
setIsLoggingOut(true);
|
|
358
405
|
setError(null);
|
|
359
406
|
try {
|
|
407
|
+
clearStoredIdToken();
|
|
360
408
|
await (0, import_react4.signOut)({ redirect: false });
|
|
361
409
|
(0, import_auth2.logoutWithRedirect)(config);
|
|
362
410
|
} catch (err) {
|
package/dist/node/index.js
CHANGED
|
@@ -12,6 +12,34 @@ var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
|
|
|
12
12
|
var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
|
|
13
13
|
var TOKEN_EXPIRY_BUFFER_MS = 60 * 1e3;
|
|
14
14
|
|
|
15
|
+
// src/idTokenStorage.ts
|
|
16
|
+
var ID_TOKEN_STORAGE_KEY = "imtbl_id_token";
|
|
17
|
+
function storeIdToken(idToken) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
20
|
+
window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getStoredIdToken() {
|
|
26
|
+
try {
|
|
27
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
28
|
+
return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? void 0;
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
function clearStoredIdToken() {
|
|
35
|
+
try {
|
|
36
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
37
|
+
window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
// src/callback.tsx
|
|
16
44
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
45
|
function getSearchParams() {
|
|
@@ -61,6 +89,9 @@ function CallbackPage({
|
|
|
61
89
|
window.close();
|
|
62
90
|
} else {
|
|
63
91
|
const tokenData = mapTokensToSignInData(tokens);
|
|
92
|
+
if (tokens.idToken) {
|
|
93
|
+
storeIdToken(tokens.idToken);
|
|
94
|
+
}
|
|
64
95
|
const result = await signIn(IMMUTABLE_PROVIDER_ID, {
|
|
65
96
|
tokens: JSON.stringify(tokenData),
|
|
66
97
|
redirect: false
|
|
@@ -195,6 +226,11 @@ function useImmutableSession() {
|
|
|
195
226
|
deduplicatedUpdate(() => updateRef.current());
|
|
196
227
|
}
|
|
197
228
|
}, [session?.accessTokenExpires]);
|
|
229
|
+
useEffect2(() => {
|
|
230
|
+
if (session?.idToken) {
|
|
231
|
+
storeIdToken(session.idToken);
|
|
232
|
+
}
|
|
233
|
+
}, [session?.idToken]);
|
|
198
234
|
const getUser = useCallback(async (forceRefresh) => {
|
|
199
235
|
let currentSession;
|
|
200
236
|
if (forceRefresh) {
|
|
@@ -204,6 +240,9 @@ function useImmutableSession() {
|
|
|
204
240
|
currentSession = updatedSession;
|
|
205
241
|
if (currentSession) {
|
|
206
242
|
sessionRef.current = currentSession;
|
|
243
|
+
if (currentSession.idToken) {
|
|
244
|
+
storeIdToken(currentSession.idToken);
|
|
245
|
+
}
|
|
207
246
|
}
|
|
208
247
|
} catch (error) {
|
|
209
248
|
console.error("[auth-next-client] Force refresh failed:", error);
|
|
@@ -216,6 +255,9 @@ function useImmutableSession() {
|
|
|
216
255
|
if (refreshed) {
|
|
217
256
|
currentSession = refreshed;
|
|
218
257
|
sessionRef.current = currentSession;
|
|
258
|
+
if (currentSession.idToken) {
|
|
259
|
+
storeIdToken(currentSession.idToken);
|
|
260
|
+
}
|
|
219
261
|
} else {
|
|
220
262
|
currentSession = sessionRef.current;
|
|
221
263
|
}
|
|
@@ -232,7 +274,9 @@ function useImmutableSession() {
|
|
|
232
274
|
return {
|
|
233
275
|
accessToken: currentSession.accessToken,
|
|
234
276
|
refreshToken: currentSession.refreshToken,
|
|
235
|
-
idToken
|
|
277
|
+
// Prefer session idToken (fresh after sign-in or refresh, before useEffect
|
|
278
|
+
// stores it), fall back to localStorage for normal reads (cookie has no idToken).
|
|
279
|
+
idToken: currentSession.idToken || getStoredIdToken(),
|
|
236
280
|
profile: {
|
|
237
281
|
sub: currentSession.user?.sub ?? "",
|
|
238
282
|
email: currentSession.user?.email ?? void 0,
|
|
@@ -272,6 +316,9 @@ function useLogin() {
|
|
|
272
316
|
const [isLoggingIn, setIsLoggingIn] = useState2(false);
|
|
273
317
|
const [error, setError] = useState2(null);
|
|
274
318
|
const signInWithTokens = useCallback(async (tokens) => {
|
|
319
|
+
if (tokens.idToken) {
|
|
320
|
+
storeIdToken(tokens.idToken);
|
|
321
|
+
}
|
|
275
322
|
const result = await signIn2(IMMUTABLE_PROVIDER_ID, {
|
|
276
323
|
tokens: JSON.stringify(tokens),
|
|
277
324
|
redirect: false
|
|
@@ -338,6 +385,7 @@ function useLogout() {
|
|
|
338
385
|
setIsLoggingOut(true);
|
|
339
386
|
setError(null);
|
|
340
387
|
try {
|
|
388
|
+
clearStoredIdToken();
|
|
341
389
|
await signOut({ redirect: false });
|
|
342
390
|
rawLogoutWithRedirect(config);
|
|
343
391
|
} catch (err) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for persisting idToken in localStorage.
|
|
3
|
+
*
|
|
4
|
+
* The idToken is stripped from the NextAuth session cookie (via a custom
|
|
5
|
+
* jwt.encode in @imtbl/auth-next-server) to keep cookie size under CDN header
|
|
6
|
+
* limits (CloudFront 20 KB). Instead, the client stores idToken in
|
|
7
|
+
* localStorage so that wallet operations (e.g., MagicTEESigner) can still
|
|
8
|
+
* access it via getUser().
|
|
9
|
+
*
|
|
10
|
+
* All functions are safe to call during SSR or in restricted environments
|
|
11
|
+
* (e.g., incognito mode with localStorage disabled) -- they silently no-op.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Store the idToken in localStorage.
|
|
15
|
+
* @param idToken - The raw ID token JWT string
|
|
16
|
+
*/
|
|
17
|
+
export declare function storeIdToken(idToken: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Retrieve the idToken from localStorage.
|
|
20
|
+
* @returns The stored idToken, or undefined if not available.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getStoredIdToken(): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Remove the idToken from localStorage (e.g., on logout).
|
|
25
|
+
*/
|
|
26
|
+
export declare function clearStoredIdToken(): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imtbl/auth-next-client",
|
|
3
|
-
"version": "2.12.7-alpha.
|
|
3
|
+
"version": "2.12.7-alpha.8",
|
|
4
4
|
"description": "Immutable Auth.js v5 integration for Next.js - Client-side components",
|
|
5
5
|
"author": "Immutable",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@imtbl/auth": "2.12.7-alpha.
|
|
31
|
-
"@imtbl/auth-next-server": "2.12.7-alpha.
|
|
30
|
+
"@imtbl/auth": "2.12.7-alpha.8",
|
|
31
|
+
"@imtbl/auth-next-server": "2.12.7-alpha.8"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"next": "^15.0.0",
|
package/src/callback.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { signIn } from 'next-auth/react';
|
|
|
6
6
|
import { handleLoginCallback as handleAuthCallback, type TokenResponse } from '@imtbl/auth';
|
|
7
7
|
import type { ImmutableUserClient } from './types';
|
|
8
8
|
import { IMMUTABLE_PROVIDER_ID } from './constants';
|
|
9
|
+
import { storeIdToken } from './idTokenStorage';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Config for CallbackPage - matches LoginConfig from @imtbl/auth
|
|
@@ -159,6 +160,12 @@ export function CallbackPage({
|
|
|
159
160
|
// Not in a popup - sign in to NextAuth with the tokens
|
|
160
161
|
const tokenData = mapTokensToSignInData(tokens);
|
|
161
162
|
|
|
163
|
+
// Persist idToken to localStorage before signIn so it's available
|
|
164
|
+
// immediately. The cookie won't contain idToken (stripped by jwt.encode).
|
|
165
|
+
if (tokens.idToken) {
|
|
166
|
+
storeIdToken(tokens.idToken);
|
|
167
|
+
}
|
|
168
|
+
|
|
162
169
|
const result = await signIn(IMMUTABLE_PROVIDER_ID, {
|
|
163
170
|
tokens: JSON.stringify(tokenData),
|
|
164
171
|
redirect: false,
|
package/src/hooks.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
logoutWithRedirect as rawLogoutWithRedirect,
|
|
20
20
|
} from '@imtbl/auth';
|
|
21
21
|
import { IMMUTABLE_PROVIDER_ID, TOKEN_EXPIRY_BUFFER_MS } from './constants';
|
|
22
|
+
import { storeIdToken, getStoredIdToken, clearStoredIdToken } from './idTokenStorage';
|
|
22
23
|
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
// Module-level deduplication for session refresh
|
|
@@ -189,6 +190,20 @@ export function useImmutableSession(): UseImmutableSessionReturn {
|
|
|
189
190
|
}
|
|
190
191
|
}, [session?.accessTokenExpires]);
|
|
191
192
|
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Sync idToken to localStorage
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
// The idToken is stripped from the cookie by jwt.encode on the server to avoid
|
|
198
|
+
// CloudFront 413 errors. It is only present in the session response transiently
|
|
199
|
+
// after sign-in or token refresh. When present, persist it in localStorage so
|
|
200
|
+
// that getUser() can always return it (used by wallet's MagicTEESigner).
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (session?.idToken) {
|
|
203
|
+
storeIdToken(session.idToken);
|
|
204
|
+
}
|
|
205
|
+
}, [session?.idToken]);
|
|
206
|
+
|
|
192
207
|
/**
|
|
193
208
|
* Get user function for wallet integration.
|
|
194
209
|
* Returns a User object compatible with @imtbl/wallet's getUser option.
|
|
@@ -213,6 +228,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
|
|
|
213
228
|
// Also update the ref so subsequent calls get the fresh data
|
|
214
229
|
if (currentSession) {
|
|
215
230
|
sessionRef.current = currentSession;
|
|
231
|
+
// Immediately persist fresh idToken to localStorage (avoids race with useEffect)
|
|
232
|
+
if (currentSession.idToken) {
|
|
233
|
+
storeIdToken(currentSession.idToken);
|
|
234
|
+
}
|
|
216
235
|
}
|
|
217
236
|
} catch (error) {
|
|
218
237
|
// eslint-disable-next-line no-console
|
|
@@ -229,6 +248,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
|
|
|
229
248
|
if (refreshed) {
|
|
230
249
|
currentSession = refreshed as ImmutableSessionInternal;
|
|
231
250
|
sessionRef.current = currentSession;
|
|
251
|
+
// Persist fresh idToken to localStorage immediately
|
|
252
|
+
if (currentSession.idToken) {
|
|
253
|
+
storeIdToken(currentSession.idToken);
|
|
254
|
+
}
|
|
232
255
|
} else {
|
|
233
256
|
currentSession = sessionRef.current;
|
|
234
257
|
}
|
|
@@ -252,7 +275,9 @@ export function useImmutableSession(): UseImmutableSessionReturn {
|
|
|
252
275
|
return {
|
|
253
276
|
accessToken: currentSession.accessToken,
|
|
254
277
|
refreshToken: currentSession.refreshToken,
|
|
255
|
-
idToken
|
|
278
|
+
// Prefer session idToken (fresh after sign-in or refresh, before useEffect
|
|
279
|
+
// stores it), fall back to localStorage for normal reads (cookie has no idToken).
|
|
280
|
+
idToken: currentSession.idToken || getStoredIdToken(),
|
|
256
281
|
profile: {
|
|
257
282
|
sub: currentSession.user?.sub ?? '',
|
|
258
283
|
email: currentSession.user?.email ?? undefined,
|
|
@@ -387,6 +412,12 @@ export function useLogin(): UseLoginReturn {
|
|
|
387
412
|
profile: { sub: string; email?: string; nickname?: string };
|
|
388
413
|
zkEvm?: ZkEvmInfo;
|
|
389
414
|
}) => {
|
|
415
|
+
// Persist idToken to localStorage before signIn so it's available immediately.
|
|
416
|
+
// The cookie won't contain idToken (stripped by jwt.encode on the server).
|
|
417
|
+
if (tokens.idToken) {
|
|
418
|
+
storeIdToken(tokens.idToken);
|
|
419
|
+
}
|
|
420
|
+
|
|
390
421
|
const result = await signIn(IMMUTABLE_PROVIDER_ID, {
|
|
391
422
|
tokens: JSON.stringify(tokens),
|
|
392
423
|
redirect: false,
|
|
@@ -550,6 +581,9 @@ export function useLogout(): UseLogoutReturn {
|
|
|
550
581
|
setError(null);
|
|
551
582
|
|
|
552
583
|
try {
|
|
584
|
+
// Clear idToken from localStorage before clearing session
|
|
585
|
+
clearStoredIdToken();
|
|
586
|
+
|
|
553
587
|
// First, clear the NextAuth session (this clears the JWT cookie)
|
|
554
588
|
// We use redirect: false to handle the redirect ourselves for federated logout
|
|
555
589
|
await signOut({ redirect: false });
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for persisting idToken in localStorage.
|
|
3
|
+
*
|
|
4
|
+
* The idToken is stripped from the NextAuth session cookie (via a custom
|
|
5
|
+
* jwt.encode in @imtbl/auth-next-server) to keep cookie size under CDN header
|
|
6
|
+
* limits (CloudFront 20 KB). Instead, the client stores idToken in
|
|
7
|
+
* localStorage so that wallet operations (e.g., MagicTEESigner) can still
|
|
8
|
+
* access it via getUser().
|
|
9
|
+
*
|
|
10
|
+
* All functions are safe to call during SSR or in restricted environments
|
|
11
|
+
* (e.g., incognito mode with localStorage disabled) -- they silently no-op.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const ID_TOKEN_STORAGE_KEY = 'imtbl_id_token';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Store the idToken in localStorage.
|
|
18
|
+
* @param idToken - The raw ID token JWT string
|
|
19
|
+
*/
|
|
20
|
+
export function storeIdToken(idToken: string): void {
|
|
21
|
+
try {
|
|
22
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
23
|
+
window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
// Silently ignore -- localStorage may be unavailable (SSR, incognito, etc.)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Retrieve the idToken from localStorage.
|
|
32
|
+
* @returns The stored idToken, or undefined if not available.
|
|
33
|
+
*/
|
|
34
|
+
export function getStoredIdToken(): string | undefined {
|
|
35
|
+
try {
|
|
36
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
37
|
+
return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? undefined;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Silently ignore
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Remove the idToken from localStorage (e.g., on logout).
|
|
47
|
+
*/
|
|
48
|
+
export function clearStoredIdToken(): void {
|
|
49
|
+
try {
|
|
50
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
51
|
+
window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Silently ignore
|
|
55
|
+
}
|
|
56
|
+
}
|