@insforge/react 0.2.3 → 0.2.4

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/hooks.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { InsforgeUser } from './types.mjs';
2
- import { OAuthProvidersSchema, PublicEmailAuthConfig } from '@insforge/shared-schemas';
2
+ import { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -110,6 +110,9 @@ declare function useUser(): {
110
110
  * API calls on every page load.
111
111
  *
112
112
  * @returns Object containing OAuth providers, email auth config, and loading state
113
+ * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])
114
+ * - `emailConfig`: Email authentication configuration object with password rules
115
+ * - `isLoaded`: Boolean indicating if the config has been fetched
113
116
  *
114
117
  * @example
115
118
  * ```tsx
@@ -131,8 +134,7 @@ declare function useUser(): {
131
134
  * @requires Must be used within InsforgeProvider
132
135
  */
133
136
  declare function usePublicAuthConfig(): {
134
- oauthProviders: OAuthProvidersSchema[];
135
- emailConfig: PublicEmailAuthConfig | null;
137
+ emailConfig: GetPublicAuthConfigResponse | null;
136
138
  isLoaded: boolean;
137
139
  };
138
140
 
package/dist/hooks.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { InsforgeUser } from './types.js';
2
- import { OAuthProvidersSchema, PublicEmailAuthConfig } from '@insforge/shared-schemas';
2
+ import { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -110,6 +110,9 @@ declare function useUser(): {
110
110
  * API calls on every page load.
111
111
  *
112
112
  * @returns Object containing OAuth providers, email auth config, and loading state
113
+ * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])
114
+ * - `emailConfig`: Email authentication configuration object with password rules
115
+ * - `isLoaded`: Boolean indicating if the config has been fetched
113
116
  *
114
117
  * @example
115
118
  * ```tsx
@@ -131,8 +134,7 @@ declare function useUser(): {
131
134
  * @requires Must be used within InsforgeProvider
132
135
  */
133
136
  declare function usePublicAuthConfig(): {
134
- oauthProviders: OAuthProvidersSchema[];
135
- emailConfig: PublicEmailAuthConfig | null;
137
+ emailConfig: GetPublicAuthConfigResponse | null;
136
138
  isLoaded: boolean;
137
139
  };
138
140
 
package/dist/hooks.js CHANGED
@@ -27,42 +27,23 @@ function useUser() {
27
27
  return { user, isLoaded, updateUser, setUser };
28
28
  }
29
29
  function usePublicAuthConfig() {
30
- const { baseUrl } = useInsforge();
31
- const [oauthProviders, setOAuthProviders] = react.useState([]);
30
+ const { getPublicAuthConfig } = useInsforge();
32
31
  const [emailConfig, setEmailConfig] = react.useState(null);
33
32
  const [isLoaded, setIsLoaded] = react.useState(false);
34
33
  react.useEffect(() => {
35
- let mounted = true;
36
34
  async function fetchConfig() {
37
- try {
38
- const response = await fetch(`${baseUrl}/api/auth/public-config`);
39
- if (!mounted) return;
40
- if (!response.ok) {
41
- console.warn("[usePublicAuthConfig] Failed to fetch public auth config:", response.statusText);
42
- setOAuthProviders([]);
43
- setEmailConfig(null);
44
- } else {
45
- const data = await response.json();
46
- const providerNames = data.providers?.map((p) => p.provider) || [];
47
- setOAuthProviders(providerNames);
48
- setEmailConfig(data.email || null);
49
- }
50
- setIsLoaded(true);
51
- } catch (error) {
52
- console.warn("[usePublicAuthConfig] Unexpected error:", error);
53
- if (mounted) {
54
- setOAuthProviders([]);
55
- setEmailConfig(null);
56
- setIsLoaded(true);
57
- }
35
+ const result = await getPublicAuthConfig();
36
+ if (result) {
37
+ setEmailConfig(result);
38
+ } else {
39
+ console.error("[usePublicAuthConfig] Failed to get public auth config");
40
+ setEmailConfig(null);
58
41
  }
42
+ setIsLoaded(true);
59
43
  }
60
44
  fetchConfig();
61
- return () => {
62
- mounted = false;
63
- };
64
- }, [baseUrl]);
65
- return { oauthProviders, emailConfig, isLoaded };
45
+ }, [getPublicAuthConfig]);
46
+ return { emailConfig, isLoaded };
66
47
  }
67
48
 
