@insforge/react 0.2.2 → 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/atoms.js +17 -1
- package/dist/atoms.js.map +1 -1
- package/dist/atoms.mjs +17 -1
- package/dist/atoms.mjs.map +1 -1
- package/dist/components.js +37 -36
- package/dist/components.js.map +1 -1
- package/dist/components.mjs +37 -36
- package/dist/components.mjs.map +1 -1
- package/dist/forms.js +17 -1
- package/dist/forms.js.map +1 -1
- package/dist/forms.mjs +17 -1
- package/dist/forms.mjs.map +1 -1
- package/dist/hooks.d.mts +5 -3
- package/dist/hooks.d.ts +5 -3
- package/dist/hooks.js +10 -29
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.mjs +10 -29
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +60 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -38
- package/dist/index.mjs.map +1 -1
- package/dist/router.js.map +1 -1
- package/dist/router.mjs.map +1 -1
- package/dist/types.d.mts +2 -2
- package/dist/types.d.ts +2 -2
- package/package.json +3 -3
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 {
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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 {
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
}, [baseUrl]);
|
|
63
|
-
return { oauthProviders, emailConfig, isLoaded };
|
|
43
|
+
}, [getPublicAuthConfig]);
|
|
44
|
+
return { emailConfig, isLoaded };
|
|
64
45
|
}
|
|
65
46
|
|
|
66
47
|
export { useAuth, usePublicAuthConfig, useUser };
|
package/dist/hooks.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useState","useEffect"],"mappings":";;;;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({
|
|
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 {
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
315
|
-
|
|
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: [
|
|
@@ -667,6 +669,22 @@ function AuthSubmitButton({
|
|
|
667
669
|
);
|
|
668
670
|
}
|
|
669
671
|
function AuthLink({ text, linkText, href, appearance = {} }) {
|
|
672
|
+
const currentSearch = typeof window !== "undefined" ? window.location.search : "";
|
|
673
|
+
const finalHref = (() => {
|
|
674
|
+
if (!currentSearch) return href;
|
|
675
|
+
try {
|
|
676
|
+
const url = new URL(href, window.location.origin);
|
|
677
|
+
const currentParams = new URLSearchParams(currentSearch);
|
|
678
|
+
currentParams.forEach((value, key) => {
|
|
679
|
+
if (!url.searchParams.has(key)) {
|
|
680
|
+
url.searchParams.set(key, value);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
return url.pathname + url.search;
|
|
684
|
+
} catch {
|
|
685
|
+
return href;
|
|
686
|
+
}
|
|
687
|
+
})();
|
|
670
688
|
return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: cn(
|
|
671
689
|
"text-center text-sm font-normal text-[#828282] leading-6",
|
|
672
690
|
appearance.containerClassName
|
|
@@ -676,7 +694,7 @@ function AuthLink({ text, linkText, href, appearance = {} }) {
|
|
|
676
694
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
677
695
|
"a",
|
|
678
696
|
{
|
|
679
|
-
href,
|
|
697
|
+
href: finalHref,
|
|
680
698
|
className: cn(
|
|
681
699
|
"text-sm font-medium text-black leading-6",
|
|
682
700
|
appearance.linkClassName
|
|
@@ -1186,7 +1204,7 @@ function SignIn({
|
|
|
1186
1204
|
...uiProps
|
|
1187
1205
|
}) {
|
|
1188
1206
|
const { signIn, baseUrl } = useInsforge();
|
|
1189
|
-
const {
|
|
1207
|
+
const { emailConfig } = usePublicAuthConfig();
|
|
1190
1208
|
const [email, setEmail] = react.useState("");
|
|
1191
1209
|
const [password, setPassword] = react.useState("");
|
|
1192
1210
|
const [error, setError] = react.useState("");
|
|
@@ -1238,7 +1256,9 @@ function SignIn({
|
|
|
1238
1256
|
setOauthLoading(null);
|
|
1239
1257
|
}
|
|
1240
1258
|
}
|
|
1241
|
-
if (!emailConfig)
|
|
1259
|
+
if (!emailConfig) {
|
|
1260
|
+
return null;
|
|
1261
|
+
}
|
|
1242
1262
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1243
1263
|
SignInForm,
|
|
1244
1264
|
{
|
|
@@ -1250,7 +1270,7 @@ function SignIn({
|
|
|
1250
1270
|
error,
|
|
1251
1271
|
loading,
|
|
1252
1272
|
oauthLoading,
|
|
1253
|
-
availableProviders:
|
|
1273
|
+
availableProviders: emailConfig?.oAuthProviders || [],
|
|
1254
1274
|
onOAuthClick: handleOAuth,
|
|
1255
1275
|
emailAuthConfig: emailConfig,
|
|
1256
1276
|
...uiProps
|
|
@@ -1413,7 +1433,7 @@ function SignUp({
|
|
|
1413
1433
|
...uiProps
|
|
1414
1434
|
}) {
|
|
1415
1435
|
const { signUp, baseUrl } = useInsforge();
|
|
1416
|
-
const {
|
|
1436
|
+
const { emailConfig } = usePublicAuthConfig();
|
|
1417
1437
|
const [email, setEmail] = react.useState("");
|
|
1418
1438
|
const [password, setPassword] = react.useState("");
|
|
1419
1439
|
const [error, setError] = react.useState("");
|
|
@@ -1470,7 +1490,9 @@ function SignUp({
|
|
|
1470
1490
|
setOauthLoading(null);
|
|
1471
1491
|
}
|
|
1472
1492
|
}
|
|
1473
|
-
if (!emailConfig)
|
|
1493
|
+
if (!emailConfig) {
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1474
1496
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1475
1497
|
SignUpForm,
|
|
1476
1498
|
{
|
|
@@ -1482,7 +1504,7 @@ function SignUp({
|
|
|
1482
1504
|
error,
|
|
1483
1505
|
loading,
|
|
1484
1506
|
oauthLoading,
|
|
1485
|
-
availableProviders:
|
|
1507
|
+
availableProviders: emailConfig?.oAuthProviders || [],
|
|
1486
1508
|
onOAuthClick: handleOAuth,
|
|
1487
1509
|
emailAuthConfig: emailConfig,
|
|
1488
1510
|
...uiProps
|