@insforge/react 0.5.7 → 0.5.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insforge/react",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "type": "module",
5
5
  "description": "Framework-agnostic React authentication UI components for Insforge - reusable across all frameworks",
6
6
  "main": "./dist/index.cjs",
@@ -47,11 +47,6 @@
47
47
  "import": "./dist/navigation.js",
48
48
  "require": "./dist/navigation.cjs"
49
49
  },
50
- "./routes": {
51
- "types": "./dist/routes.d.ts",
52
- "import": "./dist/routes.js",
53
- "require": "./dist/routes.cjs"
54
- },
55
50
  "./provider": {
56
51
  "types": "./dist/provider.d.ts",
57
52
  "import": "./dist/provider.js",
package/dist/routes.cjs DELETED
@@ -1,132 +0,0 @@
1
- 'use strict';
2
-
3
- var react = require('react');
4
- require('@insforge/sdk');
5
- var jsxRuntime = require('react/jsx-runtime');
6
-
7
- // src/routes/RouteGuard.tsx
8
- react.createContext(null);
9
- var InsforgeContext = react.createContext(void 0);
10
- function useInsforge() {
11
- const context = react.useContext(InsforgeContext);
12
- if (!context) {
13
- return {
14
- user: null,
15
- isLoaded: false,
16
- isSignedIn: false,
17
- setUser: () => {
18
- },
19
- signIn: async () => ({ error: "SSR mode" }),
20
- signUp: async () => ({ error: "SSR mode" }),
21
- signOut: async () => {
22
- },
23
- updateUser: async () => ({ error: "SSR mode" }),
24
- reloadAuth: async () => ({ success: false, error: "SSR mode" }),
25
- sendVerificationEmail: async () => null,
26
- sendResetPasswordEmail: async () => null,
27
- resetPassword: async () => null,
28
- verifyEmail: async () => null,
29
- exchangeResetPasswordToken: async () => ({ error: { message: "SSR mode" } }),
30
- loginWithOAuth: async () => {
31
- },
32
- getPublicAuthConfig: async () => null,
33
- baseUrl: "",
34
- afterSignInUrl: "/"
35
- };
36
- }
37
- return context;
38
- }
39
- function RouteGuard({
40
- children,
41
- builtInAuth = true,
42
- paths = {},
43
- publicRoutes = [],
44
- loadingFallback
45
- }) {
46
- const { isSignedIn, isLoaded, afterSignInUrl, baseUrl } = useInsforge();
47
- const { signIn = "/sign-in", signUp = "/sign-up", forgotPassword = "/forgot-password" } = paths;
48
- const [currentPath, setCurrentPath] = react.useState("");
49
- react.useEffect(() => {
50
- const updatePath = () => {
51
- const path = window.location.hash ? window.location.hash.slice(1) : window.location.pathname;
52
- setCurrentPath(path || "/");
53
- };
54
- updatePath();
55
- window.addEventListener("hashchange", updatePath);
56
- window.addEventListener("popstate", updatePath);
57
- return () => {
58
- window.removeEventListener("hashchange", updatePath);
59
- window.removeEventListener("popstate", updatePath);
60
- };
61
- }, []);
62
- const isPublicRoute = publicRoutes.some((route) => {
63
- if (route.endsWith("/*")) {
64
- const prefix = route.slice(0, -2);
65
- return currentPath.startsWith(prefix);
66
- }
67
- return currentPath === route || currentPath.startsWith(route + "?");
68
- });
69
- react.useEffect(() => {
70
- if (!isLoaded) return;
71
- if (isSignedIn) return;
72
- if (!isSignedIn) {
73
- if (builtInAuth) {
74
- const isSignInPage = currentPath === signIn || currentPath.startsWith(signIn + "?");
75
- const isSignUpPage = currentPath === signUp || currentPath.startsWith(signUp + "?");
76
- const isForgotPasswordPage = currentPath === forgotPassword || currentPath.startsWith(forgotPassword + "?");
77
- if (isSignInPage) {
78
- const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
79
- const authUrl = new URL("/auth/sign-in", baseUrl);
80
- authUrl.searchParams.set("redirect", redirectUrl);
81
- window.location.replace(authUrl.toString());
82
- return;
83
- }
84
- if (isSignUpPage) {
85
- const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
86
- const authUrl = new URL("/auth/sign-up", baseUrl);
87
- authUrl.searchParams.set("redirect", redirectUrl);
88
- window.location.replace(authUrl.toString());
89
- return;
90
- }
91
- if (isForgotPasswordPage) {
92
- const authUrl = new URL("/auth/forgot-password", baseUrl);
93
- window.location.replace(authUrl.toString());
94
- return;
95
- }
96
- if (isPublicRoute) {
97
- return;
98
- } else {
99
- const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
100
- const authUrl = new URL("/auth/sign-in", baseUrl);
101
- authUrl.searchParams.set("redirect", redirectUrl);
102
- window.location.replace(authUrl.toString());
103
- }
104
- } else {
105
- if (isPublicRoute) {
106
- return;
107
- } else {
108
- window.location.href = signIn + "?redirect=" + afterSignInUrl;
109
- }
110
- }
111
- }
112
- }, [
113
- isLoaded,
114
- isSignedIn,
115
- currentPath,
116
- isPublicRoute,
117
- builtInAuth,
118
- baseUrl,
119
- signIn,
120
- signUp,
121
- forgotPassword,
122
- afterSignInUrl
123
- ]);
124
- if (!isLoaded) {
125
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingFallback });
126
- }
127
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
128
- }
129
-
130
- exports.RouteGuard = RouteGuard;
131
- //# sourceMappingURL=routes.cjs.map
132
- //# sourceMappingURL=routes.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/routes/RouteGuard.tsx"],"names":["createContext","useContext","useState","useEffect","jsx","Fragment"],"mappings":"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","file":"routes.cjs","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children }) => <a href={href}>{children}</a>,\n };\n }\n\n return adapter;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useRef,\n type ReactNode,\n} from 'react';\nimport { UserSchema, createClient } from '@insforge/sdk';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport {\n CreateSessionResponse,\n CreateUserResponse,\n GetPublicAuthConfigResponse,\n ResetPasswordResponse,\n} from '@insforge/shared-schemas';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\n\ninterface InsforgeContextValue {\n // Auth state\n user: InsforgeUser | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n\n // Auth methods\n setUser: (user: InsforgeUser | null) => void;\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n updateUser: (data: Partial<InsforgeUser>) => Promise<{ error: string } | null>;\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\n\n // Email verification methods\n sendVerificationEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n sendResetPasswordEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n resetPassword: (token: string, newPassword: string) => Promise<ResetPasswordResponse | null>;\n verifyEmail: (\n otp: string,\n email?: string\n ) => Promise<{\n accessToken: string;\n user?: UserSchema;\n redirectTo?: string;\n error?: { message: string };\n } | null>;\n exchangeResetPasswordToken: (\n email: string,\n code: string\n ) => Promise<{ token: string; expiresAt?: string } | { error: { message: string } }>;\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) => Promise<void>;\n // Public auth config\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\n // Base config\n baseUrl: string;\n afterSignInUrl: string;\n}\n\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(undefined);\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\n /**\n * URL to redirect to after successful sign in (when token is detected in URL)\n * @default '/'\n */\n afterSignInUrl?: string;\n onAuthChange?: (user: InsforgeUser | null) => void;\n onSignIn?: (authToken: string) => Promise<void>;\n onSignOut?: () => Promise<void>;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Manages user authentication state and provides all necessary context to child components.\n * Works with any React framework (Next.js, Vite, Remix, etc.).\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * afterSignInUrl=\"/dashboard\"\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\n * onSignIn={async (authToken) => {\n * await signIn(authToken);\n * }}\n * onSignOut={async () => {\n * await signOut();\n * }}\n * >\n * {children}\n * </InsforgeProvider>\n * ```\n */\nexport function InsforgeProvider({\n children,\n baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Auth state\n const [user, setUser] = useState<InsforgeUser | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\n const hasProcessedCallbackRef = useRef(false);\n\n // Initialize SDK client with lazy initialization - only runs once\n const [insforge] = useState(() => createClient({ baseUrl }));\n\n // Load auth state - returns explicit success/error status\n const loadAuthState = useCallback(async (): Promise<{\n success: boolean;\n error?: string;\n }> => {\n try {\n // Use SDK's getCurrentSession() to check for existing session\n const sessionResult = insforge.auth.getCurrentSession();\n const session = sessionResult.data?.session;\n const token = session?.accessToken || null;\n\n if (!token) {\n // No token, user is not authenticated\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'no_session' };\n }\n\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n // Token is valid, update user state with fresh data\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n setIsLoaded(true);\n return { success: true };\n } else {\n // Token invalid or expired\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'invalid_token' };\n }\n } catch (error) {\n // Token validation failed\n console.error('[InsforgeProvider] Token validation failed:', error);\n\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Authentication failed',\n };\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n useEffect(() => {\n // Run loadAuthState only once on mount\n loadAuthState();\n\n // Capture the ref value when effect runs\n const intervalId = refreshIntervalRef.current;\n\n return () => {\n // Use the captured value in cleanup\n if (intervalId) {\n clearInterval(intervalId);\n }\n };\n }, [loadAuthState]);\n\n // Handle authentication callback from URL\n useEffect(() => {\n // Only run once and only after auth is loaded\n if (!isLoaded || hasProcessedCallbackRef.current) {\n return;\n }\n\n const searchParams = new URLSearchParams(window.location.search);\n const accessToken = searchParams.get('access_token');\n\n if (accessToken && !!user) {\n // Mark as processed\n hasProcessedCallbackRef.current = true;\n\n // Clean URL by removing query parameters\n const url = new URL(window.location.href);\n url.search = '';\n window.history.replaceState({}, '', url.toString());\n\n // Redirect to afterSignInUrl\n setTimeout(() => {\n window.location.href = afterSignInUrl;\n }, 100);\n }\n }, [isLoaded, user, afterSignInUrl]);\n\n const getPublicAuthConfig = useCallback(async () => {\n try {\n const result = await insforge.auth.getPublicAuthConfig();\n if (result.data) {\n return result.data;\n } else {\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\n return null;\n }\n } catch (error) {\n console.error('[InsforgeProvider] Failed to get public auth config:', error);\n return null;\n }\n }, [insforge]);\n\n /**\n * Helper function to handle successful authentication\n */\n const handleAuthSuccess = useCallback(\n async (authToken: string, fallbackUser?: { id?: string; email?: string; name?: string }) => {\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n\n // Try to sync token to cookie if function provided\n if (onSignIn) {\n try {\n await onSignIn(authToken);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error syncing token to cookie:', error.message);\n }\n }\n }\n } else if (fallbackUser) {\n // Fallback to basic user data if getCurrentUser fails\n const userData: InsforgeUser = {\n id: fallbackUser.id || '',\n email: fallbackUser.email || '',\n name: fallbackUser.name || '',\n avatarUrl: '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n }\n },\n [insforge, onAuthChange, onSignIn]\n );\n\n const signIn = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signInWithPassword({\n email,\n password,\n });\n\n if (sdkResult.data) {\n await handleAuthSuccess(\n sdkResult.data.accessToken || '',\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Invalid email or password',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signUp = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signUp({ email, password });\n\n if (sdkResult.data) {\n // Only set auth state if we got a token back (email verification might be required)\n if (sdkResult.data.accessToken) {\n await handleAuthSuccess(\n sdkResult.data.accessToken,\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n }\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Sign up failed',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signOut = useCallback(async () => {\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n // Clear refresh interval if exists\n if (refreshIntervalRef.current) {\n clearInterval(refreshIntervalRef.current);\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n const updateUser = useCallback(\n async (data: Partial<InsforgeUser>) => {\n if (!user) {\n return { error: 'No user signed in' };\n }\n\n const profileUpdate: Record<string, string | undefined> = {\n nickname: data.name,\n avatarUrl: data.avatarUrl,\n };\n\n const result = await insforge.auth.setProfile(profileUpdate);\n\n if (result.data) {\n const userResult = await insforge.auth.getCurrentUser();\n if (userResult.data) {\n const profile = userResult.data.profile;\n const updatedUser: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: profile?.nickname || '',\n avatarUrl: profile?.avatarUrl || '',\n };\n setUser(updatedUser);\n if (onAuthChange) {\n onAuthChange(updatedUser);\n }\n }\n return null;\n } else {\n return { error: result.error?.message || 'Failed to update user' };\n }\n },\n [user, onAuthChange, insforge]\n );\n\n const sendVerificationEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendVerificationEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const sendResetPasswordEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendResetPasswordEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const resetPassword = useCallback(\n async (token: string, newPassword: string) => {\n const sdkResult = await insforge.auth.resetPassword({\n newPassword,\n otp: token,\n });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const verifyEmail = useCallback(\n async (otp: string, email?: string) => {\n const sdkResult = await insforge.auth.verifyEmail({ otp, email: email || undefined });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n accessToken: '',\n error: {\n message: sdkResult.error?.message || 'Email verification failed',\n },\n };\n }\n },\n [insforge]\n );\n\n const exchangeResetPasswordToken = useCallback(\n async (email: string, code: string) => {\n const sdkResult = await insforge.auth.exchangeResetPasswordToken({ email, code });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n error: {\n message: sdkResult.error?.message || 'Failed to exchange reset password token',\n },\n };\n }\n },\n [insforge]\n );\n\n const loginWithOAuth = useCallback(\n async (provider: OAuthProvider, redirectTo: string) => {\n const sdkResult = await insforge.auth.signInWithOAuth({\n provider,\n redirectTo: redirectTo || window.location.origin + afterSignInUrl,\n });\n if (sdkResult.error) {\n throw new Error(sdkResult.error.message);\n }\n },\n [insforge, afterSignInUrl]\n );\n\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeContext.Provider\n value={{\n user,\n isLoaded,\n isSignedIn: !!user,\n setUser,\n signIn,\n signUp,\n signOut,\n updateUser,\n reloadAuth: loadAuthState,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n baseUrl,\n afterSignInUrl,\n }}\n >\n {children}\n </InsforgeContext.Provider>\n </NavigationProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { user, isSignedIn, signOut } = useInsforge();\n *\n * if (!isSignedIn) return <SignIn />;\n *\n * return (\n * <div>\n * <p>Welcome {user.email}</p>\n * <button onClick={signOut}>Sign Out</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useInsforge(): InsforgeContextValue {\n const context = useContext(InsforgeContext);\n\n // During SSR in Next.js, context might be undefined temporarily\n // Return a safe default that won't break SSR rendering\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\n setUser: () => {},\n signIn: async () => ({ error: 'SSR mode' }),\n signUp: async () => ({ error: 'SSR mode' }),\n signOut: async () => {},\n updateUser: async () => ({ error: 'SSR mode' }),\n reloadAuth: async () => ({ success: false, error: 'SSR mode' }),\n sendVerificationEmail: async () => null,\n sendResetPasswordEmail: async () => null,\n resetPassword: async () => null,\n verifyEmail: async () => null,\n exchangeResetPasswordToken: async () => ({ error: { message: 'SSR mode' } }),\n loginWithOAuth: async () => {},\n getPublicAuthConfig: async () => null,\n baseUrl: '',\n afterSignInUrl: '/',\n } as InsforgeContextValue;\n }\n\n return context;\n}\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface RouteGuardProps {\n children: ReactNode;\n\n /**\n * Whether to use built-in auth (external deployed Insforge Auth)\n * - true: redirects to baseUrl/auth/sign-in with redirect param\n * - false: redirects to paths.signIn (your own login page)\n * @default true\n */\n builtInAuth?: boolean;\n\n /**\n * Custom paths for auth pages (only used when builtInAuth is false)\n * @default { signIn: '/sign-in', signUp: '/sign-up', forgotPassword: '/forgot-password' }\n */\n paths?: {\n signIn?: string;\n signUp?: string;\n forgotPassword?: string;\n };\n\n /**\n * List of public routes that don't require authentication\n * These routes will be accessible even when not signed in\n *\n * Note: When builtInAuth=true, you DON'T need to include auth paths here\n * (e.g., '/sign-in', '/sign-up') as they will be auto-redirected to external auth\n *\n * @example ['/', '/about', '/blog/*']\n */\n publicRoutes?: string[];\n\n /**\n * Loading fallback while checking auth (required)\n * You must provide a loading component\n */\n loadingFallback: ReactNode;\n}\n\n/**\n * Route Guard for React apps\n *\n * Ensures user is authenticated before rendering protected routes.\n *\n * @example\n * ```tsx\n * // Using external built-in auth (deployed Insforge Auth)\n * // User visits /sign-up → redirects to baseUrl/auth/sign-up\n * // User visits /dashboard → redirects to baseUrl/auth/sign-in\n * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>\n * <RouteGuard\n * builtInAuth\n * publicRoutes={['/', '/about']}\n * loadingFallback={<div>Loading...</div>}\n * >\n * <App />\n * </RouteGuard>\n * </InsforgeProvider>\n * ```\n *\n * @example\n * ```tsx\n * // Using custom login page (@insforge/react components)\n * // User visits /login → renders your <SignIn /> component (must be in publicRoutes)\n * // User visits /dashboard → redirects to /login\n * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>\n * <RouteGuard\n * builtInAuth={false}\n * publicRoutes={['/login', '/register', '/forgot-password', '/']}\n * paths={{ signIn: '/login', signUp: '/register' }}\n * loadingFallback={<Spinner />}\n * >\n * <App />\n * </RouteGuard>\n * </InsforgeProvider>\n * ```\n */\nexport function RouteGuard({\n children,\n builtInAuth = true,\n paths = {},\n publicRoutes = [],\n loadingFallback,\n}: RouteGuardProps) {\n const { isSignedIn, isLoaded, afterSignInUrl, baseUrl } = useInsforge();\n\n const { signIn = '/sign-in', signUp = '/sign-up', forgotPassword = '/forgot-password' } = paths;\n\n // Track current path (supports both pathname and hash routing)\n const [currentPath, setCurrentPath] = useState('');\n\n useEffect(() => {\n const updatePath = () => {\n // Support both hash routing (#/path) and pathname routing (/path)\n const path = window.location.hash ? window.location.hash.slice(1) : window.location.pathname;\n setCurrentPath(path || '/');\n };\n\n updatePath();\n\n // Listen to both hash and path changes\n window.addEventListener('hashchange', updatePath);\n window.addEventListener('popstate', updatePath);\n\n return () => {\n window.removeEventListener('hashchange', updatePath);\n window.removeEventListener('popstate', updatePath);\n };\n }, []);\n\n // Check if current route is public\n const isPublicRoute = publicRoutes.some((route) => {\n // Support exact match and wildcard\n if (route.endsWith('/*')) {\n const prefix = route.slice(0, -2);\n return currentPath.startsWith(prefix);\n }\n // Exact match or with query params\n return currentPath === route || currentPath.startsWith(route + '?');\n });\n\n useEffect(() => {\n // Still loading - don't do anything yet\n if (!isLoaded) return;\n\n // User is signed in - allow access to everything\n if (isSignedIn) return;\n\n // User is NOT signed in - check routing logic\n if (!isSignedIn) {\n if (builtInAuth) {\n // Built-in auth mode: redirect to external auth pages\n\n // Check if user is trying to access auth pages\n const isSignInPage = currentPath === signIn || currentPath.startsWith(signIn + '?');\n const isSignUpPage = currentPath === signUp || currentPath.startsWith(signUp + '?');\n const isForgotPasswordPage =\n currentPath === forgotPassword || currentPath.startsWith(forgotPassword + '?');\n\n if (isSignInPage) {\n // Redirect to external sign-in\n const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;\n const authUrl = new URL('/auth/sign-in', baseUrl);\n authUrl.searchParams.set('redirect', redirectUrl);\n window.location.replace(authUrl.toString());\n return;\n }\n\n if (isSignUpPage) {\n // Redirect to external sign-up\n const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;\n const authUrl = new URL('/auth/sign-up', baseUrl);\n authUrl.searchParams.set('redirect', redirectUrl);\n window.location.replace(authUrl.toString());\n return;\n }\n\n if (isForgotPasswordPage) {\n // Redirect to external forgot-password (no redirect param for password reset)\n const authUrl = new URL('/auth/forgot-password', baseUrl);\n window.location.replace(authUrl.toString());\n return;\n }\n\n // Not an auth page - check if it's public or protected\n if (isPublicRoute) {\n // Public route - allow access\n return;\n } else {\n // Protected route - redirect to external sign-in\n const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;\n const authUrl = new URL('/auth/sign-in', baseUrl);\n authUrl.searchParams.set('redirect', redirectUrl);\n window.location.replace(authUrl.toString());\n }\n } else {\n // Custom auth mode: redirect to local auth pages\n if (isPublicRoute) {\n // Public route (including local auth pages) - allow access\n return;\n } else {\n // Protected route - redirect to local sign-in\n window.location.href = signIn + '?redirect=' + afterSignInUrl;\n }\n }\n }\n }, [\n isLoaded,\n isSignedIn,\n currentPath,\n isPublicRoute,\n builtInAuth,\n baseUrl,\n signIn,\n signUp,\n forgotPassword,\n afterSignInUrl,\n ]);\n\n // Show loading state\n if (!isLoaded) {\n return <>{loadingFallback}</>;\n }\n\n // Render children - either public route or signed in\n return <>{children}</>;\n}\n"]}
package/dist/routes.d.cts DELETED
@@ -1,78 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
-
4
- interface RouteGuardProps {
5
- children: ReactNode;
6
- /**
7
- * Whether to use built-in auth (external deployed Insforge Auth)
8
- * - true: redirects to baseUrl/auth/sign-in with redirect param
9
- * - false: redirects to paths.signIn (your own login page)
10
- * @default true
11
- */
12
- builtInAuth?: boolean;
13
- /**
14
- * Custom paths for auth pages (only used when builtInAuth is false)
15
- * @default { signIn: '/sign-in', signUp: '/sign-up', forgotPassword: '/forgot-password' }
16
- */
17
- paths?: {
18
- signIn?: string;
19
- signUp?: string;
20
- forgotPassword?: string;
21
- };
22
- /**
23
- * List of public routes that don't require authentication
24
- * These routes will be accessible even when not signed in
25
- *
26
- * Note: When builtInAuth=true, you DON'T need to include auth paths here
27
- * (e.g., '/sign-in', '/sign-up') as they will be auto-redirected to external auth
28
- *
29
- * @example ['/', '/about', '/blog/*']
30
- */
31
- publicRoutes?: string[];
32
- /**
33
- * Loading fallback while checking auth (required)
34
- * You must provide a loading component
35
- */
36
- loadingFallback: ReactNode;
37
- }
38
- /**
39
- * Route Guard for React apps
40
- *
41
- * Ensures user is authenticated before rendering protected routes.
42
- *
43
- * @example
44
- * ```tsx
45
- * // Using external built-in auth (deployed Insforge Auth)
46
- * // User visits /sign-up → redirects to baseUrl/auth/sign-up
47
- * // User visits /dashboard → redirects to baseUrl/auth/sign-in
48
- * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>
49
- * <RouteGuard
50
- * builtInAuth
51
- * publicRoutes={['/', '/about']}
52
- * loadingFallback={<div>Loading...</div>}
53
- * >
54
- * <App />
55
- * </RouteGuard>
56
- * </InsforgeProvider>
57
- * ```
58
- *
59
- * @example
60
- * ```tsx
61
- * // Using custom login page (@insforge/react components)
62
- * // User visits /login → renders your <SignIn /> component (must be in publicRoutes)
63
- * // User visits /dashboard → redirects to /login
64
- * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>
65
- * <RouteGuard
66
- * builtInAuth={false}
67
- * publicRoutes={['/login', '/register', '/forgot-password', '/']}
68
- * paths={{ signIn: '/login', signUp: '/register' }}
69
- * loadingFallback={<Spinner />}
70
- * >
71
- * <App />
72
- * </RouteGuard>
73
- * </InsforgeProvider>
74
- * ```
75
- */
76
- declare function RouteGuard({ children, builtInAuth, paths, publicRoutes, loadingFallback, }: RouteGuardProps): react_jsx_runtime.JSX.Element;
77
-
78
- export { RouteGuard, type RouteGuardProps };
package/dist/routes.d.ts DELETED
@@ -1,78 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
-
4
- interface RouteGuardProps {
5
- children: ReactNode;
6
- /**
7
- * Whether to use built-in auth (external deployed Insforge Auth)
8
- * - true: redirects to baseUrl/auth/sign-in with redirect param
9
- * - false: redirects to paths.signIn (your own login page)
10
- * @default true
11
- */
12
- builtInAuth?: boolean;
13
- /**
14
- * Custom paths for auth pages (only used when builtInAuth is false)
15
- * @default { signIn: '/sign-in', signUp: '/sign-up', forgotPassword: '/forgot-password' }
16
- */
17
- paths?: {
18
- signIn?: string;
19
- signUp?: string;
20
- forgotPassword?: string;
21
- };
22
- /**
23
- * List of public routes that don't require authentication
24
- * These routes will be accessible even when not signed in
25
- *
26
- * Note: When builtInAuth=true, you DON'T need to include auth paths here
27
- * (e.g., '/sign-in', '/sign-up') as they will be auto-redirected to external auth
28
- *
29
- * @example ['/', '/about', '/blog/*']
30
- */
31
- publicRoutes?: string[];
32
- /**
33
- * Loading fallback while checking auth (required)
34
- * You must provide a loading component
35
- */
36
- loadingFallback: ReactNode;
37
- }
38
- /**
39
- * Route Guard for React apps
40
- *
41
- * Ensures user is authenticated before rendering protected routes.
42
- *
43
- * @example
44
- * ```tsx
45
- * // Using external built-in auth (deployed Insforge Auth)
46
- * // User visits /sign-up → redirects to baseUrl/auth/sign-up
47
- * // User visits /dashboard → redirects to baseUrl/auth/sign-in
48
- * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>
49
- * <RouteGuard
50
- * builtInAuth
51
- * publicRoutes={['/', '/about']}
52
- * loadingFallback={<div>Loading...</div>}
53
- * >
54
- * <App />
55
- * </RouteGuard>
56
- * </InsforgeProvider>
57
- * ```
58
- *
59
- * @example
60
- * ```tsx
61
- * // Using custom login page (@insforge/react components)
62
- * // User visits /login → renders your <SignIn /> component (must be in publicRoutes)
63
- * // User visits /dashboard → redirects to /login
64
- * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>
65
- * <RouteGuard
66
- * builtInAuth={false}
67
- * publicRoutes={['/login', '/register', '/forgot-password', '/']}
68
- * paths={{ signIn: '/login', signUp: '/register' }}
69
- * loadingFallback={<Spinner />}
70
- * >
71
- * <App />
72
- * </RouteGuard>
73
- * </InsforgeProvider>
74
- * ```
75
- */
76
- declare function RouteGuard({ children, builtInAuth, paths, publicRoutes, loadingFallback, }: RouteGuardProps): react_jsx_runtime.JSX.Element;
77
-
78
- export { RouteGuard, type RouteGuardProps };
package/dist/routes.js DELETED
@@ -1,130 +0,0 @@
1
- import { createContext, useState, useEffect, useContext } from 'react';
2
- import '@insforge/sdk';
3
- import { jsx, Fragment } from 'react/jsx-runtime';
4
-
5
- // src/routes/RouteGuard.tsx
6
- createContext(null);
7
- var InsforgeContext = createContext(void 0);
8
- function useInsforge() {
9
- const context = useContext(InsforgeContext);
10
- if (!context) {
11
- return {
12
- user: null,
13
- isLoaded: false,
14
- isSignedIn: false,
15
- setUser: () => {
16
- },
17
- signIn: async () => ({ error: "SSR mode" }),
18
- signUp: async () => ({ error: "SSR mode" }),
19
- signOut: async () => {
20
- },
21
- updateUser: async () => ({ error: "SSR mode" }),
22
- reloadAuth: async () => ({ success: false, error: "SSR mode" }),
23
- sendVerificationEmail: async () => null,
24
- sendResetPasswordEmail: async () => null,
25
- resetPassword: async () => null,
26
- verifyEmail: async () => null,
27
- exchangeResetPasswordToken: async () => ({ error: { message: "SSR mode" } }),
28
- loginWithOAuth: async () => {
29
- },
30
- getPublicAuthConfig: async () => null,
31
- baseUrl: "",
32
- afterSignInUrl: "/"
33
- };
34
- }
35
- return context;
36
- }
37
- function RouteGuard({
38
- children,
39
- builtInAuth = true,
40
- paths = {},
41
- publicRoutes = [],
42
- loadingFallback
43
- }) {
44
- const { isSignedIn, isLoaded, afterSignInUrl, baseUrl } = useInsforge();
45
- const { signIn = "/sign-in", signUp = "/sign-up", forgotPassword = "/forgot-password" } = paths;
46
- const [currentPath, setCurrentPath] = useState("");
47
- useEffect(() => {
48
- const updatePath = () => {
49
- const path = window.location.hash ? window.location.hash.slice(1) : window.location.pathname;
50
- setCurrentPath(path || "/");
51
- };
52
- updatePath();
53
- window.addEventListener("hashchange", updatePath);
54
- window.addEventListener("popstate", updatePath);
55
- return () => {
56
- window.removeEventListener("hashchange", updatePath);
57
- window.removeEventListener("popstate", updatePath);
58
- };
59
- }, []);
60
- const isPublicRoute = publicRoutes.some((route) => {
61
- if (route.endsWith("/*")) {
62
- const prefix = route.slice(0, -2);
63
- return currentPath.startsWith(prefix);
64
- }
65
- return currentPath === route || currentPath.startsWith(route + "?");
66
- });
67
- useEffect(() => {
68
- if (!isLoaded) return;
69
- if (isSignedIn) return;
70
- if (!isSignedIn) {
71
- if (builtInAuth) {
72
- const isSignInPage = currentPath === signIn || currentPath.startsWith(signIn + "?");
73
- const isSignUpPage = currentPath === signUp || currentPath.startsWith(signUp + "?");
74
- const isForgotPasswordPage = currentPath === forgotPassword || currentPath.startsWith(forgotPassword + "?");
75
- if (isSignInPage) {
76
- const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
77
- const authUrl = new URL("/auth/sign-in", baseUrl);
78
- authUrl.searchParams.set("redirect", redirectUrl);
79
- window.location.replace(authUrl.toString());
80
- return;
81
- }
82
- if (isSignUpPage) {
83
- const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
84
- const authUrl = new URL("/auth/sign-up", baseUrl);
85
- authUrl.searchParams.set("redirect", redirectUrl);
86
- window.location.replace(authUrl.toString());
87
- return;
88
- }
89
- if (isForgotPasswordPage) {
90
- const authUrl = new URL("/auth/forgot-password", baseUrl);
91
- window.location.replace(authUrl.toString());
92
- return;
93
- }
94
- if (isPublicRoute) {
95
- return;
96
- } else {
97
- const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;
98
- const authUrl = new URL("/auth/sign-in", baseUrl);
99
- authUrl.searchParams.set("redirect", redirectUrl);
100
- window.location.replace(authUrl.toString());
101
- }
102
- } else {
103
- if (isPublicRoute) {
104
- return;
105
- } else {
106
- window.location.href = signIn + "?redirect=" + afterSignInUrl;
107
- }
108
- }
109
- }
110
- }, [
111
- isLoaded,
112
- isSignedIn,
113
- currentPath,
114
- isPublicRoute,
115
- builtInAuth,
116
- baseUrl,
117
- signIn,
118
- signUp,
119
- forgotPassword,
120
- afterSignInUrl
121
- ]);
122
- if (!isLoaded) {
123
- return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
124
- }
125
- return /* @__PURE__ */ jsx(Fragment, { children });
126
- }
127
-
128
- export { RouteGuard };
129
- //# sourceMappingURL=routes.js.map
130
- //# sourceMappingURL=routes.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/routes/RouteGuard.tsx"],"names":["createContext","useContext","useState","useEffect","jsx"],"mappings":"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","file":"routes.js","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children }) => <a href={href}>{children}</a>,\n };\n }\n\n return adapter;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useRef,\n type ReactNode,\n} from 'react';\nimport { UserSchema, createClient } from '@insforge/sdk';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport {\n CreateSessionResponse,\n CreateUserResponse,\n GetPublicAuthConfigResponse,\n ResetPasswordResponse,\n} from '@insforge/shared-schemas';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\n\ninterface InsforgeContextValue {\n // Auth state\n user: InsforgeUser | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n\n // Auth methods\n setUser: (user: InsforgeUser | null) => void;\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n updateUser: (data: Partial<InsforgeUser>) => Promise<{ error: string } | null>;\n reloadAuth: () => Promise<{ success: boolean; error?: string }>;\n\n // Email verification methods\n sendVerificationEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n sendResetPasswordEmail: (email: string) => Promise<{ success: boolean; message: string } | null>;\n resetPassword: (token: string, newPassword: string) => Promise<ResetPasswordResponse | null>;\n verifyEmail: (\n otp: string,\n email?: string\n ) => Promise<{\n accessToken: string;\n user?: UserSchema;\n redirectTo?: string;\n error?: { message: string };\n } | null>;\n exchangeResetPasswordToken: (\n email: string,\n code: string\n ) => Promise<{ token: string; expiresAt?: string } | { error: { message: string } }>;\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) => Promise<void>;\n // Public auth config\n getPublicAuthConfig: () => Promise<GetPublicAuthConfigResponse | null>;\n // Base config\n baseUrl: string;\n afterSignInUrl: string;\n}\n\nconst InsforgeContext = createContext<InsforgeContextValue | undefined>(undefined);\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\n /**\n * URL to redirect to after successful sign in (when token is detected in URL)\n * @default '/'\n */\n afterSignInUrl?: string;\n onAuthChange?: (user: InsforgeUser | null) => void;\n onSignIn?: (authToken: string) => Promise<void>;\n onSignOut?: () => Promise<void>;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Manages user authentication state and provides all necessary context to child components.\n * Works with any React framework (Next.js, Vite, Remix, etc.).\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * afterSignInUrl=\"/dashboard\"\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\n * onSignIn={async (authToken) => {\n * await signIn(authToken);\n * }}\n * onSignOut={async () => {\n * await signOut();\n * }}\n * >\n * {children}\n * </InsforgeProvider>\n * ```\n */\nexport function InsforgeProvider({\n children,\n baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Auth state\n const [user, setUser] = useState<InsforgeUser | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);\n const hasProcessedCallbackRef = useRef(false);\n\n // Initialize SDK client with lazy initialization - only runs once\n const [insforge] = useState(() => createClient({ baseUrl }));\n\n // Load auth state - returns explicit success/error status\n const loadAuthState = useCallback(async (): Promise<{\n success: boolean;\n error?: string;\n }> => {\n try {\n // Use SDK's getCurrentSession() to check for existing session\n const sessionResult = insforge.auth.getCurrentSession();\n const session = sessionResult.data?.session;\n const token = session?.accessToken || null;\n\n if (!token) {\n // No token, user is not authenticated\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'no_session' };\n }\n\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n // Token is valid, update user state with fresh data\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n setIsLoaded(true);\n return { success: true };\n } else {\n // Token invalid or expired\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n return { success: false, error: 'invalid_token' };\n }\n } catch (error) {\n // Token validation failed\n console.error('[InsforgeProvider] Token validation failed:', error);\n\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n setIsLoaded(true);\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Authentication failed',\n };\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n useEffect(() => {\n // Run loadAuthState only once on mount\n loadAuthState();\n\n // Capture the ref value when effect runs\n const intervalId = refreshIntervalRef.current;\n\n return () => {\n // Use the captured value in cleanup\n if (intervalId) {\n clearInterval(intervalId);\n }\n };\n }, [loadAuthState]);\n\n // Handle authentication callback from URL\n useEffect(() => {\n // Only run once and only after auth is loaded\n if (!isLoaded || hasProcessedCallbackRef.current) {\n return;\n }\n\n const searchParams = new URLSearchParams(window.location.search);\n const accessToken = searchParams.get('access_token');\n\n if (accessToken && !!user) {\n // Mark as processed\n hasProcessedCallbackRef.current = true;\n\n // Clean URL by removing query parameters\n const url = new URL(window.location.href);\n url.search = '';\n window.history.replaceState({}, '', url.toString());\n\n // Redirect to afterSignInUrl\n setTimeout(() => {\n window.location.href = afterSignInUrl;\n }, 100);\n }\n }, [isLoaded, user, afterSignInUrl]);\n\n const getPublicAuthConfig = useCallback(async () => {\n try {\n const result = await insforge.auth.getPublicAuthConfig();\n if (result.data) {\n return result.data;\n } else {\n console.error('[InsforgeProvider] Failed to get public auth config:', result.error);\n return null;\n }\n } catch (error) {\n console.error('[InsforgeProvider] Failed to get public auth config:', error);\n return null;\n }\n }, [insforge]);\n\n /**\n * Helper function to handle successful authentication\n */\n const handleAuthSuccess = useCallback(\n async (authToken: string, fallbackUser?: { id?: string; email?: string; name?: string }) => {\n const userResult = await insforge.auth.getCurrentUser();\n\n if (userResult.data) {\n const profile = userResult.data.profile;\n const userData: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: (profile?.nickname as string | undefined) || '',\n avatarUrl: (profile?.avatarUrl as string | undefined) || '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n\n // Try to sync token to cookie if function provided\n if (onSignIn) {\n try {\n await onSignIn(authToken);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error syncing token to cookie:', error.message);\n }\n }\n }\n } else if (fallbackUser) {\n // Fallback to basic user data if getCurrentUser fails\n const userData: InsforgeUser = {\n id: fallbackUser.id || '',\n email: fallbackUser.email || '',\n name: fallbackUser.name || '',\n avatarUrl: '',\n };\n\n setUser(userData);\n\n if (onAuthChange) {\n onAuthChange(userData);\n }\n }\n },\n [insforge, onAuthChange, onSignIn]\n );\n\n const signIn = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signInWithPassword({\n email,\n password,\n });\n\n if (sdkResult.data) {\n await handleAuthSuccess(\n sdkResult.data.accessToken || '',\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Invalid email or password',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signUp = useCallback(\n async (email: string, password: string) => {\n const sdkResult = await insforge.auth.signUp({ email, password });\n\n if (sdkResult.data) {\n // Only set auth state if we got a token back (email verification might be required)\n if (sdkResult.data.accessToken) {\n await handleAuthSuccess(\n sdkResult.data.accessToken,\n sdkResult.data.user\n ? {\n id: sdkResult.data.user.id,\n email: sdkResult.data.user.email,\n name: sdkResult.data.user.name,\n }\n : undefined\n );\n }\n return sdkResult.data;\n } else {\n // Return the full error object to preserve statusCode and other properties\n return {\n error: sdkResult.error?.message || 'Sign up failed',\n statusCode: sdkResult.error?.statusCode,\n errorCode: sdkResult.error?.error,\n };\n }\n },\n [insforge, handleAuthSuccess]\n );\n\n const signOut = useCallback(async () => {\n await insforge.auth.signOut();\n\n // Clear cookie if function provided\n if (onSignOut) {\n try {\n await onSignOut();\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error('[InsforgeProvider] Error clearing cookie:', error.message);\n }\n }\n }\n\n // Clear refresh interval if exists\n if (refreshIntervalRef.current) {\n clearInterval(refreshIntervalRef.current);\n }\n\n setUser(null);\n if (onAuthChange) {\n onAuthChange(null);\n }\n }, [insforge, onAuthChange, onSignOut]);\n\n const updateUser = useCallback(\n async (data: Partial<InsforgeUser>) => {\n if (!user) {\n return { error: 'No user signed in' };\n }\n\n const profileUpdate: Record<string, string | undefined> = {\n nickname: data.name,\n avatarUrl: data.avatarUrl,\n };\n\n const result = await insforge.auth.setProfile(profileUpdate);\n\n if (result.data) {\n const userResult = await insforge.auth.getCurrentUser();\n if (userResult.data) {\n const profile = userResult.data.profile;\n const updatedUser: InsforgeUser = {\n id: userResult.data.user.id,\n email: userResult.data.user.email,\n name: profile?.nickname || '',\n avatarUrl: profile?.avatarUrl || '',\n };\n setUser(updatedUser);\n if (onAuthChange) {\n onAuthChange(updatedUser);\n }\n }\n return null;\n } else {\n return { error: result.error?.message || 'Failed to update user' };\n }\n },\n [user, onAuthChange, insforge]\n );\n\n const sendVerificationEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendVerificationEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const sendResetPasswordEmail = useCallback(\n async (email: string) => {\n const sdkResult = await insforge.auth.sendResetPasswordEmail({ email });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const resetPassword = useCallback(\n async (token: string, newPassword: string) => {\n const sdkResult = await insforge.auth.resetPassword({\n newPassword,\n otp: token,\n });\n return sdkResult.data;\n },\n [insforge]\n );\n\n const verifyEmail = useCallback(\n async (otp: string, email?: string) => {\n const sdkResult = await insforge.auth.verifyEmail({ otp, email: email || undefined });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n accessToken: '',\n error: {\n message: sdkResult.error?.message || 'Email verification failed',\n },\n };\n }\n },\n [insforge]\n );\n\n const exchangeResetPasswordToken = useCallback(\n async (email: string, code: string) => {\n const sdkResult = await insforge.auth.exchangeResetPasswordToken({ email, code });\n if (sdkResult.data) {\n return sdkResult.data;\n } else {\n return {\n error: {\n message: sdkResult.error?.message || 'Failed to exchange reset password token',\n },\n };\n }\n },\n [insforge]\n );\n\n const loginWithOAuth = useCallback(\n async (provider: OAuthProvider, redirectTo: string) => {\n const sdkResult = await insforge.auth.signInWithOAuth({\n provider,\n redirectTo: redirectTo || window.location.origin + afterSignInUrl,\n });\n if (sdkResult.error) {\n throw new Error(sdkResult.error.message);\n }\n },\n [insforge, afterSignInUrl]\n );\n\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeContext.Provider\n value={{\n user,\n isLoaded,\n isSignedIn: !!user,\n setUser,\n signIn,\n signUp,\n signOut,\n updateUser,\n reloadAuth: loadAuthState,\n sendVerificationEmail,\n sendResetPasswordEmail,\n resetPassword,\n verifyEmail,\n exchangeResetPasswordToken,\n getPublicAuthConfig,\n loginWithOAuth,\n baseUrl,\n afterSignInUrl,\n }}\n >\n {children}\n </InsforgeContext.Provider>\n </NavigationProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { user, isSignedIn, signOut } = useInsforge();\n *\n * if (!isSignedIn) return <SignIn />;\n *\n * return (\n * <div>\n * <p>Welcome {user.email}</p>\n * <button onClick={signOut}>Sign Out</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useInsforge(): InsforgeContextValue {\n const context = useContext(InsforgeContext);\n\n // During SSR in Next.js, context might be undefined temporarily\n // Return a safe default that won't break SSR rendering\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\n setUser: () => {},\n signIn: async () => ({ error: 'SSR mode' }),\n signUp: async () => ({ error: 'SSR mode' }),\n signOut: async () => {},\n updateUser: async () => ({ error: 'SSR mode' }),\n reloadAuth: async () => ({ success: false, error: 'SSR mode' }),\n sendVerificationEmail: async () => null,\n sendResetPasswordEmail: async () => null,\n resetPassword: async () => null,\n verifyEmail: async () => null,\n exchangeResetPasswordToken: async () => ({ error: { message: 'SSR mode' } }),\n loginWithOAuth: async () => {},\n getPublicAuthConfig: async () => null,\n baseUrl: '',\n afterSignInUrl: '/',\n } as InsforgeContextValue;\n }\n\n return context;\n}\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface RouteGuardProps {\n children: ReactNode;\n\n /**\n * Whether to use built-in auth (external deployed Insforge Auth)\n * - true: redirects to baseUrl/auth/sign-in with redirect param\n * - false: redirects to paths.signIn (your own login page)\n * @default true\n */\n builtInAuth?: boolean;\n\n /**\n * Custom paths for auth pages (only used when builtInAuth is false)\n * @default { signIn: '/sign-in', signUp: '/sign-up', forgotPassword: '/forgot-password' }\n */\n paths?: {\n signIn?: string;\n signUp?: string;\n forgotPassword?: string;\n };\n\n /**\n * List of public routes that don't require authentication\n * These routes will be accessible even when not signed in\n *\n * Note: When builtInAuth=true, you DON'T need to include auth paths here\n * (e.g., '/sign-in', '/sign-up') as they will be auto-redirected to external auth\n *\n * @example ['/', '/about', '/blog/*']\n */\n publicRoutes?: string[];\n\n /**\n * Loading fallback while checking auth (required)\n * You must provide a loading component\n */\n loadingFallback: ReactNode;\n}\n\n/**\n * Route Guard for React apps\n *\n * Ensures user is authenticated before rendering protected routes.\n *\n * @example\n * ```tsx\n * // Using external built-in auth (deployed Insforge Auth)\n * // User visits /sign-up → redirects to baseUrl/auth/sign-up\n * // User visits /dashboard → redirects to baseUrl/auth/sign-in\n * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>\n * <RouteGuard\n * builtInAuth\n * publicRoutes={['/', '/about']}\n * loadingFallback={<div>Loading...</div>}\n * >\n * <App />\n * </RouteGuard>\n * </InsforgeProvider>\n * ```\n *\n * @example\n * ```tsx\n * // Using custom login page (@insforge/react components)\n * // User visits /login → renders your <SignIn /> component (must be in publicRoutes)\n * // User visits /dashboard → redirects to /login\n * <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>\n * <RouteGuard\n * builtInAuth={false}\n * publicRoutes={['/login', '/register', '/forgot-password', '/']}\n * paths={{ signIn: '/login', signUp: '/register' }}\n * loadingFallback={<Spinner />}\n * >\n * <App />\n * </RouteGuard>\n * </InsforgeProvider>\n * ```\n */\nexport function RouteGuard({\n children,\n builtInAuth = true,\n paths = {},\n publicRoutes = [],\n loadingFallback,\n}: RouteGuardProps) {\n const { isSignedIn, isLoaded, afterSignInUrl, baseUrl } = useInsforge();\n\n const { signIn = '/sign-in', signUp = '/sign-up', forgotPassword = '/forgot-password' } = paths;\n\n // Track current path (supports both pathname and hash routing)\n const [currentPath, setCurrentPath] = useState('');\n\n useEffect(() => {\n const updatePath = () => {\n // Support both hash routing (#/path) and pathname routing (/path)\n const path = window.location.hash ? window.location.hash.slice(1) : window.location.pathname;\n setCurrentPath(path || '/');\n };\n\n updatePath();\n\n // Listen to both hash and path changes\n window.addEventListener('hashchange', updatePath);\n window.addEventListener('popstate', updatePath);\n\n return () => {\n window.removeEventListener('hashchange', updatePath);\n window.removeEventListener('popstate', updatePath);\n };\n }, []);\n\n // Check if current route is public\n const isPublicRoute = publicRoutes.some((route) => {\n // Support exact match and wildcard\n if (route.endsWith('/*')) {\n const prefix = route.slice(0, -2);\n return currentPath.startsWith(prefix);\n }\n // Exact match or with query params\n return currentPath === route || currentPath.startsWith(route + '?');\n });\n\n useEffect(() => {\n // Still loading - don't do anything yet\n if (!isLoaded) return;\n\n // User is signed in - allow access to everything\n if (isSignedIn) return;\n\n // User is NOT signed in - check routing logic\n if (!isSignedIn) {\n if (builtInAuth) {\n // Built-in auth mode: redirect to external auth pages\n\n // Check if user is trying to access auth pages\n const isSignInPage = currentPath === signIn || currentPath.startsWith(signIn + '?');\n const isSignUpPage = currentPath === signUp || currentPath.startsWith(signUp + '?');\n const isForgotPasswordPage =\n currentPath === forgotPassword || currentPath.startsWith(forgotPassword + '?');\n\n if (isSignInPage) {\n // Redirect to external sign-in\n const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;\n const authUrl = new URL('/auth/sign-in', baseUrl);\n authUrl.searchParams.set('redirect', redirectUrl);\n window.location.replace(authUrl.toString());\n return;\n }\n\n if (isSignUpPage) {\n // Redirect to external sign-up\n const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;\n const authUrl = new URL('/auth/sign-up', baseUrl);\n authUrl.searchParams.set('redirect', redirectUrl);\n window.location.replace(authUrl.toString());\n return;\n }\n\n if (isForgotPasswordPage) {\n // Redirect to external forgot-password (no redirect param for password reset)\n const authUrl = new URL('/auth/forgot-password', baseUrl);\n window.location.replace(authUrl.toString());\n return;\n }\n\n // Not an auth page - check if it's public or protected\n if (isPublicRoute) {\n // Public route - allow access\n return;\n } else {\n // Protected route - redirect to external sign-in\n const redirectUrl = new URL(afterSignInUrl, window.location.origin).href;\n const authUrl = new URL('/auth/sign-in', baseUrl);\n authUrl.searchParams.set('redirect', redirectUrl);\n window.location.replace(authUrl.toString());\n }\n } else {\n // Custom auth mode: redirect to local auth pages\n if (isPublicRoute) {\n // Public route (including local auth pages) - allow access\n return;\n } else {\n // Protected route - redirect to local sign-in\n window.location.href = signIn + '?redirect=' + afterSignInUrl;\n }\n }\n }\n }, [\n isLoaded,\n isSignedIn,\n currentPath,\n isPublicRoute,\n builtInAuth,\n baseUrl,\n signIn,\n signUp,\n forgotPassword,\n afterSignInUrl,\n ]);\n\n // Show loading state\n if (!isLoaded) {\n return <>{loadingFallback}</>;\n }\n\n // Render children - either public route or signed in\n return <>{children}</>;\n}\n"]}