68
49
  exports.useAuth = useAuth;
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":["createContext","useContext","useState","useEffect"],"mappings":";;;;;;AAuDA,IAAM,eAAA,GAAkBA,mBAAA;AAAA,EACtB;AACF,CAAA;AAsYO,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;;;ACjaO,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;ACEO,SAAS,mBAAA,GAId;AACA,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,WAAA,EAAY;AAChC,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIC,cAAAA,CAAiC,EAAE,CAAA;AAC/E,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAuC,IAAI,CAAA;AACjF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAS,KAAK,CAAA;AAE9C,EAAAC,gBAAU,MAAM;AACd,IAAA,IAAI,OAAA,GAAU,IAAA;AAEd,IAAA,eAAe,WAAA,GAAc;AAC3B,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,uBAAA,CAAyB,CAAA;AAEhE,QAAA,IAAI,CAAC,OAAA,EAAS;AAEd,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,OAAA,CAAQ,IAAA,CAAK,2DAAA,EAA6D,QAAA,CAAS,UAAU,CAAA;AAC7F,UAAA,iBAAA,CAAkB,EAAE,CAAA;AACpB,UAAA,cAAA,CAAe,IAAI,CAAA;AAAA,QACrB,CAAA,MAAO;AACL,UAAA,MAAM,IAAA,GAA2E,MAAM,QAAA,CAAS,IAAA,EAAK;AACrG,UAAA,MAAM,aAAA,GAAgB,KAAK,SAAA,EAAW,GAAA,CAAI,CAAC,CAAA,KAA2B,CAAA,CAAE,QAAQ,CAAA,IAAK,EAAC;AACtF,UAAA,iBAAA,CAAkB,aAAa,CAAA;AAC/B,UAAA,cAAA,CAAe,IAAA,CAAK,SAAS,IAAI,CAAA;AAAA,QACnC;AAEA,QAAA,WAAA,CAAY,IAAI,CAAA;AAAA,MAClB,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,IAAA,CAAK,2CAA2C,KAAK,CAAA;AAC7D,QAAA,IAAI,OAAA,EAAS;AACX,UAAA,iBAAA,CAAkB,EAAE,CAAA;AACpB,UAAA,cAAA,CAAe,IAAI,CAAA;AACnB,UAAA,WAAA,CAAY,IAAI,CAAA;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAEA,IAAA,WAAA,EAAY;AAEZ,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,GAAU,KAAA;AAAA,IACZ,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO,EAAE,cAAA,EAAgB,WAAA,EAAa,QAAA,EAAS;AACjD","file":"hooks.js","sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n useContext,\r\n useEffect,\r\n useState,\r\n useCallback,\r\n useRef,\r\n type ReactNode,\r\n} from \"react\";\r\nimport { createClient } from \"@insforge/sdk\";\r\nimport type { InsforgeUser } from \"../types\";\r\n\r\ninterface InsforgeContextValue {\r\n // Auth state\r\n user: InsforgeUser | null;\r\n isLoaded: boolean;\r\n isSignedIn: boolean;\r\n\r\n // Auth methods\r\n setUser: (user: InsforgeUser | null) => void;\r\n signIn: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signUp: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signOut: () => Promise<void>;\r\n updateUser: (data: Partial<InsforgeUser>) => Promise<void>;\r\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\r\n\r\n // Email verification methods\r\n sendPasswordResetCode: (email: string) => Promise<{ success: boolean; message: string } | null>;\r\n resetPassword: (token: string, newPassword: string) => Promise<{ message: string; redirectTo?: string } | null>;\r\n verifyEmail: (token: string) => Promise<{ accessToken: string; user?: any } | null>;\r\n\r\n // Base config\r\n baseUrl: string;\r\n}\r\n\r\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(\r\n undefined\r\n);\r\n\r\nexport interface InsforgeProviderProps {\r\n children: ReactNode;\r\n baseUrl: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n // Optional: custom token sync functions (e.g., for Next.js cookie sync)\r\n syncTokenToCookie?: (token: string) => Promise<boolean>;\r\n clearCookie?: () => Promise<void>;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Manages user authentication state and provides all necessary context to child components.\r\n * Works with any React framework (Next.js, Vite, Remix, etc.).\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n *\r\n * export default function App() {\r\n * return (\r\n * <InsforgeProvider baseUrl={process.env.VITE_INSFORGE_BASE_URL}>\r\n * {children}\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 * baseUrl={baseUrl}\r\n * syncTokenToCookie={async (token) => {\r\n * await fetch('/api/auth', {\r\n * method: 'POST',\r\n * body: JSON.stringify({ token })\r\n * });\r\n * return true;\r\n * }}\r\n * clearCookie={async () => {\r\n * await fetch('/api/auth', { method: 'DELETE' });\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProvider({\r\n children,\r\n baseUrl,\r\n onAuthChange,\r\n syncTokenToCookie,\r\n clearCookie,\r\n}: InsforgeProviderProps) {\r\n // Auth state\r\n const [user, setUser] = useState<InsforgeUser | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\r\n\r\n // Initialize SDK client with lazy initialization - only runs once\r\n const [insforge] = useState(() => createClient({ baseUrl }));\r\n\r\n // Load auth state - returns explicit success/error status\r\n const loadAuthState = useCallback(async (): Promise<{\r\n success: boolean;\r\n error?: string;\r\n }> => {\r\n try {\r\n // Use SDK's getCurrentSession() to check for existing session\r\n const sessionResult = insforge.auth.getCurrentSession();\r\n const session = sessionResult.data?.session;\r\n const token = session?.accessToken || null;\r\n\r\n if (!token) {\r\n // No token, user is not authenticated\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"no_session\" };\r\n }\r\n\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n // Token is valid, update user state with fresh data\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n setIsLoaded(true);\r\n return { success: true };\r\n } else {\r\n // Token invalid or expired\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"invalid_token\" };\r\n }\r\n } catch (error) {\r\n // Token validation failed\r\n console.error(\"[InsforgeProvider] Token validation failed:\", error);\r\n\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error.message : \"authentication_failed\",\r\n };\r\n }\r\n }, [insforge, onAuthChange, syncTokenToCookie, clearCookie]);\r\n\r\n useEffect(() => {\r\n // Run loadAuthState only once on mount\r\n loadAuthState();\r\n\r\n return () => {\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n };\r\n }, []); // Empty deps - run only on mount\r\n\r\n /**\r\n * Helper function to handle successful authentication\r\n */\r\n const handleAuthSuccess = useCallback(\r\n async (\r\n authToken: string,\r\n fallbackUser?: { id?: string; email?: string; name?: string }\r\n ) => {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n\r\n // Try to sync token to cookie if function provided\r\n if (syncTokenToCookie) {\r\n try {\r\n await syncTokenToCookie(authToken);\r\n } catch (error) {\r\n // Cookie sync failed - that's okay\r\n }\r\n }\r\n } else if (fallbackUser) {\r\n // Fallback to basic user data if getCurrentUser fails\r\n const userData: InsforgeUser = {\r\n id: fallbackUser.id || \"\",\r\n email: fallbackUser.email || \"\",\r\n name: fallbackUser.name || \"\",\r\n avatarUrl: \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n }\r\n },\r\n [insforge, onAuthChange, syncTokenToCookie]\r\n );\r\n\r\n const signIn = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signInWithPassword({\r\n email,\r\n password,\r\n });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage =\r\n sdkResult.error?.message || \"Invalid email or password\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signUp = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signUp({ email, password });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage = sdkResult.error?.message || \"Sign up failed\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signOut = useCallback(async () => {\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n // Clear refresh interval if exists\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n }, [insforge, onAuthChange, clearCookie]);\r\n\r\n const updateUser = useCallback(\r\n async (data: Partial<InsforgeUser>) => {\r\n if (!user) throw new Error(\"No user signed in\");\r\n\r\n const profileUpdate: Record<string, any> = {\r\n nickname: data.name,\r\n avatarUrl: data.avatarUrl,\r\n };\r\n\r\n const result = await insforge.auth.setProfile(profileUpdate);\r\n\r\n if (result.data) {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const updatedUser: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n setUser(updatedUser);\r\n if (onAuthChange) {\r\n onAuthChange(updatedUser);\r\n }\r\n }\r\n }\r\n },\r\n [user, onAuthChange, insforge]\r\n );\r\n\r\n const sendPasswordResetCode = useCallback(\r\n async (email: string) => {\r\n const sdkResult = await insforge.auth.sendPasswordResetCode({ email });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const resetPassword = useCallback(\r\n async (token: string, newPassword: string) => {\r\n const sdkResult = await insforge.auth.resetPassword({ newPassword, otp: token });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const verifyEmail = useCallback(\r\n async (token: string) => {\r\n const sdkResult = await insforge.auth.verifyEmail({ otp: token });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n return (\r\n <InsforgeContext.Provider\r\n value={{\r\n user,\r\n isLoaded,\r\n isSignedIn: !!user,\r\n setUser,\r\n signIn,\r\n signUp,\r\n signOut,\r\n updateUser,\r\n reloadAuth: loadAuthState,\r\n baseUrl,\r\n sendPasswordResetCode,\r\n resetPassword,\r\n verifyEmail,\r\n }}\r\n >\r\n {children}\r\n </InsforgeContext.Provider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\r\n if (!context) {\r\n throw new Error(\"useInsforge must be used within InsforgeProvider\");\r\n }\r\n return context;\r\n}\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access authentication methods\r\n * \r\n * @returns Object containing:\r\n * - `signIn`: Function to sign in with email and password\r\n * - `signUp`: Function to sign up with email and password\r\n * - `signOut`: Function to sign out the current user\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `isSignedIn`: Boolean indicating if user is currently signed in\r\n * \r\n * @example\r\n * ```tsx\r\n * function LoginForm() {\r\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\r\n * \r\n * async function handleLogin(email: string, password: string) {\r\n * try {\r\n * await signIn(email, password);\r\n * // User is now signed in\r\n * } catch (error) {\r\n * console.error('Sign in failed:', error);\r\n * }\r\n * }\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\r\n * {/* form fields *\\/}\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useAuth() {\r\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\r\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\r\n}\r\n\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access current user data\r\n * \r\n * @returns Object containing:\r\n * - `user`: Current user object (InsforgeUser | null)\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `updateUser`: Function to update user profile data\r\n * - `setUser`: Internal function to manually set user state\r\n * \r\n * @example\r\n * ```tsx\r\n * function UserProfile() {\r\n * const { user, isLoaded, updateUser } = useUser();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * if (!user) return <div>Not signed in</div>;\r\n * \r\n * async function handleUpdate(name: string) {\r\n * await updateUser({ name });\r\n * }\r\n * \r\n * return (\r\n * <div>\r\n * <p>Email: {user.email}</p>\r\n * {user.name && <p>Name: {user.name}</p>}\r\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useUser() {\r\n const { user, isLoaded, updateUser, setUser } = useInsforge();\r\n return { user, isLoaded, updateUser, setUser };\r\n}\r\n\r\n","import { useState, useEffect } from 'react';\r\nimport type { \r\n OAuthProvidersSchema,\r\n PublicEmailAuthConfig,\r\n PublicOAuthProvider \r\n} from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\r\n *\r\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\r\n *\r\n * This hook lazily fetches all public authentication configuration from the backend\r\n * only when the component mounts. Using it in other components will cause unnecessary\r\n * API calls on every page load.\r\n *\r\n * @returns Object containing OAuth providers, email auth config, and loading state\r\n *\r\n * @example\r\n * ```tsx\r\n * // ✅ Correct usage - only in SignIn/SignUp components\r\n * function SignUp() {\r\n * const { oauthProviders, emailConfig, isLoaded } = usePublicAuthConfig();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <div>\r\n * <p>OAuth providers: {oauthProviders.length}</p>\r\n * <p>Password min length: {emailConfig?.passwordMinLength}</p>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @requires Must be used within InsforgeProvider\r\n */\r\nexport function usePublicAuthConfig(): { \r\n oauthProviders: OAuthProvidersSchema[];\r\n emailConfig: PublicEmailAuthConfig | null;\r\n isLoaded: boolean;\r\n} {\r\n const { baseUrl } = useInsforge();\r\n const [oauthProviders, setOAuthProviders] = useState<OAuthProvidersSchema[]>([]);\r\n const [emailConfig, setEmailConfig] = useState<PublicEmailAuthConfig | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n useEffect(() => {\r\n let mounted = true;\r\n\r\n async function fetchConfig() {\r\n try {\r\n const response = await fetch(`${baseUrl}/api/auth/public-config`);\r\n \r\n if (!mounted) return;\r\n\r\n if (!response.ok) {\r\n console.warn('[usePublicAuthConfig] Failed to fetch public auth config:', response.statusText);\r\n setOAuthProviders([]);\r\n setEmailConfig(null);\r\n } else {\r\n const data: { providers: PublicOAuthProvider[], email: PublicEmailAuthConfig } = await response.json();\r\n const providerNames = data.providers?.map((p: PublicOAuthProvider) => p.provider) || [];\r\n setOAuthProviders(providerNames);\r\n setEmailConfig(data.email || null);\r\n }\r\n \r\n setIsLoaded(true);\r\n } catch (error) {\r\n console.warn('[usePublicAuthConfig] Unexpected error:', error);\r\n if (mounted) {\r\n setOAuthProviders([]);\r\n setEmailConfig(null);\r\n setIsLoaded(true);\r\n }\r\n }\r\n }\r\n\r\n fetchConfig();\r\n\r\n return () => {\r\n mounted = false;\r\n };\r\n }, [baseUrl]);\r\n\r\n return { oauthProviders, emailConfig, isLoaded };\r\n}\r\n\r\n"]}
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":";;;;;;AAiEA,IAAM,eAAA,GAAkBA,mBAAA;AAAA,EACtB;AACF,CAAA;AA4ZO,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;;;ACjcO,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;ACGO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIC,eAA6C,IAAI,CAAA;AACvF,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,cAAA,CAAe,MAAM,CAAA;AAAA,MACvB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,wDAAwD,CAAA;AACtE,QAAA,cAAA,CAAe,IAAI,CAAA;AAAA,MACrB;AACA,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB;AAEA,IAAA,WAAA,EAAY;AAAA,EACd,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,aAAa,QAAA,EAAS;AACjC","file":"hooks.js","sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n useContext,\r\n useEffect,\r\n useState,\r\n useCallback,\r\n useRef,\r\n type ReactNode,\r\n} from \"react\";\r\nimport { createClient } from \"@insforge/sdk\";\r\nimport type { InsforgeUser } from \"../types\";\r\nimport { GetPublicAuthConfigResponse } from \"@insforge/shared-schemas\";\r\n\r\ninterface InsforgeContextValue {\r\n // Auth state\r\n user: InsforgeUser | null;\r\n isLoaded: boolean;\r\n isSignedIn: boolean;\r\n\r\n // Auth methods\r\n setUser: (user: InsforgeUser | null) => void;\r\n signIn: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signUp: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signOut: () => Promise<void>;\r\n updateUser: (data: Partial<InsforgeUser>) => Promise<void>;\r\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\r\n\r\n // Email verification methods\r\n sendPasswordResetCode: (\r\n email: string\r\n ) => Promise<{ success: boolean; message: string } | null>;\r\n resetPassword: (\r\n token: string,\r\n newPassword: string\r\n ) => Promise<{ message: string; redirectTo?: string } | null>;\r\n verifyEmail: (\r\n token: string\r\n ) => Promise<{ accessToken: string; user?: any } | null>;\r\n\r\n // Public auth config\r\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\r\n // Base config\r\n baseUrl: string;\r\n}\r\n\r\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(\r\n undefined\r\n);\r\n\r\nexport interface InsforgeProviderProps {\r\n children: ReactNode;\r\n baseUrl: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n // Optional: custom token sync functions (e.g., for Next.js cookie sync)\r\n syncTokenToCookie?: (token: string) => Promise<boolean>;\r\n clearCookie?: () => Promise<void>;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Manages user authentication state and provides all necessary context to child components.\r\n * Works with any React framework (Next.js, Vite, Remix, etc.).\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n *\r\n * export default function App() {\r\n * return (\r\n * <InsforgeProvider baseUrl={process.env.VITE_INSFORGE_BASE_URL}>\r\n * {children}\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 * baseUrl={baseUrl}\r\n * syncTokenToCookie={async (token) => {\r\n * await fetch('/api/auth', {\r\n * method: 'POST',\r\n * body: JSON.stringify({ token })\r\n * });\r\n * return true;\r\n * }}\r\n * clearCookie={async () => {\r\n * await fetch('/api/auth', { method: 'DELETE' });\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProvider({\r\n children,\r\n baseUrl,\r\n onAuthChange,\r\n syncTokenToCookie,\r\n clearCookie,\r\n}: InsforgeProviderProps) {\r\n // Auth state\r\n const [user, setUser] = useState<InsforgeUser | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\r\n\r\n // Initialize SDK client with lazy initialization - only runs once\r\n const [insforge] = useState(() => createClient({ baseUrl }));\r\n\r\n // Load auth state - returns explicit success/error status\r\n const loadAuthState = useCallback(async (): Promise<{\r\n success: boolean;\r\n error?: string;\r\n }> => {\r\n try {\r\n // Use SDK's getCurrentSession() to check for existing session\r\n const sessionResult = insforge.auth.getCurrentSession();\r\n const session = sessionResult.data?.session;\r\n const token = session?.accessToken || null;\r\n\r\n if (!token) {\r\n // No token, user is not authenticated\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"no_session\" };\r\n }\r\n\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n // Token is valid, update user state with fresh data\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n setIsLoaded(true);\r\n return { success: true };\r\n } else {\r\n // Token invalid or expired\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"invalid_token\" };\r\n }\r\n } catch (error) {\r\n // Token validation failed\r\n console.error(\"[InsforgeProvider] Token validation failed:\", error);\r\n\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error.message : \"authentication_failed\",\r\n };\r\n }\r\n }, [insforge, onAuthChange, syncTokenToCookie, clearCookie]);\r\n\r\n useEffect(() => {\r\n // Run loadAuthState only once on mount\r\n loadAuthState();\r\n\r\n return () => {\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n };\r\n }, []); // Empty deps - run only on mount\r\n\r\n const getPublicAuthConfig = useCallback(async () => {\r\n try {\r\n const result = await insforge.auth.getPublicAuthConfig();\r\n if (result.data) {\r\n return result.data;\r\n } else {\r\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\r\n return null;\r\n }\r\n } catch (error) {\r\n console.error(\r\n \"[InsforgeProvider] Failed to get public auth config:\",\r\n error\r\n );\r\n return null;\r\n }\r\n }, [insforge]);\r\n\r\n /**\r\n * Helper function to handle successful authentication\r\n */\r\n const handleAuthSuccess = useCallback(\r\n async (\r\n authToken: string,\r\n fallbackUser?: { id?: string; email?: string; name?: string }\r\n ) => {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n\r\n // Try to sync token to cookie if function provided\r\n if (syncTokenToCookie) {\r\n try {\r\n await syncTokenToCookie(authToken);\r\n } catch (error) {\r\n // Cookie sync failed - that's okay\r\n }\r\n }\r\n } else if (fallbackUser) {\r\n // Fallback to basic user data if getCurrentUser fails\r\n const userData: InsforgeUser = {\r\n id: fallbackUser.id || \"\",\r\n email: fallbackUser.email || \"\",\r\n name: fallbackUser.name || \"\",\r\n avatarUrl: \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n }\r\n },\r\n [insforge, onAuthChange, syncTokenToCookie]\r\n );\r\n\r\n const signIn = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signInWithPassword({\r\n email,\r\n password,\r\n });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage =\r\n sdkResult.error?.message || \"Invalid email or password\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signUp = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signUp({ email, password });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage = sdkResult.error?.message || \"Sign up failed\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signOut = useCallback(async () => {\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n // Clear refresh interval if exists\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n }, [insforge, onAuthChange, clearCookie]);\r\n\r\n const updateUser = useCallback(\r\n async (data: Partial<InsforgeUser>) => {\r\n if (!user) throw new Error(\"No user signed in\");\r\n\r\n const profileUpdate: Record<string, any> = {\r\n nickname: data.name,\r\n avatarUrl: data.avatarUrl,\r\n };\r\n\r\n const result = await insforge.auth.setProfile(profileUpdate);\r\n\r\n if (result.data) {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const updatedUser: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n setUser(updatedUser);\r\n if (onAuthChange) {\r\n onAuthChange(updatedUser);\r\n }\r\n }\r\n }\r\n },\r\n [user, onAuthChange, insforge]\r\n );\r\n\r\n const sendPasswordResetCode = useCallback(\r\n async (email: string) => {\r\n const sdkResult = await insforge.auth.sendPasswordResetCode({ email });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const resetPassword = useCallback(\r\n async (token: string, newPassword: string) => {\r\n const sdkResult = await insforge.auth.resetPassword({\r\n newPassword,\r\n otp: token,\r\n });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const verifyEmail = useCallback(\r\n async (token: string) => {\r\n const sdkResult = await insforge.auth.verifyEmail({ otp: token });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n return (\r\n <InsforgeContext.Provider\r\n value={{\r\n user,\r\n isLoaded,\r\n isSignedIn: !!user,\r\n setUser,\r\n signIn,\r\n signUp,\r\n signOut,\r\n updateUser,\r\n reloadAuth: loadAuthState,\r\n baseUrl,\r\n sendPasswordResetCode,\r\n resetPassword,\r\n verifyEmail,\r\n getPublicAuthConfig,\r\n }}\r\n >\r\n {children}\r\n </InsforgeContext.Provider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\r\n if (!context) {\r\n throw new Error(\"useInsforge must be used within InsforgeProvider\");\r\n }\r\n return context;\r\n}\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access authentication methods\r\n * \r\n * @returns Object containing:\r\n * - `signIn`: Function to sign in with email and password\r\n * - `signUp`: Function to sign up with email and password\r\n * - `signOut`: Function to sign out the current user\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `isSignedIn`: Boolean indicating if user is currently signed in\r\n * \r\n * @example\r\n * ```tsx\r\n * function LoginForm() {\r\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\r\n * \r\n * async function handleLogin(email: string, password: string) {\r\n * try {\r\n * await signIn(email, password);\r\n * // User is now signed in\r\n * } catch (error) {\r\n * console.error('Sign in failed:', error);\r\n * }\r\n * }\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\r\n * {/* form fields *\\/}\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useAuth() {\r\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\r\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\r\n}\r\n\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access current user data\r\n * \r\n * @returns Object containing:\r\n * - `user`: Current user object (InsforgeUser | null)\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `updateUser`: Function to update user profile data\r\n * - `setUser`: Internal function to manually set user state\r\n * \r\n * @example\r\n * ```tsx\r\n * function UserProfile() {\r\n * const { user, isLoaded, updateUser } = useUser();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * if (!user) return <div>Not signed in</div>;\r\n * \r\n * async function handleUpdate(name: string) {\r\n * await updateUser({ name });\r\n * }\r\n * \r\n * return (\r\n * <div>\r\n * <p>Email: {user.email}</p>\r\n * {user.name && <p>Name: {user.name}</p>}\r\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useUser() {\r\n const { user, isLoaded, updateUser, setUser } = useInsforge();\r\n return { user, isLoaded, updateUser, setUser };\r\n}\r\n\r\n","import { useState, useEffect } from 'react';\r\nimport type {\r\n GetPublicAuthConfigResponse\r\n} from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\r\n *\r\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\r\n *\r\n * This hook lazily fetches all public authentication configuration from the backend\r\n * only when the component mounts. Using it in other components will cause unnecessary\r\n * API calls on every page load.\r\n *\r\n * @returns Object containing OAuth providers, email auth config, and loading state\r\n * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])\r\n * - `emailConfig`: Email authentication configuration object with password rules\r\n * - `isLoaded`: Boolean indicating if the config has been fetched\r\n *\r\n * @example\r\n * ```tsx\r\n * // ✅ Correct usage - only in SignIn/SignUp components\r\n * function SignUp() {\r\n * const { oauthProviders, emailConfig, isLoaded } = usePublicAuthConfig();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <div>\r\n * <p>OAuth providers: {oauthProviders.length}</p>\r\n * <p>Password min length: {emailConfig?.passwordMinLength}</p>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @requires Must be used within InsforgeProvider\r\n */\r\nexport function usePublicAuthConfig(): {\r\n emailConfig: GetPublicAuthConfigResponse | null;\r\n isLoaded: boolean;\r\n} {\r\n const { getPublicAuthConfig } = useInsforge();\r\n const [emailConfig, setEmailConfig] = useState<GetPublicAuthConfigResponse | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n useEffect(() => {\r\n async function fetchConfig() {\r\n const result = await getPublicAuthConfig();\r\n if (result) {\r\n setEmailConfig(result);\r\n } else {\r\n console.error('[usePublicAuthConfig] Failed to get public auth config');\r\n setEmailConfig(null);\r\n }\r\n setIsLoaded(true);\r\n }\r\n\r\n fetchConfig();\r\n }, [getPublicAuthConfig]);\r\n\r\n return { emailConfig, isLoaded };\r\n}\r\n\r\n"]}
package/dist/hooks.mjs CHANGED
@@ -25,42 +25,23 @@ function useUser() {
25
25
  return { user, isLoaded, updateUser, setUser };
26
26
  }
