@insforge/react 0.4.12 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +426 -439
- package/dist/atoms.cjs +46 -6
- package/dist/atoms.cjs.map +1 -1
- package/dist/atoms.js +46 -6
- package/dist/atoms.js.map +1 -1
- package/dist/components.cjs +51 -11
- package/dist/components.cjs.map +1 -1
- package/dist/components.js +51 -11
- package/dist/components.js.map +1 -1
- package/dist/forms.cjs +46 -6
- package/dist/forms.cjs.map +1 -1
- package/dist/forms.js +46 -6
- package/dist/forms.js.map +1 -1
- package/dist/hooks.cjs +27 -1
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.js +27 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +83 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +81 -50
- package/dist/index.js.map +1 -1
- package/dist/navigation.cjs +53 -0
- package/dist/navigation.cjs.map +1 -0
- package/dist/navigation.d.cts +64 -0
- package/dist/navigation.d.ts +64 -0
- package/dist/navigation.js +48 -0
- package/dist/navigation.js.map +1 -0
- package/package.json +6 -7
- package/dist/router.cjs +0 -45
- package/dist/router.cjs.map +0 -1
- package/dist/router.d.cts +0 -79
- package/dist/router.d.ts +0 -79
- package/dist/router.js +0 -43
- package/dist/router.js.map +0 -1
package/dist/hooks.cjs
CHANGED
|
@@ -5,11 +5,37 @@ require('@insforge/sdk');
|
|
|
5
5
|
require('react/jsx-runtime');
|
|
6
6
|
|
|
7
7
|
// src/provider/InsforgeProvider.tsx
|
|
8
|
+
react.createContext(null);
|
|
8
9
|
var InsforgeContext = react.createContext(void 0);
|
|
9
10
|
function useInsforge() {
|
|
10
11
|
const context = react.useContext(InsforgeContext);
|
|
11
12
|
if (!context) {
|
|
12
|
-
|
|
13
|
+
if (typeof window !== "undefined") {
|
|
14
|
+
throw new Error("useInsforge must be used within InsforgeProvider");
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
user: null,
|
|
18
|
+
isLoaded: false,
|
|
19
|
+
isSignedIn: false,
|
|
20
|
+
setUser: () => {
|
|
21
|
+
},
|
|
22
|
+
signIn: async () => ({ error: "SSR mode" }),
|
|
23
|
+
signUp: async () => ({ error: "SSR mode" }),
|
|
24
|
+
signOut: async () => {
|
|
25
|
+
},
|
|
26
|
+
updateUser: async () => {
|
|
27
|
+
},
|
|
28
|
+
reloadAuth: async () => ({ success: false, error: "SSR mode" }),
|
|
29
|
+
sendVerificationEmail: async () => null,
|
|
30
|
+
sendResetPasswordEmail: async () => null,
|
|
31
|
+
resetPassword: async () => null,
|
|
32
|
+
verifyEmail: async () => null,
|
|
33
|
+
exchangeResetPasswordToken: async () => ({ error: { message: "SSR mode" } }),
|
|
34
|
+
loginWithOAuth: async () => {
|
|
35
|
+
},
|
|
36
|
+
getPublicAuthConfig: async () => null,
|
|
37
|
+
baseUrl: ""
|
|
38
|
+
};
|
|
13
39
|
}
|
|
14
40
|
return context;
|
|
15
41
|
}
|
package/dist/hooks.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["createContext","useContext","useState","useEffect"],"mappings":";;;;;;;AA8DA,IAAM,eAAA,GAAkBA,oBAAgD,MAAS,CAAA;AA8f1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUC,iBAAW,eAAe,CAAA;AAC1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,EACpE;AACA,EAAA,OAAO,OAAA;AACT;;;AC9hBO,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;;;ACNO,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;ACCO,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 {\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';\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<void>;\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}\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={process.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 throw new 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 }\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 <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 baseUrl,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n }}\n >\n {children}\n </InsforgeContext.Provider>\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 if (!context) {\n throw new Error('useInsforge must be used within InsforgeProvider');\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\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 * await updateUser({ name });\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 * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])\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 { oauthProviders, authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {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;AC4DtE,IAAM,eAAA,GAAkBA,oBAAgD,MAAS,CAAA;AAggB1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUC,iBAAW,eAAe,CAAA;AAI1C,EAAA,IAAI,CAAC,OAAA,EAAS;AAEZ,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEjC,MAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,IACpE;AAIA,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,YAAY,YAAY;AAAA,MAAC,CAAA;AAAA,MACzB,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;AAAA,KACX;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC/jBO,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;;;ACNO,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;ACCO,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 throw new Error('useNavigationAdapter must be used within NavigationProvider');\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<void>;\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}\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={process.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 throw new 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 }\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 baseUrl,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\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 // Check if we're in a browser environment\n if (typeof window !== 'undefined') {\n // Client-side but no provider - this is an error\n throw new Error('useInsforge must be used within InsforgeProvider');\n }\n\n // SSR - return safe defaults that make components render nothing\n // SignedIn/SignedOut will both return null when isLoaded = false\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 () => {},\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 } 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\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 * await updateUser({ name });\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 * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])\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 { oauthProviders, authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {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
CHANGED
|
@@ -3,11 +3,37 @@ import '@insforge/sdk';
|
|
|
3
3
|
import 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/provider/InsforgeProvider.tsx
|
|
6
|
+
createContext(null);
|
|
6
7
|
var InsforgeContext = createContext(void 0);
|
|
7
8
|
function useInsforge() {
|
|
8
9
|
const context = useContext(InsforgeContext);
|
|
9
10
|
if (!context) {
|
|
10
|
-
|
|
11
|
+
if (typeof window !== "undefined") {
|
|
12
|
+
throw new Error("useInsforge must be used within InsforgeProvider");
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
user: null,
|
|
16
|
+
isLoaded: false,
|
|
17
|
+
isSignedIn: false,
|
|
18
|
+
setUser: () => {
|
|
19
|
+
},
|
|
20
|
+
signIn: async () => ({ error: "SSR mode" }),
|
|
21
|
+
signUp: async () => ({ error: "SSR mode" }),
|
|
22
|
+
signOut: async () => {
|
|
23
|
+
},
|
|
24
|
+
updateUser: async () => {
|
|
25
|
+
},
|
|
26
|
+
reloadAuth: async () => ({ success: false, error: "SSR mode" }),
|
|
27
|
+
sendVerificationEmail: async () => null,
|
|
28
|
+
sendResetPasswordEmail: async () => null,
|
|
29
|
+
resetPassword: async () => null,
|
|
30
|
+
verifyEmail: async () => null,
|
|
31
|
+
exchangeResetPasswordToken: async () => ({ error: { message: "SSR mode" } }),
|
|
32
|
+
loginWithOAuth: async () => {
|
|
33
|
+
},
|
|
34
|
+
getPublicAuthConfig: async () => null,
|
|
35
|
+
baseUrl: ""
|
|
36
|
+
};
|
|
11
37
|
}
|
|
12
38
|
return context;
|
|
13
39
|
}
|
package/dist/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useState","useEffect"],"mappings":";;;;;AA8DA,IAAM,eAAA,GAAkB,cAAgD,MAAS,CAAA;AA8f1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAU,WAAW,eAAe,CAAA;AAC1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,EACpE;AACA,EAAA,OAAO,OAAA;AACT;;;AC9hBO,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;;;ACNO,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;ACCO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIA,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 {\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';\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<void>;\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}\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={process.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 throw new 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 }\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 <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 baseUrl,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n }}\n >\n {children}\n </InsforgeContext.Provider>\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 if (!context) {\n throw new Error('useInsforge must be used within InsforgeProvider');\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\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 * await updateUser({ name });\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 * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])\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 { oauthProviders, authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {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;AC4DtE,IAAM,eAAA,GAAkBA,cAAgD,MAAS,CAAA;AAggB1E,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUC,WAAW,eAAe,CAAA;AAI1C,EAAA,IAAI,CAAC,OAAA,EAAS;AAEZ,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEjC,MAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,IACpE;AAIA,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,YAAY,YAAY;AAAA,MAAC,CAAA;AAAA,MACzB,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;AAAA,KACX;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC/jBO,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;;;ACNO,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;ACCO,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 throw new Error('useNavigationAdapter must be used within NavigationProvider');\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<void>;\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}\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={process.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 throw new 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 }\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 baseUrl,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\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 // Check if we're in a browser environment\n if (typeof window !== 'undefined') {\n // Client-side but no provider - this is an error\n throw new Error('useInsforge must be used within InsforgeProvider');\n }\n\n // SSR - return safe defaults that make components render nothing\n // SignedIn/SignedOut will both return null when isLoaded = false\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 () => {},\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 } 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\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 * await updateUser({ name });\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 * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])\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 { oauthProviders, authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {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
|
@@ -15,13 +15,52 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
17
|
var react = require('react');
|
|
18
|
-
var reactRouterDom = require('react-router-dom');
|
|
19
|
-
var sdk = require('@insforge/sdk');
|
|
20
18
|
var jsxRuntime = require('react/jsx-runtime');
|
|
19
|
+
var sdk = require('@insforge/sdk');
|
|
21
20
|
var lucideReact = require('lucide-react');
|
|
22
21
|
var zod = require('zod');
|
|
23
22
|
|
|
24
23
|
// src/components/SignIn.tsx
|
|
24
|
+
var NavigationContext = react.createContext(null);
|
|
25
|
+
function NavigationProvider({ adapter, children }) {
|
|
26
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NavigationContext.Provider, { value: adapter, children });
|
|
27
|
+
}
|
|
28
|
+
function useNavigationAdapter() {
|
|
29
|
+
const adapter = react.useContext(NavigationContext);
|
|
30
|
+
if (!adapter) {
|
|
31
|
+
throw new Error("useNavigationAdapter must be used within NavigationProvider");
|
|
32
|
+
}
|
|
33
|
+
return adapter;
|
|
34
|
+
}
|
|
35
|
+
var BrowserNavigationAdapter = {
|
|
36
|
+
/**
|
|
37
|
+
* Returns URLSearchParams from current window.location.search
|
|
38
|
+
* Note: Not reactive - reads once on component mount
|
|
39
|
+
* This is sufficient for auth flows where we read initial URL params
|
|
40
|
+
*/
|
|
41
|
+
useSearchParams() {
|
|
42
|
+
const [searchParams] = react.useState(() => {
|
|
43
|
+
if (typeof window === "undefined") {
|
|
44
|
+
return new URLSearchParams();
|
|
45
|
+
}
|
|
46
|
+
return new URLSearchParams(window.location.search);
|
|
47
|
+
});
|
|
48
|
+
return searchParams;
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Native <a> tag for navigation
|
|
52
|
+
* Uses full page reload
|
|
53
|
+
*/
|
|
54
|
+
Link({ href, className, children }) {
|
|
55
|
+
return /* @__PURE__ */ jsxRuntime.jsx("a", { href, className, children });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/navigation/useSearchParams.ts
|
|
60
|
+
function useSearchParams() {
|
|
61
|
+
const adapter = useNavigationAdapter();
|
|
62
|
+
return adapter.useSearchParams();
|
|
63
|
+
}
|
|
25
64
|
var InsforgeContext = react.createContext(void 0);
|
|
26
65
|
function InsforgeProvider({
|
|
27
66
|
children,
|
|
@@ -349,7 +388,7 @@ function InsforgeProvider({
|
|
|
349
388
|
},
|
|
350
389
|
[insforge, afterSignInUrl]
|
|
351
390
|
);
|
|
352
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
391
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NavigationProvider, { adapter: BrowserNavigationAdapter, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
353
392
|
InsforgeContext.Provider,
|
|
354
393
|
{
|
|
355
394
|
value: {
|
|
@@ -373,12 +412,37 @@ function InsforgeProvider({
|
|
|
373
412
|
},
|
|
374
413
|
children
|
|
375
414
|
}
|
|
376
|
-
);
|
|
415
|
+
) });
|
|
377
416
|
}
|
|
378
417
|
function useInsforge() {
|
|
379
418
|
const context = react.useContext(InsforgeContext);
|
|
380
419
|
if (!context) {
|
|
381
|
-
|
|
420
|
+
if (typeof window !== "undefined") {
|
|
421
|
+
throw new Error("useInsforge must be used within InsforgeProvider");
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
user: null,
|
|
425
|
+
isLoaded: false,
|
|
426
|
+
isSignedIn: false,
|
|
427
|
+
setUser: () => {
|
|
428
|
+
},
|
|
429
|
+
signIn: async () => ({ error: "SSR mode" }),
|
|
430
|
+
signUp: async () => ({ error: "SSR mode" }),
|
|
431
|
+
signOut: async () => {
|
|
432
|
+
},
|
|
433
|
+
updateUser: async () => {
|
|
434
|
+
},
|
|
435
|
+
reloadAuth: async () => ({ success: false, error: "SSR mode" }),
|
|
436
|
+
sendVerificationEmail: async () => null,
|
|
437
|
+
sendResetPasswordEmail: async () => null,
|
|
438
|
+
resetPassword: async () => null,
|
|
439
|
+
verifyEmail: async () => null,
|
|
440
|
+
exchangeResetPasswordToken: async () => ({ error: { message: "SSR mode" } }),
|
|
441
|
+
loginWithOAuth: async () => {
|
|
442
|
+
},
|
|
443
|
+
getPublicAuthConfig: async () => null,
|
|
444
|
+
baseUrl: ""
|
|
445
|
+
};
|
|
382
446
|
}
|
|
383
447
|
return context;
|
|
384
448
|
}
|
|
@@ -591,7 +655,8 @@ function AuthPasswordField({
|
|
|
591
655
|
onFocus,
|
|
592
656
|
...props
|
|
593
657
|
}) {
|
|
594
|
-
const
|
|
658
|
+
const searchParams = useSearchParams();
|
|
659
|
+
const { Link } = useNavigationAdapter();
|
|
595
660
|
const [showPassword, setShowPassword] = react.useState(false);
|
|
596
661
|
const [showStrength, setShowStrength] = react.useState(false);
|
|
597
662
|
const resolvedForgotPasswordHref = react.useMemo(
|
|
@@ -607,7 +672,7 @@ function AuthPasswordField({
|
|
|
607
672
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "if-passwordField if-internal-p5w9m7", children: [
|
|
608
673
|
(label || forgotPasswordLink) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "if-passwordField-labelRow", children: [
|
|
609
674
|
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: "if-passwordField-label", children: label }),
|
|
610
|
-
forgotPasswordLink && resolvedForgotPasswordHref && /* @__PURE__ */ jsxRuntime.jsx(
|
|
675
|
+
forgotPasswordLink && resolvedForgotPasswordHref && /* @__PURE__ */ jsxRuntime.jsx(Link, { href: resolvedForgotPasswordHref, className: "if-passwordField-forgotLink", children: forgotPasswordLink.text || "Forget Password?" })
|
|
611
676
|
] }),
|
|
612
677
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "if-passwordField-inputWrapper", children: [
|
|
613
678
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -659,12 +724,13 @@ function AuthSubmitButton({
|
|
|
659
724
|
);
|
|
660
725
|
}
|
|
661
726
|
function AuthLink({ text, linkText, href }) {
|
|
662
|
-
const
|
|
727
|
+
const searchParams = useSearchParams();
|
|
728
|
+
const { Link } = useNavigationAdapter();
|
|
663
729
|
const finalHref = resolveAuthUrl(href, searchParams);
|
|
664
730
|
return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "if-authLink if-internal-al5w9p", children: [
|
|
665
731
|
text && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "if-authLink-text", children: text }),
|
|
666
732
|
text && " ",
|
|
667
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
733
|
+
/* @__PURE__ */ jsxRuntime.jsx(Link, { href: finalHref, className: "if-authLink-link", children: linkText })
|
|
668
734
|
] });
|
|
669
735
|
}
|
|
670
736
|
function AuthDivider({ text = "or" }) {
|
|
@@ -1307,7 +1373,7 @@ function SignIn({ onError, ...uiProps }) {
|
|
|
1307
1373
|
const [loading, setLoading] = react.useState(false);
|
|
1308
1374
|
const [step, setStep] = react.useState("form");
|
|
1309
1375
|
const [oauthLoading] = react.useState(null);
|
|
1310
|
-
const
|
|
1376
|
+
const searchParams = useSearchParams();
|
|
1311
1377
|
const redirectUrl = searchParams.get("redirect");
|
|
1312
1378
|
async function handleSubmit(e) {
|
|
1313
1379
|
e.preventDefault();
|
|
@@ -1559,7 +1625,7 @@ function SignUp({ onError, ...uiProps }) {
|
|
|
1559
1625
|
const [loading, setLoading] = react.useState(false);
|
|
1560
1626
|
const [step, setStep] = react.useState("form");
|
|
1561
1627
|
const [oauthLoading] = react.useState(null);
|
|
1562
|
-
const
|
|
1628
|
+
const searchParams = useSearchParams();
|
|
1563
1629
|
const redirectUrl = searchParams.get("redirect");
|
|
1564
1630
|
async function handleSubmit(e) {
|
|
1565
1631
|
e.preventDefault();
|
|
@@ -1824,7 +1890,7 @@ function ResetPasswordForm({
|
|
|
1824
1890
|
function ForgotPassword({ onError, ...uiProps }) {
|
|
1825
1891
|
const { sendResetPasswordEmail, exchangeResetPasswordToken, resetPassword } = useInsforge();
|
|
1826
1892
|
const { authConfig } = usePublicAuthConfig();
|
|
1827
|
-
const
|
|
1893
|
+
const searchParams = useSearchParams();
|
|
1828
1894
|
const [step, setStep] = react.useState("email");
|
|
1829
1895
|
const [email, setEmail] = react.useState("");
|
|
1830
1896
|
const [resetToken, setResetToken] = react.useState("");
|
|
@@ -1980,7 +2046,7 @@ function ForgotPassword({ onError, ...uiProps }) {
|
|
|
1980
2046
|
);
|
|
1981
2047
|
}
|
|
1982
2048
|
function ResetPassword({ onError, ...uiProps }) {
|
|
1983
|
-
const
|
|
2049
|
+
const searchParams = useSearchParams();
|
|
1984
2050
|
const token = searchParams.get("token");
|
|
1985
2051
|
const { resetPassword } = useInsforge();
|
|
1986
2052
|
const { authConfig } = usePublicAuthConfig();
|
|
@@ -2401,41 +2467,6 @@ function useUser() {
|
|
|
2401
2467
|
const { user, isLoaded, updateUser, setUser } = useInsforge();
|
|
2402
2468
|
return { user, isLoaded, updateUser, setUser };
|
|
2403
2469
|
}
|
|
2404
|
-
function RedirectToAuth({
|
|
2405
|
-
baseUrl,
|
|
2406
|
-
path,
|
|
2407
|
-
afterSignInUrl
|
|
2408
|
-
}) {
|
|
2409
|
-
react.useEffect(() => {
|
|
2410
|
-
const currentUrl = window.location.origin + afterSignInUrl;
|
|
2411
|
-
const authUrl = new URL(path, baseUrl);
|
|
2412
|
-
authUrl.searchParams.set("redirect", currentUrl);
|
|
2413
|
-
window.location.replace(authUrl.toString());
|
|
2414
|
-
}, [baseUrl, path, afterSignInUrl]);
|
|
2415
|
-
return null;
|
|
2416
|
-
}
|
|
2417
|
-
function getInsforgeRoutes(config) {
|
|
2418
|
-
const { baseUrl, builtInAuth = true, paths = {}, afterSignInUrl = "/" } = config;
|
|
2419
|
-
const { signIn = "/sign-in", signUp = "/sign-up", forgotPassword = "/forgot-password" } = paths;
|
|
2420
|
-
const routes = [];
|
|
2421
|
-
if (builtInAuth) {
|
|
2422
|
-
routes.push(
|
|
2423
|
-
{
|
|
2424
|
-
path: signIn,
|
|
2425
|
-
element: /* @__PURE__ */ jsxRuntime.jsx(RedirectToAuth, { baseUrl, path: "/auth/sign-in", afterSignInUrl })
|
|
2426
|
-
},
|
|
2427
|
-
{
|
|
2428
|
-
path: signUp,
|
|
2429
|
-
element: /* @__PURE__ */ jsxRuntime.jsx(RedirectToAuth, { baseUrl, path: "/auth/sign-up", afterSignInUrl })
|
|
2430
|
-
},
|
|
2431
|
-
{
|
|
2432
|
-
path: forgotPassword,
|
|
2433
|
-
element: /* @__PURE__ */ jsxRuntime.jsx(RedirectToAuth, { baseUrl, path: "/auth/forgot-password" })
|
|
2434
|
-
}
|
|
2435
|
-
);
|
|
2436
|
-
}
|
|
2437
|
-
return routes;
|
|
2438
|
-
}
|
|
2439
2470
|
|
|
2440
2471
|
exports.AuthBranding = AuthBranding;
|
|
2441
2472
|
exports.AuthContainer = AuthContainer;
|
|
@@ -2452,9 +2483,11 @@ exports.AuthPasswordStrengthIndicator = AuthPasswordStrengthIndicator;
|
|
|
2452
2483
|
exports.AuthResetPasswordVerificationStep = AuthResetPasswordVerificationStep;
|
|
2453
2484
|
exports.AuthSubmitButton = AuthSubmitButton;
|
|
2454
2485
|
exports.AuthVerificationCodeInput = AuthVerificationCodeInput;
|
|
2486
|
+
exports.BrowserNavigationAdapter = BrowserNavigationAdapter;
|
|
2455
2487
|
exports.ForgotPassword = ForgotPassword;
|
|
2456
2488
|
exports.ForgotPasswordForm = ForgotPasswordForm;
|
|
2457
2489
|
exports.InsforgeProvider = InsforgeProvider;
|
|
2490
|
+
exports.NavigationProvider = NavigationProvider;
|
|
2458
2491
|
exports.OAUTH_PROVIDER_CONFIG = OAUTH_PROVIDER_CONFIG;
|
|
2459
2492
|
exports.Protect = Protect;
|
|
2460
2493
|
exports.ResetPassword = ResetPassword;
|
|
@@ -2472,14 +2505,15 @@ exports.checkPasswordStrength = checkPasswordStrength;
|
|
|
2472
2505
|
exports.createPasswordSchema = createPasswordSchema;
|
|
2473
2506
|
exports.emailSchema = emailSchema;
|
|
2474
2507
|
exports.getAllProviderConfigs = getAllProviderConfigs;
|
|
2475
|
-
exports.getInsforgeRoutes = getInsforgeRoutes;
|
|
2476
2508
|
exports.getProviderConfig = getProviderConfig;
|
|
2477
2509
|
exports.passwordSchema = passwordSchema;
|
|
2478
2510
|
exports.resolveAuthPath = resolveAuthPath;
|
|
2479
2511
|
exports.resolveAuthUrl = resolveAuthUrl;
|
|
2480
2512
|
exports.useAuth = useAuth;
|
|
2481
2513
|
exports.useInsforge = useInsforge;
|
|
2514
|
+
exports.useNavigationAdapter = useNavigationAdapter;
|
|
2482
2515
|
exports.usePublicAuthConfig = usePublicAuthConfig;
|
|
2516
|
+
exports.useSearchParams = useSearchParams;
|
|
2483
2517
|
exports.useUser = useUser;
|
|
2484
2518
|
exports.validateEmail = validateEmail;
|
|
2485
2519
|
exports.validatePassword = validatePassword;
|