@insforge/react 1.1.5 → 1.1.6-test.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atoms.cjs +1 -0
- package/dist/atoms.cjs.map +1 -1
- package/dist/atoms.js +1 -0
- package/dist/atoms.js.map +1 -1
- package/dist/components.cjs +95 -4
- package/dist/components.cjs.map +1 -1
- package/dist/components.js +95 -4
- package/dist/components.js.map +1 -1
- package/dist/forms.cjs +1 -0
- package/dist/forms.cjs.map +1 -1
- package/dist/forms.js +1 -0
- package/dist/forms.js.map +1 -1
- package/dist/hooks.cjs +1 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.js +1 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +119 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +118 -6
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +30 -0
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +39 -1
- package/dist/lib.d.ts +39 -1
- package/dist/lib.js +29 -1
- package/dist/lib.js.map +1 -1
- package/package.json +2 -2
package/dist/hooks.cjs
CHANGED
|
@@ -28,6 +28,7 @@ function useInsforge() {
|
|
|
28
28
|
exchangeResetPasswordToken: () => Promise.resolve({ error: { message: "SSR mode" } }),
|
|
29
29
|
loginWithOAuth: () => Promise.resolve(),
|
|
30
30
|
getPublicAuthConfig: () => Promise.resolve(null),
|
|
31
|
+
getSession: () => Promise.resolve(null),
|
|
31
32
|
baseUrl: "",
|
|
32
33
|
afterSignInUrl: "/"
|
|
33
34
|
};
|
package/dist/hooks.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["createContext","useContext","InsforgeContext","useState","useEffect"],"mappings":";;;;;;;AAG0BA,oBAAwC,IAAI;AC4Q/D,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAA4CC,iBAAWC,uBAAe,CAAA;AAE5E,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,MAAA;AAAA,MACN,MAAA,EAAQ,MAAA;AAAA,MACR,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY,MAAA;AAAA,MACZ,SAAS,MAAM;AAAA,MAAC,CAAA;AAAA,MAChB,QAAQ,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACnD,QAAQ,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACnD,OAAA,EAAS,MAAM,OAAA,CAAQ,OAAA,EAAQ;AAAA,MAC/B,YAAY,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACvD,UAAA,EAAY,MAAM,OAAA,CAAQ,OAAA,CAAQ,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,UAAA,EAAY,CAAA;AAAA,MACvE,uBAAA,EAAyB,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MACnD,sBAAA,EAAwB,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MAClD,aAAA,EAAe,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MACzC,aAAa,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACxD,0BAAA,EAA4B,MAAM,OAAA,CAAQ,OAAA,CAAQ,EAAE,OAAO,EAAE,OAAA,EAAS,UAAA,EAAW,EAAG,CAAA;AAAA,MACpF,cAAA,EAAgB,MAAM,OAAA,CAAQ,OAAA,EAAQ;AAAA,MACtC,mBAAA,EAAqB,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MAC/C,OAAA,EAAS,EAAA;AAAA,MACT,cAAA,EAAgB;AAAA,KAClB;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;ACtQO,SAAS,OAAA,GAYd;AACA,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,GAAyB;AACvC,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;ACbO,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,KAAK,WAAA,EAAY;AAAA,EACnB,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAChC","file":"hooks.cjs","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["createContext","useContext","InsforgeContext","useState","useEffect"],"mappings":"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","file":"hooks.cjs","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n getSession: () => manager.getSession(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n getSession: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
|
package/dist/hooks.js
CHANGED
|
@@ -26,6 +26,7 @@ function useInsforge() {
|
|
|
26
26
|
exchangeResetPasswordToken: () => Promise.resolve({ error: { message: "SSR mode" } }),
|
|
27
27
|
loginWithOAuth: () => Promise.resolve(),
|
|
28
28
|
getPublicAuthConfig: () => Promise.resolve(null),
|
|
29
|
+
getSession: () => Promise.resolve(null),
|
|
29
30
|
baseUrl: "",
|
|
30
31
|
afterSignInUrl: "/"
|
|
31
32
|
};
|
package/dist/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useContext","useState","useEffect"],"mappings":"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","file":"hooks.js","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useContext","useState","useEffect"],"mappings":"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","file":"hooks.js","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n getSession: () => manager.getSession(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n getSession: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -842,6 +842,26 @@ var InsforgeManager = class _InsforgeManager {
|
|
|
842
842
|
getSDK() {
|
|
843
843
|
return this.sdk;
|
|
844
844
|
}
|
|
845
|
+
/**
|
|
846
|
+
* Get current session with accessToken
|
|
847
|
+
* Used for hosted auth to construct legacy flow redirect URL
|
|
848
|
+
*
|
|
849
|
+
* @returns Session with accessToken and user, or null if not signed in
|
|
850
|
+
*/
|
|
851
|
+
async getSession() {
|
|
852
|
+
const result = await this.sdk.auth.getCurrentSession();
|
|
853
|
+
if (result.data?.session) {
|
|
854
|
+
return {
|
|
855
|
+
accessToken: result.data.session.accessToken,
|
|
856
|
+
user: {
|
|
857
|
+
id: result.data.session.user.id,
|
|
858
|
+
email: result.data.session.user.email,
|
|
859
|
+
profile: result.data.session.user.profile
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
845
865
|
// Cleanup
|
|
846
866
|
cleanup() {
|
|
847
867
|
if (this.refreshIntervalRef) {
|
|
@@ -2221,7 +2241,8 @@ function InsforgeProviderCore({
|
|
|
2221
2241
|
verifyEmail: (otp, email) => manager.verifyEmail(otp, email),
|
|
2222
2242
|
exchangeResetPasswordToken: (email, code) => manager.exchangeResetPasswordToken(email, code),
|
|
2223
2243
|
loginWithOAuth: (provider, redirectTo) => manager.loginWithOAuth(provider, redirectTo),
|
|
2224
|
-
getPublicAuthConfig: () => manager.getPublicAuthConfig()
|
|
2244
|
+
getPublicAuthConfig: () => manager.getPublicAuthConfig(),
|
|
2245
|
+
getSession: () => manager.getSession()
|
|
2225
2246
|
}),
|
|
2226
2247
|
[manager]
|
|
2227
2248
|
// Only depends on manager (stable)
|
|
@@ -2281,6 +2302,7 @@ function useInsforge() {
|
|
|
2281
2302
|
exchangeResetPasswordToken: () => Promise.resolve({ error: { message: "SSR mode" } }),
|
|
2282
2303
|
loginWithOAuth: () => Promise.resolve(),
|
|
2283
2304
|
getPublicAuthConfig: () => Promise.resolve(null),
|
|
2305
|
+
getSession: () => Promise.resolve(null),
|
|
2284
2306
|
baseUrl: "",
|
|
2285
2307
|
afterSignInUrl: "/"
|
|
2286
2308
|
};
|
|
@@ -4161,8 +4183,36 @@ function SignInForm({
|
|
|
4161
4183
|
] })
|
|
4162
4184
|
] });
|
|
4163
4185
|
}
|
|
4186
|
+
|
|
4187
|
+
// src/lib/hosted-auth.ts
|
|
4188
|
+
function isHostedAuthEnvironment() {
|
|
4189
|
+
if (typeof window === "undefined") {
|
|
4190
|
+
return false;
|
|
4191
|
+
}
|
|
4192
|
+
const { hostname, port, protocol } = window.location;
|
|
4193
|
+
if (hostname === "localhost" && port === "7130") {
|
|
4194
|
+
return true;
|
|
4195
|
+
}
|
|
4196
|
+
if (protocol === "https:" && hostname.endsWith(".insforge.app")) {
|
|
4197
|
+
return true;
|
|
4198
|
+
}
|
|
4199
|
+
return false;
|
|
4200
|
+
}
|
|
4201
|
+
function buildLegacyAuthUrl(redirectUrl, session) {
|
|
4202
|
+
const url = new URL(redirectUrl);
|
|
4203
|
+
url.searchParams.set("access_token", session.accessToken);
|
|
4204
|
+
url.searchParams.set("user_id", session.userId);
|
|
4205
|
+
url.searchParams.set("email", session.email);
|
|
4206
|
+
if (session.name) {
|
|
4207
|
+
url.searchParams.set("name", session.name);
|
|
4208
|
+
}
|
|
4209
|
+
if (session.csrfToken) {
|
|
4210
|
+
url.searchParams.set("csrf_token", session.csrfToken);
|
|
4211
|
+
}
|
|
4212
|
+
return url.toString();
|
|
4213
|
+
}
|
|
4164
4214
|
function SignIn({ onError, ...uiProps }) {
|
|
4165
|
-
const { signIn, verifyEmail, loginWithOAuth } = useInsforge();
|
|
4215
|
+
const { signIn, verifyEmail, loginWithOAuth, isSignedIn, getSession, afterSignInUrl } = useInsforge();
|
|
4166
4216
|
const { authConfig } = usePublicAuthConfig();
|
|
4167
4217
|
const [email, setEmail] = React2.useState("");
|
|
4168
4218
|
const [password, setPassword] = React2.useState("");
|
|
@@ -4172,6 +4222,37 @@ function SignIn({ onError, ...uiProps }) {
|
|
|
4172
4222
|
const [oauthLoading] = React2.useState(null);
|
|
4173
4223
|
const searchParams = useSearchParams();
|
|
4174
4224
|
const redirectUrl = searchParams.get("redirect");
|
|
4225
|
+
const isHandlingOAuthRedirectRef = React2.useRef(false);
|
|
4226
|
+
React2.useEffect(() => {
|
|
4227
|
+
async function handleOAuthComplete() {
|
|
4228
|
+
if (!isSignedIn || isHandlingOAuthRedirectRef.current) return;
|
|
4229
|
+
const isHosted = isHostedAuthEnvironment();
|
|
4230
|
+
if (isHosted && redirectUrl) {
|
|
4231
|
+
isHandlingOAuthRedirectRef.current = true;
|
|
4232
|
+
try {
|
|
4233
|
+
const session = await getSession();
|
|
4234
|
+
if (!session) {
|
|
4235
|
+
isHandlingOAuthRedirectRef.current = false;
|
|
4236
|
+
return;
|
|
4237
|
+
}
|
|
4238
|
+
const legacyUrl = buildLegacyAuthUrl(redirectUrl, {
|
|
4239
|
+
accessToken: session.accessToken,
|
|
4240
|
+
userId: session.user.id,
|
|
4241
|
+
email: session.user.email,
|
|
4242
|
+
name: session.user.profile?.name || ""
|
|
4243
|
+
});
|
|
4244
|
+
window.location.href = legacyUrl;
|
|
4245
|
+
} catch (err) {
|
|
4246
|
+
console.error("Failed to redirect after OAuth:", err);
|
|
4247
|
+
isHandlingOAuthRedirectRef.current = false;
|
|
4248
|
+
}
|
|
4249
|
+
} else if (!isHosted) {
|
|
4250
|
+
const finalUrl = redirectUrl || afterSignInUrl || "/";
|
|
4251
|
+
window.location.href = new URL(finalUrl, window.location.origin).toString();
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
void handleOAuthComplete();
|
|
4255
|
+
}, [isSignedIn, redirectUrl, getSession, afterSignInUrl]);
|
|
4175
4256
|
async function handleSubmit(e) {
|
|
4176
4257
|
e.preventDefault();
|
|
4177
4258
|
setLoading(true);
|
|
@@ -4231,7 +4312,7 @@ function SignIn({ onError, ...uiProps }) {
|
|
|
4231
4312
|
}
|
|
4232
4313
|
function handleOAuth(provider) {
|
|
4233
4314
|
try {
|
|
4234
|
-
void loginWithOAuth(provider,
|
|
4315
|
+
void loginWithOAuth(provider, window.location.href);
|
|
4235
4316
|
} catch (err) {
|
|
4236
4317
|
const errorMessage = err instanceof Error ? err.message : "OAuth login failed";
|
|
4237
4318
|
setError(errorMessage);
|
|
@@ -4426,7 +4507,7 @@ function checkPasswordStrength(password) {
|
|
|
4426
4507
|
return { score, feedback };
|
|
4427
4508
|
}
|
|
4428
4509
|
function SignUp({ onError, emailRedirectTo, ...uiProps }) {
|
|
4429
|
-
const { signUp, verifyEmail, loginWithOAuth } = useInsforge();
|
|
4510
|
+
const { signUp, verifyEmail, loginWithOAuth, isSignedIn, getSession, afterSignInUrl } = useInsforge();
|
|
4430
4511
|
const { authConfig } = usePublicAuthConfig();
|
|
4431
4512
|
const [email, setEmail] = React2.useState("");
|
|
4432
4513
|
const [password, setPassword] = React2.useState("");
|
|
@@ -4436,6 +4517,37 @@ function SignUp({ onError, emailRedirectTo, ...uiProps }) {
|
|
|
4436
4517
|
const [oauthLoading] = React2.useState(null);
|
|
4437
4518
|
const searchParams = useSearchParams();
|
|
4438
4519
|
const redirectUrl = searchParams.get("redirect");
|
|
4520
|
+
const isHandlingOAuthRedirectRef = React2.useRef(false);
|
|
4521
|
+
React2.useEffect(() => {
|
|
4522
|
+
async function handleOAuthComplete() {
|
|
4523
|
+
if (!isSignedIn || isHandlingOAuthRedirectRef.current) return;
|
|
4524
|
+
const isHosted = isHostedAuthEnvironment();
|
|
4525
|
+
if (isHosted && redirectUrl) {
|
|
4526
|
+
isHandlingOAuthRedirectRef.current = true;
|
|
4527
|
+
try {
|
|
4528
|
+
const session = await getSession();
|
|
4529
|
+
if (!session) {
|
|
4530
|
+
isHandlingOAuthRedirectRef.current = false;
|
|
4531
|
+
return;
|
|
4532
|
+
}
|
|
4533
|
+
const legacyUrl = buildLegacyAuthUrl(redirectUrl, {
|
|
4534
|
+
accessToken: session.accessToken,
|
|
4535
|
+
userId: session.user.id,
|
|
4536
|
+
email: session.user.email,
|
|
4537
|
+
name: session.user.profile?.name || ""
|
|
4538
|
+
});
|
|
4539
|
+
window.location.href = legacyUrl;
|
|
4540
|
+
} catch (err) {
|
|
4541
|
+
console.error("Failed to redirect after OAuth:", err);
|
|
4542
|
+
isHandlingOAuthRedirectRef.current = false;
|
|
4543
|
+
}
|
|
4544
|
+
} else if (!isHosted) {
|
|
4545
|
+
const finalUrl = redirectUrl || afterSignInUrl || "/";
|
|
4546
|
+
window.location.href = new URL(finalUrl, window.location.origin).toString();
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
void handleOAuthComplete();
|
|
4550
|
+
}, [isSignedIn, redirectUrl, getSession, afterSignInUrl]);
|
|
4439
4551
|
async function handleSubmit(e) {
|
|
4440
4552
|
e.preventDefault();
|
|
4441
4553
|
setLoading(true);
|
|
@@ -4521,7 +4633,7 @@ function SignUp({ onError, emailRedirectTo, ...uiProps }) {
|
|
|
4521
4633
|
}
|
|
4522
4634
|
function handleOAuth(provider) {
|
|
4523
4635
|
try {
|
|
4524
|
-
void loginWithOAuth(provider,
|
|
4636
|
+
void loginWithOAuth(provider, window.location.href);
|
|
4525
4637
|
} catch (err) {
|
|
4526
4638
|
const errorMessage = err instanceof Error ? err.message : "OAuth login failed";
|
|
4527
4639
|
setError(errorMessage);
|
|
@@ -5869,11 +5981,13 @@ exports.UserButton = UserButton;
|
|
|
5869
5981
|
exports.UserProfileModal = UserProfileModal;
|
|
5870
5982
|
exports.VerifyEmail = VerifyEmail;
|
|
5871
5983
|
exports.VerifyEmailStatus = VerifyEmailStatus;
|
|
5984
|
+
exports.buildLegacyAuthUrl = buildLegacyAuthUrl;
|
|
5872
5985
|
exports.checkPasswordStrength = checkPasswordStrength;
|
|
5873
5986
|
exports.createPasswordSchema = createPasswordSchema;
|
|
5874
5987
|
exports.emailSchema = emailSchema;
|
|
5875
5988
|
exports.getAllProviderConfigs = getAllProviderConfigs;
|
|
5876
5989
|
exports.getProviderConfig = getProviderConfig;
|
|
5990
|
+
exports.isHostedAuthEnvironment = isHostedAuthEnvironment;
|
|
5877
5991
|
exports.passwordSchema = passwordSchema;
|
|
5878
5992
|
exports.resolveAuthPath = resolveAuthPath;
|
|
5879
5993
|
exports.resolveAuthUrl = resolveAuthUrl;
|