27
27
  function usePublicAuthConfig() {
28
- const { baseUrl } = useInsforge();
29
- const [oauthProviders, setOAuthProviders] = useState([]);
28
+ const { getPublicAuthConfig } = useInsforge();
30
29
  const [emailConfig, setEmailConfig] = useState(null);
31
30
  const [isLoaded, setIsLoaded] = useState(false);
32
31
  useEffect(() => {
33
- let mounted = true;
34
32
  async function fetchConfig() {
35
- try {
36
- const response = await fetch(`${baseUrl}/api/auth/public-config`);
37
- if (!mounted) return;
38
- if (!response.ok) {
39
- console.warn("[usePublicAuthConfig] Failed to fetch public auth config:", response.statusText);
40
- setOAuthProviders([]);
41
- setEmailConfig(null);
42
- } else {
43
- const data = await response.json();
44
- const providerNames = data.providers?.map((p) => p.provider) || [];
45
- setOAuthProviders(providerNames);
46
- setEmailConfig(data.email || null);
47
- }
48
- setIsLoaded(true);
49
- } catch (error) {
50
- console.warn("[usePublicAuthConfig] Unexpected error:", error);
51
- if (mounted) {
52
- setOAuthProviders([]);
53
- setEmailConfig(null);
54
- setIsLoaded(true);
55
- }
33
+ const result = await getPublicAuthConfig();
34
+ if (result) {
35
+ setEmailConfig(result);
36
+ } else {
37
+ console.error("[usePublicAuthConfig] Failed to get public auth config");
38
+ setEmailConfig(null);
56
39
  }
40
+ setIsLoaded(true);
57
41
  }
58
42
  fetchConfig();
59
- return () => {
60
- mounted = false;
61
- };
62
- }, [baseUrl]);
63
- return { oauthProviders, emailConfig, isLoaded };
43
+ }, [getPublicAuthConfig]);
44
+ return { emailConfig, isLoaded };
64
45
  }
