@swr-login/react 0.1.0 → 0.3.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > React bindings for swr-login: Provider, Hooks, and AuthGuard component.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@swr-login/react?color=blue)](https://www.npmjs.com/package/@swr-login/react)
6
- [![license](https://img.shields.io/github/license/swr-login/swr-login)](https://github.com/swr-login/swr-login/blob/main/LICENSE)
6
+ [![license](https://img.shields.io/github/license/swr-login/swr-login)](https://github.com/tobytovi/swr-login/blob/main/LICENSE)
7
7
 
8
8
  ## Install
9
9
 
@@ -84,8 +84,8 @@ function LoginButton() {
84
84
 
85
85
  ## Part of swr-login
86
86
 
87
- This package is the React layer of [swr-login](https://github.com/swr-login/swr-login). The framework-agnostic core is [`@swr-login/core`](https://www.npmjs.com/package/@swr-login/core).
87
+ This package is the React layer of [swr-login](https://github.com/tobytovi/swr-login). The framework-agnostic core is [`@swr-login/core`](https://www.npmjs.com/package/@swr-login/core).
88
88
 
89
89
  ## License
90
90
 
91
- [MIT](https://github.com/swr-login/swr-login/blob/main/LICENSE)
91
+ [MIT](https://github.com/tobytovi/swr-login/blob/main/LICENSE)
package/dist/index.cjs CHANGED
@@ -140,6 +140,16 @@ function useLogin(pluginName) {
140
140
  setIsLoading(true);
141
141
  setError(null);
142
142
  stateMachine.transition("authenticating");
143
+ const targetPlugin = pluginManager.getPlugin(resolvedPlugin);
144
+ if (targetPlugin && core.isMultiStepPlugin(targetPlugin)) {
145
+ const err = new Error(
146
+ `[swr-login] Plugin "${resolvedPlugin}" is a multi-step plugin. Use useMultiStepLogin() instead of useLogin() for multi-step login flows.`
147
+ );
148
+ setError(err);
149
+ stateMachine.transition("error");
150
+ setIsLoading(false);
151
+ throw err;
152
+ }
143
153
  try {
144
154
  const response = await pluginManager.login(resolvedPlugin, resolvedCredentials);
145
155
  stateMachine.transition("authenticated");
@@ -163,6 +173,133 @@ function useLogin(pluginName) {
163
173
  }, []);
164
174
  return { login, isLoading, error, reset };
165
175
  }
176
+ function useMultiStepLogin(pluginName) {
177
+ const { pluginManager, stateMachine, config } = useAuthContext();
178
+ const [currentStep, setCurrentStep] = react.useState(-1);
179
+ const [stepData, setStepData] = react.useState(null);
180
+ const [isLoading, setIsLoading] = react.useState(false);
181
+ const [error, setError] = react.useState(null);
182
+ const [isComplete, setIsComplete] = react.useState(false);
183
+ const [authResponse, setAuthResponse] = react.useState(null);
184
+ const stepOutputsRef = react.useRef([]);
185
+ const steps = pluginManager.getSteps(pluginName);
186
+ const totalSteps = steps?.length ?? 0;
187
+ const currentStepName = currentStep >= 0 && steps ? steps[currentStep]?.name ?? null : null;
188
+ const executeStepAt = react.useCallback(
189
+ async (stepIndex, input) => {
190
+ setIsLoading(true);
191
+ setError(null);
192
+ try {
193
+ const output = await pluginManager.executeStep(pluginName, stepIndex, input);
194
+ stepOutputsRef.current[stepIndex] = output;
195
+ setStepData(output);
196
+ setCurrentStep(stepIndex);
197
+ if (stepIndex === totalSteps - 1) {
198
+ const response = await pluginManager.finalizeMultiStep(pluginName, output);
199
+ stateMachine.transition("authenticated");
200
+ if (config.cacheAdapter) {
201
+ await config.cacheAdapter.setUser(response.user);
202
+ }
203
+ setAuthResponse(response);
204
+ setIsComplete(true);
205
+ }
206
+ } catch (err) {
207
+ const authError = err instanceof Error ? err : new Error("Step execution failed");
208
+ setError(authError);
209
+ throw authError;
210
+ } finally {
211
+ setIsLoading(false);
212
+ }
213
+ },
214
+ [pluginManager, pluginName, totalSteps, stateMachine, config]
215
+ );
216
+ const start = react.useCallback(
217
+ async (credentials) => {
218
+ stepOutputsRef.current = [];
219
+ setCurrentStep(-1);
220
+ setStepData(null);
221
+ setIsComplete(false);
222
+ setAuthResponse(null);
223
+ setError(null);
224
+ stateMachine.transition("authenticating");
225
+ await executeStepAt(0, credentials);
226
+ },
227
+ [executeStepAt, stateMachine]
228
+ );
229
+ const next = react.useCallback(
230
+ async (input) => {
231
+ const nextIndex = currentStep + 1;
232
+ if (nextIndex >= totalSteps) {
233
+ throw new Error("[swr-login] No more steps to execute.");
234
+ }
235
+ await executeStepAt(nextIndex, input);
236
+ },
237
+ [currentStep, totalSteps, executeStepAt]
238
+ );
239
+ const back = react.useCallback(() => {
240
+ if (currentStep > 0) {
241
+ const prevStep = currentStep - 1;
242
+ setCurrentStep(prevStep);
243
+ setStepData(stepOutputsRef.current[prevStep] ?? null);
244
+ setError(null);
245
+ }
246
+ }, [currentStep]);
247
+ const cancel = react.useCallback(() => {
248
+ setCurrentStep(-1);
249
+ setStepData(null);
250
+ setIsLoading(false);
251
+ setError(null);
252
+ setIsComplete(false);
253
+ setAuthResponse(null);
254
+ stepOutputsRef.current = [];
255
+ stateMachine.transition("unauthenticated");
256
+ }, [stateMachine]);
257
+ const reset = react.useCallback(() => {
258
+ setError(null);
259
+ }, []);
260
+ return {
261
+ currentStep,
262
+ currentStepName,
263
+ totalSteps,
264
+ start,
265
+ next,
266
+ back,
267
+ cancel,
268
+ stepData,
269
+ isLoading,
270
+ error,
271
+ reset,
272
+ isComplete,
273
+ authResponse
274
+ };
275
+ }
276
+ function useAuthInjector() {
277
+ const { tokenManager, emitter, stateMachine, config } = useAuthContext();
278
+ const injectAuth = react.useCallback(
279
+ async (response) => {
280
+ tokenManager.setTokens({
281
+ accessToken: response.accessToken,
282
+ refreshToken: response.refreshToken,
283
+ expiresAt: response.expiresAt
284
+ });
285
+ stateMachine.transition("authenticated");
286
+ emitter.emit("login", { user: response.user });
287
+ if (config.cacheAdapter) {
288
+ await config.cacheAdapter.setUser(response.user);
289
+ }
290
+ },
291
+ [tokenManager, emitter, stateMachine, config]
292
+ );
293
+ const injectLogout = react.useCallback(async () => {
294
+ tokenManager.clearTokens();
295
+ stateMachine.transition("unauthenticated");
296
+ emitter.emit("logout", void 0);
297
+ if (config.cacheAdapter) {
298
+ await config.cacheAdapter.clearUser();
299
+ }
300
+ }, [tokenManager, emitter, stateMachine, config]);
301
+ return { injectAuth, injectLogout };
302
+ }
166
303
  var AUTH_KEY = "__swr_login_user__";
167
304
  function useUser() {
168
305
  const { tokenManager, config } = useAuthContext();
@@ -335,8 +472,10 @@ exports.AUTH_KEY = AUTH_KEY;
335
472
  exports.AuthGuard = AuthGuard;
336
473
  exports.SWRLoginProvider = SWRLoginProvider;
337
474
  exports.useAuthContext = useAuthContext;
475
+ exports.useAuthInjector = useAuthInjector;
338
476
  exports.useLogin = useLogin;
339
477
  exports.useLogout = useLogout;
478
+ exports.useMultiStepLogin = useMultiStepLogin;
340
479
  exports.usePermission = usePermission;
341
480
  exports.useSession = useSession;
342
481
  exports.useUser = useUser;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useLogin.ts","../src/hooks/useUser.ts","../src/hooks/useLogout.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","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 if (existingToken && !tokenManager.isExpired()) {\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 { AuthResponse } from '@swr-login/core';\nimport { useCallback, useState } from 'react';\nimport { useAuthContext } from '../context';\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, 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 try {\n const response = await pluginManager.login(resolvedPlugin, resolvedCredentials);\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, stateMachine, config, pluginName],\n );\n\n const reset = useCallback(() => {\n setError(null);\n }, []);\n\n return { login, isLoading, error, reset };\n}\n","import type { User } from '@swr-login/core';\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 * 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, config } = useAuthContext();\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 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: true,\n revalidateOnReconnect: true,\n shouldRetryOnError: false,\n });\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 mutate,\n };\n}\n\n/** SWR cache key used for user data. Exported for advanced usage. */\nexport { AUTH_KEY };\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 { 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/useLogin.ts","../src/hooks/useMultiStepLogin.ts","../src/hooks/useAuthInjector.ts","../src/hooks/useUser.ts","../src/hooks/useLogout.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","isMultiStepPlugin","useSWR","mutate","isTokenExpired","jsx","Fragment"],"mappings":";;;;;;;;;;;;AAoBO,IAAM,WAAA,GAAcA,oBAAuC,IAAI,CAAA;AAO/D,SAAS,cAAA,GAAmC;AACjD,EAAA,MAAM,GAAA,GAAMC,iBAAW,WAAW,CAAA;AAClC,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;ACKO,SAAS,gBAAA,CAAiB,EAAE,MAAA,EAAQ,QAAA,EAAS,EAA0B;AAC5E,EAAA,MAAM,cAAA,GAAiBC,aAAO,KAAK,CAAA;AAEnC,EAAA,MAAM,YAAA,GAAeC,cAA0B,MAAM;AACnD,IAAA,MAAM,OAAA,GAAU,IAAIC,qBAAA,EAAiB;AACrC,IAAA,MAAM,YAAA,GAAe,IAAIC,qBAAA,CAAiB,OAAO,CAAA;AACjD,IAAA,MAAM,eAAe,IAAIC,iBAAA,CAAa,MAAA,CAAO,OAAA,EAAS,SAAS,YAAY,CAAA;AAC3E,IAAA,MAAM,aAAA,GAAgB,IAAIC,kBAAA,CAAc,YAAA,EAAc,OAAO,CAAA;AAG7D,IAAA,aAAA,CAAc,QAAA,CAAS,GAAG,MAAA,CAAO,OAAO,CAAA;AAGxC,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,QAAA,EAAU,mBAAA,KAAwB,KAAA;AAC5D,IAAA,MAAM,gBAAgB,UAAA,IAAc,OAAO,WAAW,WAAA,GAAc,IAAIC,oBAAc,GAAI,IAAA;AAG1F,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,OAAA,CAAQ,EAAA,CAAG,SAAS,CAAC,EAAE,MAAK,KAAM,MAAA,CAAO,OAAA,GAAU,IAAI,CAAC,CAAA;AAAA,IAC1D;AACA,IAAA,IAAI,OAAO,QAAA,EAAU;AACnB,MAAA,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,MAAM,MAAA,CAAO,YAAY,CAAA;AAAA,IAChD;AACA,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,OAAA,CAAQ,EAAA,CAAG,SAAS,CAAC,EAAE,OAAM,KAAM,MAAA,CAAO,OAAA,GAAU,KAAK,CAAC,CAAA;AAAA,IAC5D;AAEA,IAAA,OAAO;AAAA,MACL,aAAA;AAAA,MACA,YAAA;AAAA,MACA,OAAA;AAAA,MACA,YAAA;AAAA,MACA,aAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAGX,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,eAAe,OAAA,EAAS;AAC5B,IAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AAEzB,IAAA,MAAM;AAAA,MACJ,aAAA;AAAA,MACA,aAAA;AAAA,MACA,YAAA;AAAA,MACA,YAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA,EAAQ;AAAA,KACV,GAAI,YAAA;AAGJ,IAAA,aAAA,CAAc,aAAA,EAAc,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AAC3C,MAAA,OAAA,CAAQ,KAAA,CAAM,4CAA4C,GAAG,CAAA;AAAA,IAC/D,CAAC,CAAA;AAGD,IAAA,MAAM,aAAA,GAAgB,aAAa,cAAA,EAAe;AAClD,IAAA,IAAI,aAAA,IAAiB,CAAC,YAAA,CAAa,SAAA,EAAU,EAAG;AAC9C,MAAA,YAAA,CAAa,WAAW,eAAe,CAAA;AAAA,IACzC,CAAA,MAAA,IAAW,aAAA,IAAiB,YAAA,CAAa,SAAA,EAAU,EAAG;AACpD,MAAA,YAAA,CAAa,WAAW,iBAAiB,CAAA;AACzC,MAAA,OAAA,CAAQ,IAAA,CAAK,iBAAiB,MAAS,CAAA;AAAA,IACzC;AAGA,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,MAAM,WAAA,GAAc,aAAA,CAAc,SAAA,CAAU,CAAC,OAAA,KAAY;AACvD,QAAA,QAAQ,QAAQ,IAAA;AAAM,UACpB,KAAK,QAAA;AACH,YAAA,YAAA,CAAa,WAAA,EAAY;AACzB,YAAA,YAAA,CAAa,WAAW,iBAAiB,CAAA;AACzC,YAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,MAAS,CAAA;AAChC,YAAA;AAAA,UACF,KAAK,OAAA;AAAA,UACL,KAAK,eAAA;AAEH,YAAA,IAAI,IAAI,YAAA,EAAc;AACpB,cAAA,GAAA,CAAI,aAAa,UAAA,EAAW;AAAA,YAC9B;AACA,YAAA;AAAA;AACJ,MACF,CAAC,CAAA;AAED,MAAA,OAAO,MAAM;AACX,QAAA,WAAA,EAAY;AACZ,QAAA,aAAA,CAAc,OAAA,EAAQ;AAAA,MACxB,CAAA;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAGjB,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,EAAE,MAAA,EAAQ,GAAA,EAAK,YAAA,EAAc,YAAA,EAAc,SAAQ,GAAI,YAAA;AAE7D,IAAA,IAAI,CAAC,GAAA,CAAI,QAAA,EAAU,aAAA,IAAiB,OAAO,aAAa,WAAA,EAAa;AAErE,IAAA,IAAI,SAAA,GAAkD,IAAA;AAEtD,IAAA,MAAM,yBAAyB,MAAM;AACnC,MAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,QAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,QAAA,EAAU,kBAAA,IAAsB,GAAA;AAClD,QAAA,SAAA,GAAY,WAAW,MAAM;AAC3B,UAAA,YAAA,CAAa,WAAA,EAAY;AACzB,UAAA,YAAA,CAAa,WAAW,iBAAiB,CAAA;AACzC,UAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,MAAS,CAAA;AAAA,QAClC,GAAG,KAAK,CAAA;AAAA,MACV,CAAA,MAAO;AACL,QAAA,IAAI,SAAA,EAAW;AACb,UAAA,YAAA,CAAa,SAAS,CAAA;AACtB,UAAA,SAAA,GAAY,IAAA;AAAA,QACd;AAAA,MACF;AAAA,IACF,CAAA;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,oBAAoB,sBAAsB,CAAA;AACpE,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAoB,sBAAsB,CAAA;AACvE,MAAA,IAAI,SAAA,eAAwB,SAAS,CAAA;AAAA,IACvC,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,sCAAQ,WAAA,CAAY,QAAA,EAAZ,EAAqB,KAAA,EAAO,cAAe,QAAA,EAAS,CAAA;AAC9D;ACzHO,SAAS,SACd,UAAA,EAC8B;AAC9B,EAAA,MAAM,EAAE,aAAA,EAAe,YAAA,EAAc,MAAA,KAAW,cAAA,EAAe;AAC/D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIC,eAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,KAAA,GAAQC,iBAAA;AAAA,IACZ,OACE,yBACA,gBAAA,KAC0B;AAC1B,MAAA,IAAI,cAAA;AACJ,MAAA,IAAI,mBAAA;AAEJ,MAAA,IAAI,OAAO,uBAAA,KAA4B,QAAA,IAAY,CAAC,UAAA,EAAY;AAC9D,QAAA,cAAA,GAAiB,uBAAA;AACjB,QAAA,mBAAA,GAAuB,oBAAoB,EAAC;AAAA,MAC9C,WAAW,UAAA,EAAY;AACrB,QAAA,cAAA,GAAiB,UAAA;AACjB,QAAA,mBAAA,GAAsB,uBAAA;AAAA,MACxB,CAAA,MAAO;AACL,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SACF;AAAA,MACF;AAEA,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,YAAA,CAAa,WAAW,gBAAgB,CAAA;AAGxC,MAAA,MAAM,YAAA,GAAe,aAAA,CAAc,SAAA,CAAU,cAAc,CAAA;AAC3D,MAAA,IAAI,YAAA,IAAgBC,sBAAA,CAAkB,YAAY,CAAA,EAAG;AACnD,QAAA,MAAM,MAAM,IAAI,KAAA;AAAA,UACd,uBAAuB,cAAc,CAAA,mGAAA;AAAA,SACvC;AACA,QAAA,QAAA,CAAS,GAAG,CAAA;AACZ,QAAA,YAAA,CAAa,WAAW,OAAO,CAAA;AAC/B,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,MAAM,GAAA;AAAA,MACR;AAEA,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,aAAA,CAAc,KAAA,CAAM,gBAAgB,mBAAmB,CAAA;AAC9E,QAAA,YAAA,CAAa,WAAW,eAAe,CAAA;AAGvC,QAAA,IAAI,OAAO,YAAA,EAAc;AACvB,UAAA,MAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA;AAAA,QACjD;AAEA,QAAA,OAAO,QAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,YAAY,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,cAAc,CAAA;AACvE,QAAA,QAAA,CAAS,SAAS,CAAA;AAClB,QAAA,YAAA,CAAa,WAAW,OAAO,CAAA;AAC/B,QAAA,MAAM,SAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,aAAA,EAAe,YAAA,EAAc,MAAA,EAAQ,UAAU;AAAA,GAClD;AAEA,EAAA,MAAM,KAAA,GAAQD,kBAAY,MAAM;AAC9B,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,KAAA,EAAO,SAAA,EAAW,KAAA,EAAO,KAAA,EAAM;AAC1C;ACrCO,SAAS,kBACd,UAAA,EACuC;AACvC,EAAA,MAAM,EAAE,aAAA,EAAe,YAAA,EAAc,MAAA,KAAW,cAAA,EAAe;AAE/D,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAID,eAAS,EAAE,CAAA;AACjD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAkB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,eAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIA,eAAS,KAAK,CAAA;AAClD,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,eAA8B,IAAI,CAAA;AAG1E,EAAA,MAAM,cAAA,GAAiBR,YAAAA,CAAkB,EAAE,CAAA;AAE3C,EAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,QAAA,CAAS,UAAU,CAAA;AAC/C,EAAA,MAAM,UAAA,GAAa,OAAO,MAAA,IAAU,CAAA;AAEpC,EAAA,MAAM,eAAA,GAAkB,eAAe,CAAA,IAAK,KAAA,GAAS,MAAM,WAAW,CAAA,EAAG,QAAQ,IAAA,GAAQ,IAAA;AAEzF,EAAA,MAAM,aAAA,GAAgBS,iBAAAA;AAAA,IACpB,OAAO,WAAmB,KAAA,KAAmB;AAC3C,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,IAAI;AACF,QAAA,MAAM,SAAS,MAAM,aAAA,CAAc,WAAA,CAAY,UAAA,EAAY,WAAW,KAAK,CAAA;AAC3E,QAAA,cAAA,CAAe,OAAA,CAAQ,SAAS,CAAA,GAAI,MAAA;AACpC,QAAA,WAAA,CAAY,MAAM,CAAA;AAClB,QAAA,cAAA,CAAe,SAAS,CAAA;AAGxB,QAAA,IAAI,SAAA,KAAc,aAAa,CAAA,EAAG;AAChC,UAAA,MAAM,QAAA,GAAW,MAAM,aAAA,CAAc,iBAAA,CAAkB,YAAY,MAAM,CAAA;AACzE,UAAA,YAAA,CAAa,WAAW,eAAe,CAAA;AAEvC,UAAA,IAAI,OAAO,YAAA,EAAc;AACvB,YAAA,MAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA;AAAA,UACjD;AAEA,UAAA,eAAA,CAAgB,QAAQ,CAAA;AACxB,UAAA,aAAA,CAAc,IAAI,CAAA;AAAA,QACpB;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,YAAY,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,uBAAuB,CAAA;AAChF,QAAA,QAAA,CAAS,SAAS,CAAA;AAClB,QAAA,MAAM,SAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,aAAA,EAAe,UAAA,EAAY,UAAA,EAAY,cAAc,MAAM;AAAA,GAC9D;AAEA,EAAA,MAAM,KAAA,GAAQA,iBAAAA;AAAA,IACZ,OAAO,WAAA,KAA8B;AAEnC,MAAA,cAAA,CAAe,UAAU,EAAC;AAC1B,MAAA,cAAA,CAAe,EAAE,CAAA;AACjB,MAAA,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA,aAAA,CAAc,KAAK,CAAA;AACnB,MAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,YAAA,CAAa,WAAW,gBAAgB,CAAA;AAExC,MAAA,MAAM,aAAA,CAAc,GAAG,WAAW,CAAA;AAAA,IACpC,CAAA;AAAA,IACA,CAAC,eAAe,YAAY;AAAA,GAC9B;AAEA,EAAA,MAAM,IAAA,GAAOA,iBAAAA;AAAA,IACX,OAAO,KAAA,KAAmB;AACxB,MAAA,MAAM,YAAY,WAAA,GAAc,CAAA;AAChC,MAAA,IAAI,aAAa,UAAA,EAAY;AAC3B,QAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AAAA,MACzD;AACA,MAAA,MAAM,aAAA,CAAc,WAAW,KAAK,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,CAAC,WAAA,EAAa,UAAA,EAAY,aAAa;AAAA,GACzC;AAEA,EAAA,MAAM,IAAA,GAAOA,kBAAY,MAAM;AAC7B,IAAA,IAAI,cAAc,CAAA,EAAG;AACnB,MAAA,MAAM,WAAW,WAAA,GAAc,CAAA;AAC/B,MAAA,cAAA,CAAe,QAAQ,CAAA;AACvB,MAAA,WAAA,CAAY,cAAA,CAAe,OAAA,CAAQ,QAAQ,CAAA,IAAK,IAAI,CAAA;AACpD,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf;AAAA,EACF,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,MAAM,MAAA,GAASA,kBAAY,MAAM;AAC/B,IAAA,cAAA,CAAe,EAAE,CAAA;AACjB,IAAA,WAAA,CAAY,IAAI,CAAA;AAChB,IAAA,YAAA,CAAa,KAAK,CAAA;AAClB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,aAAA,CAAc,KAAK,CAAA;AACnB,IAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,IAAA,cAAA,CAAe,UAAU,EAAC;AAC1B,IAAA,YAAA,CAAa,WAAW,iBAAiB,CAAA;AAAA,EAC3C,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,MAAM,KAAA,GAAQA,kBAAY,MAAM;AAC9B,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,eAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AACF;ACxKO,SAAS,eAAA,GAAgC;AAC9C,EAAA,MAAM,EAAE,YAAA,EAAc,OAAA,EAAS,YAAA,EAAc,MAAA,KAAW,cAAA,EAAe;AAEvE,EAAA,MAAM,UAAA,GAAaA,iBAAAA;AAAA,IACjB,OAAO,QAAA,KAA0C;AAE/C,MAAA,YAAA,CAAa,SAAA,CAAU;AAAA,QACrB,aAAa,QAAA,CAAS,WAAA;AAAA,QACtB,cAAc,QAAA,CAAS,YAAA;AAAA,QACvB,WAAW,QAAA,CAAS;AAAA,OACrB,CAAA;AAGD,MAAA,YAAA,CAAa,WAAW,eAAe,CAAA;AAGvC,MAAA,OAAA,CAAQ,KAAK,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,CAAS,MAAM,CAAA;AAG7C,MAAA,IAAI,OAAO,YAAA,EAAc;AACvB,QAAA,MAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA;AAAA,MACjD;AAAA,IACF,CAAA;AAAA,IACA,CAAC,YAAA,EAAc,OAAA,EAAS,YAAA,EAAc,MAAM;AAAA,GAC9C;AAEA,EAAA,MAAM,YAAA,GAAeA,kBAAY,YAA2B;AAE1D,IAAA,YAAA,CAAa,WAAA,EAAY;AAGzB,IAAA,YAAA,CAAa,WAAW,iBAAiB,CAAA;AAGzC,IAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,MAAS,CAAA;AAGhC,IAAA,IAAI,OAAO,YAAA,EAAc;AACvB,MAAA,MAAM,MAAA,CAAO,aAAa,SAAA,EAAU;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,YAAA,EAAc,OAAA,EAAS,YAAA,EAAc,MAAM,CAAC,CAAA;AAEhD,EAAA,OAAO,EAAE,YAAY,YAAA,EAAa;AACpC;ACnEA,IAAM,QAAA,GAAW;AAmCV,SAAS,OAAA,GAAmD;AACjE,EAAA,MAAM,EAAE,YAAA,EAAc,MAAA,EAAO,GAAI,cAAA,EAAe;AAEhD,EAAA,MAAM,UAAU,YAA+B;AAC7C,IAAA,MAAM,KAAA,GAAQ,aAAa,cAAA,EAAe;AAC1C,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AAEnB,IAAA,IAAI,YAAA,CAAa,WAAU,EAAG;AAC5B,MAAA,IAAI;AACF,QAAA,MAAM,aAAa,OAAA,EAAQ;AAAA,MAC7B,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAEA,IAAA,IAAI,OAAO,SAAA,EAAW;AACpB,MAAA,MAAM,YAAA,GAAe,aAAa,cAAA,EAAe;AACjD,MAAA,IAAI,CAAC,cAAc,OAAO,IAAA;AAC1B,MAAA,OAAQ,MAAM,MAAA,CAAO,SAAA,CAAU,YAAY,CAAA;AAAA,IAC7C;AAEA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM;AAAA,IACJ,IAAA;AAAA,IACA,KAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA,EAAQ;AAAA,GACV,GAAIE,uBAAA,CAAiB,QAAA,EAAU,OAAA,EAAS;AAAA,IACtC,iBAAA,EAAmB,IAAA;AAAA,IACnB,qBAAA,EAAuB,IAAA;AAAA,IACvB,kBAAA,EAAoB;AAAA,GACrB,CAAA;AAED,EAAA,MAAMC,OAAAA,GAAS,OAAO,OAAA,KAAsC;AAC1D,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,MAAM,SAAA,EAAU;AAAA,IAClB,CAAA,MAAO;AACL,MAAA,MAAM,SAAA,CAAU,OAAA,EAAS,EAAE,UAAA,EAAY,OAAO,CAAA;AAAA,IAChD;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,IAAA,IAAQ,IAAA;AAAA,IACd,SAAA;AAAA,IACA,eAAA,EAAiB,CAAC,CAAC,IAAA;AAAA,IACnB,KAAA;AAAA,IACA,MAAA,EAAAA;AAAA,GACF;AACF;ACnDO,SAAS,SAAA,GAA6B;AAC3C,EAAA,MAAM,EAAE,aAAA,EAAe,YAAA,EAAc,aAAA,KAAkB,cAAA,EAAe;AACtE,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIJ,eAAS,KAAK,CAAA;AAEhD,EAAA,MAAM,MAAA,GAASC,iBAAAA;AAAA,IACb,OAAO,OAAA,KAA+B;AACpC,MAAA,YAAA,CAAa,IAAI,CAAA;AAEjB,MAAA,IAAI;AAEF,QAAA,MAAM,aAAA,CAAc,MAAA,CAAO,OAAA,EAAS,UAAU,CAAA;AAG9C,QAAA,MAAMG,cAAO,QAAA,EAAU,IAAA,EAAM,EAAE,UAAA,EAAY,OAAO,CAAA;AAGlD,QAAA,YAAA,CAAa,WAAW,iBAAiB,CAAA;AAGzC,QAAA,MAAM,eAAA,GAAkB,SAAS,SAAA,KAAc,KAAA;AAC/C,QAAA,IAAI,mBAAmB,aAAA,EAAe;AACpC,UAAA,aAAA,CAAc,KAAK,QAAQ,CAAA;AAAA,QAC7B;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,OAAA,CAAQ,KAAA,CAAM,6BAA6B,GAAG,CAAA;AAE9C,QAAA,MAAMA,cAAO,QAAA,EAAU,IAAA,EAAM,EAAE,UAAA,EAAY,OAAO,CAAA;AAClD,QAAA,YAAA,CAAa,WAAW,iBAAiB,CAAA;AAAA,MAC3C,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,aAAA,EAAe,YAAA,EAAc,aAAa;AAAA,GAC7C;AAEA,EAAA,OAAO,EAAE,QAAQ,SAAA,EAAU;AAC7B;ACxCO,SAAS,UAAA,GAA0B;AACxC,EAAA,MAAM,EAAE,YAAA,EAAa,GAAI,cAAA,EAAe;AAExC,EAAA,OAAOX,cAAQ,MAAM;AACnB,IAAA,MAAM,WAAA,GAAc,aAAa,cAAA,EAAe;AAChD,IAAA,MAAM,YAAA,GAAe,aAAa,eAAA,EAAgB;AAClD,IAAA,MAAM,YAAY,YAAA,CAAa,cAAA,EAAe,GAAI,YAAA,CAAa,cAAa,GAAI,IAAA;AAEhF,IAAA,OAAO;AAAA,MACL,WAAA;AAAA,MACA,YAAA;AAAA,MACA,SAAA;AAAA,MACA,SAAA,EAAWY,oBAAe,SAAS,CAAA;AAAA,MACnC,SAAA,EAAW,CAAC,CAAC;AAAA,KACf;AAAA,EACF,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AACnB;ACdO,SAAS,aAAA,GAAqC;AACnD,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,OAAA,EAAQ;AAEzB,EAAA,MAAM,aAAA,GAAgBJ,iBAAAA;AAAA,IACpB,CAAC,UAAA,KAAgC;AAC/B,MAAA,OAAO,IAAA,EAAM,WAAA,EAAa,QAAA,CAAS,UAAU,CAAA,IAAK,KAAA;AAAA,IACpD,CAAA;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AAEA,EAAA,MAAM,OAAA,GAAUA,iBAAAA;AAAA,IACd,CAAC,IAAA,KAA0B;AACzB,MAAA,OAAO,IAAA,EAAM,KAAA,EAAO,QAAA,CAAS,IAAI,CAAA,IAAK,KAAA;AAAA,IACxC,CAAA;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AAEA,EAAA,MAAM,iBAAA,GAAoBA,iBAAAA;AAAA,IACxB,CAAC,WAAA,KAAmC;AAClC,MAAA,IAAI,CAAC,IAAA,EAAM,WAAA,EAAa,OAAO,KAAA;AAC/B,MAAA,OAAO,WAAA,CAAY,MAAM,CAAC,CAAA,KAAM,KAAK,WAAA,EAAa,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,IAC/D,CAAA;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AAEA,EAAA,MAAM,gBAAA,GAAmBA,iBAAAA;AAAA,IACvB,CAAC,WAAA,KAAmC;AAClC,MAAA,IAAI,CAAC,IAAA,EAAM,WAAA,EAAa,OAAO,KAAA;AAC/B,MAAA,OAAO,WAAA,CAAY,KAAK,CAAC,CAAA,KAAM,KAAK,WAAA,EAAa,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,IAC9D,CAAA;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AAEA,EAAA,MAAM,WAAA,GAAcA,iBAAAA;AAAA,IAClB,CAAC,KAAA,KAA6B;AAC5B,MAAA,IAAI,CAAC,IAAA,EAAM,KAAA,EAAO,OAAO,KAAA;AACzB,MAAA,OAAO,KAAA,CAAM,MAAM,CAAC,CAAA,KAAM,KAAK,KAAA,EAAO,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,IACnD,CAAA;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AAEA,EAAA,MAAM,UAAA,GAAaA,iBAAAA;AAAA,IACjB,CAAC,KAAA,KAA6B;AAC5B,MAAA,IAAI,CAAC,IAAA,EAAM,KAAA,EAAO,OAAO,KAAA;AACzB,MAAA,OAAO,KAAA,CAAM,KAAK,CAAC,CAAA,KAAM,KAAK,KAAA,EAAO,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,IAClD,CAAA;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AAEA,EAAA,OAAO;AAAA,IACL,aAAA;AAAA,IACA,OAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AACF;AC3DO,SAAS,SAAA,CAAU;AAAA,EACxB,QAAA;AAAA,EACA,WAAA;AAAA,EACA,KAAA;AAAA,EACA,UAAA,GAAa,KAAA;AAAA,EACb,QAAA,GAAW,IAAA;AAAA,EACX,gBAAA,GAAmB;AACrB,CAAA,EAAmB;AACjB,EAAA,MAAM,EAAE,SAAA,EAAW,eAAA,EAAgB,GAAI,OAAA,EAAQ;AAC/C,EAAA,MAAM,EAAE,iBAAA,EAAmB,gBAAA,EAAkB,WAAA,EAAa,UAAA,KAAe,aAAA,EAAc;AAEvF,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,uBAAOK,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,gBAAA,EAAiB,CAAA;AAAA,EAC7B;AAEA,EAAA,IAAI,CAAC,eAAA,EAAiB;AACpB,IAAA,uBAAOD,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,EACrB;AAGA,EAAA,IAAI,WAAA,IAAe,WAAA,CAAY,MAAA,GAAS,CAAA,EAAG;AACzC,IAAA,MAAM,yBAAyB,UAAA,GAC3B,iBAAA,CAAkB,WAAW,CAAA,GAC7B,iBAAiB,WAAW,CAAA;AAEhC,IAAA,IAAI,CAAC,sBAAA,EAAwB;AAC3B,MAAA,uBAAOD,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,IACrB;AAAA,EACF;AAGA,EAAA,IAAI,KAAA,IAAS,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG;AAC7B,IAAA,MAAM,mBAAmB,UAAA,GAAa,WAAA,CAAY,KAAK,CAAA,GAAI,WAAW,KAAK,CAAA;AAE3E,IAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,uBAAOD,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,IACrB;AAAA,EACF;AAEA,EAAA,uBAAOD,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAS,CAAA;AACrB","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 if (existingToken && !tokenManager.isExpired()) {\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 { AuthResponse } from '@swr-login/core';\nimport { isMultiStepPlugin } from '@swr-login/core';\nimport { useCallback, useState } from 'react';\nimport { useAuthContext } from '../context';\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, 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 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, 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 } from '@swr-login/core';\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 * 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, config } = useAuthContext();\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 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: true,\n revalidateOnReconnect: true,\n shouldRetryOnError: false,\n });\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 mutate,\n };\n}\n\n/** SWR cache key used for user data. Exported for advanced usage. */\nexport { AUTH_KEY };\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 { 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, User, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
2
+ import { SWRLoginConfig, AuthResponse, AuthInjector, User, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
3
3
  import React from 'react';
4
4
 
5
5
  interface SWRLoginProviderProps {
@@ -67,6 +67,105 @@ interface UseLoginReturn<TCredentials = unknown> {
67
67
  */
68
68
  declare function useLogin<TCredentials = unknown>(pluginName?: string): UseLoginReturn<TCredentials>;
69
69
 
70
+ interface UseMultiStepLoginReturn<TCredentials = unknown> {
71
+ /** 当前步骤索引(从 0 开始,-1 表示未开始) */
72
+ currentStep: number;
73
+ /** 当前步骤名称(未开始时为 null) */
74
+ currentStepName: string | null;
75
+ /** 总步骤数 */
76
+ totalSteps: number;
77
+ /** 启动流程(执行第一步) */
78
+ start: (credentials: TCredentials) => Promise<void>;
79
+ /** 继续下一步 */
80
+ next: (input: unknown) => Promise<void>;
81
+ /** 回到上一步(仅 UI 层回退,不重新执行步骤逻辑) */
82
+ back: () => void;
83
+ /** 取消整个流程,重置状态 */
84
+ cancel: () => void;
85
+ /** 当前步骤的输出数据(传给 UI 渲染) */
86
+ stepData: unknown;
87
+ /** 是否正在执行当前步骤 */
88
+ isLoading: boolean;
89
+ /** 当前步骤的错误 */
90
+ error: Error | null;
91
+ /** 重置错误状态 */
92
+ reset: () => void;
93
+ /** 流程是否已完成 */
94
+ isComplete: boolean;
95
+ /** 最终的 AuthResponse(流程完成后可用) */
96
+ authResponse: AuthResponse | null;
97
+ }
98
+ /**
99
+ * Hook 用于驱动多步骤登录插件的流程。
100
+ *
101
+ * 提供步骤状态管理、前进/后退/取消等操作,自动在最后一步完成后
102
+ * 调用 finalizeAuth 并更新全局登录态。
103
+ *
104
+ * @param pluginName - 多步骤插件名称
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * function ClassCodeLoginFlow() {
109
+ * const {
110
+ * currentStepName, stepData, start, next, back, isLoading, error, isComplete,
111
+ * } = useMultiStepLogin<{ classCode: string; loginCode: string }>('class-code');
112
+ *
113
+ * if (isComplete) {
114
+ * return <Redirect to="/dashboard" />;
115
+ * }
116
+ *
117
+ * // 步骤 1:输入班级码
118
+ * if (currentStepName === 'verify-code' || currentStep === -1) {
119
+ * return <ClassCodeForm onSubmit={start} isLoading={isLoading} error={error} />;
120
+ * }
121
+ *
122
+ * // 步骤 2:选择学生
123
+ * if (currentStepName === 'select-student') {
124
+ * return (
125
+ * <StudentList
126
+ * students={stepData.students}
127
+ * onSelect={(userId) => next({ userId, classLoginToken: stepData.classLoginToken })}
128
+ * onBack={back}
129
+ * isLoading={isLoading}
130
+ * />
131
+ * );
132
+ * }
133
+ *
134
+ * // 步骤 3:获取 Token(自动执行,显示 loading)
135
+ * if (currentStepName === 'get-token') {
136
+ * return <LoadingSpinner text="登录中..." />;
137
+ * }
138
+ * }
139
+ * ```
140
+ */
141
+ declare function useMultiStepLogin<TCredentials = unknown>(pluginName: string): UseMultiStepLoginReturn<TCredentials>;
142
+
143
+ /**
144
+ * Hook 用于从外部注入登录态到 swr-login 体系。
145
+ *
146
+ * 适用场景:
147
+ * - 多步骤登录流程(如班级码登录、MFA)
148
+ * - 第三方 SDK 登录完成后同步状态
149
+ * - iframe / WebView 登录回调
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * function ExternalLoginCallback() {
154
+ * const { injectAuth } = useAuthInjector();
155
+ *
156
+ * const handleLoginComplete = async (token: string, user: User) => {
157
+ * await injectAuth({
158
+ * user,
159
+ * accessToken: token,
160
+ * expiresAt: Date.now() + 86400000,
161
+ * });
162
+ * router.push('/dashboard');
163
+ * };
164
+ * }
165
+ * ```
166
+ */
167
+ declare function useAuthInjector(): AuthInjector;
168
+
70
169
  declare const AUTH_KEY = "__swr_login_user__";
71
170
  interface UseUserReturn<T extends User = User> {
72
171
  /** Current authenticated user, or null if not logged in */
@@ -247,4 +346,4 @@ interface AuthContextValue {
247
346
  */
248
347
  declare function useAuthContext(): AuthContextValue;
249
348
 
250
- export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UsePermissionReturn, type UseUserReturn, useAuthContext, useLogin, useLogout, usePermission, useSession, useUser };
349
+ export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UseMultiStepLoginReturn, type UsePermissionReturn, type UseUserReturn, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser };
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, User, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
2
+ import { SWRLoginConfig, AuthResponse, AuthInjector, User, PluginManager, TokenManager, AuthEventEmitter, AuthStateMachine, BroadcastSync } from '@swr-login/core';
3
3
  import React from 'react';
4
4
 
5
5
  interface SWRLoginProviderProps {
@@ -67,6 +67,105 @@ interface UseLoginReturn<TCredentials = unknown> {
67
67
  */
68
68
  declare function useLogin<TCredentials = unknown>(pluginName?: string): UseLoginReturn<TCredentials>;
69
69
 
70
+ interface UseMultiStepLoginReturn<TCredentials = unknown> {
71
+ /** 当前步骤索引(从 0 开始,-1 表示未开始) */
72
+ currentStep: number;
73
+ /** 当前步骤名称(未开始时为 null) */
74
+ currentStepName: string | null;
75
+ /** 总步骤数 */
76
+ totalSteps: number;
77
+ /** 启动流程(执行第一步) */
78
+ start: (credentials: TCredentials) => Promise<void>;
79
+ /** 继续下一步 */
80
+ next: (input: unknown) => Promise<void>;
81
+ /** 回到上一步(仅 UI 层回退,不重新执行步骤逻辑) */
82
+ back: () => void;
83
+ /** 取消整个流程,重置状态 */
84
+ cancel: () => void;
85
+ /** 当前步骤的输出数据(传给 UI 渲染) */
86
+ stepData: unknown;
87
+ /** 是否正在执行当前步骤 */
88
+ isLoading: boolean;
89
+ /** 当前步骤的错误 */
90
+ error: Error | null;
91
+ /** 重置错误状态 */
92
+ reset: () => void;
93
+ /** 流程是否已完成 */
94
+ isComplete: boolean;
95
+ /** 最终的 AuthResponse(流程完成后可用) */
96
+ authResponse: AuthResponse | null;
97
+ }
98
+ /**
99
+ * Hook 用于驱动多步骤登录插件的流程。
100
+ *
101
+ * 提供步骤状态管理、前进/后退/取消等操作,自动在最后一步完成后
102
+ * 调用 finalizeAuth 并更新全局登录态。
103
+ *
104
+ * @param pluginName - 多步骤插件名称
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * function ClassCodeLoginFlow() {
109
+ * const {
110
+ * currentStepName, stepData, start, next, back, isLoading, error, isComplete,
111
+ * } = useMultiStepLogin<{ classCode: string; loginCode: string }>('class-code');
112
+ *
113
+ * if (isComplete) {
114
+ * return <Redirect to="/dashboard" />;
115
+ * }
116
+ *
117
+ * // 步骤 1:输入班级码
118
+ * if (currentStepName === 'verify-code' || currentStep === -1) {
119
+ * return <ClassCodeForm onSubmit={start} isLoading={isLoading} error={error} />;
120
+ * }
121
+ *
122
+ * // 步骤 2:选择学生
123
+ * if (currentStepName === 'select-student') {
124
+ * return (
125
+ * <StudentList
126
+ * students={stepData.students}
127
+ * onSelect={(userId) => next({ userId, classLoginToken: stepData.classLoginToken })}
128
+ * onBack={back}
129
+ * isLoading={isLoading}
130
+ * />
131
+ * );
132
+ * }
133
+ *
134
+ * // 步骤 3:获取 Token(自动执行,显示 loading)
135
+ * if (currentStepName === 'get-token') {
136
+ * return <LoadingSpinner text="登录中..." />;
137
+ * }
138
+ * }
139
+ * ```
140
+ */
141
+ declare function useMultiStepLogin<TCredentials = unknown>(pluginName: string): UseMultiStepLoginReturn<TCredentials>;
142
+
143
+ /**
144
+ * Hook 用于从外部注入登录态到 swr-login 体系。
145
+ *
146
+ * 适用场景:
147
+ * - 多步骤登录流程(如班级码登录、MFA)
148
+ * - 第三方 SDK 登录完成后同步状态
149
+ * - iframe / WebView 登录回调
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * function ExternalLoginCallback() {
154
+ * const { injectAuth } = useAuthInjector();
155
+ *
156
+ * const handleLoginComplete = async (token: string, user: User) => {
157
+ * await injectAuth({
158
+ * user,
159
+ * accessToken: token,
160
+ * expiresAt: Date.now() + 86400000,
161
+ * });
162
+ * router.push('/dashboard');
163
+ * };
164
+ * }
165
+ * ```
166
+ */
167
+ declare function useAuthInjector(): AuthInjector;
168
+
70
169
  declare const AUTH_KEY = "__swr_login_user__";
71
170
  interface UseUserReturn<T extends User = User> {
72
171
  /** Current authenticated user, or null if not logged in */
@@ -247,4 +346,4 @@ interface AuthContextValue {
247
346
  */
248
347
  declare function useAuthContext(): AuthContextValue;
249
348
 
250
- export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UsePermissionReturn, type UseUserReturn, useAuthContext, useLogin, useLogout, usePermission, useSession, useUser };
349
+ export { AUTH_KEY, type AuthContextValue, AuthGuard, type AuthGuardProps, SWRLoginProvider, type SWRLoginProviderProps, type SessionInfo, type UseLoginOptions, type UseLoginReturn, type UseLogoutOptions, type UseLogoutReturn, type UseMultiStepLoginReturn, type UsePermissionReturn, type UseUserReturn, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AuthEventEmitter, AuthStateMachine, TokenManager, PluginManager, BroadcastSync, isTokenExpired } from '@swr-login/core';
1
+ import { AuthEventEmitter, AuthStateMachine, TokenManager, PluginManager, BroadcastSync, isMultiStepPlugin, isTokenExpired } from '@swr-login/core';
2
2
  import { createContext, useContext, useRef, useMemo, useEffect, useState, useCallback } from 'react';
3
3
  import { jsx, Fragment } from 'react/jsx-runtime';
4
4
  import useSWR, { mutate } from 'swr';
@@ -134,6 +134,16 @@ function useLogin(pluginName) {
134
134
  setIsLoading(true);
135
135
  setError(null);
136
136
  stateMachine.transition("authenticating");
137
+ const targetPlugin = pluginManager.getPlugin(resolvedPlugin);
138
+ if (targetPlugin && isMultiStepPlugin(targetPlugin)) {
139
+ const err = new Error(
140
+ `[swr-login] Plugin "${resolvedPlugin}" is a multi-step plugin. Use useMultiStepLogin() instead of useLogin() for multi-step login flows.`
141
+ );
142
+ setError(err);
143
+ stateMachine.transition("error");
144
+ setIsLoading(false);
145
+ throw err;
146
+ }
137
147
  try {
138
148
  const response = await pluginManager.login(resolvedPlugin, resolvedCredentials);
139
149
  stateMachine.transition("authenticated");
@@ -157,6 +167,133 @@ function useLogin(pluginName) {
157
167
  }, []);
158
168
  return { login, isLoading, error, reset };
159
169
  }
170
+ function useMultiStepLogin(pluginName) {
171
+ const { pluginManager, stateMachine, config } = useAuthContext();
172
+ const [currentStep, setCurrentStep] = useState(-1);
173
+ const [stepData, setStepData] = useState(null);
174
+ const [isLoading, setIsLoading] = useState(false);
175
+ const [error, setError] = useState(null);
176
+ const [isComplete, setIsComplete] = useState(false);
177
+ const [authResponse, setAuthResponse] = useState(null);
178
+ const stepOutputsRef = useRef([]);
179
+ const steps = pluginManager.getSteps(pluginName);
180
+ const totalSteps = steps?.length ?? 0;
181
+ const currentStepName = currentStep >= 0 && steps ? steps[currentStep]?.name ?? null : null;
182
+ const executeStepAt = useCallback(
183
+ async (stepIndex, input) => {
184
+ setIsLoading(true);
185
+ setError(null);
186
+ try {
187
+ const output = await pluginManager.executeStep(pluginName, stepIndex, input);
188
+ stepOutputsRef.current[stepIndex] = output;
189
+ setStepData(output);
190
+ setCurrentStep(stepIndex);
191
+ if (stepIndex === totalSteps - 1) {
192
+ const response = await pluginManager.finalizeMultiStep(pluginName, output);
193
+ stateMachine.transition("authenticated");
194
+ if (config.cacheAdapter) {
195
+ await config.cacheAdapter.setUser(response.user);
196
+ }
197
+ setAuthResponse(response);
198
+ setIsComplete(true);
199
+ }
200
+ } catch (err) {
201
+ const authError = err instanceof Error ? err : new Error("Step execution failed");
202
+ setError(authError);
203
+ throw authError;
204
+ } finally {
205
+ setIsLoading(false);
206
+ }
207
+ },
208
+ [pluginManager, pluginName, totalSteps, stateMachine, config]
209
+ );
210
+ const start = useCallback(
211
+ async (credentials) => {
212
+ stepOutputsRef.current = [];
213
+ setCurrentStep(-1);
214
+ setStepData(null);
215
+ setIsComplete(false);
216
+ setAuthResponse(null);
217
+ setError(null);
218
+ stateMachine.transition("authenticating");
219
+ await executeStepAt(0, credentials);
220
+ },
221
+ [executeStepAt, stateMachine]
222
+ );
223
+ const next = useCallback(
224
+ async (input) => {
225
+ const nextIndex = currentStep + 1;
226
+ if (nextIndex >= totalSteps) {
227
+ throw new Error("[swr-login] No more steps to execute.");
228
+ }
229
+ await executeStepAt(nextIndex, input);
230
+ },
231
+ [currentStep, totalSteps, executeStepAt]
232
+ );
233
+ const back = useCallback(() => {
234
+ if (currentStep > 0) {
235
+ const prevStep = currentStep - 1;
236
+ setCurrentStep(prevStep);
237
+ setStepData(stepOutputsRef.current[prevStep] ?? null);
238
+ setError(null);
239
+ }
240
+ }, [currentStep]);
241
+ const cancel = useCallback(() => {
242
+ setCurrentStep(-1);
243
+ setStepData(null);
244
+ setIsLoading(false);
245
+ setError(null);
246
+ setIsComplete(false);
247
+ setAuthResponse(null);
248
+ stepOutputsRef.current = [];
249
+ stateMachine.transition("unauthenticated");
250
+ }, [stateMachine]);
251
+ const reset = useCallback(() => {
252
+ setError(null);
253
+ }, []);
254
+ return {
255
+ currentStep,
256
+ currentStepName,
257
+ totalSteps,
258
+ start,
259
+ next,
260
+ back,
261
+ cancel,
262
+ stepData,
263
+ isLoading,
264
+ error,
265
+ reset,
266
+ isComplete,
267
+ authResponse
268
+ };
269
+ }
270
+ function useAuthInjector() {
271
+ const { tokenManager, emitter, stateMachine, config } = useAuthContext();
272
+ const injectAuth = useCallback(
273
+ async (response) => {
274
+ tokenManager.setTokens({
275
+ accessToken: response.accessToken,
276
+ refreshToken: response.refreshToken,
277
+ expiresAt: response.expiresAt
278
+ });
279
+ stateMachine.transition("authenticated");
280
+ emitter.emit("login", { user: response.user });
281
+ if (config.cacheAdapter) {
282
+ await config.cacheAdapter.setUser(response.user);
283
+ }
284
+ },
285
+ [tokenManager, emitter, stateMachine, config]
286
+ );
287
+ const injectLogout = useCallback(async () => {
288
+ tokenManager.clearTokens();
289
+ stateMachine.transition("unauthenticated");
290
+ emitter.emit("logout", void 0);
291
+ if (config.cacheAdapter) {
292
+ await config.cacheAdapter.clearUser();
293
+ }
294
+ }, [tokenManager, emitter, stateMachine, config]);
295
+ return { injectAuth, injectLogout };
296
+ }
160
297
  var AUTH_KEY = "__swr_login_user__";
161
298
  function useUser() {
162
299
  const { tokenManager, config } = useAuthContext();
@@ -325,6 +462,6 @@ function AuthGuard({
325
462
  return /* @__PURE__ */ jsx(Fragment, { children });
326
463
  }
327
464
 
328
- export { AUTH_KEY, AuthGuard, SWRLoginProvider, useAuthContext, useLogin, useLogout, usePermission, useSession, useUser };
465
+ export { AUTH_KEY, AuthGuard, SWRLoginProvider, useAuthContext, useAuthInjector, useLogin, useLogout, useMultiStepLogin, usePermission, useSession, useUser };
329
466
  //# sourceMappingURL=index.js.map
330
467
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useLogin.ts","../src/hooks/useUser.ts","../src/hooks/useLogout.ts","../src/hooks/useSession.ts","../src/hooks/usePermission.ts","../src/components/AuthGuard.tsx"],"names":["mutate","useState","useCallback","useMemo","jsx"],"mappings":"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","file":"index.js","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 if (existingToken && !tokenManager.isExpired()) {\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 { AuthResponse } from '@swr-login/core';\nimport { useCallback, useState } from 'react';\nimport { useAuthContext } from '../context';\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, 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 try {\n const response = await pluginManager.login(resolvedPlugin, resolvedCredentials);\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, stateMachine, config, pluginName],\n );\n\n const reset = useCallback(() => {\n setError(null);\n }, []);\n\n return { login, isLoading, error, reset };\n}\n","import type { User } from '@swr-login/core';\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 * 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, config } = useAuthContext();\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 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: true,\n revalidateOnReconnect: true,\n shouldRetryOnError: false,\n });\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 mutate,\n };\n}\n\n/** SWR cache key used for user data. Exported for advanced usage. */\nexport { AUTH_KEY };\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 { 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/useLogin.ts","../src/hooks/useMultiStepLogin.ts","../src/hooks/useAuthInjector.ts","../src/hooks/useUser.ts","../src/hooks/useLogout.ts","../src/hooks/useSession.ts","../src/hooks/usePermission.ts","../src/components/AuthGuard.tsx"],"names":["useState","useRef","useCallback","mutate","useMemo","jsx"],"mappings":"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","file":"index.js","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 if (existingToken && !tokenManager.isExpired()) {\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 { AuthResponse } from '@swr-login/core';\nimport { isMultiStepPlugin } from '@swr-login/core';\nimport { useCallback, useState } from 'react';\nimport { useAuthContext } from '../context';\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, 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 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, 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 } from '@swr-login/core';\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 * 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, config } = useAuthContext();\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 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: true,\n revalidateOnReconnect: true,\n shouldRetryOnError: false,\n });\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 mutate,\n };\n}\n\n/** SWR cache key used for user data. Exported for advanced usage. */\nexport { AUTH_KEY };\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 { 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swr-login/react",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "React bindings for swr-login: Provider, Hooks, and AuthGuard component",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -27,7 +27,7 @@
27
27
  "swr": ">=2.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@swr-login/core": "0.1.0"
30
+ "@swr-login/core": "0.3.0"
31
31
  },
32
32
  "keywords": [
33
33
  "swr",
@@ -39,11 +39,11 @@
39
39
  "license": "MIT",
40
40
  "repository": {
41
41
  "type": "git",
42
- "url": "https://github.com/swr-login/swr-login",
42
+ "url": "https://github.com/tobytovi/swr-login",
43
43
  "directory": "packages/react"
44
44
  },
45
- "homepage": "https://github.com/swr-login/swr-login#readme",
46
- "bugs": "https://github.com/swr-login/swr-login/issues",
45
+ "homepage": "https://github.com/tobytovi/swr-login#readme",
46
+ "bugs": "https://github.com/tobytovi/swr-login/issues",
47
47
  "publishConfig": {
48
48
  "access": "public"
49
49
  },
@@ -53,7 +53,7 @@
53
53
  "scripts": {
54
54
  "build": "tsup",
55
55
  "dev": "tsup --watch",
56
- "test": "vitest run",
56
+ "test": "vitest run --passWithNoTests",
57
57
  "clean": "rm -rf dist"
58
58
  }
59
59
  }