@imtbl/auth-next-client 2.12.7 → 2.12.8-alpha.0
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/dist/node/index.cjs +2 -1
- package/dist/node/index.js +2 -1
- package/package.json +3 -3
- package/src/hooks.test.tsx +47 -0
- package/src/hooks.tsx +3 -1
package/dist/node/index.cjs
CHANGED
|
@@ -288,11 +288,12 @@ function useImmutableSession() {
|
|
|
288
288
|
setIsRefreshingRef.current = setIsRefreshing;
|
|
289
289
|
(0, import_react3.useEffect)(() => {
|
|
290
290
|
if (!session?.accessTokenExpires) return;
|
|
291
|
+
if (session?.error) return;
|
|
291
292
|
const timeUntilExpiry = session.accessTokenExpires - Date.now() - TOKEN_EXPIRY_BUFFER_MS;
|
|
292
293
|
if (timeUntilExpiry <= 0) {
|
|
293
294
|
deduplicatedUpdate(() => updateRef.current());
|
|
294
295
|
}
|
|
295
|
-
}, [session?.accessTokenExpires]);
|
|
296
|
+
}, [session?.accessTokenExpires, session?.error]);
|
|
296
297
|
(0, import_react3.useEffect)(() => {
|
|
297
298
|
if (session?.idToken) {
|
|
298
299
|
storeIdToken(session.idToken);
|
package/dist/node/index.js
CHANGED
|
@@ -261,11 +261,12 @@ function useImmutableSession() {
|
|
|
261
261
|
setIsRefreshingRef.current = setIsRefreshing;
|
|
262
262
|
useEffect2(() => {
|
|
263
263
|
if (!session?.accessTokenExpires) return;
|
|
264
|
+
if (session?.error) return;
|
|
264
265
|
const timeUntilExpiry = session.accessTokenExpires - Date.now() - TOKEN_EXPIRY_BUFFER_MS;
|
|
265
266
|
if (timeUntilExpiry <= 0) {
|
|
266
267
|
deduplicatedUpdate(() => updateRef.current());
|
|
267
268
|
}
|
|
268
|
-
}, [session?.accessTokenExpires]);
|
|
269
|
+
}, [session?.accessTokenExpires, session?.error]);
|
|
269
270
|
useEffect2(() => {
|
|
270
271
|
if (session?.idToken) {
|
|
271
272
|
storeIdToken(session.idToken);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imtbl/auth-next-client",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.8-alpha.0",
|
|
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.
|
|
31
|
-
"@imtbl/auth-next-server": "2.12.
|
|
30
|
+
"@imtbl/auth": "2.12.8-alpha.0",
|
|
31
|
+
"@imtbl/auth-next-server": "2.12.8-alpha.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"next": "^14.0.0 || ^15.0.0",
|
package/src/hooks.test.tsx
CHANGED
|
@@ -291,6 +291,23 @@ describe('useImmutableSession', () => {
|
|
|
291
291
|
// Should NOT have called update -- token is still valid
|
|
292
292
|
expect(mockUpdate).not.toHaveBeenCalled();
|
|
293
293
|
});
|
|
294
|
+
|
|
295
|
+
it('does not trigger refresh when session has error (prevents infinite loop)', async () => {
|
|
296
|
+
// Simulate: token expired and last refresh failed (e.g. RefreshTokenError)
|
|
297
|
+
const sessionWithError = createSession({
|
|
298
|
+
accessTokenExpires: Date.now() - 1000, // expired
|
|
299
|
+
error: 'RefreshTokenError',
|
|
300
|
+
});
|
|
301
|
+
setupUseSession(sessionWithError);
|
|
302
|
+
|
|
303
|
+
await act(async () => {
|
|
304
|
+
renderHook(() => useImmutableSession());
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Must NOT call update - otherwise we would retry refresh repeatedly
|
|
308
|
+
// and cause an infinite loop (update -> same session with error -> effect re-runs -> update again).
|
|
309
|
+
expect(mockUpdate).not.toHaveBeenCalled();
|
|
310
|
+
});
|
|
294
311
|
});
|
|
295
312
|
|
|
296
313
|
describe('getUser() respects pending refresh', () => {
|
|
@@ -317,5 +334,35 @@ describe('useImmutableSession', () => {
|
|
|
317
334
|
// getUser() should have waited for the refresh and gotten the fresh token
|
|
318
335
|
expect(user?.accessToken).toBe('user-fresh-token');
|
|
319
336
|
});
|
|
337
|
+
|
|
338
|
+
it('getUser(true) still calls update with forceRefresh even when session has error', async () => {
|
|
339
|
+
// Session is in error state (e.g. previous refresh failed)
|
|
340
|
+
const sessionWithError = createSession({
|
|
341
|
+
accessTokenExpires: Date.now() - 1000,
|
|
342
|
+
error: 'RefreshTokenError',
|
|
343
|
+
});
|
|
344
|
+
setupUseSession(sessionWithError);
|
|
345
|
+
|
|
346
|
+
// Server recovers and returns a valid session (e.g. user re-authenticated elsewhere)
|
|
347
|
+
const recoveredSession = createSession({
|
|
348
|
+
accessToken: 'recovered-token',
|
|
349
|
+
accessTokenExpires: Date.now() + 10 * 60 * 1000,
|
|
350
|
+
user: { sub: 'user-1', email: 'recovered@test.com' },
|
|
351
|
+
});
|
|
352
|
+
mockUpdate.mockResolvedValue(recoveredSession);
|
|
353
|
+
|
|
354
|
+
const { result } = renderHook(() => useImmutableSession());
|
|
355
|
+
|
|
356
|
+
let user: any;
|
|
357
|
+
await act(async () => {
|
|
358
|
+
user = await result.current.getUser(true);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// forceRefresh must have been attempted (proactive effect does NOT run when session.error is set)
|
|
362
|
+
expect(mockUpdate).toHaveBeenCalledWith({ forceRefresh: true });
|
|
363
|
+
// When server returns a good session, we get the user
|
|
364
|
+
expect(user?.accessToken).toBe('recovered-token');
|
|
365
|
+
expect(user?.profile?.email).toBe('recovered@test.com');
|
|
366
|
+
});
|
|
320
367
|
});
|
|
321
368
|
});
|
package/src/hooks.tsx
CHANGED
|
@@ -221,6 +221,8 @@ export function useImmutableSession(): UseImmutableSessionReturn {
|
|
|
221
221
|
// `!isRefreshing` to briefly lose their cached data, resulting in UI flicker.
|
|
222
222
|
useEffect(() => {
|
|
223
223
|
if (!session?.accessTokenExpires) return;
|
|
224
|
+
// Don't retry if the last refresh already failed - prevents infinite loops
|
|
225
|
+
if (session?.error) return;
|
|
224
226
|
|
|
225
227
|
const timeUntilExpiry = session.accessTokenExpires - Date.now() - TOKEN_EXPIRY_BUFFER_MS;
|
|
226
228
|
|
|
@@ -228,7 +230,7 @@ export function useImmutableSession(): UseImmutableSessionReturn {
|
|
|
228
230
|
// Already expired -- refresh silently
|
|
229
231
|
deduplicatedUpdate(() => updateRef.current());
|
|
230
232
|
}
|
|
231
|
-
}, [session?.accessTokenExpires]);
|
|
233
|
+
}, [session?.accessTokenExpires, session?.error]);
|
|
232
234
|
|
|
233
235
|
// ---------------------------------------------------------------------------
|
|
234
236
|
// Sync idToken to localStorage
|