65
46
 
66
47
  export { useAuth, usePublicAuthConfig, useUser };
@@ -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":";;;;AAuDA,IAAM,eAAA,GAAkB,aAAA;AAAA,EACtB;AACF,CAAA;AAsYO,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;;;ACjaO,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;ACEO,SAAS,mBAAA,GAId;AACA,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,WAAA,EAAY;AAChC,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIA,QAAAA,CAAiC,EAAE,CAAA;AAC/E,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,SAAuC,IAAI,CAAA;AACjF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,SAAS,KAAK,CAAA;AAE9C,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,OAAA,GAAU,IAAA;AAEd,IAAA,eAAe,WAAA,GAAc;AAC3B,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,uBAAA,CAAyB,CAAA;AAEhE,QAAA,IAAI,CAAC,OAAA,EAAS;AAEd,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,OAAA,CAAQ,IAAA,CAAK,2DAAA,EAA6D,QAAA,CAAS,UAAU,CAAA;AAC7F,UAAA,iBAAA,CAAkB,EAAE,CAAA;AACpB,UAAA,cAAA,CAAe,IAAI,CAAA;AAAA,QACrB,CAAA,MAAO;AACL,UAAA,MAAM,IAAA,GAA2E,MAAM,QAAA,CAAS,IAAA,EAAK;AACrG,UAAA,MAAM,aAAA,GAAgB,KAAK,SAAA,EAAW,GAAA,CAAI,CAAC,CAAA,KAA2B,CAAA,CAAE,QAAQ,CAAA,IAAK,EAAC;AACtF,UAAA,iBAAA,CAAkB,aAAa,CAAA;AAC/B,UAAA,cAAA,CAAe,IAAA,CAAK,SAAS,IAAI,CAAA;AAAA,QACnC;AAEA,QAAA,WAAA,CAAY,IAAI,CAAA;AAAA,MAClB,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,IAAA,CAAK,2CAA2C,KAAK,CAAA;AAC7D,QAAA,IAAI,OAAA,EAAS;AACX,UAAA,iBAAA,CAAkB,EAAE,CAAA;AACpB,UAAA,cAAA,CAAe,IAAI,CAAA;AACnB,UAAA,WAAA,CAAY,IAAI,CAAA;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAEA,IAAA,WAAA,EAAY;AAEZ,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,GAAU,KAAA;AAAA,IACZ,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO,EAAE,cAAA,EAAgB,WAAA,EAAa,QAAA,EAAS;AACjD","file":"hooks.mjs","sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n useContext,\r\n useEffect,\r\n useState,\r\n useCallback,\r\n useRef,\r\n type ReactNode,\r\n} from \"react\";\r\nimport { createClient } from \"@insforge/sdk\";\r\nimport type { InsforgeUser } from \"../types\";\r\n\r\ninterface InsforgeContextValue {\r\n // Auth state\r\n user: InsforgeUser | null;\r\n isLoaded: boolean;\r\n isSignedIn: boolean;\r\n\r\n // Auth methods\r\n setUser: (user: InsforgeUser | null) => void;\r\n signIn: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signUp: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signOut: () => Promise<void>;\r\n updateUser: (data: Partial<InsforgeUser>) => Promise<void>;\r\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\r\n\r\n // Email verification methods\r\n sendPasswordResetCode: (email: string) => Promise<{ success: boolean; message: string } | null>;\r\n resetPassword: (token: string, newPassword: string) => Promise<{ message: string; redirectTo?: string } | null>;\r\n verifyEmail: (token: string) => Promise<{ accessToken: string; user?: any } | null>;\r\n\r\n // Base config\r\n baseUrl: string;\r\n}\r\n\r\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(\r\n undefined\r\n);\r\n\r\nexport interface InsforgeProviderProps {\r\n children: ReactNode;\r\n baseUrl: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n // Optional: custom token sync functions (e.g., for Next.js cookie sync)\r\n syncTokenToCookie?: (token: string) => Promise<boolean>;\r\n clearCookie?: () => Promise<void>;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Manages user authentication state and provides all necessary context to child components.\r\n * Works with any React framework (Next.js, Vite, Remix, etc.).\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n *\r\n * export default function App() {\r\n * return (\r\n * <InsforgeProvider baseUrl={process.env.VITE_INSFORGE_BASE_URL}>\r\n * {children}\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 * baseUrl={baseUrl}\r\n * syncTokenToCookie={async (token) => {\r\n * await fetch('/api/auth', {\r\n * method: 'POST',\r\n * body: JSON.stringify({ token })\r\n * });\r\n * return true;\r\n * }}\r\n * clearCookie={async () => {\r\n * await fetch('/api/auth', { method: 'DELETE' });\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProvider({\r\n children,\r\n baseUrl,\r\n onAuthChange,\r\n syncTokenToCookie,\r\n clearCookie,\r\n}: InsforgeProviderProps) {\r\n // Auth state\r\n const [user, setUser] = useState<InsforgeUser | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\r\n\r\n // Initialize SDK client with lazy initialization - only runs once\r\n const [insforge] = useState(() => createClient({ baseUrl }));\r\n\r\n // Load auth state - returns explicit success/error status\r\n const loadAuthState = useCallback(async (): Promise<{\r\n success: boolean;\r\n error?: string;\r\n }> => {\r\n try {\r\n // Use SDK's getCurrentSession() to check for existing session\r\n const sessionResult = insforge.auth.getCurrentSession();\r\n const session = sessionResult.data?.session;\r\n const token = session?.accessToken || null;\r\n\r\n if (!token) {\r\n // No token, user is not authenticated\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"no_session\" };\r\n }\r\n\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n // Token is valid, update user state with fresh data\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n setIsLoaded(true);\r\n return { success: true };\r\n } else {\r\n // Token invalid or expired\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"invalid_token\" };\r\n }\r\n } catch (error) {\r\n // Token validation failed\r\n console.error(\"[InsforgeProvider] Token validation failed:\", error);\r\n\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error.message : \"authentication_failed\",\r\n };\r\n }\r\n }, [insforge, onAuthChange, syncTokenToCookie, clearCookie]);\r\n\r\n useEffect(() => {\r\n // Run loadAuthState only once on mount\r\n loadAuthState();\r\n\r\n return () => {\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n };\r\n }, []); // Empty deps - run only on mount\r\n\r\n /**\r\n * Helper function to handle successful authentication\r\n */\r\n const handleAuthSuccess = useCallback(\r\n async (\r\n authToken: string,\r\n fallbackUser?: { id?: string; email?: string; name?: string }\r\n ) => {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n\r\n // Try to sync token to cookie if function provided\r\n if (syncTokenToCookie) {\r\n try {\r\n await syncTokenToCookie(authToken);\r\n } catch (error) {\r\n // Cookie sync failed - that's okay\r\n }\r\n }\r\n } else if (fallbackUser) {\r\n // Fallback to basic user data if getCurrentUser fails\r\n const userData: InsforgeUser = {\r\n id: fallbackUser.id || \"\",\r\n email: fallbackUser.email || \"\",\r\n name: fallbackUser.name || \"\",\r\n avatarUrl: \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n }\r\n },\r\n [insforge, onAuthChange, syncTokenToCookie]\r\n );\r\n\r\n const signIn = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signInWithPassword({\r\n email,\r\n password,\r\n });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage =\r\n sdkResult.error?.message || \"Invalid email or password\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signUp = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signUp({ email, password });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage = sdkResult.error?.message || \"Sign up failed\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signOut = useCallback(async () => {\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n // Clear refresh interval if exists\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n }, [insforge, onAuthChange, clearCookie]);\r\n\r\n const updateUser = useCallback(\r\n async (data: Partial<InsforgeUser>) => {\r\n if (!user) throw new Error(\"No user signed in\");\r\n\r\n const profileUpdate: Record<string, any> = {\r\n nickname: data.name,\r\n avatarUrl: data.avatarUrl,\r\n };\r\n\r\n const result = await insforge.auth.setProfile(profileUpdate);\r\n\r\n if (result.data) {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const updatedUser: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n setUser(updatedUser);\r\n if (onAuthChange) {\r\n onAuthChange(updatedUser);\r\n }\r\n }\r\n }\r\n },\r\n [user, onAuthChange, insforge]\r\n );\r\n\r\n const sendPasswordResetCode = useCallback(\r\n async (email: string) => {\r\n const sdkResult = await insforge.auth.sendPasswordResetCode({ email });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const resetPassword = useCallback(\r\n async (token: string, newPassword: string) => {\r\n const sdkResult = await insforge.auth.resetPassword({ newPassword, otp: token });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const verifyEmail = useCallback(\r\n async (token: string) => {\r\n const sdkResult = await insforge.auth.verifyEmail({ otp: token });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n return (\r\n <InsforgeContext.Provider\r\n value={{\r\n user,\r\n isLoaded,\r\n isSignedIn: !!user,\r\n setUser,\r\n signIn,\r\n signUp,\r\n signOut,\r\n updateUser,\r\n reloadAuth: loadAuthState,\r\n baseUrl,\r\n sendPasswordResetCode,\r\n resetPassword,\r\n verifyEmail,\r\n }}\r\n >\r\n {children}\r\n </InsforgeContext.Provider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\r\n if (!context) {\r\n throw new Error(\"useInsforge must be used within InsforgeProvider\");\r\n }\r\n return context;\r\n}\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access authentication methods\r\n * \r\n * @returns Object containing:\r\n * - `signIn`: Function to sign in with email and password\r\n * - `signUp`: Function to sign up with email and password\r\n * - `signOut`: Function to sign out the current user\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `isSignedIn`: Boolean indicating if user is currently signed in\r\n * \r\n * @example\r\n * ```tsx\r\n * function LoginForm() {\r\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\r\n * \r\n * async function handleLogin(email: string, password: string) {\r\n * try {\r\n * await signIn(email, password);\r\n * // User is now signed in\r\n * } catch (error) {\r\n * console.error('Sign in failed:', error);\r\n * }\r\n * }\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\r\n * {/* form fields *\\/}\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useAuth() {\r\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\r\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\r\n}\r\n\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access current user data\r\n * \r\n * @returns Object containing:\r\n * - `user`: Current user object (InsforgeUser | null)\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `updateUser`: Function to update user profile data\r\n * - `setUser`: Internal function to manually set user state\r\n * \r\n * @example\r\n * ```tsx\r\n * function UserProfile() {\r\n * const { user, isLoaded, updateUser } = useUser();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * if (!user) return <div>Not signed in</div>;\r\n * \r\n * async function handleUpdate(name: string) {\r\n * await updateUser({ name });\r\n * }\r\n * \r\n * return (\r\n * <div>\r\n * <p>Email: {user.email}</p>\r\n * {user.name && <p>Name: {user.name}</p>}\r\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useUser() {\r\n const { user, isLoaded, updateUser, setUser } = useInsforge();\r\n return { user, isLoaded, updateUser, setUser };\r\n}\r\n\r\n","import { useState, useEffect } from 'react';\r\nimport type { \r\n OAuthProvidersSchema,\r\n PublicEmailAuthConfig,\r\n PublicOAuthProvider \r\n} from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\r\n *\r\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\r\n *\r\n * This hook lazily fetches all public authentication configuration from the backend\r\n * only when the component mounts. Using it in other components will cause unnecessary\r\n * API calls on every page load.\r\n *\r\n * @returns Object containing OAuth providers, email auth config, and loading state\r\n *\r\n * @example\r\n * ```tsx\r\n * // ✅ Correct usage - only in SignIn/SignUp components\r\n * function SignUp() {\r\n * const { oauthProviders, emailConfig, isLoaded } = usePublicAuthConfig();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <div>\r\n * <p>OAuth providers: {oauthProviders.length}</p>\r\n * <p>Password min length: {emailConfig?.passwordMinLength}</p>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @requires Must be used within InsforgeProvider\r\n */\r\nexport function usePublicAuthConfig(): { \r\n oauthProviders: OAuthProvidersSchema[];\r\n emailConfig: PublicEmailAuthConfig | null;\r\n isLoaded: boolean;\r\n} {\r\n const { baseUrl } = useInsforge();\r\n const [oauthProviders, setOAuthProviders] = useState<OAuthProvidersSchema[]>([]);\r\n const [emailConfig, setEmailConfig] = useState<PublicEmailAuthConfig | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n useEffect(() => {\r\n let mounted = true;\r\n\r\n async function fetchConfig() {\r\n try {\r\n const response = await fetch(`${baseUrl}/api/auth/public-config`);\r\n \r\n if (!mounted) return;\r\n\r\n if (!response.ok) {\r\n console.warn('[usePublicAuthConfig] Failed to fetch public auth config:', response.statusText);\r\n setOAuthProviders([]);\r\n setEmailConfig(null);\r\n } else {\r\n const data: { providers: PublicOAuthProvider[], email: PublicEmailAuthConfig } = await response.json();\r\n const providerNames = data.providers?.map((p: PublicOAuthProvider) => p.provider) || [];\r\n setOAuthProviders(providerNames);\r\n setEmailConfig(data.email || null);\r\n }\r\n \r\n setIsLoaded(true);\r\n } catch (error) {\r\n console.warn('[usePublicAuthConfig] Unexpected error:', error);\r\n if (mounted) {\r\n setOAuthProviders([]);\r\n setEmailConfig(null);\r\n setIsLoaded(true);\r\n }\r\n }\r\n }\r\n\r\n fetchConfig();\r\n\r\n return () => {\r\n mounted = false;\r\n };\r\n }, [baseUrl]);\r\n\r\n return { oauthProviders, emailConfig, isLoaded };\r\n}\r\n\r\n"]}
1
+ {"version":3,"sources":["../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useState","useEffect"],"mappings":";;;;AAiEA,IAAM,eAAA,GAAkB,aAAA;AAAA,EACtB;AACF,CAAA;AA4ZO,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;;;ACjcO,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;ACGO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,SAA6C,IAAI,CAAA;AACvF,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,cAAA,CAAe,MAAM,CAAA;AAAA,MACvB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,wDAAwD,CAAA;AACtE,QAAA,cAAA,CAAe,IAAI,CAAA;AAAA,MACrB;AACA,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB;AAEA,IAAA,WAAA,EAAY;AAAA,EACd,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,aAAa,QAAA,EAAS;AACjC","file":"hooks.mjs","sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n useContext,\r\n useEffect,\r\n useState,\r\n useCallback,\r\n useRef,\r\n type ReactNode,\r\n} from \"react\";\r\nimport { createClient } from \"@insforge/sdk\";\r\nimport type { InsforgeUser } from \"../types\";\r\nimport { GetPublicAuthConfigResponse } from \"@insforge/shared-schemas\";\r\n\r\ninterface InsforgeContextValue {\r\n // Auth state\r\n user: InsforgeUser | null;\r\n isLoaded: boolean;\r\n isSignedIn: boolean;\r\n\r\n // Auth methods\r\n setUser: (user: InsforgeUser | null) => void;\r\n signIn: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signUp: (\r\n email: string,\r\n password: string\r\n ) => Promise<\r\n | {\r\n user?: { id: string; email: string; name: string };\r\n accessToken: string | null;\r\n }\r\n | { error: string }\r\n >;\r\n signOut: () => Promise<void>;\r\n updateUser: (data: Partial<InsforgeUser>) => Promise<void>;\r\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\r\n\r\n // Email verification methods\r\n sendPasswordResetCode: (\r\n email: string\r\n ) => Promise<{ success: boolean; message: string } | null>;\r\n resetPassword: (\r\n token: string,\r\n newPassword: string\r\n ) => Promise<{ message: string; redirectTo?: string } | null>;\r\n verifyEmail: (\r\n token: string\r\n ) => Promise<{ accessToken: string; user?: any } | null>;\r\n\r\n // Public auth config\r\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\r\n // Base config\r\n baseUrl: string;\r\n}\r\n\r\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(\r\n undefined\r\n);\r\n\r\nexport interface InsforgeProviderProps {\r\n children: ReactNode;\r\n baseUrl: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n // Optional: custom token sync functions (e.g., for Next.js cookie sync)\r\n syncTokenToCookie?: (token: string) => Promise<boolean>;\r\n clearCookie?: () => Promise<void>;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Manages user authentication state and provides all necessary context to child components.\r\n * Works with any React framework (Next.js, Vite, Remix, etc.).\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n *\r\n * export default function App() {\r\n * return (\r\n * <InsforgeProvider baseUrl={process.env.VITE_INSFORGE_BASE_URL}>\r\n * {children}\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 * baseUrl={baseUrl}\r\n * syncTokenToCookie={async (token) => {\r\n * await fetch('/api/auth', {\r\n * method: 'POST',\r\n * body: JSON.stringify({ token })\r\n * });\r\n * return true;\r\n * }}\r\n * clearCookie={async () => {\r\n * await fetch('/api/auth', { method: 'DELETE' });\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProvider({\r\n children,\r\n baseUrl,\r\n onAuthChange,\r\n syncTokenToCookie,\r\n clearCookie,\r\n}: InsforgeProviderProps) {\r\n // Auth state\r\n const [user, setUser] = useState<InsforgeUser | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\r\n\r\n // Initialize SDK client with lazy initialization - only runs once\r\n const [insforge] = useState(() => createClient({ baseUrl }));\r\n\r\n // Load auth state - returns explicit success/error status\r\n const loadAuthState = useCallback(async (): Promise<{\r\n success: boolean;\r\n error?: string;\r\n }> => {\r\n try {\r\n // Use SDK's getCurrentSession() to check for existing session\r\n const sessionResult = insforge.auth.getCurrentSession();\r\n const session = sessionResult.data?.session;\r\n const token = session?.accessToken || null;\r\n\r\n if (!token) {\r\n // No token, user is not authenticated\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"no_session\" };\r\n }\r\n\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n // Token is valid, update user state with fresh data\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n setIsLoaded(true);\r\n return { success: true };\r\n } else {\r\n // Token invalid or expired\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n return { success: false, error: \"invalid_token\" };\r\n }\r\n } catch (error) {\r\n // Token validation failed\r\n console.error(\"[InsforgeProvider] Token validation failed:\", error);\r\n\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n setIsLoaded(true);\r\n\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error.message : \"authentication_failed\",\r\n };\r\n }\r\n }, [insforge, onAuthChange, syncTokenToCookie, clearCookie]);\r\n\r\n useEffect(() => {\r\n // Run loadAuthState only once on mount\r\n loadAuthState();\r\n\r\n return () => {\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n };\r\n }, []); // Empty deps - run only on mount\r\n\r\n const getPublicAuthConfig = useCallback(async () => {\r\n try {\r\n const result = await insforge.auth.getPublicAuthConfig();\r\n if (result.data) {\r\n return result.data;\r\n } else {\r\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\r\n return null;\r\n }\r\n } catch (error) {\r\n console.error(\r\n \"[InsforgeProvider] Failed to get public auth config:\",\r\n error\r\n );\r\n return null;\r\n }\r\n }, [insforge]);\r\n\r\n /**\r\n * Helper function to handle successful authentication\r\n */\r\n const handleAuthSuccess = useCallback(\r\n async (\r\n authToken: string,\r\n fallbackUser?: { id?: string; email?: string; name?: string }\r\n ) => {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const userData: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n\r\n // Try to sync token to cookie if function provided\r\n if (syncTokenToCookie) {\r\n try {\r\n await syncTokenToCookie(authToken);\r\n } catch (error) {\r\n // Cookie sync failed - that's okay\r\n }\r\n }\r\n } else if (fallbackUser) {\r\n // Fallback to basic user data if getCurrentUser fails\r\n const userData: InsforgeUser = {\r\n id: fallbackUser.id || \"\",\r\n email: fallbackUser.email || \"\",\r\n name: fallbackUser.name || \"\",\r\n avatarUrl: \"\",\r\n };\r\n\r\n setUser(userData);\r\n\r\n if (onAuthChange) {\r\n onAuthChange(userData);\r\n }\r\n }\r\n },\r\n [insforge, onAuthChange, syncTokenToCookie]\r\n );\r\n\r\n const signIn = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signInWithPassword({\r\n email,\r\n password,\r\n });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage =\r\n sdkResult.error?.message || \"Invalid email or password\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signUp = useCallback(\r\n async (email: string, password: string) => {\r\n const sdkResult = await insforge.auth.signUp({ email, password });\r\n\r\n if (sdkResult.data) {\r\n await handleAuthSuccess(\r\n sdkResult.data.accessToken || \"\",\r\n sdkResult.data.user\r\n ? {\r\n id: sdkResult.data.user.id,\r\n email: sdkResult.data.user.email,\r\n name: sdkResult.data.user.name,\r\n }\r\n : undefined\r\n );\r\n return sdkResult.data;\r\n } else {\r\n const errorMessage = sdkResult.error?.message || \"Sign up failed\";\r\n return { error: errorMessage };\r\n }\r\n },\r\n [insforge, handleAuthSuccess]\r\n );\r\n\r\n const signOut = useCallback(async () => {\r\n await insforge.auth.signOut();\r\n\r\n // Clear cookie if function provided\r\n if (clearCookie) {\r\n try {\r\n await clearCookie();\r\n } catch (error) {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n // Clear refresh interval if exists\r\n if (refreshIntervalRef.current) {\r\n clearInterval(refreshIntervalRef.current);\r\n }\r\n\r\n setUser(null);\r\n if (onAuthChange) {\r\n onAuthChange(null);\r\n }\r\n }, [insforge, onAuthChange, clearCookie]);\r\n\r\n const updateUser = useCallback(\r\n async (data: Partial<InsforgeUser>) => {\r\n if (!user) throw new Error(\"No user signed in\");\r\n\r\n const profileUpdate: Record<string, any> = {\r\n nickname: data.name,\r\n avatarUrl: data.avatarUrl,\r\n };\r\n\r\n const result = await insforge.auth.setProfile(profileUpdate);\r\n\r\n if (result.data) {\r\n const userResult = await insforge.auth.getCurrentUser();\r\n if (userResult.data) {\r\n const profile = userResult.data.profile;\r\n const updatedUser: InsforgeUser = {\r\n id: userResult.data.user.id,\r\n email: userResult.data.user.email,\r\n name: (profile?.nickname as string | undefined) || \"\",\r\n avatarUrl: (profile?.avatarUrl as string | undefined) || \"\",\r\n };\r\n setUser(updatedUser);\r\n if (onAuthChange) {\r\n onAuthChange(updatedUser);\r\n }\r\n }\r\n }\r\n },\r\n [user, onAuthChange, insforge]\r\n );\r\n\r\n const sendPasswordResetCode = useCallback(\r\n async (email: string) => {\r\n const sdkResult = await insforge.auth.sendPasswordResetCode({ email });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const resetPassword = useCallback(\r\n async (token: string, newPassword: string) => {\r\n const sdkResult = await insforge.auth.resetPassword({\r\n newPassword,\r\n otp: token,\r\n });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n const verifyEmail = useCallback(\r\n async (token: string) => {\r\n const sdkResult = await insforge.auth.verifyEmail({ otp: token });\r\n return sdkResult.data;\r\n },\r\n [insforge]\r\n );\r\n\r\n return (\r\n <InsforgeContext.Provider\r\n value={{\r\n user,\r\n isLoaded,\r\n isSignedIn: !!user,\r\n setUser,\r\n signIn,\r\n signUp,\r\n signOut,\r\n updateUser,\r\n reloadAuth: loadAuthState,\r\n baseUrl,\r\n sendPasswordResetCode,\r\n resetPassword,\r\n verifyEmail,\r\n getPublicAuthConfig,\r\n }}\r\n >\r\n {children}\r\n </InsforgeContext.Provider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\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 = useContext(InsforgeContext);\r\n if (!context) {\r\n throw new Error(\"useInsforge must be used within InsforgeProvider\");\r\n }\r\n return context;\r\n}\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access authentication methods\r\n * \r\n * @returns Object containing:\r\n * - `signIn`: Function to sign in with email and password\r\n * - `signUp`: Function to sign up with email and password\r\n * - `signOut`: Function to sign out the current user\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `isSignedIn`: Boolean indicating if user is currently signed in\r\n * \r\n * @example\r\n * ```tsx\r\n * function LoginForm() {\r\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\r\n * \r\n * async function handleLogin(email: string, password: string) {\r\n * try {\r\n * await signIn(email, password);\r\n * // User is now signed in\r\n * } catch (error) {\r\n * console.error('Sign in failed:', error);\r\n * }\r\n * }\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\r\n * {/* form fields *\\/}\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useAuth() {\r\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\r\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\r\n}\r\n\r\n","import { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access current user data\r\n * \r\n * @returns Object containing:\r\n * - `user`: Current user object (InsforgeUser | null)\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `updateUser`: Function to update user profile data\r\n * - `setUser`: Internal function to manually set user state\r\n * \r\n * @example\r\n * ```tsx\r\n * function UserProfile() {\r\n * const { user, isLoaded, updateUser } = useUser();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * if (!user) return <div>Not signed in</div>;\r\n * \r\n * async function handleUpdate(name: string) {\r\n * await updateUser({ name });\r\n * }\r\n * \r\n * return (\r\n * <div>\r\n * <p>Email: {user.email}</p>\r\n * {user.name && <p>Name: {user.name}</p>}\r\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useUser() {\r\n const { user, isLoaded, updateUser, setUser } = useInsforge();\r\n return { user, isLoaded, updateUser, setUser };\r\n}\r\n\r\n","import { useState, useEffect } from 'react';\r\nimport type {\r\n GetPublicAuthConfigResponse\r\n} from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\r\n *\r\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\r\n *\r\n * This hook lazily fetches all public authentication configuration from the backend\r\n * only when the component mounts. Using it in other components will cause unnecessary\r\n * API calls on every page load.\r\n *\r\n * @returns Object containing OAuth providers, email auth config, and loading state\r\n * - `oauthProviders`: Array of enabled OAuth provider names (e.g., ['google', 'github'])\r\n * - `emailConfig`: Email authentication configuration object with password rules\r\n * - `isLoaded`: Boolean indicating if the config has been fetched\r\n *\r\n * @example\r\n * ```tsx\r\n * // ✅ Correct usage - only in SignIn/SignUp components\r\n * function SignUp() {\r\n * const { oauthProviders, emailConfig, isLoaded } = usePublicAuthConfig();\r\n * \r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * \r\n * return (\r\n * <div>\r\n * <p>OAuth providers: {oauthProviders.length}</p>\r\n * <p>Password min length: {emailConfig?.passwordMinLength}</p>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @requires Must be used within InsforgeProvider\r\n */\r\nexport function usePublicAuthConfig(): {\r\n emailConfig: GetPublicAuthConfigResponse | null;\r\n isLoaded: boolean;\r\n} {\r\n const { getPublicAuthConfig } = useInsforge();\r\n const [emailConfig, setEmailConfig] = useState<GetPublicAuthConfigResponse | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n useEffect(() => {\r\n async function fetchConfig() {\r\n const result = await getPublicAuthConfig();\r\n if (result) {\r\n setEmailConfig(result);\r\n } else {\r\n console.error('[usePublicAuthConfig] Failed to get public auth config');\r\n setEmailConfig(null);\r\n }\r\n setIsLoaded(true);\r\n }\r\n\r\n fetchConfig();\r\n }, [getPublicAuthConfig]);\r\n\r\n return { emailConfig, isLoaded };\r\n}\r\n\r\n"]}
package/dist/index.d.mts CHANGED
@@ -5,10 +5,10 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import { ReactNode } from 'react';
6
6
  import { InsforgeUser, OAuthProvider, OAuthProviderConfig } from './types.mjs';
