@insforge/react 1.1.4-pkce.0 → 1.1.5

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["createContext","useContext","InsforgeContext","useState","useEffect"],"mappings":"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","file":"hooks.cjs","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\nimport {\n InsforgeContext,\n InsforgeAuthStateContext,\n InsforgeMethodsContext,\n InsforgeConfigContext,\n type InsforgeContextValue,\n type InsforgeAuthState,\n type InsforgeAuthMethods,\n type InsforgeAuthConfig,\n} from '../contexts';\nimport { StyleProvider } from '../styles/StyleProvider';\nimport { InsForgeClient } from '@insforge/sdk';\n\nexport interface InitialAuthState {\n user?: InsforgeUser | null;\n userId?: string | null;\n}\n\nexport interface InsforgeProviderProps {\n /**\n * The base URL of the InsForge backend.\n * @deprecated This prop is no longer used.\n * Use the client prop instead.\n */\n baseUrl?: string;\n children: ReactNode;\n /**\n * The InsForge SDK client instance.\n */\n client: InsForgeClient;\n /**\n * URL to redirect to after successful sign in (when token is detected in URL)\n * @default '/'\n */\n afterSignInUrl?: string;\n onAuthChange?: (user: InsforgeUser | null) => void;\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\n onSignOut?: () => Promise<void>;\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\n /**\n * Initial auth state from server (for SSR hydration)\n * @internal - Not intended for public use, used by Next.js package\n */\n initialState?: InitialAuthState;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Uses singleton InsforgeManager to manage state across packages.\n * Context only subscribes to Manager and triggers React re-renders.\n *\n * Architecture (pattern learned from Clerk):\n * - InsforgeMethodsContext: Stable method references (NEVER change)\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\n * - InsforgeConfigContext: Static configuration values\n * - InsforgeContext: Combined context for backward compatibility\n *\n * This architecture prevents useEffect re-runs when auth state changes,\n * solving the \"duplicate request\" bug in components like VerifyEmail.\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n * import { createClient } from '@insforge/sdk';\n *\n * const client = createClient({\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\n * });\n *\n * export default function MyApp() {\n * return (\n * <InsforgeProvider client={client}>\n * <App />\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * client={client}\n * afterSignInUrl=\"/dashboard\"\n * onSignIn={async (authToken) => {\n * await signIn(authToken);\n * }}\n * onSignOut={async () => {\n * await signOut();\n * }}\n * >\n * {children}\n * </InsforgeProvider>\n * ```\n */\nexport function InsforgeProviderCore({\n children,\n client,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n initialState,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager: InsforgeManager = useMemo(\n () =>\n InsforgeManager.getInstance({\n client,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n }),\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\n );\n\n // Set initialState if provided (from Server Component)\n // This must happen during render, before useState initialization\n if (initialState) {\n const currentState = manager.getState();\n if (currentState.userId === undefined && initialState.userId !== undefined) {\n manager.setInitialState(initialState);\n }\n }\n\n // Subscribe to Manager state\n // Start with initial state from manager (will include initialState if set above)\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n // Subscribe to state changes\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\n setState(newState);\n });\n\n // Initialize auth state\n void manager.initialize();\n\n return () => {\n unsubscribe();\n };\n }, [manager]);\n\n // ============================================\n // STABLE METHOD REFERENCES\n // These NEVER change once created (only depend on manager)\n // This prevents useEffect re-runs when state changes\n // ============================================\n const stableMethods = useMemo<InsforgeAuthMethods>(\n () => ({\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\n manager.signUp(email, password, options),\n signOut: () => manager.signOut(),\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\n manager.resendVerificationEmail(email, options),\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\n resetPassword: (token: string, newPassword: string) =>\n manager.resetPassword(token, newPassword),\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\n exchangeResetPasswordToken: (email: string, code: string) =>\n manager.exchangeResetPasswordToken(email, code),\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\n manager.loginWithOAuth(provider, redirectTo),\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // STABLE CONFIG REFERENCES\n // These rarely change after initialization\n // ============================================\n const stableConfig = useMemo<InsforgeAuthConfig>(\n () => ({\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // REACTIVE AUTH STATE\n // This updates when auth state changes (sign in/out)\n // ============================================\n const authState = useMemo<InsforgeAuthState>(\n () => ({\n user: state.user,\n userId: state.userId,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n }),\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\n );\n\n // ============================================\n // COMBINED CONTEXT VALUE (for backward compatibility)\n // Uses stable method references from stableMethods\n // ============================================\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // Reactive state\n ...authState,\n // Stable methods (references don't change!)\n ...stableMethods,\n // Stable config\n ...stableConfig,\n }),\n [authState, stableMethods, stableConfig]\n );\n\n // Multi-context architecture: each context has its own responsibility\n // - MethodsContext: stable method references\n // - AuthStateContext: reactive auth state\n // - ConfigContext: static configuration\n // - InsforgeContext: combined for backward compatibility\n return (\n <InsforgeMethodsContext.Provider value={stableMethods}>\n <InsforgeConfigContext.Provider value={stableConfig}>\n <InsforgeAuthStateContext.Provider value={authState}>\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\n </InsforgeAuthStateContext.Provider>\n </InsforgeConfigContext.Provider>\n </InsforgeMethodsContext.Provider>\n );\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <StyleProvider>\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\n </StyleProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\n *\n * Works seamlessly across packages thanks to singleton Manager.\n * Context instance is guaranteed to be consistent.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { user, isSignedIn, signOut } = useInsforge();\n *\n * if (!isSignedIn) return <SignIn />;\n *\n * return (\n * <div>\n * <p>Welcome {user.email}</p>\n * <button onClick={signOut}>Sign Out</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useInsforge(): InsforgeContextValue {\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\n\n if (!context) {\n return {\n user: undefined,\n userId: undefined,\n isLoaded: false,\n isSignedIn: undefined,\n setUser: () => {},\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\n signOut: () => Promise.resolve(),\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\n resendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\n loginWithOAuth: () => Promise.resolve(),\n getPublicAuthConfig: () => Promise.resolve(null),\n baseUrl: '',\n afterSignInUrl: '/',\n };\n }\n\n return context;\n}\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
1
+ {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["createContext","useContext","InsforgeContext","useState","useEffect"],"mappings":"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","file":"hooks.cjs","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useContext","useState","useEffect"],"mappings":"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","file":"hooks.js","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\nimport {\n InsforgeContext,\n InsforgeAuthStateContext,\n InsforgeMethodsContext,\n InsforgeConfigContext,\n type InsforgeContextValue,\n type InsforgeAuthState,\n type InsforgeAuthMethods,\n type InsforgeAuthConfig,\n} from '../contexts';\nimport { StyleProvider } from '../styles/StyleProvider';\nimport { InsForgeClient } from '@insforge/sdk';\n\nexport interface InitialAuthState {\n user?: InsforgeUser | null;\n userId?: string | null;\n}\n\nexport interface InsforgeProviderProps {\n /**\n * The base URL of the InsForge backend.\n * @deprecated This prop is no longer used.\n * Use the client prop instead.\n */\n baseUrl?: string;\n children: ReactNode;\n /**\n * The InsForge SDK client instance.\n */\n client: InsForgeClient;\n /**\n * URL to redirect to after successful sign in (when token is detected in URL)\n * @default '/'\n */\n afterSignInUrl?: string;\n onAuthChange?: (user: InsforgeUser | null) => void;\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\n onSignOut?: () => Promise<void>;\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\n /**\n * Initial auth state from server (for SSR hydration)\n * @internal - Not intended for public use, used by Next.js package\n */\n initialState?: InitialAuthState;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Uses singleton InsforgeManager to manage state across packages.\n * Context only subscribes to Manager and triggers React re-renders.\n *\n * Architecture (pattern learned from Clerk):\n * - InsforgeMethodsContext: Stable method references (NEVER change)\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\n * - InsforgeConfigContext: Static configuration values\n * - InsforgeContext: Combined context for backward compatibility\n *\n * This architecture prevents useEffect re-runs when auth state changes,\n * solving the \"duplicate request\" bug in components like VerifyEmail.\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n * import { createClient } from '@insforge/sdk';\n *\n * const client = createClient({\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\n * });\n *\n * export default function MyApp() {\n * return (\n * <InsforgeProvider client={client}>\n * <App />\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * client={client}\n * afterSignInUrl=\"/dashboard\"\n * onSignIn={async (authToken) => {\n * await signIn(authToken);\n * }}\n * onSignOut={async () => {\n * await signOut();\n * }}\n * >\n * {children}\n * </InsforgeProvider>\n * ```\n */\nexport function InsforgeProviderCore({\n children,\n client,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n initialState,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager: InsforgeManager = useMemo(\n () =>\n InsforgeManager.getInstance({\n client,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n }),\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\n );\n\n // Set initialState if provided (from Server Component)\n // This must happen during render, before useState initialization\n if (initialState) {\n const currentState = manager.getState();\n if (currentState.userId === undefined && initialState.userId !== undefined) {\n manager.setInitialState(initialState);\n }\n }\n\n // Subscribe to Manager state\n // Start with initial state from manager (will include initialState if set above)\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n // Subscribe to state changes\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\n setState(newState);\n });\n\n // Initialize auth state\n void manager.initialize();\n\n return () => {\n unsubscribe();\n };\n }, [manager]);\n\n // ============================================\n // STABLE METHOD REFERENCES\n // These NEVER change once created (only depend on manager)\n // This prevents useEffect re-runs when state changes\n // ============================================\n const stableMethods = useMemo<InsforgeAuthMethods>(\n () => ({\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\n manager.signUp(email, password, options),\n signOut: () => manager.signOut(),\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\n manager.resendVerificationEmail(email, options),\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\n resetPassword: (token: string, newPassword: string) =>\n manager.resetPassword(token, newPassword),\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\n exchangeResetPasswordToken: (email: string, code: string) =>\n manager.exchangeResetPasswordToken(email, code),\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\n manager.loginWithOAuth(provider, redirectTo),\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // STABLE CONFIG REFERENCES\n // These rarely change after initialization\n // ============================================\n const stableConfig = useMemo<InsforgeAuthConfig>(\n () => ({\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // REACTIVE AUTH STATE\n // This updates when auth state changes (sign in/out)\n // ============================================\n const authState = useMemo<InsforgeAuthState>(\n () => ({\n user: state.user,\n userId: state.userId,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n }),\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\n );\n\n // ============================================\n // COMBINED CONTEXT VALUE (for backward compatibility)\n // Uses stable method references from stableMethods\n // ============================================\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // Reactive state\n ...authState,\n // Stable methods (references don't change!)\n ...stableMethods,\n // Stable config\n ...stableConfig,\n }),\n [authState, stableMethods, stableConfig]\n );\n\n // Multi-context architecture: each context has its own responsibility\n // - MethodsContext: stable method references\n // - AuthStateContext: reactive auth state\n // - ConfigContext: static configuration\n // - InsforgeContext: combined for backward compatibility\n return (\n <InsforgeMethodsContext.Provider value={stableMethods}>\n <InsforgeConfigContext.Provider value={stableConfig}>\n <InsforgeAuthStateContext.Provider value={authState}>\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\n </InsforgeAuthStateContext.Provider>\n </InsforgeConfigContext.Provider>\n </InsforgeMethodsContext.Provider>\n );\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <StyleProvider>\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\n </StyleProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\n *\n * Works seamlessly across packages thanks to singleton Manager.\n * Context instance is guaranteed to be consistent.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { user, isSignedIn, signOut } = useInsforge();\n *\n * if (!isSignedIn) return <SignIn />;\n *\n * return (\n * <div>\n * <p>Welcome {user.email}</p>\n * <button onClick={signOut}>Sign Out</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useInsforge(): InsforgeContextValue {\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\n\n if (!context) {\n return {\n user: undefined,\n userId: undefined,\n isLoaded: false,\n isSignedIn: undefined,\n setUser: () => {},\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\n signOut: () => Promise.resolve(),\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\n resendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\n loginWithOAuth: () => Promise.resolve(),\n getPublicAuthConfig: () => Promise.resolve(null),\n baseUrl: '',\n afterSignInUrl: '/',\n };\n }\n\n return context;\n}\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
1
+ {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useContext","useState","useEffect"],"mappings":"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","file":"hooks.js","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
package/dist/index.cjs CHANGED
@@ -4186,9 +4186,16 @@ function SignIn({ onError, ...uiProps }) {
4186
4186
  }
4187
4187
  throw new Error(result.error);
4188
4188
  }
