@insforge/react 0.5.7 → 0.5.9
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 +51 -68
- package/dist/atoms.cjs.map +1 -1
- package/dist/atoms.js.map +1 -1
- package/dist/components.cjs +44 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +50 -3
- package/dist/components.d.ts +50 -3
- package/dist/components.js +44 -2
- package/dist/components.js.map +1 -1
- package/dist/forms.cjs.map +1 -1
- package/dist/forms.js.map +1 -1
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +44 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +44 -92
- package/dist/index.js.map +1 -1
- package/package.json +1 -6
- package/dist/routes.cjs +0 -132
- package/dist/routes.cjs.map +0 -1
- package/dist/routes.d.cts +0 -78
- package/dist/routes.d.ts +0 -78
- package/dist/routes.js +0 -130
- package/dist/routes.js.map +0 -1
package/dist/hooks.cjs.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":["createContext","useContext","useState","useEffect"],"mappings":";;;;;;;AAG0BA,oBAAwC,IAAI;AC6DtE,IAAM,eAAA,GAAkBA,oBAAgD,MAAS,CAAA;AAogB1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUC,iBAAW,eAAe,CAAA;AAI1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA;AAAA,MACN,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY,KAAA;AAAA,MACZ,SAAS,MAAM;AAAA,MAAC,CAAA;AAAA,MAChB,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,SAAS,YAAY;AAAA,MAAC,CAAA;AAAA,MACtB,UAAA,EAAY,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MAC7C,YAAY,aAAa,EAAE,OAAA,EAAS,KAAA,EAAO,OAAO,UAAA,EAAW,CAAA;AAAA,MAC7D,uBAAuB,YAAY,IAAA;AAAA,MACnC,wBAAwB,YAAY,IAAA;AAAA,MACpC,eAAe,YAAY,IAAA;AAAA,MAC3B,aAAa,YAAY,IAAA;AAAA,MACzB,4BAA4B,aAAa,EAAE,OAAO,EAAE,OAAA,EAAS,YAAW,EAAE,CAAA;AAAA,MAC1E,gBAAgB,YAAY;AAAA,MAAC,CAAA;AAAA,MAC7B,qBAAqB,YAAY,IAAA;AAAA,MACjC,OAAA,EAAS,EAAA;AAAA,MACT,cAAA,EAAgB;AAAA,KAClB;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC7jBO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAS,QAAA,EAAU,UAAA,KAAe,WAAA,EAAY;AACtE,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,UAAU,UAAA,EAAW;AACzD;;;ACDO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,KAAY,WAAA,EAAY;AAC5D,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,EAAQ;AAC/C;ACLO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIC,eAA6C,IAAI,CAAA;AACrF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAS,KAAK,CAAA;AAE9C,EAAAC,gBAAU,MAAM;AACd,IAAA,eAAe,WAAA,GAAc;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,EAAoB;AACzC,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,aAAA,CAAc,MAAM,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,wDAAwD,CAAA;AACtE,QAAA,aAAA,CAAc,IAAI,CAAA;AAAA,MACpB;AACA,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB;AAEA,IAAA,WAAA,EAAY;AAAA,EACd,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAChC","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 }) => <a href={href}>{children}</a>,\n };\n }\n\n return adapter;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useRef,\n type ReactNode,\n} from 'react';\nimport { UserSchema, createClient } from '@insforge/sdk';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport {\n CreateSessionResponse,\n CreateUserResponse,\n GetPublicAuthConfigResponse,\n ResetPasswordResponse,\n} from '@insforge/shared-schemas';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\n\ninterface InsforgeContextValue {\n // Auth state\n user: InsforgeUser | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n\n // Auth methods\n setUser: (user: InsforgeUser | null) => void;\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 updateUser: (data: Partial<InsforgeUser>) => Promise<{ error: string } | null>;\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\n\n // Email verification methods\n sendVerificationEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n sendResetPasswordEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n resetPassword: (token: string, newPassword: string) => Promise<ResetPasswordResponse | null>;\n verifyEmail: (\n otp: string,\n email?: string\n ) => Promise<{\n accessToken: string;\n user?: UserSchema;\n redirectTo?: string;\n error?: { message: string };\n } | null>;\n exchangeResetPasswordToken: (\n email: string,\n code: string\n ) => Promise<{ token: string; expiresAt?: string } | { error: { message: string } }>;\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) => Promise<void>;\n // Public auth config\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\n // Base config\n baseUrl: string;\n afterSignInUrl: string;\n}\n\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(undefined);\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Manages user authentication state and provides all necessary context to child components.\n * Works with any React framework (Next.js, Vite, Remix, etc.).\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * afterSignInUrl=\"/dashboard\"\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 InsforgeProvider({\n children,\n baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Auth state\n const [user, setUser] = useState<InsforgeUser | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\n const hasProcessedCallbackRef = useRef(false);\n\n // Initialize SDK client with lazy initialization - only runs once\n const [insforge] = useState(() => createClient({ baseUrl }));\n\n // Load auth state - returns explicit success/error status\n const loadAuthState = useCallback(async (): Promise<{\n success: boolean;\n error?: string;\n }> => {\n try {\n // Use SDK's getCurrentSession() to check for existing session\n const sessionResult = insforge.auth.getCurrentSession();\n const session = sessionResult.data?.session;\n const token = session?.accessToken || null;\n\n if (!token) {\n // No token, user is not authenticated\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'no_session' };\n }\n\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n // Token is valid, update user state with fresh data\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n setIsLoaded(true);\n return { success: true };\n } else {\n // Token invalid or expired\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'invalid_token' };\n }\n } catch (error) {\n // Token validation failed\n console.error('[InsforgeProvider] Token validation failed:', error);\n\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Authentication failed',\n };\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n useEffect(() => {\n // Run loadAuthState only once on mount\n loadAuthState();\n\n // Capture the ref value when effect runs\n const intervalId = refreshIntervalRef.current;\n\n return () => {\n // Use the captured value in cleanup\n if (intervalId) {\n clearInterval(intervalId);\n }\n };\n }, [loadAuthState]);\n\n // Handle authentication callback from URL\n useEffect(() => {\n // Only run once and only after auth is loaded\n if (!isLoaded || hasProcessedCallbackRef.current) {\n return;\n }\n\n const searchParams = new URLSearchParams(window.location.search);\n const accessToken = searchParams.get('access_token');\n\n if (accessToken && !!user) {\n // Mark as processed\n hasProcessedCallbackRef.current = true;\n\n // Clean URL by removing query parameters\n const url = new URL(window.location.href);\n url.search = '';\n window.history.replaceState({}, '', url.toString());\n\n // Redirect to afterSignInUrl\n setTimeout(() => {\n window.location.href = afterSignInUrl;\n }, 100);\n }\n }, [isLoaded, user, afterSignInUrl]);\n\n const getPublicAuthConfig = useCallback(async () => {\n try {\n const result = await insforge.auth.getPublicAuthConfig();\n if (result.data) {\n return result.data;\n } else {\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\n return null;\n }\n } catch (error) {\n console.error('[InsforgeProvider] Failed to get public auth config:', error);\n return null;\n }\n }, [insforge]);\n\n /**\n * Helper function to handle successful authentication\n */\n const handleAuthSuccess = useCallback(\n async (authToken: string, fallbackUser?: { id?: string; email?: string; name?: string }) => {\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n\n // Try to sync token to cookie if function provided\n if (onSignIn) {\n try {\n await onSignIn(authToken);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error syncing token to cookie:', error.message);\n }\n }\n }\n } else if (fallbackUser) {\n // Fallback to basic user data if getCurrentUser fails\n const userData: InsforgeUser = {\n id: fallbackUser.id || '',\n email: fallbackUser.email || '',\n name: fallbackUser.name || '',\n avatarUrl: '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n }\n },\n [insforge, onAuthChange, onSignIn]\n );\n\n const signIn = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signInWithPassword({\n email,\n password,\n });\n\n if (sdkResult.data) {\n await handleAuthSuccess(\n sdkResult.data.accessToken || '',\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Invalid email or password',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signUp = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signUp({ email, password });\n\n if (sdkResult.data) {\n // Only set auth state if we got a token back (email verification might be required)\n if (sdkResult.data.accessToken) {\n await handleAuthSuccess(\n sdkResult.data.accessToken,\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n }\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Sign up failed',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signOut = useCallback(async () => {\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n // Clear refresh interval if exists\n if (refreshIntervalRef.current) {\n clearInterval(refreshIntervalRef.current);\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n const updateUser = useCallback(\n async (data: Partial<InsforgeUser>) => {\n if (!user) {\n return { error: 'No user signed in' };\n }\n\n const profileUpdate: Record<string, string | undefined> = {\n nickname: data.name,\n avatarUrl: data.avatarUrl,\n };\n\n const result = await insforge.auth.setProfile(profileUpdate);\n\n if (result.data) {\n const userResult = await insforge.auth.getCurrentUser();\n if (userResult.data) {\n const profile = userResult.data.profile;\n const updatedUser: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: profile?.nickname || '',\n avatarUrl: profile?.avatarUrl || '',\n };\n setUser(updatedUser);\n if (onAuthChange) {\n onAuthChange(updatedUser);\n }\n }\n return null;\n } else {\n return { error: result.error?.message || 'Failed to update user' };\n }\n },\n [user, onAuthChange, insforge]\n );\n\n const sendVerificationEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendVerificationEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const sendResetPasswordEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendResetPasswordEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const resetPassword = useCallback(\n async (token: string, newPassword: string) => {\n const sdkResult = await insforge.auth.resetPassword({\n newPassword,\n otp: token,\n });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const verifyEmail = useCallback(\n async (otp: string, email?: string) => {\n const sdkResult = await insforge.auth.verifyEmail({ otp, email: email || undefined });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n accessToken: '',\n error: {\n message: sdkResult.error?.message || 'Email verification failed',\n },\n };\n }\n },\n [insforge]\n );\n\n const exchangeResetPasswordToken = useCallback(\n async (email: string, code: string) => {\n const sdkResult = await insforge.auth.exchangeResetPasswordToken({ email, code });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n error: {\n message: sdkResult.error?.message || 'Failed to exchange reset password token',\n },\n };\n }\n },\n [insforge]\n );\n\n const loginWithOAuth = useCallback(\n async (provider: OAuthProvider, redirectTo: string) => {\n const sdkResult = await insforge.auth.signInWithOAuth({\n provider,\n redirectTo: redirectTo || window.location.origin + afterSignInUrl,\n });\n if (sdkResult.error) {\n throw new Error(sdkResult.error.message);\n }\n },\n [insforge, afterSignInUrl]\n );\n\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeContext.Provider\n value={{\n user,\n isLoaded,\n isSignedIn: !!user,\n setUser,\n signIn,\n signUp,\n signOut,\n updateUser,\n reloadAuth: loadAuthState,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n baseUrl,\n afterSignInUrl,\n }}\n >\n {children}\n </InsforgeContext.Provider>\n </NavigationProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\n\n // During SSR in Next.js, context might be undefined temporarily\n // Return a safe default that won't break SSR rendering\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\n setUser: () => {},\n signIn: async () => ({ error: 'SSR mode' }),\n signUp: async () => ({ error: 'SSR mode' }),\n signOut: async () => {},\n updateUser: async () => ({ error: 'SSR mode' }),\n reloadAuth: async () => ({ success: false, error: 'SSR mode' }),\n sendVerificationEmail: async () => null,\n sendResetPasswordEmail: async () => null,\n resetPassword: async () => null,\n verifyEmail: async () => null,\n exchangeResetPasswordToken: async () => ({ error: { message: 'SSR mode' } }),\n loginWithOAuth: async () => {},\n getPublicAuthConfig: async () => null,\n baseUrl: '',\n afterSignInUrl: '/',\n } as InsforgeContextValue;\n }\n\n return context;\n}\n","import { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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 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","useState","useEffect"],"mappings":";;;;;;;AAG0BA,oBAAwC,IAAI;AC6DtE,IAAM,eAAA,GAAkBA,oBAAgD,MAAS,CAAA;AAmgB1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUC,iBAAW,eAAe,CAAA;AAI1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA;AAAA,MACN,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY,KAAA;AAAA,MACZ,SAAS,MAAM;AAAA,MAAC,CAAA;AAAA,MAChB,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,SAAS,YAAY;AAAA,MAAC,CAAA;AAAA,MACtB,UAAA,EAAY,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MAC7C,YAAY,aAAa,EAAE,OAAA,EAAS,KAAA,EAAO,OAAO,UAAA,EAAW,CAAA;AAAA,MAC7D,uBAAuB,YAAY,IAAA;AAAA,MACnC,wBAAwB,YAAY,IAAA;AAAA,MACpC,eAAe,YAAY,IAAA;AAAA,MAC3B,aAAa,YAAY,IAAA;AAAA,MACzB,4BAA4B,aAAa,EAAE,OAAO,EAAE,OAAA,EAAS,YAAW,EAAE,CAAA;AAAA,MAC1E,gBAAgB,YAAY;AAAA,MAAC,CAAA;AAAA,MAC7B,qBAAqB,YAAY,IAAA;AAAA,MACjC,OAAA,EAAS,EAAA;AAAA,MACT,cAAA,EAAgB;AAAA,KAClB;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC5jBO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAS,QAAA,EAAU,UAAA,KAAe,WAAA,EAAY;AACtE,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,UAAU,UAAA,EAAW;AACzD;;;ACDO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,KAAY,WAAA,EAAY;AAC5D,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,EAAQ;AAC/C;ACLO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIC,eAA6C,IAAI,CAAA;AACrF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAS,KAAK,CAAA;AAE9C,EAAAC,gBAAU,MAAM;AACd,IAAA,eAAe,WAAA,GAAc;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,EAAoB;AACzC,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,aAAA,CAAc,MAAM,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,wDAAwD,CAAA;AACtE,QAAA,aAAA,CAAc,IAAI,CAAA;AAAA,MACpB;AACA,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB;AAEA,IAAA,WAAA,EAAY;AAAA,EACd,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAChC","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 }) => <a href={href}>{children}</a>,\n };\n }\n\n return adapter;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useRef,\n type ReactNode,\n} from 'react';\nimport { UserSchema, createClient } from '@insforge/sdk';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport {\n CreateSessionResponse,\n CreateUserResponse,\n GetPublicAuthConfigResponse,\n ResetPasswordResponse,\n} from '@insforge/shared-schemas';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\n\ninterface InsforgeContextValue {\n // Auth state\n user: InsforgeUser | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n\n // Auth methods\n setUser: (user: InsforgeUser | null) => void;\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 updateUser: (data: Partial<InsforgeUser>) => Promise<{ error: string } | null>;\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\n\n // Email verification methods\n sendVerificationEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n sendResetPasswordEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n resetPassword: (token: string, newPassword: string) => Promise<ResetPasswordResponse | null>;\n verifyEmail: (\n otp: string,\n email?: string\n ) => Promise<{\n accessToken: string;\n user?: UserSchema;\n redirectTo?: string;\n error?: { message: string };\n } | null>;\n exchangeResetPasswordToken: (\n email: string,\n code: string\n ) => Promise<{ token: string; expiresAt?: string } | { error: { message: string } }>;\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) => Promise<void>;\n // Public auth config\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\n // Base config\n baseUrl: string;\n afterSignInUrl: string;\n}\n\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(undefined);\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Manages user authentication state and provides all necessary context to child components.\n * Works with any React framework (Next.js, Vite, Remix, etc.).\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 InsforgeProvider({\n children,\n baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Auth state\n const [user, setUser] = useState<InsforgeUser | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\n const hasProcessedCallbackRef = useRef(false);\n\n // Initialize SDK client with lazy initialization - only runs once\n const [insforge] = useState(() => createClient({ baseUrl }));\n\n // Load auth state - returns explicit success/error status\n const loadAuthState = useCallback(async (): Promise<{\n success: boolean;\n error?: string;\n }> => {\n try {\n // Use SDK's getCurrentSession() to check for existing session\n const sessionResult = insforge.auth.getCurrentSession();\n const session = sessionResult.data?.session;\n const token = session?.accessToken || null;\n\n if (!token) {\n // No token, user is not authenticated\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'no_session' };\n }\n\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n // Token is valid, update user state with fresh data\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n setIsLoaded(true);\n return { success: true };\n } else {\n // Token invalid or expired\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'invalid_token' };\n }\n } catch (error) {\n // Token validation failed\n console.error('[InsforgeProvider] Token validation failed:', error);\n\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Authentication failed',\n };\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n useEffect(() => {\n // Run loadAuthState only once on mount\n loadAuthState();\n\n // Capture the ref value when effect runs\n const intervalId = refreshIntervalRef.current;\n\n return () => {\n // Use the captured value in cleanup\n if (intervalId) {\n clearInterval(intervalId);\n }\n };\n }, [loadAuthState]);\n\n // Handle authentication callback from URL\n useEffect(() => {\n // Only run once and only after auth is loaded\n if (!isLoaded || hasProcessedCallbackRef.current) {\n return;\n }\n\n const searchParams = new URLSearchParams(window.location.search);\n const accessToken = searchParams.get('access_token');\n\n if (accessToken && !!user) {\n // Mark as processed\n hasProcessedCallbackRef.current = true;\n\n // Clean URL by removing query parameters\n const url = new URL(window.location.href);\n url.search = '';\n window.history.replaceState({}, '', url.toString());\n\n // Redirect to afterSignInUrl\n setTimeout(() => {\n window.location.href = afterSignInUrl;\n }, 100);\n }\n }, [isLoaded, user, afterSignInUrl]);\n\n const getPublicAuthConfig = useCallback(async () => {\n try {\n const result = await insforge.auth.getPublicAuthConfig();\n if (result.data) {\n return result.data;\n } else {\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\n return null;\n }\n } catch (error) {\n console.error('[InsforgeProvider] Failed to get public auth config:', error);\n return null;\n }\n }, [insforge]);\n\n /**\n * Helper function to handle successful authentication\n */\n const handleAuthSuccess = useCallback(\n async (authToken: string, fallbackUser?: { id?: string; email?: string; name?: string }) => {\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n\n // Try to sync token to cookie if function provided\n if (onSignIn) {\n try {\n await onSignIn(authToken);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error syncing token to cookie:', error.message);\n }\n }\n }\n } else if (fallbackUser) {\n // Fallback to basic user data if getCurrentUser fails\n const userData: InsforgeUser = {\n id: fallbackUser.id || '',\n email: fallbackUser.email || '',\n name: fallbackUser.name || '',\n avatarUrl: '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n }\n },\n [insforge, onAuthChange, onSignIn]\n );\n\n const signIn = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signInWithPassword({\n email,\n password,\n });\n\n if (sdkResult.data) {\n await handleAuthSuccess(\n sdkResult.data.accessToken || '',\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Invalid email or password',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signUp = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signUp({ email, password });\n\n if (sdkResult.data) {\n // Only set auth state if we got a token back (email verification might be required)\n if (sdkResult.data.accessToken) {\n await handleAuthSuccess(\n sdkResult.data.accessToken,\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n }\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Sign up failed',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signOut = useCallback(async () => {\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n // Clear refresh interval if exists\n if (refreshIntervalRef.current) {\n clearInterval(refreshIntervalRef.current);\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n const updateUser = useCallback(\n async (data: Partial<InsforgeUser>) => {\n if (!user) {\n return { error: 'No user signed in' };\n }\n\n const profileUpdate: Record<string, string | undefined> = {\n nickname: data.name,\n avatarUrl: data.avatarUrl,\n };\n\n const result = await insforge.auth.setProfile(profileUpdate);\n\n if (result.data) {\n const userResult = await insforge.auth.getCurrentUser();\n if (userResult.data) {\n const profile = userResult.data.profile;\n const updatedUser: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: profile?.nickname || '',\n avatarUrl: profile?.avatarUrl || '',\n };\n setUser(updatedUser);\n if (onAuthChange) {\n onAuthChange(updatedUser);\n }\n }\n return null;\n } else {\n return { error: result.error?.message || 'Failed to update user' };\n }\n },\n [user, onAuthChange, insforge]\n );\n\n const sendVerificationEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendVerificationEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const sendResetPasswordEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendResetPasswordEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const resetPassword = useCallback(\n async (token: string, newPassword: string) => {\n const sdkResult = await insforge.auth.resetPassword({\n newPassword,\n otp: token,\n });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const verifyEmail = useCallback(\n async (otp: string, email?: string) => {\n const sdkResult = await insforge.auth.verifyEmail({ otp, email: email || undefined });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n accessToken: '',\n error: {\n message: sdkResult.error?.message || 'Email verification failed',\n },\n };\n }\n },\n [insforge]\n );\n\n const exchangeResetPasswordToken = useCallback(\n async (email: string, code: string) => {\n const sdkResult = await insforge.auth.exchangeResetPasswordToken({ email, code });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n error: {\n message: sdkResult.error?.message || 'Failed to exchange reset password token',\n },\n };\n }\n },\n [insforge]\n );\n\n const loginWithOAuth = useCallback(\n async (provider: OAuthProvider, redirectTo: string) => {\n const sdkResult = await insforge.auth.signInWithOAuth({\n provider,\n redirectTo: redirectTo || window.location.origin + afterSignInUrl,\n });\n if (sdkResult.error) {\n throw new Error(sdkResult.error.message);\n }\n },\n [insforge, afterSignInUrl]\n );\n\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeContext.Provider\n value={{\n user,\n isLoaded,\n isSignedIn: !!user,\n setUser,\n signIn,\n signUp,\n signOut,\n updateUser,\n reloadAuth: loadAuthState,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n baseUrl,\n afterSignInUrl,\n }}\n >\n {children}\n </InsforgeContext.Provider>\n </NavigationProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\n\n // During SSR in Next.js, context might be undefined temporarily\n // Return a safe default that won't break SSR rendering\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\n setUser: () => {},\n signIn: async () => ({ error: 'SSR mode' }),\n signUp: async () => ({ error: 'SSR mode' }),\n signOut: async () => {},\n updateUser: async () => ({ error: 'SSR mode' }),\n reloadAuth: async () => ({ success: false, error: 'SSR mode' }),\n sendVerificationEmail: async () => null,\n sendResetPasswordEmail: async () => null,\n resetPassword: async () => null,\n verifyEmail: async () => null,\n exchangeResetPasswordToken: async () => ({ error: { message: 'SSR mode' } }),\n loginWithOAuth: async () => {},\n getPublicAuthConfig: async () => null,\n baseUrl: '',\n afterSignInUrl: '/',\n } as InsforgeContextValue;\n }\n\n return context;\n}\n","import { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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 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":["createContext","useContext","useState","useEffect"],"mappings":";;;;;AAG0B,cAAwC,IAAI;AC6DtE,IAAM,eAAA,GAAkBA,cAAgD,MAAS,CAAA;AAogB1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUC,WAAW,eAAe,CAAA;AAI1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA;AAAA,MACN,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY,KAAA;AAAA,MACZ,SAAS,MAAM;AAAA,MAAC,CAAA;AAAA,MAChB,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,SAAS,YAAY;AAAA,MAAC,CAAA;AAAA,MACtB,UAAA,EAAY,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MAC7C,YAAY,aAAa,EAAE,OAAA,EAAS,KAAA,EAAO,OAAO,UAAA,EAAW,CAAA;AAAA,MAC7D,uBAAuB,YAAY,IAAA;AAAA,MACnC,wBAAwB,YAAY,IAAA;AAAA,MACpC,eAAe,YAAY,IAAA;AAAA,MAC3B,aAAa,YAAY,IAAA;AAAA,MACzB,4BAA4B,aAAa,EAAE,OAAO,EAAE,OAAA,EAAS,YAAW,EAAE,CAAA;AAAA,MAC1E,gBAAgB,YAAY;AAAA,MAAC,CAAA;AAAA,MAC7B,qBAAqB,YAAY,IAAA;AAAA,MACjC,OAAA,EAAS,EAAA;AAAA,MACT,cAAA,EAAgB;AAAA,KAClB;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC7jBO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAS,QAAA,EAAU,UAAA,KAAe,WAAA,EAAY;AACtE,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,UAAU,UAAA,EAAW;AACzD;;;ACDO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,KAAY,WAAA,EAAY;AAC5D,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,EAAQ;AAC/C;ACLO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIC,SAA6C,IAAI,CAAA;AACrF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,SAAS,KAAK,CAAA;AAE9C,EAAAC,UAAU,MAAM;AACd,IAAA,eAAe,WAAA,GAAc;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,EAAoB;AACzC,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,aAAA,CAAc,MAAM,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,wDAAwD,CAAA;AACtE,QAAA,aAAA,CAAc,IAAI,CAAA;AAAA,MACpB;AACA,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB;AAEA,IAAA,WAAA,EAAY;AAAA,EACd,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAChC","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 }) => <a href={href}>{children}</a>,\n };\n }\n\n return adapter;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useRef,\n type ReactNode,\n} from 'react';\nimport { UserSchema, createClient } from '@insforge/sdk';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport {\n CreateSessionResponse,\n CreateUserResponse,\n GetPublicAuthConfigResponse,\n ResetPasswordResponse,\n} from '@insforge/shared-schemas';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\n\ninterface InsforgeContextValue {\n // Auth state\n user: InsforgeUser | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n\n // Auth methods\n setUser: (user: InsforgeUser | null) => void;\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 updateUser: (data: Partial<InsforgeUser>) => Promise<{ error: string } | null>;\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\n\n // Email verification methods\n sendVerificationEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n sendResetPasswordEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n resetPassword: (token: string, newPassword: string) => Promise<ResetPasswordResponse | null>;\n verifyEmail: (\n otp: string,\n email?: string\n ) => Promise<{\n accessToken: string;\n user?: UserSchema;\n redirectTo?: string;\n error?: { message: string };\n } | null>;\n exchangeResetPasswordToken: (\n email: string,\n code: string\n ) => Promise<{ token: string; expiresAt?: string } | { error: { message: string } }>;\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) => Promise<void>;\n // Public auth config\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\n // Base config\n baseUrl: string;\n afterSignInUrl: string;\n}\n\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(undefined);\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Manages user authentication state and provides all necessary context to child components.\n * Works with any React framework (Next.js, Vite, Remix, etc.).\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * afterSignInUrl=\"/dashboard\"\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 InsforgeProvider({\n children,\n baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Auth state\n const [user, setUser] = useState<InsforgeUser | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\n const hasProcessedCallbackRef = useRef(false);\n\n // Initialize SDK client with lazy initialization - only runs once\n const [insforge] = useState(() => createClient({ baseUrl }));\n\n // Load auth state - returns explicit success/error status\n const loadAuthState = useCallback(async (): Promise<{\n success: boolean;\n error?: string;\n }> => {\n try {\n // Use SDK's getCurrentSession() to check for existing session\n const sessionResult = insforge.auth.getCurrentSession();\n const session = sessionResult.data?.session;\n const token = session?.accessToken || null;\n\n if (!token) {\n // No token, user is not authenticated\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'no_session' };\n }\n\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n // Token is valid, update user state with fresh data\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n setIsLoaded(true);\n return { success: true };\n } else {\n // Token invalid or expired\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'invalid_token' };\n }\n } catch (error) {\n // Token validation failed\n console.error('[InsforgeProvider] Token validation failed:', error);\n\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Authentication failed',\n };\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n useEffect(() => {\n // Run loadAuthState only once on mount\n loadAuthState();\n\n // Capture the ref value when effect runs\n const intervalId = refreshIntervalRef.current;\n\n return () => {\n // Use the captured value in cleanup\n if (intervalId) {\n clearInterval(intervalId);\n }\n };\n }, [loadAuthState]);\n\n // Handle authentication callback from URL\n useEffect(() => {\n // Only run once and only after auth is loaded\n if (!isLoaded || hasProcessedCallbackRef.current) {\n return;\n }\n\n const searchParams = new URLSearchParams(window.location.search);\n const accessToken = searchParams.get('access_token');\n\n if (accessToken && !!user) {\n // Mark as processed\n hasProcessedCallbackRef.current = true;\n\n // Clean URL by removing query parameters\n const url = new URL(window.location.href);\n url.search = '';\n window.history.replaceState({}, '', url.toString());\n\n // Redirect to afterSignInUrl\n setTimeout(() => {\n window.location.href = afterSignInUrl;\n }, 100);\n }\n }, [isLoaded, user, afterSignInUrl]);\n\n const getPublicAuthConfig = useCallback(async () => {\n try {\n const result = await insforge.auth.getPublicAuthConfig();\n if (result.data) {\n return result.data;\n } else {\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\n return null;\n }\n } catch (error) {\n console.error('[InsforgeProvider] Failed to get public auth config:', error);\n return null;\n }\n }, [insforge]);\n\n /**\n * Helper function to handle successful authentication\n */\n const handleAuthSuccess = useCallback(\n async (authToken: string, fallbackUser?: { id?: string; email?: string; name?: string }) => {\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n\n // Try to sync token to cookie if function provided\n if (onSignIn) {\n try {\n await onSignIn(authToken);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error syncing token to cookie:', error.message);\n }\n }\n }\n } else if (fallbackUser) {\n // Fallback to basic user data if getCurrentUser fails\n const userData: InsforgeUser = {\n id: fallbackUser.id || '',\n email: fallbackUser.email || '',\n name: fallbackUser.name || '',\n avatarUrl: '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n }\n },\n [insforge, onAuthChange, onSignIn]\n );\n\n const signIn = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signInWithPassword({\n email,\n password,\n });\n\n if (sdkResult.data) {\n await handleAuthSuccess(\n sdkResult.data.accessToken || '',\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Invalid email or password',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signUp = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signUp({ email, password });\n\n if (sdkResult.data) {\n // Only set auth state if we got a token back (email verification might be required)\n if (sdkResult.data.accessToken) {\n await handleAuthSuccess(\n sdkResult.data.accessToken,\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n }\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Sign up failed',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signOut = useCallback(async () => {\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n // Clear refresh interval if exists\n if (refreshIntervalRef.current) {\n clearInterval(refreshIntervalRef.current);\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n const updateUser = useCallback(\n async (data: Partial<InsforgeUser>) => {\n if (!user) {\n return { error: 'No user signed in' };\n }\n\n const profileUpdate: Record<string, string | undefined> = {\n nickname: data.name,\n avatarUrl: data.avatarUrl,\n };\n\n const result = await insforge.auth.setProfile(profileUpdate);\n\n if (result.data) {\n const userResult = await insforge.auth.getCurrentUser();\n if (userResult.data) {\n const profile = userResult.data.profile;\n const updatedUser: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: profile?.nickname || '',\n avatarUrl: profile?.avatarUrl || '',\n };\n setUser(updatedUser);\n if (onAuthChange) {\n onAuthChange(updatedUser);\n }\n }\n return null;\n } else {\n return { error: result.error?.message || 'Failed to update user' };\n }\n },\n [user, onAuthChange, insforge]\n );\n\n const sendVerificationEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendVerificationEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const sendResetPasswordEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendResetPasswordEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const resetPassword = useCallback(\n async (token: string, newPassword: string) => {\n const sdkResult = await insforge.auth.resetPassword({\n newPassword,\n otp: token,\n });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const verifyEmail = useCallback(\n async (otp: string, email?: string) => {\n const sdkResult = await insforge.auth.verifyEmail({ otp, email: email || undefined });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n accessToken: '',\n error: {\n message: sdkResult.error?.message || 'Email verification failed',\n },\n };\n }\n },\n [insforge]\n );\n\n const exchangeResetPasswordToken = useCallback(\n async (email: string, code: string) => {\n const sdkResult = await insforge.auth.exchangeResetPasswordToken({ email, code });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n error: {\n message: sdkResult.error?.message || 'Failed to exchange reset password token',\n },\n };\n }\n },\n [insforge]\n );\n\n const loginWithOAuth = useCallback(\n async (provider: OAuthProvider, redirectTo: string) => {\n const sdkResult = await insforge.auth.signInWithOAuth({\n provider,\n redirectTo: redirectTo || window.location.origin + afterSignInUrl,\n });\n if (sdkResult.error) {\n throw new Error(sdkResult.error.message);\n }\n },\n [insforge, afterSignInUrl]\n );\n\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeContext.Provider\n value={{\n user,\n isLoaded,\n isSignedIn: !!user,\n setUser,\n signIn,\n signUp,\n signOut,\n updateUser,\n reloadAuth: loadAuthState,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n baseUrl,\n afterSignInUrl,\n }}\n >\n {children}\n </InsforgeContext.Provider>\n </NavigationProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\n\n // During SSR in Next.js, context might be undefined temporarily\n // Return a safe default that won't break SSR rendering\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\n setUser: () => {},\n signIn: async () => ({ error: 'SSR mode' }),\n signUp: async () => ({ error: 'SSR mode' }),\n signOut: async () => {},\n updateUser: async () => ({ error: 'SSR mode' }),\n reloadAuth: async () => ({ success: false, error: 'SSR mode' }),\n sendVerificationEmail: async () => null,\n sendResetPasswordEmail: async () => null,\n resetPassword: async () => null,\n verifyEmail: async () => null,\n exchangeResetPasswordToken: async () => ({ error: { message: 'SSR mode' } }),\n loginWithOAuth: async () => {},\n getPublicAuthConfig: async () => null,\n baseUrl: '',\n afterSignInUrl: '/',\n } as InsforgeContextValue;\n }\n\n return context;\n}\n","import { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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 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","useState","useEffect"],"mappings":";;;;;AAG0B,cAAwC,IAAI;AC6DtE,IAAM,eAAA,GAAkBA,cAAgD,MAAS,CAAA;AAmgB1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUC,WAAW,eAAe,CAAA;AAI1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA;AAAA,MACN,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY,KAAA;AAAA,MACZ,SAAS,MAAM;AAAA,MAAC,CAAA;AAAA,MAChB,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,MAAA,EAAQ,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MACzC,SAAS,YAAY;AAAA,MAAC,CAAA;AAAA,MACtB,UAAA,EAAY,aAAa,EAAE,KAAA,EAAO,UAAA,EAAW,CAAA;AAAA,MAC7C,YAAY,aAAa,EAAE,OAAA,EAAS,KAAA,EAAO,OAAO,UAAA,EAAW,CAAA;AAAA,MAC7D,uBAAuB,YAAY,IAAA;AAAA,MACnC,wBAAwB,YAAY,IAAA;AAAA,MACpC,eAAe,YAAY,IAAA;AAAA,MAC3B,aAAa,YAAY,IAAA;AAAA,MACzB,4BAA4B,aAAa,EAAE,OAAO,EAAE,OAAA,EAAS,YAAW,EAAE,CAAA;AAAA,MAC1E,gBAAgB,YAAY;AAAA,MAAC,CAAA;AAAA,MAC7B,qBAAqB,YAAY,IAAA;AAAA,MACjC,OAAA,EAAS,EAAA;AAAA,MACT,cAAA,EAAgB;AAAA,KAClB;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC5jBO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAS,QAAA,EAAU,UAAA,KAAe,WAAA,EAAY;AACtE,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,UAAU,UAAA,EAAW;AACzD;;;ACDO,SAAS,OAAA,GAAU;AACxB,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,KAAY,WAAA,EAAY;AAC5D,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,EAAQ;AAC/C;ACLO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIC,SAA6C,IAAI,CAAA;AACrF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,SAAS,KAAK,CAAA;AAE9C,EAAAC,UAAU,MAAM;AACd,IAAA,eAAe,WAAA,GAAc;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,EAAoB;AACzC,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,aAAA,CAAc,MAAM,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,wDAAwD,CAAA;AACtE,QAAA,aAAA,CAAc,IAAI,CAAA;AAAA,MACpB;AACA,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB;AAEA,IAAA,WAAA,EAAY;AAAA,EACd,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAChC","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 }) => <a href={href}>{children}</a>,\n };\n }\n\n return adapter;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useRef,\n type ReactNode,\n} from 'react';\nimport { UserSchema, createClient } from '@insforge/sdk';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport {\n CreateSessionResponse,\n CreateUserResponse,\n GetPublicAuthConfigResponse,\n ResetPasswordResponse,\n} from '@insforge/shared-schemas';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\n\ninterface InsforgeContextValue {\n // Auth state\n user: InsforgeUser | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n\n // Auth methods\n setUser: (user: InsforgeUser | null) => void;\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 updateUser: (data: Partial<InsforgeUser>) => Promise<{ error: string } | null>;\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\n\n // Email verification methods\n sendVerificationEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n sendResetPasswordEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n resetPassword: (token: string, newPassword: string) => Promise<ResetPasswordResponse | null>;\n verifyEmail: (\n otp: string,\n email?: string\n ) => Promise<{\n accessToken: string;\n user?: UserSchema;\n redirectTo?: string;\n error?: { message: string };\n } | null>;\n exchangeResetPasswordToken: (\n email: string,\n code: string\n ) => Promise<{ token: string; expiresAt?: string } | { error: { message: string } }>;\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) => Promise<void>;\n // Public auth config\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\n // Base config\n baseUrl: string;\n afterSignInUrl: string;\n}\n\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(undefined);\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Manages user authentication state and provides all necessary context to child components.\n * Works with any React framework (Next.js, Vite, Remix, etc.).\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 InsforgeProvider({\n children,\n baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Auth state\n const [user, setUser] = useState<InsforgeUser | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\n const hasProcessedCallbackRef = useRef(false);\n\n // Initialize SDK client with lazy initialization - only runs once\n const [insforge] = useState(() => createClient({ baseUrl }));\n\n // Load auth state - returns explicit success/error status\n const loadAuthState = useCallback(async (): Promise<{\n success: boolean;\n error?: string;\n }> => {\n try {\n // Use SDK's getCurrentSession() to check for existing session\n const sessionResult = insforge.auth.getCurrentSession();\n const session = sessionResult.data?.session;\n const token = session?.accessToken || null;\n\n if (!token) {\n // No token, user is not authenticated\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'no_session' };\n }\n\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n // Token is valid, update user state with fresh data\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n setIsLoaded(true);\n return { success: true };\n } else {\n // Token invalid or expired\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'invalid_token' };\n }\n } catch (error) {\n // Token validation failed\n console.error('[InsforgeProvider] Token validation failed:', error);\n\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Authentication failed',\n };\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n useEffect(() => {\n // Run loadAuthState only once on mount\n loadAuthState();\n\n // Capture the ref value when effect runs\n const intervalId = refreshIntervalRef.current;\n\n return () => {\n // Use the captured value in cleanup\n if (intervalId) {\n clearInterval(intervalId);\n }\n };\n }, [loadAuthState]);\n\n // Handle authentication callback from URL\n useEffect(() => {\n // Only run once and only after auth is loaded\n if (!isLoaded || hasProcessedCallbackRef.current) {\n return;\n }\n\n const searchParams = new URLSearchParams(window.location.search);\n const accessToken = searchParams.get('access_token');\n\n if (accessToken && !!user) {\n // Mark as processed\n hasProcessedCallbackRef.current = true;\n\n // Clean URL by removing query parameters\n const url = new URL(window.location.href);\n url.search = '';\n window.history.replaceState({}, '', url.toString());\n\n // Redirect to afterSignInUrl\n setTimeout(() => {\n window.location.href = afterSignInUrl;\n }, 100);\n }\n }, [isLoaded, user, afterSignInUrl]);\n\n const getPublicAuthConfig = useCallback(async () => {\n try {\n const result = await insforge.auth.getPublicAuthConfig();\n if (result.data) {\n return result.data;\n } else {\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\n return null;\n }\n } catch (error) {\n console.error('[InsforgeProvider] Failed to get public auth config:', error);\n return null;\n }\n }, [insforge]);\n\n /**\n * Helper function to handle successful authentication\n */\n const handleAuthSuccess = useCallback(\n async (authToken: string, fallbackUser?: { id?: string; email?: string; name?: string }) => {\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n\n // Try to sync token to cookie if function provided\n if (onSignIn) {\n try {\n await onSignIn(authToken);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error syncing token to cookie:', error.message);\n }\n }\n }\n } else if (fallbackUser) {\n // Fallback to basic user data if getCurrentUser fails\n const userData: InsforgeUser = {\n id: fallbackUser.id || '',\n email: fallbackUser.email || '',\n name: fallbackUser.name || '',\n avatarUrl: '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n }\n },\n [insforge, onAuthChange, onSignIn]\n );\n\n const signIn = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signInWithPassword({\n email,\n password,\n });\n\n if (sdkResult.data) {\n await handleAuthSuccess(\n sdkResult.data.accessToken || '',\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Invalid email or password',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signUp = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signUp({ email, password });\n\n if (sdkResult.data) {\n // Only set auth state if we got a token back (email verification might be required)\n if (sdkResult.data.accessToken) {\n await handleAuthSuccess(\n sdkResult.data.accessToken,\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n }\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Sign up failed',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signOut = useCallback(async () => {\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n // Clear refresh interval if exists\n if (refreshIntervalRef.current) {\n clearInterval(refreshIntervalRef.current);\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n const updateUser = useCallback(\n async (data: Partial<InsforgeUser>) => {\n if (!user) {\n return { error: 'No user signed in' };\n }\n\n const profileUpdate: Record<string, string | undefined> = {\n nickname: data.name,\n avatarUrl: data.avatarUrl,\n };\n\n const result = await insforge.auth.setProfile(profileUpdate);\n\n if (result.data) {\n const userResult = await insforge.auth.getCurrentUser();\n if (userResult.data) {\n const profile = userResult.data.profile;\n const updatedUser: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: profile?.nickname || '',\n avatarUrl: profile?.avatarUrl || '',\n };\n setUser(updatedUser);\n if (onAuthChange) {\n onAuthChange(updatedUser);\n }\n }\n return null;\n } else {\n return { error: result.error?.message || 'Failed to update user' };\n }\n },\n [user, onAuthChange, insforge]\n );\n\n const sendVerificationEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendVerificationEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const sendResetPasswordEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendResetPasswordEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const resetPassword = useCallback(\n async (token: string, newPassword: string) => {\n const sdkResult = await insforge.auth.resetPassword({\n newPassword,\n otp: token,\n });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const verifyEmail = useCallback(\n async (otp: string, email?: string) => {\n const sdkResult = await insforge.auth.verifyEmail({ otp, email: email || undefined });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n accessToken: '',\n error: {\n message: sdkResult.error?.message || 'Email verification failed',\n },\n };\n }\n },\n [insforge]\n );\n\n const exchangeResetPasswordToken = useCallback(\n async (email: string, code: string) => {\n const sdkResult = await insforge.auth.exchangeResetPasswordToken({ email, code });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n error: {\n message: sdkResult.error?.message || 'Failed to exchange reset password token',\n },\n };\n }\n },\n [insforge]\n );\n\n const loginWithOAuth = useCallback(\n async (provider: OAuthProvider, redirectTo: string) => {\n const sdkResult = await insforge.auth.signInWithOAuth({\n provider,\n redirectTo: redirectTo || window.location.origin + afterSignInUrl,\n });\n if (sdkResult.error) {\n throw new Error(sdkResult.error.message);\n }\n },\n [insforge, afterSignInUrl]\n );\n\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeContext.Provider\n value={{\n user,\n isLoaded,\n isSignedIn: !!user,\n setUser,\n signIn,\n signUp,\n signOut,\n updateUser,\n reloadAuth: loadAuthState,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n baseUrl,\n afterSignInUrl,\n }}\n >\n {children}\n </InsforgeContext.Provider>\n </NavigationProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\n\n // During SSR in Next.js, context might be undefined temporarily\n // Return a safe default that won't break SSR rendering\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\n setUser: () => {},\n signIn: async () => ({ error: 'SSR mode' }),\n signUp: async () => ({ error: 'SSR mode' }),\n signOut: async () => {},\n updateUser: async () => ({ error: 'SSR mode' }),\n reloadAuth: async () => ({ success: false, error: 'SSR mode' }),\n sendVerificationEmail: async () => null,\n sendResetPasswordEmail: async () => null,\n resetPassword: async () => null,\n verifyEmail: async () => null,\n exchangeResetPasswordToken: async () => ({ error: { message: 'SSR mode' } }),\n loginWithOAuth: async () => {},\n getPublicAuthConfig: async () => null,\n baseUrl: '',\n afterSignInUrl: '/',\n } as InsforgeContextValue;\n }\n\n return context;\n}\n","import { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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 fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -2459,6 +2459,48 @@ function SignedOut({ children }) {
|
|
|
2459
2459
|
}
|
|
2460
2460
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
2461
2461
|
}
|
|
2462
|
+
function SignInButton({ children, className }) {
|
|
2463
|
+
const { afterSignInUrl, baseUrl } = useInsforge();
|
|
2464
|
+
const handleClick = () => {
|
|
2465
|
+
const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
|
|
2466
|
+
const authUrl = new URL("/auth/sign-in", baseUrl);
|
|
2467
|
+
authUrl.searchParams.set("redirect", redirectUrl);
|
|
2468
|
+
window.location.replace(authUrl.toString());
|
|
2469
|
+
};
|
|
2470
|
+
if (children && react.isValidElement(children)) {
|
|
2471
|
+
const originalOnClick = children.props?.onClick;
|
|
2472
|
+
return react.cloneElement(children, {
|
|
2473
|
+
onClick: (e) => {
|
|
2474
|
+
handleClick();
|
|
2475
|
+
if (originalOnClick) {
|
|
2476
|
+
originalOnClick(e);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className, onClick: handleClick, children: children || "Sign in" });
|
|
2482
|
+
}
|
|
2483
|
+
function SignUpButton({ children, className }) {
|
|
2484
|
+
const { afterSignInUrl, baseUrl } = useInsforge();
|
|
2485
|
+
const handleClick = () => {
|
|
2486
|
+
const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
|
|
2487
|
+
const authUrl = new URL("/auth/sign-up", baseUrl);
|
|
2488
|
+
authUrl.searchParams.set("redirect", redirectUrl);
|
|
2489
|
+
window.location.replace(authUrl.toString());
|
|
2490
|
+
};
|
|
2491
|
+
if (children && react.isValidElement(children)) {
|
|
2492
|
+
const originalOnClick = children.props?.onClick;
|
|
2493
|
+
return react.cloneElement(children, {
|
|
2494
|
+
onClick: (e) => {
|
|
2495
|
+
handleClick();
|
|
2496
|
+
if (originalOnClick) {
|
|
2497
|
+
originalOnClick(e);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className, onClick: handleClick, children: children || "Sign up" });
|
|
2503
|
+
}
|
|
2462
2504
|
|
|
2463
2505
|
// src/hooks/useAuth.ts
|
|
2464
2506
|
function useAuth() {
|
|
@@ -2471,96 +2513,6 @@ function useUser() {
|
|
|
2471
2513
|
const { user, isLoaded, updateUser, setUser } = useInsforge();
|
|
2472
2514
|
return { user, isLoaded, updateUser, setUser };
|
|
2473
2515
|
}
|
|
2474
|
-
function RouteGuard({
|
|
2475
|
-
children,
|
|
2476
|
-
builtInAuth = true,
|
|
2477
|
-
paths = {},
|
|
2478
|
-
publicRoutes = [],
|
|
2479
|
-
loadingFallback
|
|
2480
|
-
}) {
|
|
2481
|
-
const { isSignedIn, isLoaded, afterSignInUrl, baseUrl } = useInsforge();
|
|
2482
|
-
const { signIn = "/sign-in", signUp = "/sign-up", forgotPassword = "/forgot-password" } = paths;
|
|
2483
|
-
const [currentPath, setCurrentPath] = react.useState("");
|
|
2484
|
-
react.useEffect(() => {
|
|
2485
|
-
const updatePath = () => {
|
|
2486
|
-
const path = window.location.hash ? window.location.hash.slice(1) : window.location.pathname;
|
|
2487
|
-
setCurrentPath(path || "/");
|
|
2488
|
-
};
|
|
2489
|
-
updatePath();
|
|
2490
|
-
window.addEventListener("hashchange", updatePath);
|
|
2491
|
-
window.addEventListener("popstate", updatePath);
|
|
2492
|
-
return () => {
|
|
2493
|
-
window.removeEventListener("hashchange", updatePath);
|
|
2494
|
-
window.removeEventListener("popstate", updatePath);
|
|
2495
|
-
};
|
|
2496
|
-
}, []);
|
|
2497
|
-
const isPublicRoute = publicRoutes.some((route) => {
|
|
2498
|
-
if (route.endsWith("/*")) {
|
|
2499
|
-
const prefix = route.slice(0, -2);
|
|
2500
|
-
return currentPath.startsWith(prefix);
|
|
2501
|
-
}
|
|
2502
|
-
return currentPath === route || currentPath.startsWith(route + "?");
|
|
2503
|
-
});
|
|
2504
|
-
react.useEffect(() => {
|
|
2505
|
-
if (!isLoaded) return;
|
|
2506
|
-
if (isSignedIn) return;
|
|
2507
|
-
if (!isSignedIn) {
|
|
2508
|
-
if (builtInAuth) {
|
|
2509
|
-
const isSignInPage = currentPath === signIn || currentPath.startsWith(signIn + "?");
|
|
2510
|
-
const isSignUpPage = currentPath === signUp || currentPath.startsWith(signUp + "?");
|
|
2511
|
-
const isForgotPasswordPage = currentPath === forgotPassword || currentPath.startsWith(forgotPassword + "?");
|
|
2512
|
-
if (isSignInPage) {
|
|
2513
|
-
const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
|
|
2514
|
-
const authUrl = new URL("/auth/sign-in", baseUrl);
|
|
2515
|
-
authUrl.searchParams.set("redirect", redirectUrl);
|
|
2516
|
-
window.location.replace(authUrl.toString());
|
|
2517
|
-
return;
|
|
2518
|
-
}
|
|
2519
|
-
if (isSignUpPage) {
|
|
2520
|
-
const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
|
|
2521
|
-
const authUrl = new URL("/auth/sign-up", baseUrl);
|
|
2522
|
-
authUrl.searchParams.set("redirect", redirectUrl);
|
|
2523
|
-
window.location.replace(authUrl.toString());
|
|
2524
|
-
return;
|
|
2525
|
-
}
|
|
2526
|
-
if (isForgotPasswordPage) {
|
|
2527
|
-
const authUrl = new URL("/auth/forgot-password", baseUrl);
|
|
2528
|
-
window.location.replace(authUrl.toString());
|
|
2529
|
-
return;
|
|
2530
|
-
}
|
|
2531
|
-
if (isPublicRoute) {
|
|
2532
|
-
return;
|
|
2533
|
-
} else {
|
|
2534
|
-
const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
|
|
2535
|
-
const authUrl = new URL("/auth/sign-in", baseUrl);
|
|
2536
|
-
authUrl.searchParams.set("redirect", redirectUrl);
|
|
2537
|
-
window.location.replace(authUrl.toString());
|
|
2538
|
-
}
|
|
2539
|
-
} else {
|
|
2540
|
-
if (isPublicRoute) {
|
|
2541
|
-
return;
|
|
2542
|
-
} else {
|
|
2543
|
-
window.location.href = signIn + "?redirect=" + afterSignInUrl;
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
}, [
|
|
2548
|
-
isLoaded,
|
|
2549
|
-
isSignedIn,
|
|
2550
|
-
currentPath,
|
|
2551
|
-
isPublicRoute,
|
|
2552
|
-
builtInAuth,
|
|
2553
|
-
baseUrl,
|
|
2554
|
-
signIn,
|
|
2555
|
-
signUp,
|
|
2556
|
-
forgotPassword,
|
|
2557
|
-
afterSignInUrl
|
|
2558
|
-
]);
|
|
2559
|
-
if (!isLoaded) {
|
|
2560
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingFallback });
|
|
2561
|
-
}
|
|
2562
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
2563
|
-
}
|
|
2564
2516
|
|
|
2565
2517
|
exports.AuthBranding = AuthBranding;
|
|
2566
2518
|
exports.AuthContainer = AuthContainer;
|
|
@@ -2586,10 +2538,11 @@ exports.OAUTH_PROVIDER_CONFIG = OAUTH_PROVIDER_CONFIG;
|
|
|
2586
2538
|
exports.Protect = Protect;
|
|
2587
2539
|
exports.ResetPassword = ResetPassword;
|
|
2588
2540
|
exports.ResetPasswordForm = ResetPasswordForm;
|
|
2589
|
-
exports.RouteGuard = RouteGuard;
|
|
2590
2541
|
exports.SignIn = SignIn;
|
|
2542
|
+
exports.SignInButton = SignInButton;
|
|
2591
2543
|
exports.SignInForm = SignInForm;
|
|
2592
2544
|
exports.SignUp = SignUp;
|
|
2545
|
+
exports.SignUpButton = SignUpButton;
|
|
2593
2546
|
exports.SignUpForm = SignUpForm;
|
|
2594
2547
|
exports.SignedIn = SignedIn;
|
|
2595
2548
|
exports.SignedOut = SignedOut;
|