7
7
  export { AuthConfig, AuthContainerProps, AuthDividerProps, AuthErrorBannerProps, AuthFormFieldProps, AuthHeaderProps, AuthLinkProps, AuthOAuthButtonProps, AuthOAuthProvidersProps, AuthPasswordFieldProps, AuthPasswordStrengthIndicatorProps, AuthSubmitButtonProps, AuthVerificationCodeInputProps, BaseAppearance, ConditionalProps, ForgotPasswordAppearance, ForgotPasswordFormProps, ProtectProps, ResetPasswordAppearance, ResetPasswordFormProps, SignInAppearance, SignInFormProps, SignInProps, SignUpAppearance, SignUpFormProps, SignUpProps, UserButtonProps, VerifyEmailStatusProps } from './types.mjs';
8
+ import { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';
8
9
  export { useAuth, usePublicAuthConfig, useUser } from './hooks.mjs';
9
10
  export { checkPasswordStrength, cn, createPasswordSchema, emailSchema, passwordSchema, validateEmail, validatePassword } from './lib.mjs';
10
11
  export { getInsforgeRoutes } from './router.mjs';
11
- import '@insforge/shared-schemas';
12
12
  import 'clsx';
13
13
  import 'zod';
14
14
  import 'react-router-dom';
@@ -56,6 +56,7 @@ interface InsforgeContextValue {
56
56
  accessToken: string;
57
57
  user?: any;
58
58
  } | null>;
59
+ getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;
59
60
  baseUrl: string;
60
61
  }