4189
- const { user, redirectTo } = result;
4189
+ const { user, accessToken, redirectTo, csrfToken } = result;
4190
4190
  if (user) {
4191
4191
  const finalUrl = new URL(redirectTo || redirectUrl || "", window.location.origin);
4192
+ finalUrl.searchParams.set("access_token", accessToken);
4193
+ finalUrl.searchParams.set("user_id", user.id);
4194
+ finalUrl.searchParams.set("email", user.email);
4195
+ finalUrl.searchParams.set("name", user.profile?.name || "");
4196
+ if (csrfToken) {
4197
+ finalUrl.searchParams.set("csrf_token", csrfToken);
4198
+ }
4192
4199
  window.location.href = finalUrl.toString();
4193
4200
  }
4194
4201
  } catch (err) {
@@ -4209,6 +4216,13 @@ function SignIn({ onError, ...uiProps }) {
4209
4216
  throw new Error("Verification failed");
4210
4217
  }
4211
4218
  const finalUrl = new URL(result.redirectTo || redirectUrl || "", window.location.origin);
4219
+ finalUrl.searchParams.set("access_token", result.accessToken);
4220
+ finalUrl.searchParams.set("user_id", result.user.id);
4221
+ finalUrl.searchParams.set("email", result.user.email);
4222
+ finalUrl.searchParams.set("name", result.user.profile?.name || "");
4223
+ if (result.csrfToken) {
4224
+ finalUrl.searchParams.set("csrf_token", result.csrfToken);
4225
+ }
4212
4226
  window.location.href = finalUrl.toString();
4213
4227
  } catch (err) {
4214
4228
  const errorMessage = err instanceof Error ? err.message : "Invalid verification code";
@@ -4463,7 +4477,15 @@ function SignUp({ onError, emailRedirectTo, ...uiProps }) {
4463
4477
  return;
4464
4478
  }
4465
4479
  if (result.accessToken && result.user) {
4480
+ const csrfToken = result.csrfToken;
4466
4481
  const finalUrl = new URL(result.redirectTo || redirectUrl || "", window.location.origin);
4482
+ finalUrl.searchParams.set("access_token", result.accessToken);
4483
+ finalUrl.searchParams.set("user_id", result.user.id);
4484
+ finalUrl.searchParams.set("email", result.user.email);
4485
+ finalUrl.searchParams.set("name", result.user.profile?.name || "");
4486
+ if (csrfToken) {
4487
+ finalUrl.searchParams.set("csrf_token", csrfToken);
4488
+ }
4467
4489
  window.location.href = finalUrl.toString();
4468
4490
  }
4469
4491
  } catch (err) {
@@ -4484,6 +4506,13 @@ function SignUp({ onError, emailRedirectTo, ...uiProps }) {
4484
4506
  throw new Error("Verification failed");
4485
4507
  }
4486
4508
  const finalUrl = new URL(result.redirectTo || redirectUrl || "", window.location.origin);
4509
+ finalUrl.searchParams.set("access_token", result.accessToken);
4510
+ finalUrl.searchParams.set("user_id", result.user.id);
4511
+ finalUrl.searchParams.set("email", result.user.email);
4512
+ finalUrl.searchParams.set("name", result.user.profile?.name || "");
4513
+ if (result.csrfToken) {
4514
+ finalUrl.searchParams.set("csrf_token", result.csrfToken);
4515
+ }
4487
4516
  window.location.href = finalUrl.toString();
4488
4517
  } catch (err) {
4489
4518
  const errorMessage = err instanceof Error ? err.message : "Invalid verification code";