@swr-login/react 0.8.0 → 0.9.1

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 CHANGED
@@ -30,6 +30,13 @@ function SWRLoginProvider({ config, children }) {
30
30
  pluginManager.register(...config.plugins);
31
31
  const enableSync = config.security?.enableBroadcastSync !== false;
32
32
  const broadcastSync = enableSync && typeof window !== "undefined" ? new core.BroadcastSync() : null;
33
+ const userChangeHint = { source: null, timestamp: 0 };
34
+ const markHint = (source) => {
35
+ userChangeHint.source = source;
36
+ userChangeHint.timestamp = Date.now();
37
+ };
38
+ emitter.on("login", () => markHint("login"));
39
+ emitter.on("logout", () => markHint("logout"));
33
40
  if (config.onLogin) {
34
41
  emitter.on("login", ({ user }) => config.onLogin?.(user));
35
42
  }
@@ -45,7 +52,8 @@ function SWRLoginProvider({ config, children }) {
45
52
  emitter,
46
53
  stateMachine,
47
54
  broadcastSync,
48
- config
55
+ config,
56
+ userChangeHint
49
57
  };
50
58
  }, [config]);
51
59
  react.useEffect(() => {
@@ -73,15 +81,22 @@ function SWRLoginProvider({ config, children }) {
73
81
  emitter.emit("token-expired", void 0);
74
82
  }
75
83
  if (broadcastSync) {
84
+ const { userChangeHint } = contextValue;
85
+ const markExternal = () => {
86
+ userChangeHint.source = "external";
87
+ userChangeHint.timestamp = Date.now();
88
+ };
76
89
  const unsubscribe = broadcastSync.onMessage((message) => {
77
90
  switch (message.type) {
78
91
  case "LOGOUT":
79
92
  tokenManager.clearTokens();
80
93
  stateMachine.transition("unauthenticated");
81
94
  emitter.emit("logout", void 0);
95
+ markExternal();
82
96
  break;
83
97
  case "LOGIN":
84
98
  case "TOKEN_REFRESH":
99
+ markExternal();
85
100
  if (cfg.cacheAdapter) {
86
101
  cfg.cacheAdapter.revalidate();
87
102
  }
@@ -122,8 +137,9 @@ function SWRLoginProvider({ config, children }) {
122
137
  return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value: contextValue, children });
123
138
  }
124
139
  var AUTH_KEY = "__swr_login_user__";
140
+ var USER_CHANGE_HINT_TTL_MS = 1e3;
125
141
  function useUser() {
126
- const { tokenManager, stateMachine, config } = useAuthContext();
142
+ const { tokenManager, stateMachine, config, emitter, userChangeHint } = useAuthContext();
127
143
  const lastErrorRef = react.useRef(void 0);
128
144
  const retryCountRef = react.useRef(0);
129
145
  const [, setTick] = react.useState(0);
@@ -132,6 +148,8 @@ function useUser() {
132
148
  lastErrorRef.current = void 0;
133
149
  forceUpdate();
134
150
  }, [forceUpdate]);
151
+ const previousUserRef = react.useRef(void 0);
152
+ const [lastChangeEvent, setLastChangeEvent] = react.useState(null);
135
153
  const fetcher = async () => {
136
154
  const token = tokenManager.getAccessToken();
137
155
  if (!token) return null;
@@ -169,6 +187,28 @@ function useUser() {
169
187
  refreshInterval: config.swrOptions.refreshInterval
170
188
  }
171
189
  });
190
+ react.useEffect(() => {
191
+ if (data === void 0) return;
192
+ const prev = previousUserRef.current;
193
+ if (Object.is(prev, data)) return;
194
+ let source;
195
+ if (prev === void 0) {
196
+ source = "initial";
197
+ } else {
198
+ const now = Date.now();
199
+ const hintFresh = userChangeHint.source !== null && now - userChangeHint.timestamp <= USER_CHANGE_HINT_TTL_MS;
200
+ source = hintFresh ? userChangeHint.source : "revalidate";
201
+ }
202
+ const event = {
203
+ source,
204
+ user: data,
205
+ previousUser: prev,
206
+ timestamp: Date.now()
207
+ };
208
+ previousUserRef.current = data;
209
+ setLastChangeEvent(event);
210
+ emitter.emit("user-change", event);
211
+ }, [data, emitter, userChangeHint]);
172
212
  react.useEffect(() => {
173
213
  if (error) {
174
214
  lastErrorRef.current = error;
@@ -205,7 +245,9 @@ function useUser() {
205
245
  error,
206
246
  lastError: lastErrorRef.current,
207
247
  clearError,
208
- mutate: mutate2
248
+ mutate: mutate2,
249
+ lastChangeSource: lastChangeEvent?.source ?? null,
250
+ lastChangeEvent
209
251
  };
210
252
  }
211
253
 
@@ -418,6 +460,40 @@ function useAuthInjector() {
418
460
  }, [tokenManager, emitter, stateMachine, config]);
419
461
  return { injectAuth, injectLogout };
420
462
  }
463
+ function useUserChange() {
464
+ const { emitter } = useAuthContext();
465
+ const [event, setEvent] = react.useState(null);
466
+ react.useEffect(() => {
467
+ const unsubscribe = emitter.on("user-change", (payload) => {
468
+ setEvent(payload);
469
+ });
470
+ return unsubscribe;
471
+ }, [emitter]);
472
+ return event;
473
+ }
474
+ function useUserChangeEffect(listener) {
475
+ const { emitter } = useAuthContext();
476
+ const listenerRef = react.useRef(listener);
477
+ listenerRef.current = listener;
478
+ react.useEffect(() => {
479
+ const unsubscribe = emitter.on("user-change", (payload) => {
480
+ try {
481
+ listenerRef.current(payload);
482
+ } catch (err) {
483
+ console.error("[swr-login] Error in useUserChangeEffect listener:", err);
484
+ }
485
+ });
486
+ return unsubscribe;
487
+ }, [emitter]);
488
+ }
489
+ function useUserChangeOn(source, listener) {
490
+ const sources = Array.isArray(source) ? source : [source];
491
+ useUserChangeEffect((event) => {
492
+ if (sources.includes(event.source)) {
493
+ listener(event);
494
+ }
495
+ });
496
+ }
421
497
  function useLogout() {
422
498
  const { pluginManager, stateMachine, broadcastSync } = useAuthContext();
423
499
  const [isLoading, setIsLoading] = react.useState(false);
@@ -564,5 +640,8 @@ exports.useMultiStepLogin = useMultiStepLogin;
564
640
  exports.usePermission = usePermission;
565
641
  exports.useSession = useSession;
566
642
  exports.useUser = useUser;
643
+ exports.useUserChange = useUserChange;
644
+ exports.useUserChangeEffect = useUserChangeEffect;
645
+ exports.useUserChangeOn = useUserChangeOn;
567
646
  //# sourceMappingURL=index.cjs.map
568
647
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useUser.ts","../src/hooks/useLogin.ts","../src/hooks/useMultiStepLogin.ts","../src/hooks/useAuthInjector.ts","../src/hooks/useLogout.ts","../src/hooks/useAdapter.ts","../src/hooks/useSession.ts","../src/hooks/usePermission.ts","../src/components/AuthGuard.tsx"],"names":["createContext","useContext","useRef","useMemo","AuthEventEmitter","AuthStateMachine","TokenManager","PluginManager","BroadcastSync","useEffect","useState","useCallback","useSWR","mutate","isMultiStepPlugin","swrGlobalMutate","isTokenExpired","jsx","Fragment"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n AuthEventEmitter,\n AuthStateMachine,\n BroadcastSync,\n PluginManager,\n SWRLoginConfig,\n TokenManager,\n} from '@swr-login/core';\nimport { createContext, useContext } from 'react';\n\n/** Internal context value passed through SWRLoginProvider */\nexport interface AuthContextValue {\n pluginManager: PluginManager;\n tokenManager: TokenManager;\n emitter: AuthEventEmitter;\n stateMachine: AuthStateMachine;\n broadcastSync: BroadcastSync | null;\n config: SWRLoginConfig;\n}\n\nexport const AuthContext = createContext<AuthContextValue | null>(null);\n\n/**\n * Internal hook to access auth context.\n * Must be used within SWRLoginProvider.\n * @throws Error if used outside of SWRLoginProvider\n */\nexport function useAuthContext(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\n '[swr-login] useAuthContext must be used within <SWRLoginProvider>. ' +\n 'Wrap your app with <SWRLoginProvider> to use swr-login hooks.',\n );\n }\n return ctx;\n}\n","import {\n AuthEventEmitter,\n AuthStateMachine,\n BroadcastSync,\n PluginManager,\n type SWRLoginConfig,\n TokenManager,\n} from '@swr-login/core';\nimport type React from 'react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { AuthContext, type AuthContextValue } from './context';\n\nexport interface SWRLoginProviderProps {\n /** Authentication configuration */\n config: SWRLoginConfig;\n children: React.ReactNode;\n}\n\n/**\n * SWRLoginProvider initializes the auth system and provides context to all child hooks.\n *\n * @example\n * ```tsx\n * import { SWRLoginProvider } from '@swr-login/react';\n * import { JWTAdapter } from '@swr-login/adapter-jwt';\n * import { PasswordPlugin } from '@swr-login/plugin-password';\n *\n * function App() {\n * return (\n * <SWRLoginProvider\n * config={{\n * adapter: JWTAdapter(),\n * plugins: [PasswordPlugin({ loginUrl: '/api/login' })],\n * }}\n * >\n * <YourApp />\n * </SWRLoginProvider>\n * );\n * }\n * ```\n */\nexport function SWRLoginProvider({ config, children }: SWRLoginProviderProps) {\n const initializedRef = useRef(false);\n\n const contextValue = useMemo<AuthContextValue>(() => {\n const emitter = new AuthEventEmitter();\n const stateMachine = new AuthStateMachine(emitter);\n const tokenManager = new TokenManager(config.adapter, emitter, stateMachine);\n const pluginManager = new PluginManager(tokenManager, emitter);\n\n // Register plugins\n pluginManager.register(...config.plugins);\n\n // Set up broadcast sync if enabled\n const enableSync = config.security?.enableBroadcastSync !== false;\n const broadcastSync = enableSync && typeof window !== 'undefined' ? new BroadcastSync() : null;\n\n // Wire up lifecycle callbacks\n if (config.onLogin) {\n emitter.on('login', ({ user }) => config.onLogin?.(user));\n }\n if (config.onLogout) {\n emitter.on('logout', () => config.onLogout?.());\n }\n if (config.onError) {\n emitter.on('error', ({ error }) => config.onError?.(error));\n }\n\n return {\n pluginManager,\n tokenManager,\n emitter,\n stateMachine,\n broadcastSync,\n config,\n };\n }, [config]);\n\n // Initialize plugins and broadcast sync on mount\n useEffect(() => {\n if (initializedRef.current) return;\n initializedRef.current = true;\n\n const {\n pluginManager,\n broadcastSync,\n tokenManager,\n stateMachine,\n emitter,\n config: cfg,\n } = contextValue;\n\n // Initialize all plugins\n pluginManager.initializeAll().catch((err) => {\n console.error('[swr-login] Plugin initialization error:', err);\n });\n\n // Check if user has existing token -> restore session\n const existingToken = tokenManager.getAccessToken();\n const expiresAt = tokenManager.getExpiresAt();\n\n if (existingToken && !tokenManager.isExpired()) {\n stateMachine.transition('authenticated');\n } else if (existingToken && expiresAt === null) {\n // expiresAt 未知(如外部登录只设置了 token 但未设置过期时间)\n // 乐观地认为已认证,让 fetchUser / SWR revalidate 来验证\n stateMachine.transition('authenticated');\n } else if (existingToken && tokenManager.isExpired()) {\n stateMachine.transition('unauthenticated');\n emitter.emit('token-expired', undefined);\n }\n\n // Listen for cross-tab events\n if (broadcastSync) {\n const unsubscribe = broadcastSync.onMessage((message) => {\n switch (message.type) {\n case 'LOGOUT':\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n emitter.emit('logout', undefined);\n break;\n case 'LOGIN':\n case 'TOKEN_REFRESH':\n // Trigger revalidation from other tab\n if (cfg.cacheAdapter) {\n cfg.cacheAdapter.revalidate();\n }\n break;\n }\n });\n\n return () => {\n unsubscribe();\n broadcastSync.destroy();\n };\n }\n }, [contextValue]);\n\n // Visibility change handler for security\n useEffect(() => {\n const { config: cfg, tokenManager, stateMachine, emitter } = contextValue;\n\n if (!cfg.security?.clearOnHidden || typeof document === 'undefined') return;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const handleVisibilityChange = () => {\n if (document.hidden) {\n const delay = cfg.security?.clearOnHiddenDelay ?? 300_000;\n timeoutId = setTimeout(() => {\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n emitter.emit('logout', undefined);\n }, delay);\n } else {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n };\n\n document.addEventListener('visibilitychange', handleVisibilityChange);\n return () => {\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [contextValue]);\n\n return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;\n}\n","import type { User } from '@swr-login/core';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport useSWR from 'swr';\nimport { useAuthContext } from '../context';\n\nconst AUTH_KEY = '__swr_login_user__';\n\nexport interface UseUserReturn<T extends User = User> {\n /** Current authenticated user, or null if not logged in */\n user: T | null;\n /** Whether user data is being fetched */\n isLoading: boolean;\n /** Whether user is authenticated */\n isAuthenticated: boolean;\n /** Error from fetching user data */\n error: Error | undefined;\n /**\n * Sticky error from the last `fetchUser` failure.\n *\n * Unlike `error`, this value is **not** cleared when the auth state\n * transitions (e.g. from `authenticated` → `unauthenticated`).\n * It persists until `fetchUser` succeeds again or `clearError()` is called.\n *\n * Useful for distinguishing \"not logged in\" from \"account disabled\" in\n * AuthGuard or error-boundary components.\n */\n lastError: Error | undefined;\n /**\n * Manually reset `lastError` to `undefined`.\n */\n clearError: () => void;\n /**\n * Manually update cached user data.\n * Call with `null` to clear, or with a user object to update.\n */\n mutate: (data?: T | null) => Promise<void>;\n}\n\n/**\n * Hook to access current user data via SWR cache.\n *\n * Uses SWR's stale-while-revalidate strategy:\n * - Immediately returns cached user data\n * - Revalidates in the background using the configured fetchUser function\n * - Automatically syncs across all components using this hook\n *\n * @example\n * ```tsx\n * const { user, isLoading, isAuthenticated } = useUser<MyUser>();\n *\n * if (isLoading) return <Spinner />;\n * if (!isAuthenticated) return <LoginPage />;\n * return <Dashboard user={user} />;\n * ```\n */\nexport function useUser<T extends User = User>(): UseUserReturn<T> {\n const { tokenManager, stateMachine, config } = useAuthContext();\n\n // ── lastError 状态管理 ──────────────────────────────────────\n const lastErrorRef = useRef<Error | undefined>(undefined);\n const retryCountRef = useRef(0);\n const [, setTick] = useState(0);\n const forceUpdate = useCallback(() => setTick((t) => t + 1), []);\n\n const clearError = useCallback(() => {\n lastErrorRef.current = undefined;\n forceUpdate();\n }, [forceUpdate]);\n\n const fetcher = async (): Promise<T | null> => {\n const token = tokenManager.getAccessToken();\n if (!token) return null;\n\n if (tokenManager.isExpired()) {\n try {\n await tokenManager.refresh();\n } catch {\n // refresh 失败,但 token 可能仍然有效(如 cookie-based 场景)\n // 如果 token 已被清除则放弃,否则继续尝试 fetchUser 让服务端验证\n const remainingToken = tokenManager.getAccessToken();\n if (!remainingToken) return null;\n }\n }\n\n if (config.fetchUser) {\n const currentToken = tokenManager.getAccessToken();\n if (!currentToken) return null;\n return (await config.fetchUser(currentToken)) as T;\n }\n\n return null;\n };\n\n const {\n data,\n error,\n isLoading,\n mutate: swrMutate,\n } = useSWR<T | null>(AUTH_KEY, fetcher, {\n revalidateOnFocus: config.swrOptions?.revalidateOnFocus ?? true,\n revalidateOnReconnect: config.swrOptions?.revalidateOnReconnect ?? true,\n shouldRetryOnError: false,\n ...(config.swrOptions?.dedupingInterval !== undefined && {\n dedupingInterval: config.swrOptions.dedupingInterval,\n }),\n ...(config.swrOptions?.focusThrottleInterval !== undefined && {\n focusThrottleInterval: config.swrOptions.focusThrottleInterval,\n }),\n ...(config.swrOptions?.refreshInterval !== undefined && {\n refreshInterval: config.swrOptions.refreshInterval,\n }),\n });\n\n // ── 同步 lastError + onFetchUserError 回调 ─────────────────\n useEffect(() => {\n if (error) {\n lastErrorRef.current = error;\n forceUpdate();\n\n // 调用 onFetchUserError 回调(如果已配置)\n if (config.onFetchUserError) {\n const action = config.onFetchUserError(error);\n\n if (action === 'retry' && retryCountRef.current < 1) {\n retryCountRef.current += 1;\n // 触发 SWR 重新请求\n swrMutate();\n } else if (action === 'logout') {\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n }\n // 'ignore' → 仅保留 lastError,不做额外操作\n }\n } else if (data !== undefined && !error) {\n // fetchUser 成功(data 可以是 null 或 user),重置 lastError 和重试计数\n if (lastErrorRef.current !== undefined) {\n lastErrorRef.current = undefined;\n forceUpdate();\n }\n retryCountRef.current = 0;\n }\n }, [error, data, forceUpdate, config, tokenManager, stateMachine, swrMutate]);\n\n const mutate = async (newData?: T | null): Promise<void> => {\n if (newData === undefined) {\n await swrMutate();\n } else {\n await swrMutate(newData, { revalidate: false });\n }\n };\n\n return {\n user: data ?? null,\n isLoading,\n isAuthenticated: !!data,\n error,\n lastError: lastErrorRef.current,\n clearError,\n mutate,\n };\n}\n\n/** SWR cache key used for user data. Exported for advanced usage. */\nexport { AUTH_KEY };\n","import type { AuthResponse } from '@swr-login/core';\nimport { isMultiStepPlugin } from '@swr-login/core';\nimport { useCallback, useState } from 'react';\nimport { mutate as swrGlobalMutate } from 'swr';\nimport { useAuthContext } from '../context';\nimport { AUTH_KEY } from './useUser';\n\nexport interface UseLoginOptions {\n /** Plugin name to use for login */\n pluginName?: string;\n}\n\nexport interface UseLoginReturn<TCredentials = unknown> {\n /**\n * Trigger login with specified credentials.\n * If pluginName was not provided in options, it must be passed as first argument.\n */\n login: (\n credentialsOrPluginName: TCredentials | string,\n credentials?: TCredentials,\n ) => Promise<AuthResponse>;\n /** Whether a login request is in progress */\n isLoading: boolean;\n /** Last login error, if any */\n error: Error | null;\n /** Reset error state */\n reset: () => void;\n}\n\n/**\n * Hook to trigger login flow via a registered plugin.\n *\n * @param pluginName - Optional default plugin name\n *\n * @example\n * ```tsx\n * // With default plugin\n * const { login, isLoading, error } = useLogin('password');\n * await login({ username: 'alice', password: 'secret' });\n *\n * // Without default plugin (specify at call time)\n * const { login } = useLogin();\n * await login('oauth-google', { redirect: false });\n * ```\n */\nexport function useLogin<TCredentials = unknown>(\n pluginName?: string,\n): UseLoginReturn<TCredentials> {\n const { pluginManager, tokenManager, stateMachine, config } = useAuthContext();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const login = useCallback(\n async (\n credentialsOrPluginName: TCredentials | string,\n maybeCredentials?: TCredentials,\n ): Promise<AuthResponse> => {\n let resolvedPlugin: string;\n let resolvedCredentials: TCredentials;\n\n if (typeof credentialsOrPluginName === 'string' && !pluginName) {\n resolvedPlugin = credentialsOrPluginName;\n resolvedCredentials = (maybeCredentials ?? {}) as TCredentials;\n } else if (pluginName) {\n resolvedPlugin = pluginName;\n resolvedCredentials = credentialsOrPluginName as TCredentials;\n } else {\n throw new Error(\n '[swr-login] Plugin name is required. Provide it to useLogin() or login().',\n );\n }\n\n setIsLoading(true);\n setError(null);\n stateMachine.transition('authenticating');\n\n // 检查是否为多步骤插件,给出友好错误提示\n const targetPlugin = pluginManager.getPlugin(resolvedPlugin);\n if (targetPlugin && isMultiStepPlugin(targetPlugin)) {\n const err = new Error(\n `[swr-login] Plugin \"${resolvedPlugin}\" is a multi-step plugin. Use useMultiStepLogin() instead of useLogin() for multi-step login flows.`,\n );\n setError(err);\n stateMachine.transition('error');\n setIsLoading(false);\n throw err;\n }\n\n try {\n const response = await pluginManager.login(resolvedPlugin, resolvedCredentials);\n\n // ── afterAuth:在 plugin 成功后、fetchUser 之前执行自定义钩子 ──\n let shouldSkipFetchUser = false;\n if (config.afterAuth) {\n try {\n await config.afterAuth({\n pluginName: resolvedPlugin,\n authResponse: response,\n skipFetchUser: () => {\n shouldSkipFetchUser = true;\n },\n });\n } catch (afterAuthErr) {\n // afterAuth 抛错:回滚 token,login() reject\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n throw afterAuthErr;\n }\n }\n\n // ── validateUserOnLogin:在 plugin 成功后调用 fetchUser 验证用户状态 ──\n if (!shouldSkipFetchUser && config.fetchUser && config.validateUserOnLogin !== false) {\n try {\n const user = await config.fetchUser(response.accessToken);\n // 将 fetchUser 返回的用户写入 SWR 缓存,避免 useUser 重复请求\n await swrGlobalMutate(AUTH_KEY, user, { revalidate: false });\n } catch (fetchUserErr) {\n // fetchUser 失败:回滚 token,转为 unauthenticated\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n throw fetchUserErr;\n }\n }\n\n stateMachine.transition('authenticated');\n\n // Update cache adapter if available\n if (config.cacheAdapter) {\n await config.cacheAdapter.setUser(response.user);\n }\n\n return response;\n } catch (err) {\n const authError = err instanceof Error ? err : new Error('Login failed');\n setError(authError);\n stateMachine.transition('error');\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [pluginManager, tokenManager, stateMachine, config, pluginName],\n );\n\n const reset = useCallback(() => {\n setError(null);\n }, []);\n\n return { login, isLoading, error, reset };\n}\n","import type { AuthResponse } from '@swr-login/core';\nimport { useCallback, useRef, useState } from 'react';\nimport { useAuthContext } from '../context';\n\nexport interface UseMultiStepLoginReturn<TCredentials = unknown> {\n /** 当前步骤索引(从 0 开始,-1 表示未开始) */\n currentStep: number;\n /** 当前步骤名称(未开始时为 null) */\n currentStepName: string | null;\n /** 总步骤数 */\n totalSteps: number;\n /** 启动流程(执行第一步) */\n start: (credentials: TCredentials) => Promise<void>;\n /** 继续下一步 */\n next: (input: unknown) => Promise<void>;\n /** 回到上一步(仅 UI 层回退,不重新执行步骤逻辑) */\n back: () => void;\n /** 取消整个流程,重置状态 */\n cancel: () => void;\n /** 当前步骤的输出数据(传给 UI 渲染) */\n stepData: unknown;\n /** 是否正在执行当前步骤 */\n isLoading: boolean;\n /** 当前步骤的错误 */\n error: Error | null;\n /** 重置错误状态 */\n reset: () => void;\n /** 流程是否已完成 */\n isComplete: boolean;\n /** 最终的 AuthResponse(流程完成后可用) */\n authResponse: AuthResponse | null;\n}\n\n/**\n * Hook 用于驱动多步骤登录插件的流程。\n *\n * 提供步骤状态管理、前进/后退/取消等操作,自动在最后一步完成后\n * 调用 finalizeAuth 并更新全局登录态。\n *\n * @param pluginName - 多步骤插件名称\n *\n * @example\n * ```tsx\n * function ClassCodeLoginFlow() {\n * const {\n * currentStepName, stepData, start, next, back, isLoading, error, isComplete,\n * } = useMultiStepLogin<{ classCode: string; loginCode: string }>('class-code');\n *\n * if (isComplete) {\n * return <Redirect to=\"/dashboard\" />;\n * }\n *\n * // 步骤 1:输入班级码\n * if (currentStepName === 'verify-code' || currentStep === -1) {\n * return <ClassCodeForm onSubmit={start} isLoading={isLoading} error={error} />;\n * }\n *\n * // 步骤 2:选择学生\n * if (currentStepName === 'select-student') {\n * return (\n * <StudentList\n * students={stepData.students}\n * onSelect={(userId) => next({ userId, classLoginToken: stepData.classLoginToken })}\n * onBack={back}\n * isLoading={isLoading}\n * />\n * );\n * }\n *\n * // 步骤 3:获取 Token(自动执行,显示 loading)\n * if (currentStepName === 'get-token') {\n * return <LoadingSpinner text=\"登录中...\" />;\n * }\n * }\n * ```\n */\nexport function useMultiStepLogin<TCredentials = unknown>(\n pluginName: string,\n): UseMultiStepLoginReturn<TCredentials> {\n const { pluginManager, stateMachine, config } = useAuthContext();\n\n const [currentStep, setCurrentStep] = useState(-1); // -1 表示未开始\n const [stepData, setStepData] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [isComplete, setIsComplete] = useState(false);\n const [authResponse, setAuthResponse] = useState<AuthResponse | null>(null);\n\n // 用 ref 保存中间步骤的累积数据,避免闭包问题\n const stepOutputsRef = useRef<unknown[]>([]);\n\n const steps = pluginManager.getSteps(pluginName);\n const totalSteps = steps?.length ?? 0;\n\n const currentStepName = currentStep >= 0 && steps ? (steps[currentStep]?.name ?? null) : null;\n\n const executeStepAt = useCallback(\n async (stepIndex: number, input: unknown) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const output = await pluginManager.executeStep(pluginName, stepIndex, input);\n stepOutputsRef.current[stepIndex] = output;\n setStepData(output);\n setCurrentStep(stepIndex);\n\n // 如果是最后一步,执行 finalizeAuth\n if (stepIndex === totalSteps - 1) {\n const response = await pluginManager.finalizeMultiStep(pluginName, output);\n stateMachine.transition('authenticated');\n\n if (config.cacheAdapter) {\n await config.cacheAdapter.setUser(response.user);\n }\n\n setAuthResponse(response);\n setIsComplete(true);\n }\n } catch (err) {\n const authError = err instanceof Error ? err : new Error('Step execution failed');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [pluginManager, pluginName, totalSteps, stateMachine, config],\n );\n\n const start = useCallback(\n async (credentials: TCredentials) => {\n // 重置状态\n stepOutputsRef.current = [];\n setCurrentStep(-1);\n setStepData(null);\n setIsComplete(false);\n setAuthResponse(null);\n setError(null);\n stateMachine.transition('authenticating');\n\n await executeStepAt(0, credentials);\n },\n [executeStepAt, stateMachine],\n );\n\n const next = useCallback(\n async (input: unknown) => {\n const nextIndex = currentStep + 1;\n if (nextIndex >= totalSteps) {\n throw new Error('[swr-login] No more steps to execute.');\n }\n await executeStepAt(nextIndex, input);\n },\n [currentStep, totalSteps, executeStepAt],\n );\n\n const back = useCallback(() => {\n if (currentStep > 0) {\n const prevStep = currentStep - 1;\n setCurrentStep(prevStep);\n setStepData(stepOutputsRef.current[prevStep] ?? null);\n setError(null);\n }\n }, [currentStep]);\n\n const cancel = useCallback(() => {\n setCurrentStep(-1);\n setStepData(null);\n setIsLoading(false);\n setError(null);\n setIsComplete(false);\n setAuthResponse(null);\n stepOutputsRef.current = [];\n stateMachine.transition('unauthenticated');\n }, [stateMachine]);\n\n const reset = useCallback(() => {\n setError(null);\n }, []);\n\n return {\n currentStep,\n currentStepName,\n totalSteps,\n start,\n next,\n back,\n cancel,\n stepData,\n isLoading,\n error,\n reset,\n isComplete,\n authResponse,\n };\n}\n","import type { AuthInjector, AuthResponse } from '@swr-login/core';\nimport { useCallback } from 'react';\nimport { useAuthContext } from '../context';\n\n/**\n * Hook 用于从外部注入登录态到 swr-login 体系。\n *\n * 适用场景:\n * - 多步骤登录流程(如班级码登录、MFA)\n * - 第三方 SDK 登录完成后同步状态\n * - iframe / WebView 登录回调\n *\n * @example\n * ```tsx\n * function ExternalLoginCallback() {\n * const { injectAuth } = useAuthInjector();\n *\n * const handleLoginComplete = async (token: string, user: User) => {\n * await injectAuth({\n * user,\n * accessToken: token,\n * expiresAt: Date.now() + 86400000,\n * });\n * router.push('/dashboard');\n * };\n * }\n * ```\n */\nexport function useAuthInjector(): AuthInjector {\n const { tokenManager, emitter, stateMachine, config } = useAuthContext();\n\n const injectAuth = useCallback(\n async (response: AuthResponse): Promise<void> => {\n // 1. 存储 token\n tokenManager.setTokens({\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n expiresAt: response.expiresAt,\n });\n\n // 2. 状态机转换为已认证\n stateMachine.transition('authenticated');\n\n // 3. 发射登录事件(触发 onLogin 回调、跨标签页同步等)\n emitter.emit('login', { user: response.user });\n\n // 4. 更新缓存(使 useUser() 等 Hook 感知到用户信息)\n if (config.cacheAdapter) {\n await config.cacheAdapter.setUser(response.user);\n }\n },\n [tokenManager, emitter, stateMachine, config],\n );\n\n const injectLogout = useCallback(async (): Promise<void> => {\n // 1. 清除 token\n tokenManager.clearTokens();\n\n // 2. 状态机转换为未认证\n stateMachine.transition('unauthenticated');\n\n // 3. 发射登出事件\n emitter.emit('logout', undefined);\n\n // 4. 清除缓存\n if (config.cacheAdapter) {\n await config.cacheAdapter.clearUser();\n }\n }, [tokenManager, emitter, stateMachine, config]);\n\n return { injectAuth, injectLogout };\n}\n","import { useCallback, useState } from 'react';\nimport { mutate } from 'swr';\nimport { useAuthContext } from '../context';\nimport { AUTH_KEY } from './useUser';\n\nexport interface UseLogoutOptions {\n /** Specific plugin to call logout on */\n pluginName?: string;\n /** Whether to broadcast logout to other tabs (default: true) */\n broadcast?: boolean;\n}\n\nexport interface UseLogoutReturn {\n /** Execute logout */\n logout: (options?: UseLogoutOptions) => Promise<void>;\n /** Whether logout is in progress */\n isLoading: boolean;\n}\n\n/**\n * Hook to perform secure logout with cross-tab sync.\n *\n * Logout flow:\n * 1. Call plugin logout (if applicable)\n * 2. Clear stored tokens\n * 3. Clear SWR cache (mutate user to null)\n * 4. Broadcast logout to other tabs\n * 5. Transition state to 'unauthenticated'\n *\n * @example\n * ```tsx\n * const { logout, isLoading } = useLogout();\n *\n * <button onClick={() => logout()} disabled={isLoading}>\n * Sign Out\n * </button>\n * ```\n */\nexport function useLogout(): UseLogoutReturn {\n const { pluginManager, stateMachine, broadcastSync } = useAuthContext();\n const [isLoading, setIsLoading] = useState(false);\n\n const logout = useCallback(\n async (options?: UseLogoutOptions) => {\n setIsLoading(true);\n\n try {\n // Call plugin logout\n await pluginManager.logout(options?.pluginName);\n\n // Clear SWR cache\n await mutate(AUTH_KEY, null, { revalidate: false });\n\n // Update state\n stateMachine.transition('unauthenticated');\n\n // Broadcast to other tabs\n const shouldBroadcast = options?.broadcast !== false;\n if (shouldBroadcast && broadcastSync) {\n broadcastSync.send('LOGOUT');\n }\n } catch (err) {\n console.error('[swr-login] Logout error:', err);\n // Even if logout API fails, clear local state\n await mutate(AUTH_KEY, null, { revalidate: false });\n stateMachine.transition('unauthenticated');\n } finally {\n setIsLoading(false);\n }\n },\n [pluginManager, stateMachine, broadcastSync],\n );\n\n return { logout, isLoading };\n}\n","import type { TokenAdapter } from '@swr-login/core';\nimport { useCallback } from 'react';\nimport { useAuthContext } from '../context';\n\nexport interface UseAdapterReturn {\n /**\n * Synchronously check whether the adapter currently holds a valid auth credential.\n *\n * Uses `adapter.hasAuth()` if the adapter implements it,\n * otherwise falls back to `adapter.getAccessToken() !== null`.\n *\n * This is a **synchronous, non-reactive** check — it reads the adapter's\n * current state at call time. It does NOT trigger re-renders when the\n * underlying token changes. For reactive auth state, use `useUser()`.\n *\n * Typical use cases:\n * - Conditional rendering before SWR hydration completes\n * - Route guard pre-checks\n * - Analytics / logging\n *\n * @example\n * ```tsx\n * const { hasAuth } = useAdapter();\n *\n * // 首页自动跳转\n * useEffect(() => {\n * if (hasAuth()) {\n * router.push('/dashboard');\n * }\n * }, []);\n * ```\n */\n hasAuth: () => boolean;\n\n /**\n * Direct reference to the underlying `TokenAdapter` instance.\n *\n * Escape hatch for advanced scenarios where you need low-level\n * access to token storage (e.g. reading refresh token, manually\n * clearing tokens outside of the normal logout flow).\n *\n * Prefer `hasAuth()` and the higher-level hooks (`useUser`, `useLogin`)\n * for most use cases.\n */\n adapter: TokenAdapter;\n}\n\n/**\n * Hook to access the token adapter and its utility methods.\n *\n * Provides a convenient `hasAuth()` check that works synchronously,\n * useful for UI decisions that need to happen before SWR data is available.\n *\n * @example\n * ```tsx\n * function HomePage() {\n * const { hasAuth } = useAdapter();\n * const router = useRouter();\n *\n * useEffect(() => {\n * if (hasAuth()) {\n * router.push('/dashboard');\n * }\n * }, []);\n *\n * return <LoginPage />;\n * }\n * ```\n */\nexport function useAdapter(): UseAdapterReturn {\n const { config } = useAuthContext();\n const adapter = config.adapter;\n\n const hasAuth = useCallback((): boolean => {\n if (typeof adapter.hasAuth === 'function') {\n return adapter.hasAuth();\n }\n // Fallback: 检查 accessToken 是否存在\n return adapter.getAccessToken() !== null;\n }, [adapter]);\n\n return { hasAuth, adapter };\n}\n","import { isTokenExpired } from '@swr-login/core';\nimport { useMemo } from 'react';\nimport { useAuthContext } from '../context';\n\nexport interface SessionInfo {\n /** Current access token */\n accessToken: string | null;\n /** Current refresh token */\n refreshToken: string | null;\n /** Token expiration timestamp (ms since epoch) */\n expiresAt: number | null;\n /** Whether the access token has expired */\n isExpired: boolean;\n /** Whether tokens exist (regardless of expiry) */\n hasTokens: boolean;\n}\n\n/**\n * Hook to access raw session/token information.\n *\n * Useful for:\n * - Adding Authorization headers to custom requests\n * - Checking token expiry status\n * - Debugging session state\n *\n * @example\n * ```tsx\n * const { accessToken, isExpired } = useSession();\n *\n * const fetchData = () => fetch('/api/data', {\n * headers: { Authorization: `Bearer ${accessToken}` },\n * });\n * ```\n */\nexport function useSession(): SessionInfo {\n const { tokenManager } = useAuthContext();\n\n return useMemo(() => {\n const accessToken = tokenManager.getAccessToken();\n const refreshToken = tokenManager.getRefreshToken();\n const expiresAt = tokenManager.getAccessToken() ? tokenManager.getExpiresAt() : null;\n\n return {\n accessToken,\n refreshToken,\n expiresAt,\n isExpired: isTokenExpired(expiresAt),\n hasTokens: !!accessToken,\n };\n }, [tokenManager]);\n}\n","import { useCallback } from 'react';\nimport { useUser } from './useUser';\n\nexport interface UsePermissionReturn {\n /** Check if user has a specific permission */\n hasPermission: (permission: string) => boolean;\n /** Check if user has a specific role */\n hasRole: (role: string) => boolean;\n /** Check if user has all specified permissions */\n hasAllPermissions: (permissions: string[]) => boolean;\n /** Check if user has any of the specified permissions */\n hasAnyPermission: (permissions: string[]) => boolean;\n /** Check if user has all specified roles */\n hasAllRoles: (roles: string[]) => boolean;\n /** Check if user has any of the specified roles */\n hasAnyRole: (roles: string[]) => boolean;\n}\n\n/**\n * Hook for checking user permissions and roles.\n *\n * Reads from the `permissions` and `roles` arrays on the User object.\n *\n * @example\n * ```tsx\n * const { hasPermission, hasRole } = usePermission();\n *\n * if (hasPermission('admin:write')) {\n * // Show admin controls\n * }\n *\n * if (hasRole('editor')) {\n * // Show editor tools\n * }\n * ```\n */\nexport function usePermission(): UsePermissionReturn {\n const { user } = useUser();\n\n const hasPermission = useCallback(\n (permission: string): boolean => {\n return user?.permissions?.includes(permission) ?? false;\n },\n [user],\n );\n\n const hasRole = useCallback(\n (role: string): boolean => {\n return user?.roles?.includes(role) ?? false;\n },\n [user],\n );\n\n const hasAllPermissions = useCallback(\n (permissions: string[]): boolean => {\n if (!user?.permissions) return false;\n return permissions.every((p) => user.permissions?.includes(p));\n },\n [user],\n );\n\n const hasAnyPermission = useCallback(\n (permissions: string[]): boolean => {\n if (!user?.permissions) return false;\n return permissions.some((p) => user.permissions?.includes(p));\n },\n [user],\n );\n\n const hasAllRoles = useCallback(\n (roles: string[]): boolean => {\n if (!user?.roles) return false;\n return roles.every((r) => user.roles?.includes(r));\n },\n [user],\n );\n\n const hasAnyRole = useCallback(\n (roles: string[]): boolean => {\n if (!user?.roles) return false;\n return roles.some((r) => user.roles?.includes(r));\n },\n [user],\n );\n\n return {\n hasPermission,\n hasRole,\n hasAllPermissions,\n hasAnyPermission,\n hasAllRoles,\n hasAnyRole,\n };\n}\n","import type React from 'react';\nimport { usePermission } from '../hooks/usePermission';\nimport { useUser } from '../hooks/useUser';\n\nexport interface AuthGuardProps {\n children: React.ReactNode;\n /** Required permissions (checked against user.permissions) */\n permissions?: string[];\n /** Required roles (checked against user.roles) */\n roles?: string[];\n /** If true, ALL permissions/roles must match. If false, ANY match suffices. (default: false) */\n requireAll?: boolean;\n /** Component to render when user is not authenticated or lacks permissions */\n fallback?: React.ReactNode;\n /** Component to render while checking auth status */\n loadingComponent?: React.ReactNode;\n}\n\n/**\n * Declarative auth guard component.\n * Protects child content based on authentication state and permissions/roles.\n *\n * @example\n * ```tsx\n * <AuthGuard\n * permissions={['admin', 'editor']}\n * requireAll={false}\n * fallback={<Navigate to=\"/login\" />}\n * loadingComponent={<Skeleton />}\n * >\n * <AdminPanel />\n * </AuthGuard>\n * ```\n */\nexport function AuthGuard({\n children,\n permissions,\n roles,\n requireAll = false,\n fallback = null,\n loadingComponent = null,\n}: AuthGuardProps) {\n const { isLoading, isAuthenticated } = useUser();\n const { hasAllPermissions, hasAnyPermission, hasAllRoles, hasAnyRole } = usePermission();\n\n if (isLoading) {\n return <>{loadingComponent}</>;\n }\n\n if (!isAuthenticated) {\n return <>{fallback}</>;\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n const hasRequiredPermissions = requireAll\n ? hasAllPermissions(permissions)\n : hasAnyPermission(permissions);\n\n if (!hasRequiredPermissions) {\n return <>{fallback}</>;\n }\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n const hasRequiredRoles = requireAll ? hasAllRoles(roles) : hasAnyRole(roles);\n\n if (!hasRequiredRoles) {\n return <>{fallback}</>;\n }\n }\n\n return <>{children}</>;\n}\n"]}
1
+ {"version":3,"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useUser.ts","../src/hooks/useLogin.ts","../src/hooks/useMultiStepLogin.ts","../src/hooks/useAuthInjector.ts","../src/hooks/useUserChange.ts","../src/hooks/useLogout.ts","../src/hooks/useAdapter.ts","../src/hooks/useSession.ts","../src/hooks/usePermission.ts","../src/components/AuthGuard.tsx"],"names":["createContext","useContext","useRef","useMemo","AuthEventEmitter","AuthStateMachine","TokenManager","PluginManager","BroadcastSync","useEffect","useState","useCallback","useSWR","mutate","isMultiStepPlugin","swrGlobalMutate","isTokenExpired","jsx","Fragment"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n AuthEventEmitter,\n AuthStateMachine,\n BroadcastSync,\n PluginManager,\n SWRLoginConfig,\n TokenManager,\n UserChangeSource,\n} from '@swr-login/core';\nimport { createContext, useContext } from 'react';\n\n/**\n * Mutable hint object shared between the Provider and `useUser()` to describe\n * *why* the next user-change event is expected to happen.\n *\n * The Provider writes into this object as it observes `login` / `logout` /\n * `external` events; `useUser()` reads and clears it when the SWR cache value\n * actually transitions. Unset/expired hint → the change is a passive\n * `revalidate` or the very first `initial` load.\n *\n * Using a mutable ref-like object (instead of React state) avoids re-renders\n * and is safe because it is written synchronously from event callbacks and\n * read synchronously during `useUser()`'s `useEffect`.\n *\n * @internal\n */\nexport interface UserChangeHint {\n source: UserChangeSource | null;\n /** `Date.now()` when the hint was written. Stale hints (>1s) are ignored. */\n timestamp: number;\n}\n\n/** Internal context value passed through SWRLoginProvider */\nexport interface AuthContextValue {\n pluginManager: PluginManager;\n tokenManager: TokenManager;\n emitter: AuthEventEmitter;\n stateMachine: AuthStateMachine;\n broadcastSync: BroadcastSync | null;\n config: SWRLoginConfig;\n /** @internal shared hint for the next user-change source */\n userChangeHint: UserChangeHint;\n}\n\nexport const AuthContext = createContext<AuthContextValue | null>(null);\n\n/**\n * Internal hook to access auth context.\n * Must be used within SWRLoginProvider.\n * @throws Error if used outside of SWRLoginProvider\n */\nexport function useAuthContext(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\n '[swr-login] useAuthContext must be used within <SWRLoginProvider>. ' +\n 'Wrap your app with <SWRLoginProvider> to use swr-login hooks.',\n );\n }\n return ctx;\n}\n","import {\n AuthEventEmitter,\n AuthStateMachine,\n BroadcastSync,\n PluginManager,\n type SWRLoginConfig,\n TokenManager,\n} from '@swr-login/core';\nimport type React from 'react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { AuthContext, type AuthContextValue, type UserChangeHint } from './context';\n\nexport interface SWRLoginProviderProps {\n /** Authentication configuration */\n config: SWRLoginConfig;\n children: React.ReactNode;\n}\n\n/**\n * SWRLoginProvider initializes the auth system and provides context to all child hooks.\n *\n * @example\n * ```tsx\n * import { SWRLoginProvider } from '@swr-login/react';\n * import { JWTAdapter } from '@swr-login/adapter-jwt';\n * import { PasswordPlugin } from '@swr-login/plugin-password';\n *\n * function App() {\n * return (\n * <SWRLoginProvider\n * config={{\n * adapter: JWTAdapter(),\n * plugins: [PasswordPlugin({ loginUrl: '/api/login' })],\n * }}\n * >\n * <YourApp />\n * </SWRLoginProvider>\n * );\n * }\n * ```\n */\nexport function SWRLoginProvider({ config, children }: SWRLoginProviderProps) {\n const initializedRef = useRef(false);\n\n const contextValue = useMemo<AuthContextValue>(() => {\n const emitter = new AuthEventEmitter();\n const stateMachine = new AuthStateMachine(emitter);\n const tokenManager = new TokenManager(config.adapter, emitter, stateMachine);\n const pluginManager = new PluginManager(tokenManager, emitter);\n\n // Register plugins\n pluginManager.register(...config.plugins);\n\n // Set up broadcast sync if enabled\n const enableSync = config.security?.enableBroadcastSync !== false;\n const broadcastSync = enableSync && typeof window !== 'undefined' ? new BroadcastSync() : null;\n\n // Mutable hint object shared with useUser() to label the next user-change\n // event with its causal source (login/logout/external).\n const userChangeHint: UserChangeHint = { source: null, timestamp: 0 };\n const markHint = (source: UserChangeHint['source']) => {\n userChangeHint.source = source;\n userChangeHint.timestamp = Date.now();\n };\n\n // Label user-change source based on lifecycle events. The actual\n // 'user-change' event is emitted by useUser() once SWR produces a\n // different value; these hints only tell us *why* it's about to change.\n emitter.on('login', () => markHint('login'));\n emitter.on('logout', () => markHint('logout'));\n\n // Wire up lifecycle callbacks\n if (config.onLogin) {\n emitter.on('login', ({ user }) => config.onLogin?.(user));\n }\n if (config.onLogout) {\n emitter.on('logout', () => config.onLogout?.());\n }\n if (config.onError) {\n emitter.on('error', ({ error }) => config.onError?.(error));\n }\n\n return {\n pluginManager,\n tokenManager,\n emitter,\n stateMachine,\n broadcastSync,\n config,\n userChangeHint,\n };\n }, [config]);\n\n // Initialize plugins and broadcast sync on mount\n useEffect(() => {\n if (initializedRef.current) return;\n initializedRef.current = true;\n\n const {\n pluginManager,\n broadcastSync,\n tokenManager,\n stateMachine,\n emitter,\n config: cfg,\n } = contextValue;\n\n // Initialize all plugins\n pluginManager.initializeAll().catch((err) => {\n console.error('[swr-login] Plugin initialization error:', err);\n });\n\n // Check if user has existing token -> restore session\n const existingToken = tokenManager.getAccessToken();\n const expiresAt = tokenManager.getExpiresAt();\n\n if (existingToken && !tokenManager.isExpired()) {\n stateMachine.transition('authenticated');\n } else if (existingToken && expiresAt === null) {\n // expiresAt 未知(如外部登录只设置了 token 但未设置过期时间)\n // 乐观地认为已认证,让 fetchUser / SWR revalidate 来验证\n stateMachine.transition('authenticated');\n } else if (existingToken && tokenManager.isExpired()) {\n stateMachine.transition('unauthenticated');\n emitter.emit('token-expired', undefined);\n }\n\n // Listen for cross-tab events\n if (broadcastSync) {\n const { userChangeHint } = contextValue;\n const markExternal = () => {\n userChangeHint.source = 'external';\n userChangeHint.timestamp = Date.now();\n };\n\n const unsubscribe = broadcastSync.onMessage((message) => {\n switch (message.type) {\n case 'LOGOUT':\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n emitter.emit('logout', undefined);\n // Emit-after-mark would be overwritten by the synchronous\n // 'logout' handler (which sets hint='logout'); re-mark to\n // 'external' so useUser labels this cross-tab logout correctly.\n markExternal();\n break;\n case 'LOGIN':\n case 'TOKEN_REFRESH':\n // Cross-tab login / refresh → refresh SWR cache; label the\n // upcoming user-change as 'external'.\n markExternal();\n if (cfg.cacheAdapter) {\n cfg.cacheAdapter.revalidate();\n }\n break;\n }\n });\n\n return () => {\n unsubscribe();\n broadcastSync.destroy();\n };\n }\n }, [contextValue]);\n\n // Visibility change handler for security\n useEffect(() => {\n const { config: cfg, tokenManager, stateMachine, emitter } = contextValue;\n\n if (!cfg.security?.clearOnHidden || typeof document === 'undefined') return;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const handleVisibilityChange = () => {\n if (document.hidden) {\n const delay = cfg.security?.clearOnHiddenDelay ?? 300_000;\n timeoutId = setTimeout(() => {\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n emitter.emit('logout', undefined);\n }, delay);\n } else {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n };\n\n document.addEventListener('visibilitychange', handleVisibilityChange);\n return () => {\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [contextValue]);\n\n return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;\n}\n","import type { User, UserChangeEvent, UserChangeSource } from '@swr-login/core';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport useSWR from 'swr';\nimport { useAuthContext } from '../context';\n\nconst AUTH_KEY = '__swr_login_user__';\n\n/**\n * Time window (ms) during which a hint written by the Provider remains\n * valid. If SWR produces a change later than this after the hint was\n * written, we treat it as a passive `revalidate` instead.\n */\nconst USER_CHANGE_HINT_TTL_MS = 1000;\n\nexport interface UseUserReturn<T extends User = User> {\n /** Current authenticated user, or null if not logged in */\n user: T | null;\n /** Whether user data is being fetched */\n isLoading: boolean;\n /** Whether user is authenticated */\n isAuthenticated: boolean;\n /** Error from fetching user data */\n error: Error | undefined;\n /**\n * Sticky error from the last `fetchUser` failure.\n *\n * Unlike `error`, this value is **not** cleared when the auth state\n * transitions (e.g. from `authenticated` → `unauthenticated`).\n * It persists until `fetchUser` succeeds again or `clearError()` is called.\n *\n * Useful for distinguishing \"not logged in\" from \"account disabled\" in\n * AuthGuard or error-boundary components.\n */\n lastError: Error | undefined;\n /**\n * Manually reset `lastError` to `undefined`.\n */\n clearError: () => void;\n /**\n * Manually update cached user data.\n * Call with `null` to clear, or with a user object to update.\n */\n mutate: (data?: T | null) => Promise<void>;\n /**\n * Why the most recent `user` transition happened.\n *\n * - `null` — No user-change has been observed yet in this component\n * (e.g. before the first `fetchUser` resolves on mount).\n * - `'initial'` — First time the Provider observed a user value\n * (including `null` for unauthenticated cold start).\n * - `'login'` — Triggered by an explicit `login()` / multi-step finalize\n * / `injectAuth()` call.\n * - `'logout'` — Triggered by an explicit `logout()` / `injectLogout()`.\n * - `'revalidate'` — SWR background revalidation produced a different user.\n * - `'external'` — Cross-tab sync via BroadcastChannel / storage events.\n *\n * Use this to differentiate user-initiated transitions from passive ones,\n * e.g. to suppress a welcome toast on page refresh.\n */\n lastChangeSource: UserChangeSource | null;\n /**\n * Full event object for the most recent `user` transition, including\n * `previousUser` and `timestamp`. `null` before the first transition.\n *\n * See `lastChangeSource` for a quick discriminator.\n */\n lastChangeEvent: UserChangeEvent<T> | null;\n}\n\n/**\n * Hook to access current user data via SWR cache.\n *\n * Uses SWR's stale-while-revalidate strategy:\n * - Immediately returns cached user data\n * - Revalidates in the background using the configured fetchUser function\n * - Automatically syncs across all components using this hook\n *\n * @example\n * ```tsx\n * const { user, isLoading, isAuthenticated } = useUser<MyUser>();\n *\n * if (isLoading) return <Spinner />;\n * if (!isAuthenticated) return <LoginPage />;\n * return <Dashboard user={user} />;\n * ```\n *\n * @example Distinguish user-change sources\n * ```tsx\n * const { user, lastChangeSource } = useUser();\n *\n * useEffect(() => {\n * // Only auto-redirect when we detect an *existing* session on mount,\n * // not when the user just pressed the \"Login\" button (the login form\n * // is responsible for its own redirect there).\n * if (user && lastChangeSource === 'initial') {\n * showRedirectOverlay();\n * }\n * }, [user, lastChangeSource]);\n * ```\n */\nexport function useUser<T extends User = User>(): UseUserReturn<T> {\n const { tokenManager, stateMachine, config, emitter, userChangeHint } = useAuthContext();\n\n // ── lastError 状态管理 ──────────────────────────────────────\n const lastErrorRef = useRef<Error | undefined>(undefined);\n const retryCountRef = useRef(0);\n const [, setTick] = useState(0);\n const forceUpdate = useCallback(() => setTick((t) => t + 1), []);\n\n const clearError = useCallback(() => {\n lastErrorRef.current = undefined;\n forceUpdate();\n }, [forceUpdate]);\n\n // ── user-change 事件:previousUser 跟踪 + lastChangeSource 状态 ──\n // Use a sentinel (`undefined`) to mean \"never observed a value yet\",\n // separate from `null` which means \"observed and was unauthenticated\".\n const previousUserRef = useRef<T | null | undefined>(undefined);\n const [lastChangeEvent, setLastChangeEvent] = useState<UserChangeEvent<T> | null>(null);\n\n const fetcher = async (): Promise<T | null> => {\n const token = tokenManager.getAccessToken();\n if (!token) return null;\n\n if (tokenManager.isExpired()) {\n try {\n await tokenManager.refresh();\n } catch {\n // refresh 失败,但 token 可能仍然有效(如 cookie-based 场景)\n // 如果 token 已被清除则放弃,否则继续尝试 fetchUser 让服务端验证\n const remainingToken = tokenManager.getAccessToken();\n if (!remainingToken) return null;\n }\n }\n\n if (config.fetchUser) {\n const currentToken = tokenManager.getAccessToken();\n if (!currentToken) return null;\n return (await config.fetchUser(currentToken)) as T;\n }\n\n return null;\n };\n\n const {\n data,\n error,\n isLoading,\n mutate: swrMutate,\n } = useSWR<T | null>(AUTH_KEY, fetcher, {\n revalidateOnFocus: config.swrOptions?.revalidateOnFocus ?? true,\n revalidateOnReconnect: config.swrOptions?.revalidateOnReconnect ?? true,\n shouldRetryOnError: false,\n ...(config.swrOptions?.dedupingInterval !== undefined && {\n dedupingInterval: config.swrOptions.dedupingInterval,\n }),\n ...(config.swrOptions?.focusThrottleInterval !== undefined && {\n focusThrottleInterval: config.swrOptions.focusThrottleInterval,\n }),\n ...(config.swrOptions?.refreshInterval !== undefined && {\n refreshInterval: config.swrOptions.refreshInterval,\n }),\n });\n\n // ── user-change 派发(每当 SWR 的 data 稳定为 user/null 时触发一次)──\n useEffect(() => {\n // SWR 尚未完成首次解析:data === undefined → 跳过\n if (data === undefined) return;\n\n const prev = previousUserRef.current;\n // 值未变化(含对象引用 + null → null)→ 不派发\n if (Object.is(prev, data)) return;\n\n // 选择 source:\n // 1) 首次(prev === undefined)→ 'initial'\n // 2) Provider 近期标记了 hint(TTL 内)→ 使用 hint.source\n // 3) 其余一律视为被动 revalidate\n let source: UserChangeSource;\n if (prev === undefined) {\n source = 'initial';\n } else {\n const now = Date.now();\n const hintFresh =\n userChangeHint.source !== null && now - userChangeHint.timestamp <= USER_CHANGE_HINT_TTL_MS;\n source = hintFresh ? (userChangeHint.source as UserChangeSource) : 'revalidate';\n }\n\n // NOTE: We intentionally do NOT clear the hint here. Multiple useUser()\n // consumers may each observe the same SWR data change within the same\n // tick; all of them should see the same source. The hint will naturally\n // expire via TTL before the next unrelated SWR revalidate arrives.\n\n const event: UserChangeEvent<T> = {\n source,\n user: data,\n previousUser: prev,\n timestamp: Date.now(),\n };\n\n previousUserRef.current = data;\n setLastChangeEvent(event);\n emitter.emit('user-change', event as UserChangeEvent);\n }, [data, emitter, userChangeHint]);\n\n // ── 同步 lastError + onFetchUserError 回调 ─────────────────\n useEffect(() => {\n if (error) {\n lastErrorRef.current = error;\n forceUpdate();\n\n // 调用 onFetchUserError 回调(如果已配置)\n if (config.onFetchUserError) {\n const action = config.onFetchUserError(error);\n\n if (action === 'retry' && retryCountRef.current < 1) {\n retryCountRef.current += 1;\n // 触发 SWR 重新请求\n swrMutate();\n } else if (action === 'logout') {\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n }\n // 'ignore' → 仅保留 lastError,不做额外操作\n }\n } else if (data !== undefined && !error) {\n // fetchUser 成功(data 可以是 null 或 user),重置 lastError 和重试计数\n if (lastErrorRef.current !== undefined) {\n lastErrorRef.current = undefined;\n forceUpdate();\n }\n retryCountRef.current = 0;\n }\n }, [error, data, forceUpdate, config, tokenManager, stateMachine, swrMutate]);\n\n const mutate = async (newData?: T | null): Promise<void> => {\n if (newData === undefined) {\n await swrMutate();\n } else {\n await swrMutate(newData, { revalidate: false });\n }\n };\n\n return {\n user: data ?? null,\n isLoading,\n isAuthenticated: !!data,\n error,\n lastError: lastErrorRef.current,\n clearError,\n mutate,\n lastChangeSource: lastChangeEvent?.source ?? null,\n lastChangeEvent,\n };\n}\n\n/** SWR cache key used for user data. Exported for advanced usage. */\nexport { AUTH_KEY };\n","import type { AuthResponse } from '@swr-login/core';\nimport { isMultiStepPlugin } from '@swr-login/core';\nimport { useCallback, useState } from 'react';\nimport { mutate as swrGlobalMutate } from 'swr';\nimport { useAuthContext } from '../context';\nimport { AUTH_KEY } from './useUser';\n\nexport interface UseLoginOptions {\n /** Plugin name to use for login */\n pluginName?: string;\n}\n\nexport interface UseLoginReturn<TCredentials = unknown> {\n /**\n * Trigger login with specified credentials.\n * If pluginName was not provided in options, it must be passed as first argument.\n */\n login: (\n credentialsOrPluginName: TCredentials | string,\n credentials?: TCredentials,\n ) => Promise<AuthResponse>;\n /** Whether a login request is in progress */\n isLoading: boolean;\n /** Last login error, if any */\n error: Error | null;\n /** Reset error state */\n reset: () => void;\n}\n\n/**\n * Hook to trigger login flow via a registered plugin.\n *\n * @param pluginName - Optional default plugin name\n *\n * @example\n * ```tsx\n * // With default plugin\n * const { login, isLoading, error } = useLogin('password');\n * await login({ username: 'alice', password: 'secret' });\n *\n * // Without default plugin (specify at call time)\n * const { login } = useLogin();\n * await login('oauth-google', { redirect: false });\n * ```\n */\nexport function useLogin<TCredentials = unknown>(\n pluginName?: string,\n): UseLoginReturn<TCredentials> {\n const { pluginManager, tokenManager, stateMachine, config } = useAuthContext();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const login = useCallback(\n async (\n credentialsOrPluginName: TCredentials | string,\n maybeCredentials?: TCredentials,\n ): Promise<AuthResponse> => {\n let resolvedPlugin: string;\n let resolvedCredentials: TCredentials;\n\n if (typeof credentialsOrPluginName === 'string' && !pluginName) {\n resolvedPlugin = credentialsOrPluginName;\n resolvedCredentials = (maybeCredentials ?? {}) as TCredentials;\n } else if (pluginName) {\n resolvedPlugin = pluginName;\n resolvedCredentials = credentialsOrPluginName as TCredentials;\n } else {\n throw new Error(\n '[swr-login] Plugin name is required. Provide it to useLogin() or login().',\n );\n }\n\n setIsLoading(true);\n setError(null);\n stateMachine.transition('authenticating');\n\n // 检查是否为多步骤插件,给出友好错误提示\n const targetPlugin = pluginManager.getPlugin(resolvedPlugin);\n if (targetPlugin && isMultiStepPlugin(targetPlugin)) {\n const err = new Error(\n `[swr-login] Plugin \"${resolvedPlugin}\" is a multi-step plugin. Use useMultiStepLogin() instead of useLogin() for multi-step login flows.`,\n );\n setError(err);\n stateMachine.transition('error');\n setIsLoading(false);\n throw err;\n }\n\n try {\n const response = await pluginManager.login(resolvedPlugin, resolvedCredentials);\n\n // ── afterAuth:在 plugin 成功后、fetchUser 之前执行自定义钩子 ──\n let shouldSkipFetchUser = false;\n if (config.afterAuth) {\n try {\n await config.afterAuth({\n pluginName: resolvedPlugin,\n authResponse: response,\n skipFetchUser: () => {\n shouldSkipFetchUser = true;\n },\n });\n } catch (afterAuthErr) {\n // afterAuth 抛错:回滚 token,login() reject\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n throw afterAuthErr;\n }\n }\n\n // ── validateUserOnLogin:在 plugin 成功后调用 fetchUser 验证用户状态 ──\n if (!shouldSkipFetchUser && config.fetchUser && config.validateUserOnLogin !== false) {\n try {\n const user = await config.fetchUser(response.accessToken);\n // 将 fetchUser 返回的用户写入 SWR 缓存,避免 useUser 重复请求\n await swrGlobalMutate(AUTH_KEY, user, { revalidate: false });\n } catch (fetchUserErr) {\n // fetchUser 失败:回滚 token,转为 unauthenticated\n tokenManager.clearTokens();\n stateMachine.transition('unauthenticated');\n throw fetchUserErr;\n }\n }\n\n stateMachine.transition('authenticated');\n\n // Update cache adapter if available\n if (config.cacheAdapter) {\n await config.cacheAdapter.setUser(response.user);\n }\n\n return response;\n } catch (err) {\n const authError = err instanceof Error ? err : new Error('Login failed');\n setError(authError);\n stateMachine.transition('error');\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [pluginManager, tokenManager, stateMachine, config, pluginName],\n );\n\n const reset = useCallback(() => {\n setError(null);\n }, []);\n\n return { login, isLoading, error, reset };\n}\n","import type { AuthResponse } from '@swr-login/core';\nimport { useCallback, useRef, useState } from 'react';\nimport { useAuthContext } from '../context';\n\nexport interface UseMultiStepLoginReturn<TCredentials = unknown> {\n /** 当前步骤索引(从 0 开始,-1 表示未开始) */\n currentStep: number;\n /** 当前步骤名称(未开始时为 null) */\n currentStepName: string | null;\n /** 总步骤数 */\n totalSteps: number;\n /** 启动流程(执行第一步) */\n start: (credentials: TCredentials) => Promise<void>;\n /** 继续下一步 */\n next: (input: unknown) => Promise<void>;\n /** 回到上一步(仅 UI 层回退,不重新执行步骤逻辑) */\n back: () => void;\n /** 取消整个流程,重置状态 */\n cancel: () => void;\n /** 当前步骤的输出数据(传给 UI 渲染) */\n stepData: unknown;\n /** 是否正在执行当前步骤 */\n isLoading: boolean;\n /** 当前步骤的错误 */\n error: Error | null;\n /** 重置错误状态 */\n reset: () => void;\n /** 流程是否已完成 */\n isComplete: boolean;\n /** 最终的 AuthResponse(流程完成后可用) */\n authResponse: AuthResponse | null;\n}\n\n/**\n * Hook 用于驱动多步骤登录插件的流程。\n *\n * 提供步骤状态管理、前进/后退/取消等操作,自动在最后一步完成后\n * 调用 finalizeAuth 并更新全局登录态。\n *\n * @param pluginName - 多步骤插件名称\n *\n * @example\n * ```tsx\n * function ClassCodeLoginFlow() {\n * const {\n * currentStepName, stepData, start, next, back, isLoading, error, isComplete,\n * } = useMultiStepLogin<{ classCode: string; loginCode: string }>('class-code');\n *\n * if (isComplete) {\n * return <Redirect to=\"/dashboard\" />;\n * }\n *\n * // 步骤 1:输入班级码\n * if (currentStepName === 'verify-code' || currentStep === -1) {\n * return <ClassCodeForm onSubmit={start} isLoading={isLoading} error={error} />;\n * }\n *\n * // 步骤 2:选择学生\n * if (currentStepName === 'select-student') {\n * return (\n * <StudentList\n * students={stepData.students}\n * onSelect={(userId) => next({ userId, classLoginToken: stepData.classLoginToken })}\n * onBack={back}\n * isLoading={isLoading}\n * />\n * );\n * }\n *\n * // 步骤 3:获取 Token(自动执行,显示 loading)\n * if (currentStepName === 'get-token') {\n * return <LoadingSpinner text=\"登录中...\" />;\n * }\n * }\n * ```\n */\nexport function useMultiStepLogin<TCredentials = unknown>(\n pluginName: string,\n): UseMultiStepLoginReturn<TCredentials> {\n const { pluginManager, stateMachine, config } = useAuthContext();\n\n const [currentStep, setCurrentStep] = useState(-1); // -1 表示未开始\n const [stepData, setStepData] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [isComplete, setIsComplete] = useState(false);\n const [authResponse, setAuthResponse] = useState<AuthResponse | null>(null);\n\n // 用 ref 保存中间步骤的累积数据,避免闭包问题\n const stepOutputsRef = useRef<unknown[]>([]);\n\n const steps = pluginManager.getSteps(pluginName);\n const totalSteps = steps?.length ?? 0;\n\n const currentStepName = currentStep >= 0 && steps ? (steps[currentStep]?.name ?? null) : null;\n\n const executeStepAt = useCallback(\n async (stepIndex: number, input: unknown) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const output = await pluginManager.executeStep(pluginName, stepIndex, input);\n stepOutputsRef.current[stepIndex] = output;\n setStepData(output);\n setCurrentStep(stepIndex);\n\n // 如果是最后一步,执行 finalizeAuth\n if (stepIndex === totalSteps - 1) {\n const response = await pluginManager.finalizeMultiStep(pluginName, output);\n stateMachine.transition('authenticated');\n\n if (config.cacheAdapter) {\n await config.cacheAdapter.setUser(response.user);\n }\n\n setAuthResponse(response);\n setIsComplete(true);\n }\n } catch (err) {\n const authError = err instanceof Error ? err : new Error('Step execution failed');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [pluginManager, pluginName, totalSteps, stateMachine, config],\n );\n\n const start = useCallback(\n async (credentials: TCredentials) => {\n // 重置状态\n stepOutputsRef.current = [];\n setCurrentStep(-1);\n setStepData(null);\n setIsComplete(false);\n setAuthResponse(null);\n setError(null);\n stateMachine.transition('authenticating');\n\n await executeStepAt(0, credentials);\n },\n [executeStepAt, stateMachine],\n );\n\n const next = useCallback(\n async (input: unknown) => {\n const nextIndex = currentStep + 1;\n if (nextIndex >= totalSteps) {\n throw new Error('[swr-login] No more steps to execute.');\n }\n await executeStepAt(nextIndex, input);\n },\n [currentStep, totalSteps, executeStepAt],\n );\n\n const back = useCallback(() => {\n if (currentStep > 0) {\n const prevStep = currentStep - 1;\n setCurrentStep(prevStep);\n setStepData(stepOutputsRef.current[prevStep] ?? null);\n setError(null);\n }\n }, [currentStep]);\n\n const cancel = useCallback(() => {\n setCurrentStep(-1);\n setStepData(null);\n setIsLoading(false);\n setError(null);\n setIsComplete(false);\n setAuthResponse(null);\n stepOutputsRef.current = [];\n stateMachine.transition('unauthenticated');\n }, [stateMachine]);\n\n const reset = useCallback(() => {\n setError(null);\n }, []);\n\n return {\n currentStep,\n currentStepName,\n totalSteps,\n start,\n next,\n back,\n cancel,\n stepData,\n isLoading,\n error,\n reset,\n isComplete,\n authResponse,\n };\n}\n","import type { AuthInjector, AuthResponse } from '@swr-login/core';\nimport { useCallback } from 'react';\nimport { useAuthContext } from '../context';\n\n/**\n * Hook 用于从外部注入登录态到 swr-login 体系。\n *\n * 适用场景:\n * - 多步骤登录流程(如班级码登录、MFA)\n * - 第三方 SDK 登录完成后同步状态\n * - iframe / WebView 登录回调\n *\n * @example\n * ```tsx\n * function ExternalLoginCallback() {\n * const { injectAuth } = useAuthInjector();\n *\n * const handleLoginComplete = async (token: string, user: User) => {\n * await injectAuth({\n * user,\n * accessToken: token,\n * expiresAt: Date.now() + 86400000,\n * });\n * router.push('/dashboard');\n * };\n * }\n * ```\n */\nexport function useAuthInjector(): AuthInjector {\n const { tokenManager, emitter, stateMachine, config } = useAuthContext();\n\n const injectAuth = useCallback(\n async (response: AuthResponse): Promise<void> => {\n // 1. 存储 token\n tokenManager.setTokens({\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n expiresAt: response.expiresAt,\n });\n\n // 2. 状态机转换为已认证\n stateMachine.transition('authenticated');\n\n // 3. 发射登录事件(触发 onLogin 回调、跨标签页同步等)\n emitter.emit('login', { user: response.user });\n\n // 4. 更新缓存(使 useUser() 等 Hook 感知到用户信息)\n if (config.cacheAdapter) {\n await config.cacheAdapter.setUser(response.user);\n }\n },\n [tokenManager, emitter, stateMachine, config],\n );\n\n const injectLogout = useCallback(async (): Promise<void> => {\n // 1. 清除 token\n tokenManager.clearTokens();\n\n // 2. 状态机转换为未认证\n stateMachine.transition('unauthenticated');\n\n // 3. 发射登出事件\n emitter.emit('logout', undefined);\n\n // 4. 清除缓存\n if (config.cacheAdapter) {\n await config.cacheAdapter.clearUser();\n }\n }, [tokenManager, emitter, stateMachine, config]);\n\n return { injectAuth, injectLogout };\n}\n","import type { User, UserChangeEvent, UserChangeSource } from '@swr-login/core';\nimport { useEffect, useRef, useState } from 'react';\nimport { useAuthContext } from '../context';\n\n/**\n * Subscribe to `user-change` events as a **discrete event stream**.\n *\n * Unlike `useUser().user` (which reflects the *current* user value), this\n * hook returns the most recent transition event and re-renders the caller\n * only when a new transition occurs. It's intended for `useEffect`-driven\n * side effects that care about *when* and *why* the user changed rather\n * than the current user value itself.\n *\n * Returns `null` until the first transition is observed by the Provider.\n *\n * @typeParam T - Concrete user type (defaults to base `User`)\n *\n * @example\n * ```tsx\n * const change = useUserChange<MyUser>();\n *\n * useEffect(() => {\n * if (change?.source === 'login') {\n * toast.success(`Welcome back, ${change.user?.name}!`);\n * }\n * }, [change]);\n * ```\n *\n * @example Filter by source\n * ```tsx\n * const change = useUserChange();\n * useEffect(() => {\n * if (change?.source === 'external') {\n * // Another tab just logged in / out — refresh local UI\n * refreshSidebar();\n * }\n * }, [change]);\n * ```\n */\nexport function useUserChange<T extends User = User>(): UserChangeEvent<T> | null {\n const { emitter } = useAuthContext();\n const [event, setEvent] = useState<UserChangeEvent<T> | null>(null);\n\n useEffect(() => {\n const unsubscribe = emitter.on('user-change', (payload) => {\n setEvent(payload as UserChangeEvent<T>);\n });\n return unsubscribe;\n }, [emitter]);\n\n return event;\n}\n\n/**\n * Register a side-effect callback that fires on every `user-change` event,\n * **without** triggering a re-render of the calling component.\n *\n * Ideal for analytics / logging / imperative side effects that don't need\n * to participate in React's render cycle.\n *\n * The callback receives the full `UserChangeEvent` (including `previousUser`,\n * `timestamp`, and `source`). The listener is automatically unsubscribed on\n * unmount.\n *\n * Note: the callback reference is stored in a ref and always called with\n * the latest closure — you do NOT need to memoize it with `useCallback`.\n *\n * @example Analytics\n * ```tsx\n * useUserChangeEffect((e) => {\n * if (e.source === 'login') {\n * analytics.track('user_login', { userId: e.user?.id });\n * }\n * if (e.source === 'logout') {\n * analytics.track('user_logout', { userId: e.previousUser?.id });\n * }\n * });\n * ```\n *\n * @example Filter sources\n * ```tsx\n * useUserChangeEffect((e) => {\n * // Only react to passive revalidations that flipped the user to null\n * // (e.g. server returned 401 → session expired silently).\n * if (e.source === 'revalidate' && e.user === null && e.previousUser) {\n * showReLoginPrompt();\n * }\n * });\n * ```\n */\nexport function useUserChangeEffect<T extends User = User>(\n listener: (event: UserChangeEvent<T>) => void,\n): void {\n const { emitter } = useAuthContext();\n\n // Store latest callback in a ref so consumers don't need useCallback.\n const listenerRef = useRef(listener);\n listenerRef.current = listener;\n\n useEffect(() => {\n const unsubscribe = emitter.on('user-change', (payload) => {\n try {\n listenerRef.current(payload as UserChangeEvent<T>);\n } catch (err) {\n console.error('[swr-login] Error in useUserChangeEffect listener:', err);\n }\n });\n return unsubscribe;\n }, [emitter]);\n}\n\n/**\n * Convenience hook: fires the callback only when the change source matches\n * the given filter. Thin wrapper around {@link useUserChangeEffect}.\n *\n * @example\n * ```tsx\n * // Only run on explicit login (not on cold-start initial load)\n * useUserChangeOn('login', (e) => router.push('/dashboard'));\n *\n * // Subscribe to multiple sources at once\n * useUserChangeOn(['login', 'external'], (e) => refreshSidebar());\n * ```\n */\nexport function useUserChangeOn<T extends User = User>(\n source: UserChangeSource | UserChangeSource[],\n listener: (event: UserChangeEvent<T>) => void,\n): void {\n const sources = Array.isArray(source) ? source : [source];\n useUserChangeEffect<T>((event) => {\n if (sources.includes(event.source)) {\n listener(event);\n }\n });\n}\n","import { useCallback, useState } from 'react';\nimport { mutate } from 'swr';\nimport { useAuthContext } from '../context';\nimport { AUTH_KEY } from './useUser';\n\nexport interface UseLogoutOptions {\n /** Specific plugin to call logout on */\n pluginName?: string;\n /** Whether to broadcast logout to other tabs (default: true) */\n broadcast?: boolean;\n}\n\nexport interface UseLogoutReturn {\n /** Execute logout */\n logout: (options?: UseLogoutOptions) => Promise<void>;\n /** Whether logout is in progress */\n isLoading: boolean;\n}\n\n/**\n * Hook to perform secure logout with cross-tab sync.\n *\n * Logout flow:\n * 1. Call plugin logout (if applicable)\n * 2. Clear stored tokens\n * 3. Clear SWR cache (mutate user to null)\n * 4. Broadcast logout to other tabs\n * 5. Transition state to 'unauthenticated'\n *\n * @example\n * ```tsx\n * const { logout, isLoading } = useLogout();\n *\n * <button onClick={() => logout()} disabled={isLoading}>\n * Sign Out\n * </button>\n * ```\n */\nexport function useLogout(): UseLogoutReturn {\n const { pluginManager, stateMachine, broadcastSync } = useAuthContext();\n const [isLoading, setIsLoading] = useState(false);\n\n const logout = useCallback(\n async (options?: UseLogoutOptions) => {\n setIsLoading(true);\n\n try {\n // Call plugin logout\n await pluginManager.logout(options?.pluginName);\n\n // Clear SWR cache\n await mutate(AUTH_KEY, null, { revalidate: false });\n\n // Update state\n stateMachine.transition('unauthenticated');\n\n // Broadcast to other tabs\n const shouldBroadcast = options?.broadcast !== false;\n if (shouldBroadcast && broadcastSync) {\n broadcastSync.send('LOGOUT');\n }\n } catch (err) {\n console.error('[swr-login] Logout error:', err);\n // Even if logout API fails, clear local state\n await mutate(AUTH_KEY, null, { revalidate: false });\n stateMachine.transition('unauthenticated');\n } finally {\n setIsLoading(false);\n }\n },\n [pluginManager, stateMachine, broadcastSync],\n );\n\n return { logout, isLoading };\n}\n","import type { TokenAdapter } from '@swr-login/core';\nimport { useCallback } from 'react';\nimport { useAuthContext } from '../context';\n\nexport interface UseAdapterReturn {\n /**\n * Synchronously check whether the adapter currently holds a valid auth credential.\n *\n * Uses `adapter.hasAuth()` if the adapter implements it,\n * otherwise falls back to `adapter.getAccessToken() !== null`.\n *\n * This is a **synchronous, non-reactive** check — it reads the adapter's\n * current state at call time. It does NOT trigger re-renders when the\n * underlying token changes. For reactive auth state, use `useUser()`.\n *\n * Typical use cases:\n * - Conditional rendering before SWR hydration completes\n * - Route guard pre-checks\n * - Analytics / logging\n *\n * @example\n * ```tsx\n * const { hasAuth } = useAdapter();\n *\n * // 首页自动跳转\n * useEffect(() => {\n * if (hasAuth()) {\n * router.push('/dashboard');\n * }\n * }, []);\n * ```\n */\n hasAuth: () => boolean;\n\n /**\n * Direct reference to the underlying `TokenAdapter` instance.\n *\n * Escape hatch for advanced scenarios where you need low-level\n * access to token storage (e.g. reading refresh token, manually\n * clearing tokens outside of the normal logout flow).\n *\n * Prefer `hasAuth()` and the higher-level hooks (`useUser`, `useLogin`)\n * for most use cases.\n */\n adapter: TokenAdapter;\n}\n\n/**\n * Hook to access the token adapter and its utility methods.\n *\n * Provides a convenient `hasAuth()` check that works synchronously,\n * useful for UI decisions that need to happen before SWR data is available.\n *\n * @example\n * ```tsx\n * function HomePage() {\n * const { hasAuth } = useAdapter();\n * const router = useRouter();\n *\n * useEffect(() => {\n * if (hasAuth()) {\n * router.push('/dashboard');\n * }\n * }, []);\n *\n * return <LoginPage />;\n * }\n * ```\n */\nexport function useAdapter(): UseAdapterReturn {\n const { config } = useAuthContext();\n const adapter = config.adapter;\n\n const hasAuth = useCallback((): boolean => {\n if (typeof adapter.hasAuth === 'function') {\n return adapter.hasAuth();\n }\n // Fallback: 检查 accessToken 是否存在\n return adapter.getAccessToken() !== null;\n }, [adapter]);\n\n return { hasAuth, adapter };\n}\n","import { isTokenExpired } from '@swr-login/core';\nimport { useMemo } from 'react';\nimport { useAuthContext } from '../context';\n\nexport interface SessionInfo {\n /** Current access token */\n accessToken: string | null;\n /** Current refresh token */\n refreshToken: string | null;\n /** Token expiration timestamp (ms since epoch) */\n expiresAt: number | null;\n /** Whether the access token has expired */\n isExpired: boolean;\n /** Whether tokens exist (regardless of expiry) */\n hasTokens: boolean;\n}\n\n/**\n * Hook to access raw session/token information.\n *\n * Useful for:\n * - Adding Authorization headers to custom requests\n * - Checking token expiry status\n * - Debugging session state\n *\n * @example\n * ```tsx\n * const { accessToken, isExpired } = useSession();\n *\n * const fetchData = () => fetch('/api/data', {\n * headers: { Authorization: `Bearer ${accessToken}` },\n * });\n * ```\n */\nexport function useSession(): SessionInfo {\n const { tokenManager } = useAuthContext();\n\n return useMemo(() => {\n const accessToken = tokenManager.getAccessToken();\n const refreshToken = tokenManager.getRefreshToken();\n const expiresAt = tokenManager.getAccessToken() ? tokenManager.getExpiresAt() : null;\n\n return {\n accessToken,\n refreshToken,\n expiresAt,\n isExpired: isTokenExpired(expiresAt),\n hasTokens: !!accessToken,\n };\n }, [tokenManager]);\n}\n","import { useCallback } from 'react';\nimport { useUser } from './useUser';\n\nexport interface UsePermissionReturn {\n /** Check if user has a specific permission */\n hasPermission: (permission: string) => boolean;\n /** Check if user has a specific role */\n hasRole: (role: string) => boolean;\n /** Check if user has all specified permissions */\n hasAllPermissions: (permissions: string[]) => boolean;\n /** Check if user has any of the specified permissions */\n hasAnyPermission: (permissions: string[]) => boolean;\n /** Check if user has all specified roles */\n hasAllRoles: (roles: string[]) => boolean;\n /** Check if user has any of the specified roles */\n hasAnyRole: (roles: string[]) => boolean;\n}\n\n/**\n * Hook for checking user permissions and roles.\n *\n * Reads from the `permissions` and `roles` arrays on the User object.\n *\n * @example\n * ```tsx\n * const { hasPermission, hasRole } = usePermission();\n *\n * if (hasPermission('admin:write')) {\n * // Show admin controls\n * }\n *\n * if (hasRole('editor')) {\n * // Show editor tools\n * }\n * ```\n */\nexport function usePermission(): UsePermissionReturn {\n const { user } = useUser();\n\n const hasPermission = useCallback(\n (permission: string): boolean => {\n return user?.permissions?.includes(permission) ?? false;\n },\n [user],\n );\n\n const hasRole = useCallback(\n (role: string): boolean => {\n return user?.roles?.includes(role) ?? false;\n },\n [user],\n );\n\n const hasAllPermissions = useCallback(\n (permissions: string[]): boolean => {\n if (!user?.permissions) return false;\n return permissions.every((p) => user.permissions?.includes(p));\n },\n [user],\n );\n\n const hasAnyPermission = useCallback(\n (permissions: string[]): boolean => {\n if (!user?.permissions) return false;\n return permissions.some((p) => user.permissions?.includes(p));\n },\n [user],\n );\n\n const hasAllRoles = useCallback(\n (roles: string[]): boolean => {\n if (!user?.roles) return false;\n return roles.every((r) => user.roles?.includes(r));\n },\n [user],\n );\n\n const hasAnyRole = useCallback(\n (roles: string[]): boolean => {\n if (!user?.roles) return false;\n return roles.some((r) => user.roles?.includes(r));\n },\n [user],\n );\n\n return {\n hasPermission,\n hasRole,\n hasAllPermissions,\n hasAnyPermission,\n hasAllRoles,\n hasAnyRole,\n };\n}\n","import type React from 'react';\nimport { usePermission } from '../hooks/usePermission';\nimport { useUser } from '../hooks/useUser';\n\nexport interface AuthGuardProps {\n children: React.ReactNode;\n /** Required permissions (checked against user.permissions) */\n permissions?: string[];\n /** Required roles (checked against user.roles) */\n roles?: string[];\n /** If true, ALL permissions/roles must match. If false, ANY match suffices. (default: false) */\n requireAll?: boolean;\n /** Component to render when user is not authenticated or lacks permissions */\n fallback?: React.ReactNode;\n /** Component to render while checking auth status */\n loadingComponent?: React.ReactNode;\n}\n\n/**\n * Declarative auth guard component.\n * Protects child content based on authentication state and permissions/roles.\n *\n * @example\n * ```tsx\n * <AuthGuard\n * permissions={['admin', 'editor']}\n * requireAll={false}\n * fallback={<Navigate to=\"/login\" />}\n * loadingComponent={<Skeleton />}\n * >\n * <AdminPanel />\n * </AuthGuard>\n * ```\n */\nexport function AuthGuard({\n children,\n permissions,\n roles,\n requireAll = false,\n fallback = null,\n loadingComponent = null,\n}: AuthGuardProps) {\n const { isLoading, isAuthenticated } = useUser();\n const { hasAllPermissions, hasAnyPermission, hasAllRoles, hasAnyRole } = usePermission();\n\n if (isLoading) {\n return <>{loadingComponent}</>;\n }\n\n if (!isAuthenticated) {\n return <>{fallback}</>;\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n const hasRequiredPermissions = requireAll\n ? hasAllPermissions(permissions)\n : hasAnyPermission(permissions);\n\n if (!hasRequiredPermissions) {\n return <>{fallback}</>;\n }\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n const hasRequiredRoles = requireAll ? hasAllRoles(roles) : hasAnyRole(roles);\n\n if (!hasRequiredRoles) {\n return <>{fallback}</>;\n }\n }\n\n return <>{children}</>;\n}\n"]}
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 };