@swr-login/react 0.7.0 → 0.9.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/index.cjs +94 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -2
- package/dist/index.d.ts +151 -2
- package/dist/index.js +92 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { SWRLoginConfig, AuthResponse, AuthInjector, User, TokenAdapter, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
|
|
2
|
+
import { SWRLoginConfig, AuthResponse, AuthInjector, User, UserChangeSource, UserChangeEvent, TokenAdapter, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
5
5
|
interface SWRLoginProviderProps {
|
|
@@ -196,6 +196,30 @@ interface UseUserReturn<T extends User = User> {
|
|
|
196
196
|
* Call with `null` to clear, or with a user object to update.
|
|
197
197
|
*/
|
|
198
198
|
mutate: (data?: T | null) => Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Why the most recent `user` transition happened.
|
|
201
|
+
*
|
|
202
|
+
* - `null` — No user-change has been observed yet in this component
|
|
203
|
+
* (e.g. before the first `fetchUser` resolves on mount).
|
|
204
|
+
* - `'initial'` — First time the Provider observed a user value
|
|
205
|
+
* (including `null` for unauthenticated cold start).
|
|
206
|
+
* - `'login'` — Triggered by an explicit `login()` / multi-step finalize
|
|
207
|
+
* / `injectAuth()` call.
|
|
208
|
+
* - `'logout'` — Triggered by an explicit `logout()` / `injectLogout()`.
|
|
209
|
+
* - `'revalidate'` — SWR background revalidation produced a different user.
|
|
210
|
+
* - `'external'` — Cross-tab sync via BroadcastChannel / storage events.
|
|
211
|
+
*
|
|
212
|
+
* Use this to differentiate user-initiated transitions from passive ones,
|
|
213
|
+
* e.g. to suppress a welcome toast on page refresh.
|
|
214
|
+
*/
|
|
215
|
+
lastChangeSource: UserChangeSource | null;
|
|
216
|
+
/**
|
|
217
|
+
* Full event object for the most recent `user` transition, including
|
|
218
|
+
* `previousUser` and `timestamp`. `null` before the first transition.
|
|
219
|
+
*
|
|
220
|
+
* See `lastChangeSource` for a quick discriminator.
|
|
221
|
+
*/
|
|
222
|
+
lastChangeEvent: UserChangeEvent<T> | null;
|
|
199
223
|
}
|
|
200
224
|
/**
|
|
201
225
|
* Hook to access current user data via SWR cache.
|
|
@@ -213,9 +237,112 @@ interface UseUserReturn<T extends User = User> {
|
|
|
213
237
|
* if (!isAuthenticated) return <LoginPage />;
|
|
214
238
|
* return <Dashboard user={user} />;
|
|
215
239
|
* ```
|
|
240
|
+
*
|
|
241
|
+
* @example Distinguish user-change sources
|
|
242
|
+
* ```tsx
|
|
243
|
+
* const { user, lastChangeSource } = useUser();
|
|
244
|
+
*
|
|
245
|
+
* useEffect(() => {
|
|
246
|
+
* // Only auto-redirect when we detect an *existing* session on mount,
|
|
247
|
+
* // not when the user just pressed the "Login" button (the login form
|
|
248
|
+
* // is responsible for its own redirect there).
|
|
249
|
+
* if (user && lastChangeSource === 'initial') {
|
|
250
|
+
* showRedirectOverlay();
|
|
251
|
+
* }
|
|
252
|
+
* }, [user, lastChangeSource]);
|
|
253
|
+
* ```
|
|
216
254
|
*/
|
|
217
255
|
declare function useUser<T extends User = User>(): UseUserReturn<T>;
|
|
218
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Subscribe to `user-change` events as a **discrete event stream**.
|
|
259
|
+
*
|
|
260
|
+
* Unlike `useUser().user` (which reflects the *current* user value), this
|
|
261
|
+
* hook returns the most recent transition event and re-renders the caller
|
|
262
|
+
* only when a new transition occurs. It's intended for `useEffect`-driven
|
|
263
|
+
* side effects that care about *when* and *why* the user changed rather
|
|
264
|
+
* than the current user value itself.
|
|
265
|
+
*
|
|
266
|
+
* Returns `null` until the first transition is observed by the Provider.
|
|
267
|
+
*
|
|
268
|
+
* @typeParam T - Concrete user type (defaults to base `User`)
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```tsx
|
|
272
|
+
* const change = useUserChange<MyUser>();
|
|
273
|
+
*
|
|
274
|
+
* useEffect(() => {
|
|
275
|
+
* if (change?.source === 'login') {
|
|
276
|
+
* toast.success(`Welcome back, ${change.user?.name}!`);
|
|
277
|
+
* }
|
|
278
|
+
* }, [change]);
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* @example Filter by source
|
|
282
|
+
* ```tsx
|
|
283
|
+
* const change = useUserChange();
|
|
284
|
+
* useEffect(() => {
|
|
285
|
+
* if (change?.source === 'external') {
|
|
286
|
+
* // Another tab just logged in / out — refresh local UI
|
|
287
|
+
* refreshSidebar();
|
|
288
|
+
* }
|
|
289
|
+
* }, [change]);
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
declare function useUserChange<T extends User = User>(): UserChangeEvent<T> | null;
|
|
293
|
+
/**
|
|
294
|
+
* Register a side-effect callback that fires on every `user-change` event,
|
|
295
|
+
* **without** triggering a re-render of the calling component.
|
|
296
|
+
*
|
|
297
|
+
* Ideal for analytics / logging / imperative side effects that don't need
|
|
298
|
+
* to participate in React's render cycle.
|
|
299
|
+
*
|
|
300
|
+
* The callback receives the full `UserChangeEvent` (including `previousUser`,
|
|
301
|
+
* `timestamp`, and `source`). The listener is automatically unsubscribed on
|
|
302
|
+
* unmount.
|
|
303
|
+
*
|
|
304
|
+
* Note: the callback reference is stored in a ref and always called with
|
|
305
|
+
* the latest closure — you do NOT need to memoize it with `useCallback`.
|
|
306
|
+
*
|
|
307
|
+
* @example Analytics
|
|
308
|
+
* ```tsx
|
|
309
|
+
* useUserChangeEffect((e) => {
|
|
310
|
+
* if (e.source === 'login') {
|
|
311
|
+
* analytics.track('user_login', { userId: e.user?.id });
|
|
312
|
+
* }
|
|
313
|
+
* if (e.source === 'logout') {
|
|
314
|
+
* analytics.track('user_logout', { userId: e.previousUser?.id });
|
|
315
|
+
* }
|
|
316
|
+
* });
|
|
317
|
+
* ```
|
|
318
|
+
*
|
|
319
|
+
* @example Filter sources
|
|
320
|
+
* ```tsx
|
|
321
|
+
* useUserChangeEffect((e) => {
|
|
322
|
+
* // Only react to passive revalidations that flipped the user to null
|
|
323
|
+
* // (e.g. server returned 401 → session expired silently).
|
|
324
|
+
* if (e.source === 'revalidate' && e.user === null && e.previousUser) {
|
|
325
|
+
* showReLoginPrompt();
|
|
326
|
+
* }
|
|
327
|
+
* });
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
declare function useUserChangeEffect<T extends User = User>(listener: (event: UserChangeEvent<T>) => void): void;
|
|
331
|
+
/**
|
|
332
|
+
* Convenience hook: fires the callback only when the change source matches
|
|
333
|
+
* the given filter. Thin wrapper around {@link useUserChangeEffect}.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```tsx
|
|
337
|
+
* // Only run on explicit login (not on cold-start initial load)
|
|
338
|
+
* useUserChangeOn('login', (e) => router.push('/dashboard'));
|
|
339
|
+
*
|
|
340
|
+
* // Subscribe to multiple sources at once
|
|
341
|
+
* useUserChangeOn(['login', 'external'], (e) => refreshSidebar());
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
declare function useUserChangeOn<T extends User = User>(source: UserChangeSource | UserChangeSource[], listener: (event: UserChangeEvent<T>) => void): void;
|
|
345
|
+
|
|
219
346
|
interface UseLogoutOptions {
|
|
220
347
|
/** Specific plugin to call logout on */
|
|
221
348
|
pluginName?: string;
|
|
@@ -410,6 +537,26 @@ interface AuthGuardProps {
|
|
|
410
537
|
*/
|
|
411
538
|
declare function AuthGuard({ children, permissions, roles, requireAll, fallback, loadingComponent, }: AuthGuardProps): react_jsx_runtime.JSX.Element;
|
|
412
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Mutable hint object shared between the Provider and `useUser()` to describe
|
|
542
|
+
* *why* the next user-change event is expected to happen.
|
|
543
|
+
*
|
|
544
|
+
* The Provider writes into this object as it observes `login` / `logout` /
|
|
545
|
+
* `external` events; `useUser()` reads and clears it when the SWR cache value
|
|
546
|
+
* actually transitions. Unset/expired hint → the change is a passive
|
|
547
|
+
* `revalidate` or the very first `initial` load.
|
|
548
|
+
*
|
|
549
|
+
* Using a mutable ref-like object (instead of React state) avoids re-renders
|
|
550
|
+
* and is safe because it is written synchronously from event callbacks and
|
|
551
|
+
* read synchronously during `useUser()`'s `useEffect`.
|
|
552
|
+
*
|
|
553
|
+
* @internal
|
|
554
|
+
*/
|
|
555
|
+
interface UserChangeHint {
|
|
556
|
+
source: UserChangeSource | null;
|
|
557
|
+
/** `Date.now()` when the hint was written. Stale hints (>1s) are ignored. */
|
|
558
|
+
timestamp: number;
|
|
559
|
+
}
|
|
413
560
|
/** Internal context value passed through SWRLoginProvider */
|
|
414
561
|
interface AuthContextValue {
|
|
415
562
|
pluginManager: PluginManager;
|
|
@@ -418,6 +565,8 @@ interface AuthContextValue {
|
|
|
418
565
|
stateMachine: AuthStateMachine;
|
|
419
566
|
broadcastSync: BroadcastSync | null;
|
|
420
567
|
config: SWRLoginConfig;
|
|
568
|
+
/** @internal shared hint for the next user-change source */
|
|
569
|
+
userChangeHint: UserChangeHint;
|
|
421
570
|
}
|
|
422
571
|
/**
|
|
423
572
|
* Internal hook to access auth context.
|
|
@@ -426,4 +575,4 @@ interface AuthContextValue {
|
|
|
426
575
|
*/
|
|
427
576
|
declare function useAuthContext(): AuthContextValue;
|
|
428
577
|
|
|
429
|
-
export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseAdapterReturn, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UseMultiStepLoginReturn, type UsePermissionReturn, type UseUserReturn, useAdapter, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser };
|
|
578
|
+
export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseAdapterReturn, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UseMultiStepLoginReturn, type UsePermissionReturn, type UseUserReturn, useAdapter, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser, useUserChange, useUserChangeEffect, useUserChangeOn };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { SWRLoginConfig, AuthResponse, AuthInjector, User, TokenAdapter, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
|
|
2
|
+
import { SWRLoginConfig, AuthResponse, AuthInjector, User, UserChangeSource, UserChangeEvent, TokenAdapter, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
5
5
|
interface SWRLoginProviderProps {
|
|
@@ -196,6 +196,30 @@ interface UseUserReturn<T extends User = User> {
|
|
|
196
196
|
* Call with `null` to clear, or with a user object to update.
|
|
197
197
|
*/
|
|
198
198
|
mutate: (data?: T | null) => Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Why the most recent `user` transition happened.
|
|
201
|
+
*
|
|
202
|
+
* - `null` — No user-change has been observed yet in this component
|
|
203
|
+
* (e.g. before the first `fetchUser` resolves on mount).
|
|
204
|
+
* - `'initial'` — First time the Provider observed a user value
|
|
205
|
+
* (including `null` for unauthenticated cold start).
|
|
206
|
+
* - `'login'` — Triggered by an explicit `login()` / multi-step finalize
|
|
207
|
+
* / `injectAuth()` call.
|
|
208
|
+
* - `'logout'` — Triggered by an explicit `logout()` / `injectLogout()`.
|
|
209
|
+
* - `'revalidate'` — SWR background revalidation produced a different user.
|
|
210
|
+
* - `'external'` — Cross-tab sync via BroadcastChannel / storage events.
|
|
211
|
+
*
|
|
212
|
+
* Use this to differentiate user-initiated transitions from passive ones,
|
|
213
|
+
* e.g. to suppress a welcome toast on page refresh.
|
|
214
|
+
*/
|
|
215
|
+
lastChangeSource: UserChangeSource | null;
|
|
216
|
+
/**
|
|
217
|
+
* Full event object for the most recent `user` transition, including
|
|
218
|
+
* `previousUser` and `timestamp`. `null` before the first transition.
|
|
219
|
+
*
|
|
220
|
+
* See `lastChangeSource` for a quick discriminator.
|
|
221
|
+
*/
|
|
222
|
+
lastChangeEvent: UserChangeEvent<T> | null;
|
|
199
223
|
}
|
|
200
224
|
/**
|
|
201
225
|
* Hook to access current user data via SWR cache.
|
|
@@ -213,9 +237,112 @@ interface UseUserReturn<T extends User = User> {
|
|
|
213
237
|
* if (!isAuthenticated) return <LoginPage />;
|
|
214
238
|
* return <Dashboard user={user} />;
|
|
215
239
|
* ```
|
|
240
|
+
*
|
|
241
|
+
* @example Distinguish user-change sources
|
|
242
|
+
* ```tsx
|
|
243
|
+
* const { user, lastChangeSource } = useUser();
|
|
244
|
+
*
|
|
245
|
+
* useEffect(() => {
|
|
246
|
+
* // Only auto-redirect when we detect an *existing* session on mount,
|
|
247
|
+
* // not when the user just pressed the "Login" button (the login form
|
|
248
|
+
* // is responsible for its own redirect there).
|
|
249
|
+
* if (user && lastChangeSource === 'initial') {
|
|
250
|
+
* showRedirectOverlay();
|
|
251
|
+
* }
|
|
252
|
+
* }, [user, lastChangeSource]);
|
|
253
|
+
* ```
|
|
216
254
|
*/
|
|
217
255
|
declare function useUser<T extends User = User>(): UseUserReturn<T>;
|
|
218
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Subscribe to `user-change` events as a **discrete event stream**.
|
|
259
|
+
*
|
|
260
|
+
* Unlike `useUser().user` (which reflects the *current* user value), this
|
|
261
|
+
* hook returns the most recent transition event and re-renders the caller
|
|
262
|
+
* only when a new transition occurs. It's intended for `useEffect`-driven
|
|
263
|
+
* side effects that care about *when* and *why* the user changed rather
|
|
264
|
+
* than the current user value itself.
|
|
265
|
+
*
|
|
266
|
+
* Returns `null` until the first transition is observed by the Provider.
|
|
267
|
+
*
|
|
268
|
+
* @typeParam T - Concrete user type (defaults to base `User`)
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```tsx
|
|
272
|
+
* const change = useUserChange<MyUser>();
|
|
273
|
+
*
|
|
274
|
+
* useEffect(() => {
|
|
275
|
+
* if (change?.source === 'login') {
|
|
276
|
+
* toast.success(`Welcome back, ${change.user?.name}!`);
|
|
277
|
+
* }
|
|
278
|
+
* }, [change]);
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* @example Filter by source
|
|
282
|
+
* ```tsx
|
|
283
|
+
* const change = useUserChange();
|
|
284
|
+
* useEffect(() => {
|
|
285
|
+
* if (change?.source === 'external') {
|
|
286
|
+
* // Another tab just logged in / out — refresh local UI
|
|
287
|
+
* refreshSidebar();
|
|
288
|
+
* }
|
|
289
|
+
* }, [change]);
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
declare function useUserChange<T extends User = User>(): UserChangeEvent<T> | null;
|
|
293
|
+
/**
|
|
294
|
+
* Register a side-effect callback that fires on every `user-change` event,
|
|
295
|
+
* **without** triggering a re-render of the calling component.
|
|
296
|
+
*
|
|
297
|
+
* Ideal for analytics / logging / imperative side effects that don't need
|
|
298
|
+
* to participate in React's render cycle.
|
|
299
|
+
*
|
|
300
|
+
* The callback receives the full `UserChangeEvent` (including `previousUser`,
|
|
301
|
+
* `timestamp`, and `source`). The listener is automatically unsubscribed on
|
|
302
|
+
* unmount.
|
|
303
|
+
*
|
|
304
|
+
* Note: the callback reference is stored in a ref and always called with
|
|
305
|
+
* the latest closure — you do NOT need to memoize it with `useCallback`.
|
|
306
|
+
*
|
|
307
|
+
* @example Analytics
|
|
308
|
+
* ```tsx
|
|
309
|
+
* useUserChangeEffect((e) => {
|
|
310
|
+
* if (e.source === 'login') {
|
|
311
|
+
* analytics.track('user_login', { userId: e.user?.id });
|
|
312
|
+
* }
|
|
313
|
+
* if (e.source === 'logout') {
|
|
314
|
+
* analytics.track('user_logout', { userId: e.previousUser?.id });
|
|
315
|
+
* }
|
|
316
|
+
* });
|
|
317
|
+
* ```
|
|
318
|
+
*
|
|
319
|
+
* @example Filter sources
|
|
320
|
+
* ```tsx
|
|
321
|
+
* useUserChangeEffect((e) => {
|
|
322
|
+
* // Only react to passive revalidations that flipped the user to null
|
|
323
|
+
* // (e.g. server returned 401 → session expired silently).
|
|
324
|
+
* if (e.source === 'revalidate' && e.user === null && e.previousUser) {
|
|
325
|
+
* showReLoginPrompt();
|
|
326
|
+
* }
|
|
327
|
+
* });
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
declare function useUserChangeEffect<T extends User = User>(listener: (event: UserChangeEvent<T>) => void): void;
|
|
331
|
+
/**
|
|
332
|
+
* Convenience hook: fires the callback only when the change source matches
|
|
333
|
+
* the given filter. Thin wrapper around {@link useUserChangeEffect}.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```tsx
|
|
337
|
+
* // Only run on explicit login (not on cold-start initial load)
|
|
338
|
+
* useUserChangeOn('login', (e) => router.push('/dashboard'));
|
|
339
|
+
*
|
|
340
|
+
* // Subscribe to multiple sources at once
|
|
341
|
+
* useUserChangeOn(['login', 'external'], (e) => refreshSidebar());
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
declare function useUserChangeOn<T extends User = User>(source: UserChangeSource | UserChangeSource[], listener: (event: UserChangeEvent<T>) => void): void;
|
|
345
|
+
|
|
219
346
|
interface UseLogoutOptions {
|
|
220
347
|
/** Specific plugin to call logout on */
|
|
221
348
|
pluginName?: string;
|
|
@@ -410,6 +537,26 @@ interface AuthGuardProps {
|
|
|
410
537
|
*/
|
|
411
538
|
declare function AuthGuard({ children, permissions, roles, requireAll, fallback, loadingComponent, }: AuthGuardProps): react_jsx_runtime.JSX.Element;
|
|
412
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Mutable hint object shared between the Provider and `useUser()` to describe
|
|
542
|
+
* *why* the next user-change event is expected to happen.
|
|
543
|
+
*
|
|
544
|
+
* The Provider writes into this object as it observes `login` / `logout` /
|
|
545
|
+
* `external` events; `useUser()` reads and clears it when the SWR cache value
|
|
546
|
+
* actually transitions. Unset/expired hint → the change is a passive
|
|
547
|
+
* `revalidate` or the very first `initial` load.
|
|
548
|
+
*
|
|
549
|
+
* Using a mutable ref-like object (instead of React state) avoids re-renders
|
|
550
|
+
* and is safe because it is written synchronously from event callbacks and
|
|
551
|
+
* read synchronously during `useUser()`'s `useEffect`.
|
|
552
|
+
*
|
|
553
|
+
* @internal
|
|
554
|
+
*/
|
|
555
|
+
interface UserChangeHint {
|
|
556
|
+
source: UserChangeSource | null;
|
|
557
|
+
/** `Date.now()` when the hint was written. Stale hints (>1s) are ignored. */
|
|
558
|
+
timestamp: number;
|
|
559
|
+
}
|
|
413
560
|
/** Internal context value passed through SWRLoginProvider */
|
|
414
561
|
interface AuthContextValue {
|
|
415
562
|
pluginManager: PluginManager;
|
|
@@ -418,6 +565,8 @@ interface AuthContextValue {
|
|
|
418
565
|
stateMachine: AuthStateMachine;
|
|
419
566
|
broadcastSync: BroadcastSync | null;
|
|
420
567
|
config: SWRLoginConfig;
|
|
568
|
+
/** @internal shared hint for the next user-change source */
|
|
569
|
+
userChangeHint: UserChangeHint;
|
|
421
570
|
}
|
|
422
571
|
/**
|
|
423
572
|
* Internal hook to access auth context.
|
|
@@ -426,4 +575,4 @@ interface AuthContextValue {
|
|
|
426
575
|
*/
|
|
427
576
|
declare function useAuthContext(): AuthContextValue;
|
|
428
577
|
|
|
429
|
-
export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseAdapterReturn, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UseMultiStepLoginReturn, type UsePermissionReturn, type UseUserReturn, useAdapter, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser };
|
|
578
|
+
export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseAdapterReturn, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UseMultiStepLoginReturn, type UsePermissionReturn, type UseUserReturn, useAdapter, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser, useUserChange, useUserChangeEffect, useUserChangeOn };
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,13 @@ function SWRLoginProvider({ config, children }) {
|
|
|
24
24
|
pluginManager.register(...config.plugins);
|
|
25
25
|
const enableSync = config.security?.enableBroadcastSync !== false;
|
|
26
26
|
const broadcastSync = enableSync && typeof window !== "undefined" ? new BroadcastSync() : null;
|
|
27
|
+
const userChangeHint = { source: null, timestamp: 0 };
|
|
28
|
+
const markHint = (source) => {
|
|
29
|
+
userChangeHint.source = source;
|
|
30
|
+
userChangeHint.timestamp = Date.now();
|
|
31
|
+
};
|
|
32
|
+
emitter.on("login", () => markHint("login"));
|
|
33
|
+
emitter.on("logout", () => markHint("logout"));
|
|
27
34
|
if (config.onLogin) {
|
|
28
35
|
emitter.on("login", ({ user }) => config.onLogin?.(user));
|
|
29
36
|
}
|
|
@@ -39,7 +46,8 @@ function SWRLoginProvider({ config, children }) {
|
|
|
39
46
|
emitter,
|
|
40
47
|
stateMachine,
|
|
41
48
|
broadcastSync,
|
|
42
|
-
config
|
|
49
|
+
config,
|
|
50
|
+
userChangeHint
|
|
43
51
|
};
|
|
44
52
|
}, [config]);
|
|
45
53
|
useEffect(() => {
|
|
@@ -67,15 +75,22 @@ function SWRLoginProvider({ config, children }) {
|
|
|
67
75
|
emitter.emit("token-expired", void 0);
|
|
68
76
|
}
|
|
69
77
|
if (broadcastSync) {
|
|
78
|
+
const { userChangeHint } = contextValue;
|
|
79
|
+
const markExternal = () => {
|
|
80
|
+
userChangeHint.source = "external";
|
|
81
|
+
userChangeHint.timestamp = Date.now();
|
|
82
|
+
};
|
|
70
83
|
const unsubscribe = broadcastSync.onMessage((message) => {
|
|
71
84
|
switch (message.type) {
|
|
72
85
|
case "LOGOUT":
|
|
73
86
|
tokenManager.clearTokens();
|
|
74
87
|
stateMachine.transition("unauthenticated");
|
|
75
88
|
emitter.emit("logout", void 0);
|
|
89
|
+
markExternal();
|
|
76
90
|
break;
|
|
77
91
|
case "LOGIN":
|
|
78
92
|
case "TOKEN_REFRESH":
|
|
93
|
+
markExternal();
|
|
79
94
|
if (cfg.cacheAdapter) {
|
|
80
95
|
cfg.cacheAdapter.revalidate();
|
|
81
96
|
}
|
|
@@ -116,8 +131,9 @@ function SWRLoginProvider({ config, children }) {
|
|
|
116
131
|
return /* @__PURE__ */ jsx(AuthContext.Provider, { value: contextValue, children });
|
|
117
132
|
}
|
|
118
133
|
var AUTH_KEY = "__swr_login_user__";
|
|
134
|
+
var USER_CHANGE_HINT_TTL_MS = 1e3;
|
|
119
135
|
function useUser() {
|
|
120
|
-
const { tokenManager, stateMachine, config } = useAuthContext();
|
|
136
|
+
const { tokenManager, stateMachine, config, emitter, userChangeHint } = useAuthContext();
|
|
121
137
|
const lastErrorRef = useRef(void 0);
|
|
122
138
|
const retryCountRef = useRef(0);
|
|
123
139
|
const [, setTick] = useState(0);
|
|
@@ -126,6 +142,8 @@ function useUser() {
|
|
|
126
142
|
lastErrorRef.current = void 0;
|
|
127
143
|
forceUpdate();
|
|
128
144
|
}, [forceUpdate]);
|
|
145
|
+
const previousUserRef = useRef(void 0);
|
|
146
|
+
const [lastChangeEvent, setLastChangeEvent] = useState(null);
|
|
129
147
|
const fetcher = async () => {
|
|
130
148
|
const token = tokenManager.getAccessToken();
|
|
131
149
|
if (!token) return null;
|
|
@@ -150,10 +168,41 @@ function useUser() {
|
|
|
150
168
|
isLoading,
|
|
151
169
|
mutate: swrMutate
|
|
152
170
|
} = useSWR(AUTH_KEY, fetcher, {
|
|
153
|
-
revalidateOnFocus: true,
|
|
154
|
-
revalidateOnReconnect: true,
|
|
155
|
-
shouldRetryOnError: false
|
|
171
|
+
revalidateOnFocus: config.swrOptions?.revalidateOnFocus ?? true,
|
|
172
|
+
revalidateOnReconnect: config.swrOptions?.revalidateOnReconnect ?? true,
|
|
173
|
+
shouldRetryOnError: false,
|
|
174
|
+
...config.swrOptions?.dedupingInterval !== void 0 && {
|
|
175
|
+
dedupingInterval: config.swrOptions.dedupingInterval
|
|
176
|
+
},
|
|
177
|
+
...config.swrOptions?.focusThrottleInterval !== void 0 && {
|
|
178
|
+
focusThrottleInterval: config.swrOptions.focusThrottleInterval
|
|
179
|
+
},
|
|
180
|
+
...config.swrOptions?.refreshInterval !== void 0 && {
|
|
181
|
+
refreshInterval: config.swrOptions.refreshInterval
|
|
182
|
+
}
|
|
156
183
|
});
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (data === void 0) return;
|
|
186
|
+
const prev = previousUserRef.current;
|
|
187
|
+
if (Object.is(prev, data)) return;
|
|
188
|
+
let source;
|
|
189
|
+
if (prev === void 0) {
|
|
190
|
+
source = "initial";
|
|
191
|
+
} else {
|
|
192
|
+
const now = Date.now();
|
|
193
|
+
const hintFresh = userChangeHint.source !== null && now - userChangeHint.timestamp <= USER_CHANGE_HINT_TTL_MS;
|
|
194
|
+
source = hintFresh ? userChangeHint.source : "revalidate";
|
|
195
|
+
}
|
|
196
|
+
const event = {
|
|
197
|
+
source,
|
|
198
|
+
user: data,
|
|
199
|
+
previousUser: prev,
|
|
200
|
+
timestamp: Date.now()
|
|
201
|
+
};
|
|
202
|
+
previousUserRef.current = data;
|
|
203
|
+
setLastChangeEvent(event);
|
|
204
|
+
emitter.emit("user-change", event);
|
|
205
|
+
}, [data, emitter, userChangeHint]);
|
|
157
206
|
useEffect(() => {
|
|
158
207
|
if (error) {
|
|
159
208
|
lastErrorRef.current = error;
|
|
@@ -190,7 +239,9 @@ function useUser() {
|
|
|
190
239
|
error,
|
|
191
240
|
lastError: lastErrorRef.current,
|
|
192
241
|
clearError,
|
|
193
|
-
mutate: mutate2
|
|
242
|
+
mutate: mutate2,
|
|
243
|
+
lastChangeSource: lastChangeEvent?.source ?? null,
|
|
244
|
+
lastChangeEvent
|
|
194
245
|
};
|
|
195
246
|
}
|
|
196
247
|
|
|
@@ -403,6 +454,40 @@ function useAuthInjector() {
|
|
|
403
454
|
}, [tokenManager, emitter, stateMachine, config]);
|
|
404
455
|
return { injectAuth, injectLogout };
|
|
405
456
|
}
|
|
457
|
+
function useUserChange() {
|
|
458
|
+
const { emitter } = useAuthContext();
|
|
459
|
+
const [event, setEvent] = useState(null);
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
const unsubscribe = emitter.on("user-change", (payload) => {
|
|
462
|
+
setEvent(payload);
|
|
463
|
+
});
|
|
464
|
+
return unsubscribe;
|
|
465
|
+
}, [emitter]);
|
|
466
|
+
return event;
|
|
467
|
+
}
|
|
468
|
+
function useUserChangeEffect(listener) {
|
|
469
|
+
const { emitter } = useAuthContext();
|
|
470
|
+
const listenerRef = useRef(listener);
|
|
471
|
+
listenerRef.current = listener;
|
|
472
|
+
useEffect(() => {
|
|
473
|
+
const unsubscribe = emitter.on("user-change", (payload) => {
|
|
474
|
+
try {
|
|
475
|
+
listenerRef.current(payload);
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.error("[swr-login] Error in useUserChangeEffect listener:", err);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
return unsubscribe;
|
|
481
|
+
}, [emitter]);
|
|
482
|
+
}
|
|
483
|
+
function useUserChangeOn(source, listener) {
|
|
484
|
+
const sources = Array.isArray(source) ? source : [source];
|
|
485
|
+
useUserChangeEffect((event) => {
|
|
486
|
+
if (sources.includes(event.source)) {
|
|
487
|
+
listener(event);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
406
491
|
function useLogout() {
|
|
407
492
|
const { pluginManager, stateMachine, broadcastSync } = useAuthContext();
|
|
408
493
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -537,6 +622,6 @@ function AuthGuard({
|
|
|
537
622
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
538
623
|
}
|
|
539
624
|
|
|
540
|
-
export { AUTH_KEY, AuthGuard, SWRLoginProvider, useAdapter, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser };
|
|
625
|
+
export { AUTH_KEY, AuthGuard, SWRLoginProvider, useAdapter, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser, useUserChange, useUserChangeEffect, useUserChangeOn };
|
|
541
626
|
//# sourceMappingURL=index.js.map
|
|
542
627
|
//# sourceMappingURL=index.js.map
|