61
62
  interface InsforgeProviderProps {
package/dist/index.d.ts CHANGED
@@ -5,10 +5,10 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import { ReactNode } from 'react';
6
6
  import { InsforgeUser, OAuthProvider, OAuthProviderConfig } from './types.js';
7
7
  export { AuthConfig, AuthContainerProps, AuthDividerProps, AuthErrorBannerProps, AuthFormFieldProps, AuthHeaderProps, AuthLinkProps, AuthOAuthButtonProps, AuthOAuthProvidersProps, AuthPasswordFieldProps, AuthPasswordStrengthIndicatorProps, AuthSubmitButtonProps, AuthVerificationCodeInputProps, BaseAppearance, ConditionalProps, ForgotPasswordAppearance, ForgotPasswordFormProps, ProtectProps, ResetPasswordAppearance, ResetPasswordFormProps, SignInAppearance, SignInFormProps, SignInProps, SignUpAppearance, SignUpFormProps, SignUpProps, UserButtonProps, VerifyEmailStatusProps } from './types.js';
8
+ import { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';
8
9
  export { useAuth, usePublicAuthConfig, useUser } from './hooks.js';
9
10
  export { checkPasswordStrength, cn, createPasswordSchema, emailSchema, passwordSchema, validateEmail, validatePassword } from './lib.js';
10
11
  export { getInsforgeRoutes } from './router.js';
11
- import '@insforge/shared-schemas';
12
12
  import 'clsx';
13
13
  import 'zod';
14
14
  import 'react-router-dom';
@@ -56,6 +56,7 @@ interface InsforgeContextValue {
56
56
  accessToken: string;
57
57
  user?: any;
58
58
  } | null>;
59
+ getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;
59
60
  baseUrl: string;
60
61
  }
61
62
  interface InsforgeProviderProps {
package/dist/index.js CHANGED
@@ -107,6 +107,23 @@ function InsforgeProvider({
107
107
  }
108
108
  };
109
109
  }, []);
110
+ const getPublicAuthConfig = react.useCallback(async () => {
111
+ try {
112
+ const result = await insforge.auth.getPublicAuthConfig();
113
+ if (result.data) {
114
+ return result.data;
115
+ } else {
116
+ console.error("[InsforgeProvider] Failed to get public auth config:", result.error);
117
+ return null;
118
+ }
119
+ } catch (error) {
120
+ console.error(
121
+ "[InsforgeProvider] Failed to get public auth config:",
122
+ error
123
+ );
124
+ return null;
125
+ }
126
+ }, [insforge]);
110
127
  const handleAuthSuccess = react.useCallback(
111
128
  async (authToken, fallbackUser) => {
112
129
  const userResult = await insforge.auth.getCurrentUser();
@@ -238,7 +255,10 @@ function InsforgeProvider({
238
255
  );
239
256
  const resetPassword = react.useCallback(
240
257
  async (token, newPassword) => {
241
- const sdkResult = await insforge.auth.resetPassword({ newPassword, otp: token });
258
+ const sdkResult = await insforge.auth.resetPassword({
259
+ newPassword,
260
+ otp: token
261
+ });
242
262
  return sdkResult.data;
243
263
  },
244
264
  [insforge]
@@ -266,7 +286,8 @@ function InsforgeProvider({
266
286
  baseUrl,
267
287
  sendPasswordResetCode,
268
288
  resetPassword,
269
- verifyEmail
289
+ verifyEmail,
290
+ getPublicAuthConfig
270
291
  },
271
292
  children
272
293
  }
@@ -280,42 +301,23 @@ function useInsforge() {
280
301
  return context;
281
302
  }
282
303
  function usePublicAuthConfig() {
283
- const { baseUrl } = useInsforge();
284
- const [oauthProviders, setOAuthProviders] = react.useState([]);
304
+ const { getPublicAuthConfig } = useInsforge();
285
305
  const [emailConfig, setEmailConfig] = react.useState(null);
286
306
  const [isLoaded, setIsLoaded] = react.useState(false);
287
307
  react.useEffect(() => {
288
- let mounted = true;
289
308
  async function fetchConfig() {
290
- try {
291
- const response = await fetch(`${baseUrl}/api/auth/public-config`);
292
- if (!mounted) return;
293
- if (!response.ok) {
294
- console.warn("[usePublicAuthConfig] Failed to fetch public auth config:", response.statusText);
295
- setOAuthProviders([]);
296
- setEmailConfig(null);
297
- } else {
298
- const data = await response.json();
299
- const providerNames = data.providers?.map((p) => p.provider) || [];
300
- setOAuthProviders(providerNames);
301
- setEmailConfig(data.email || null);
302
- }
303
- setIsLoaded(true);
304
- } catch (error) {
305
- console.warn("[usePublicAuthConfig] Unexpected error:", error);
306
- if (mounted) {
307
- setOAuthProviders([]);
308
- setEmailConfig(null);
309
- setIsLoaded(true);
310
- }
309
+ const result = await getPublicAuthConfig();
310
+ if (result) {
311
+ setEmailConfig(result);
312
+ } else {
313
+ console.error("[usePublicAuthConfig] Failed to get public auth config");
314
+ setEmailConfig(null);
311
315
  }
316
+ setIsLoaded(true);
312
317
  }
313
318
  fetchConfig();
314
- return () => {
315
- mounted = false;
316
- };
317
- }, [baseUrl]);
318
- return { oauthProviders, emailConfig, isLoaded };
319
+ }, [getPublicAuthConfig]);
320
+ return { emailConfig, isLoaded };
319
321
  }
320
322
  function AuthBranding() {
321
323
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-[#FAFAFA] px-2 py-4 flex flex-row justify-center items-center gap-1", children: [
@@ -1202,7 +1204,7 @@ function SignIn({
1202
1204
  ...uiProps
1203
1205
  }) {
1204
1206
  const { signIn, baseUrl } = useInsforge();
1205
- const { oauthProviders, emailConfig } = usePublicAuthConfig();
1207
+ const { emailConfig } = usePublicAuthConfig();
1206
1208
  const [email, setEmail] = react.useState("");
1207
1209
  const [password, setPassword] = react.useState("");
1208
1210
  const [error, setError] = react.useState("");
@@ -1254,7 +1256,9 @@ function SignIn({
1254
1256
  setOauthLoading(null);
1255
1257
  }
1256
1258
  }
1257
- if (!emailConfig) return null;
1259
+ if (!emailConfig) {
1260
+ return null;
1261
+ }
1258
1262
  return /* @__PURE__ */ jsxRuntime.jsx(
1259
1263
  SignInForm,
1260
1264
  {
@@ -1266,7 +1270,7 @@ function SignIn({
1266
1270
  error,
1267
1271
  loading,
1268
1272
  oauthLoading,
1269
- availableProviders: oauthProviders,
1273
+ availableProviders: emailConfig?.oAuthProviders || [],
1270
1274
  onOAuthClick: handleOAuth,
1271
1275
  emailAuthConfig: emailConfig,
1272
1276
  ...uiProps
@@ -1429,7 +1433,7 @@ function SignUp({
1429
1433
  ...uiProps
1430
1434
  }) {
1431
1435
  const { signUp, baseUrl } = useInsforge();
1432
- const { oauthProviders, emailConfig } = usePublicAuthConfig();
1436
+ const { emailConfig } = usePublicAuthConfig();
1433
1437
  const [email, setEmail] = react.useState("");
1434
1438
  const [password, setPassword] = react.useState("");
1435
1439
  const [error, setError] = react.useState("");
@@ -1486,7 +1490,9 @@ function SignUp({
1486
1490
  setOauthLoading(null);
1487
1491
  }
1488
1492
  }
1489
- if (!emailConfig) return null;
1493
+ if (!emailConfig) {
1494
+ return null;
1495
+ }
1490
1496
  return /* @__PURE__ */ jsxRuntime.jsx(
1491
1497
  SignUpForm,
1492
1498
  {
@@ -1498,7 +1504,7 @@ function SignUp({
1498
1504
  error,
1499
1505
  loading,
1500
1506
  oauthLoading,
1501
- availableProviders: oauthProviders,
1507
+ availableProviders: emailConfig?.oAuthProviders || [],
1502
1508
  onOAuthClick: handleOAuth,
1503
1509
  emailAuthConfig: emailConfig,
1504
1510
  ...uiProps