@acl-identity/react 0.1.6 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -73,7 +73,7 @@ function AuthProvider({
73
73
  if (!token) return;
74
74
  const expiresAtMs = _core.tokenExpiresAt.call(void 0, token);
75
75
  if (!expiresAtMs) return;
76
- const msUntilRenew = expiresAtMs - Date.now() - 3e4;
76
+ const msUntilRenew = expiresAtMs - Date.now() - 6e4;
77
77
  if (msUntilRenew <= 0) {
78
78
  handleSilentRenew();
79
79
  return;
@@ -82,6 +82,21 @@ function AuthProvider({
82
82
  });
83
83
  return () => clearTimeout(timer);
84
84
  }, [state.user, silentRedirectUri, handleSilentRenew, client]);
85
+ _react.useEffect.call(void 0, () => {
86
+ if (!silentRedirectUri) return;
87
+ const onVisible = () => {
88
+ if (document.visibilityState !== "visible") return;
89
+ client.auth.getAccessToken().then((token) => {
90
+ if (!token) return;
91
+ const expiresAtMs = _core.tokenExpiresAt.call(void 0, token);
92
+ if (expiresAtMs && Date.now() >= expiresAtMs - 6e4) {
93
+ handleSilentRenew();
94
+ }
95
+ });
96
+ };
97
+ document.addEventListener("visibilitychange", onVisible);
98
+ return () => document.removeEventListener("visibilitychange", onVisible);
99
+ }, [silentRedirectUri, client, handleSilentRenew]);
85
100
  const signIn = _react.useCallback.call(void 0,
86
101
  async (options) => {
87
102
  const { url, state: authState, codeVerifier } = await client.auth.buildAuthorizationUrl({
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/lanreyusuf/Developer/personal/acl-identity-sdk/packages/react/dist/index.js","../src/provider.tsx","../src/context.ts","../src/hooks/use-auth.ts","../src/components/sign-in-button.tsx","../src/components/callback-handler.tsx","../src/components/silent-callback-page.tsx","../src/index.ts"],"names":["useEffect"],"mappings":"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","file":"/Users/lanreyusuf/Developer/personal/acl-identity-sdk/packages/react/dist/index.js","sourcesContent":[null,"import {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport { tokenExpiresAt } from '@acl-identity/core';\nimport type { UserClaims } from '@acl-identity/types';\nimport { AuthContext, type SignInOptions, type SignOutOptions } from './context';\n\nexport interface AuthProviderProps {\n client: ACLBrowserClient;\n /** OAuth2 client_id — passed to buildAuthorizationUrl and silentRenew */\n clientId: string;\n /** Redirect URI registered with the auth server for the main auth flow */\n redirectUri: string;\n /** Organization slug for multi-tenant routing */\n orgSlug?: string;\n /** Space-separated OAuth2 scopes. Defaults to 'openid profile email' */\n scopes?: string;\n /**\n * Redirect URI for silent token renewal via hidden iframe.\n * Must be registered with the auth server. The page at this URI\n * must render <SilentCallbackPage />.\n */\n silentRedirectUri?: string;\n /**\n * Called after the OAuth2 redirect callback is processed.\n * Use this to navigate the user to their intended destination.\n */\n onRedirectCallback?: (appState?: unknown) => void;\n children: ReactNode;\n}\n\ninterface AuthState {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n}\n\nconst PKCE_KEY_PREFIX = 'acl:pkce:';\n\nexport function AuthProvider({\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n silentRedirectUri,\n onRedirectCallback,\n children,\n}: AuthProviderProps) {\n const [state, setState] = useState<AuthState>({\n user: null,\n isAuthenticated: false,\n isLoading: true,\n error: null,\n });\n\n const initialized = useRef(false);\n\n const syncAuthState = useCallback(async () => {\n const [user, isAuthenticated] = await Promise.all([\n client.auth.parseCurrentUser(),\n client.auth.isAuthenticated(),\n ]);\n setState({ user, isAuthenticated, isLoading: false, error: null });\n }, [client]);\n\n const handleSilentRenew = useCallback(async () => {\n if (!silentRedirectUri) {\n setState((s) => ({ ...s, isLoading: false }));\n return;\n }\n try {\n await client.auth.silentRenew(silentRedirectUri, clientId, { orgSlug, scope: scopes });\n await syncAuthState();\n } catch {\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n }\n }, [client, silentRedirectUri, clientId, orgSlug, scopes, syncAuthState]);\n\n // Initial session check — runs once on mount\n useEffect(() => {\n if (initialized.current) return;\n initialized.current = true;\n\n const url = new URL(window.location.href);\n const isCallbackUrl = url.searchParams.has('code') && url.searchParams.has('state');\n\n if (isCallbackUrl) {\n // Stay loading while CallbackHandler processes the code exchange\n return;\n }\n\n client.auth.isAuthenticated().then((isAuth) => {\n if (isAuth) {\n syncAuthState();\n } else {\n handleSilentRenew();\n }\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Schedule proactive silent renew before the current token expires\n useEffect(() => {\n if (!state.user || !silentRedirectUri) return;\n\n let timer: ReturnType<typeof setTimeout>;\n\n client.auth.getAccessToken().then((token) => {\n if (!token) return;\n const expiresAtMs = tokenExpiresAt(token);\n if (!expiresAtMs) return;\n\n const msUntilRenew = expiresAtMs - Date.now() - 30_000;\n if (msUntilRenew <= 0) {\n handleSilentRenew();\n return;\n }\n timer = setTimeout(handleSilentRenew, msUntilRenew);\n });\n\n return () => clearTimeout(timer);\n }, [state.user, silentRedirectUri, handleSilentRenew, client]);\n\n const signIn = useCallback(\n async (options?: SignInOptions) => {\n const { url, state: authState, codeVerifier } = await client.auth.buildAuthorizationUrl({\n clientId,\n redirectUri,\n orgSlug,\n scope: scopes,\n });\n sessionStorage.setItem(\n `${PKCE_KEY_PREFIX}${authState}`,\n JSON.stringify({ codeVerifier, appState: options?.appState ?? null })\n );\n window.location.href = url;\n },\n [client, clientId, redirectUri, orgSlug, scopes]\n );\n\n const signOut = useCallback(\n async (options?: SignOutOptions) => {\n await client.auth.logout({ revokeToken: options?.revokeToken });\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n if (options?.returnTo) {\n window.location.href = options.returnTo;\n }\n },\n [client]\n );\n\n const getAccessToken = useCallback(() => client.auth.getAccessToken(), [client]);\n\n const handleCallback = useCallback(\n async (callbackUrl?: string): Promise<{ appState?: unknown }> => {\n const url = new URL(callbackUrl ?? window.location.href);\n const error = url.searchParams.get('error');\n\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error;\n const err = new Error(desc);\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const code = url.searchParams.get('code');\n const authState = url.searchParams.get('state');\n\n if (!code || !authState) {\n const err = new Error('Missing code or state in callback URL');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const stored = sessionStorage.getItem(`${PKCE_KEY_PREFIX}${authState}`);\n if (!stored) {\n const err = new Error('Auth session expired or state mismatch. Please sign in again.');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const { codeVerifier, appState } = JSON.parse(stored) as {\n codeVerifier: string;\n appState?: unknown;\n };\n sessionStorage.removeItem(`${PKCE_KEY_PREFIX}${authState}`);\n\n await client.auth.exchangeCode(code, codeVerifier, redirectUri);\n await syncAuthState();\n\n onRedirectCallback?.(appState);\n return { appState };\n },\n [client, redirectUri, syncAuthState, onRedirectCallback]\n );\n\n return (\n <AuthContext.Provider\n value={{\n ...state,\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n signIn,\n signOut,\n getAccessToken,\n handleCallback,\n }}\n >\n {children}\n </AuthContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport type { UserClaims } from '@acl-identity/types';\n\nexport interface SignInOptions {\n appState?: unknown;\n}\n\nexport interface SignOutOptions {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport interface AuthContextValue {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n client: ACLBrowserClient;\n clientId: string;\n redirectUri: string;\n orgSlug?: string;\n scopes?: string;\n signIn(options?: SignInOptions): Promise<void>;\n signOut(options?: SignOutOptions): Promise<void>;\n getAccessToken(): Promise<string | null>;\n handleCallback(callbackUrl?: string): Promise<{ appState?: unknown }>;\n}\n\nexport const AuthContext = createContext<AuthContextValue | null>(null);\n","import { useContext } from 'react';\nimport { AuthContext, type AuthContextValue } from '../context';\n\nexport function useAuth(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\n 'useAuth must be used within an <AuthProvider>. ' +\n 'Wrap your app (or the relevant subtree) with <AuthProvider client={...} clientId={...} redirectUri={...} />.'\n );\n }\n return ctx;\n}\n","import { type ButtonHTMLAttributes } from 'react';\nimport { useAuth } from '../hooks/use-auth';\nimport type { SignOutOptions } from '../context';\n\nexport interface SignInButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n appState?: unknown;\n}\n\nexport function SignInButton({ children = 'Sign in', appState, onClick, ...props }: SignInButtonProps) {\n const { signIn } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n signIn({ appState });\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n\nexport interface SignOutButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport function SignOutButton({\n children = 'Sign out',\n returnTo,\n revokeToken,\n onClick,\n ...props\n}: SignOutButtonProps) {\n const { signOut } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n const options: SignOutOptions = {};\n if (returnTo) options.returnTo = returnTo;\n if (revokeToken !== undefined) options.revokeToken = revokeToken;\n signOut(options);\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n","import { useEffect, useRef } from 'react';\nimport { useAuth } from '../hooks/use-auth';\n\nexport interface CallbackHandlerProps {\n /** Called after tokens are stored and auth state is updated. Use this to navigate away. */\n onSuccess?: (appState?: unknown) => void;\n /** Called if the code exchange fails. */\n onError?: (error: Error) => void;\n /** Rendered while the code exchange is in progress. */\n loading?: React.ReactNode;\n}\n\n/**\n * Place this component on the route that matches your `redirectUri`.\n * It reads the `?code=` and `?state=` from the URL, exchanges them for\n * tokens, then calls `onSuccess`. The URL is cleaned up automatically.\n *\n * @example\n * // app/callback/page.tsx\n * 'use client';\n * import { CallbackHandler } from '@acl-identity/react';\n * import { useRouter } from 'next/navigation';\n *\n * export default function CallbackPage() {\n * const router = useRouter();\n * return (\n * <CallbackHandler\n * onSuccess={(appState) => router.replace(appState?.returnTo ?? '/dashboard')}\n * onError={() => router.replace('/login?error=callback_failed')}\n * />\n * );\n * }\n */\nexport function CallbackHandler({ onSuccess, onError, loading = null }: CallbackHandlerProps) {\n const { handleCallback } = useAuth();\n const called = useRef(false);\n\n useEffect(() => {\n if (called.current) return;\n called.current = true;\n\n handleCallback()\n .then(({ appState }) => {\n window.history.replaceState({}, document.title, window.location.pathname);\n onSuccess?.(appState);\n })\n .catch((err: Error) => {\n onError?.(err);\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return <>{loading}</>;\n}\n","import { useEffect } from 'react';\n\n/**\n * Render this component (and nothing else) at the route matching your\n * `silentRedirectUri`. It posts the current URL back to the parent window\n * so the hidden iframe used by silent renew can complete the token exchange.\n *\n * The route must be registered as an allowed redirect URI on your OAuth client.\n *\n * @example\n * // app/silent-callback/page.tsx (Next.js App Router)\n * 'use client';\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n *\n * @example\n * // pages/silent-callback.tsx (Next.js Pages Router)\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n */\nexport function SilentCallbackPage() {\n useEffect(() => {\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'acl:silent_callback', url: window.location.href },\n window.location.origin\n );\n }\n }, []);\n\n return null;\n}\n","export { AuthProvider } from './provider';\nexport type { AuthProviderProps } from './provider';\n\nexport { useAuth } from './hooks/use-auth';\n\nexport { SignInButton, SignOutButton } from './components/sign-in-button';\nexport type { SignInButtonProps, SignOutButtonProps } from './components/sign-in-button';\n\nexport { CallbackHandler } from './components/callback-handler';\nexport type { CallbackHandlerProps } from './components/callback-handler';\n\nexport { SilentCallbackPage } from './components/silent-callback-page';\n\nexport type { AuthContextValue, SignInOptions, SignOutOptions } from './context';\n\n// Re-export the browser client so consumers need only one install\nexport { ACLBrowserClient } from '@acl-identity/browser';\nexport type { ACLBrowserClientConfig } from '@acl-identity/browser';\n// Re-export useful types\nexport type { UserClaims, OrgContext } from '@acl-identity/types';\n"]}
1
+ {"version":3,"sources":["/Users/lanreyusuf/Developer/personal/acl-identity-sdk/packages/react/dist/index.js","../src/provider.tsx","../src/context.ts","../src/hooks/use-auth.ts","../src/components/sign-in-button.tsx","../src/components/callback-handler.tsx","../src/components/silent-callback-page.tsx","../src/index.ts"],"names":["useEffect"],"mappings":"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","file":"/Users/lanreyusuf/Developer/personal/acl-identity-sdk/packages/react/dist/index.js","sourcesContent":[null,"import {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport { tokenExpiresAt } from '@acl-identity/core';\nimport type { UserClaims } from '@acl-identity/types';\nimport { AuthContext, type SignInOptions, type SignOutOptions } from './context';\n\nexport interface AuthProviderProps {\n client: ACLBrowserClient;\n /** OAuth2 client_id — passed to buildAuthorizationUrl and silentRenew */\n clientId: string;\n /** Redirect URI registered with the auth server for the main auth flow */\n redirectUri: string;\n /** Organization slug for multi-tenant routing */\n orgSlug?: string;\n /** Space-separated OAuth2 scopes. Defaults to 'openid profile email' */\n scopes?: string;\n /**\n * Redirect URI for silent token renewal via hidden iframe.\n * Must be registered with the auth server. The page at this URI\n * must render <SilentCallbackPage />.\n */\n silentRedirectUri?: string;\n /**\n * Called after the OAuth2 redirect callback is processed.\n * Use this to navigate the user to their intended destination.\n */\n onRedirectCallback?: (appState?: unknown) => void;\n children: ReactNode;\n}\n\ninterface AuthState {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n}\n\nconst PKCE_KEY_PREFIX = 'acl:pkce:';\n\nexport function AuthProvider({\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n silentRedirectUri,\n onRedirectCallback,\n children,\n}: AuthProviderProps) {\n const [state, setState] = useState<AuthState>({\n user: null,\n isAuthenticated: false,\n isLoading: true,\n error: null,\n });\n\n const initialized = useRef(false);\n\n const syncAuthState = useCallback(async () => {\n const [user, isAuthenticated] = await Promise.all([\n client.auth.parseCurrentUser(),\n client.auth.isAuthenticated(),\n ]);\n setState({ user, isAuthenticated, isLoading: false, error: null });\n }, [client]);\n\n const handleSilentRenew = useCallback(async () => {\n if (!silentRedirectUri) {\n setState((s) => ({ ...s, isLoading: false }));\n return;\n }\n try {\n await client.auth.silentRenew(silentRedirectUri, clientId, { orgSlug, scope: scopes });\n await syncAuthState();\n } catch {\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n }\n }, [client, silentRedirectUri, clientId, orgSlug, scopes, syncAuthState]);\n\n // Initial session check — runs once on mount\n useEffect(() => {\n if (initialized.current) return;\n initialized.current = true;\n\n const url = new URL(window.location.href);\n const isCallbackUrl = url.searchParams.has('code') && url.searchParams.has('state');\n\n if (isCallbackUrl) {\n // Stay loading while CallbackHandler processes the code exchange\n return;\n }\n\n client.auth.isAuthenticated().then((isAuth) => {\n if (isAuth) {\n syncAuthState();\n } else {\n handleSilentRenew();\n }\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Schedule proactive silent renew 60 s before the current token expires.\n useEffect(() => {\n if (!state.user || !silentRedirectUri) return;\n\n let timer: ReturnType<typeof setTimeout>;\n\n client.auth.getAccessToken().then((token) => {\n if (!token) return;\n const expiresAtMs = tokenExpiresAt(token);\n if (!expiresAtMs) return;\n\n const msUntilRenew = expiresAtMs - Date.now() - 60_000;\n if (msUntilRenew <= 0) {\n handleSilentRenew();\n return;\n }\n timer = setTimeout(handleSilentRenew, msUntilRenew);\n });\n\n return () => clearTimeout(timer);\n }, [state.user, silentRedirectUri, handleSilentRenew, client]);\n\n // Trigger silent renew when the tab regains focus — JS timers are throttled\n // while backgrounded, so the token may already be expired when the user returns.\n useEffect(() => {\n if (!silentRedirectUri) return;\n\n const onVisible = () => {\n if (document.visibilityState !== 'visible') return;\n client.auth.getAccessToken().then((token) => {\n if (!token) return;\n const expiresAtMs = tokenExpiresAt(token);\n if (expiresAtMs && Date.now() >= expiresAtMs - 60_000) {\n handleSilentRenew();\n }\n });\n };\n\n document.addEventListener('visibilitychange', onVisible);\n return () => document.removeEventListener('visibilitychange', onVisible);\n }, [silentRedirectUri, client, handleSilentRenew]);\n\n const signIn = useCallback(\n async (options?: SignInOptions) => {\n const { url, state: authState, codeVerifier } = await client.auth.buildAuthorizationUrl({\n clientId,\n redirectUri,\n orgSlug,\n scope: scopes,\n });\n sessionStorage.setItem(\n `${PKCE_KEY_PREFIX}${authState}`,\n JSON.stringify({ codeVerifier, appState: options?.appState ?? null })\n );\n window.location.href = url;\n },\n [client, clientId, redirectUri, orgSlug, scopes]\n );\n\n const signOut = useCallback(\n async (options?: SignOutOptions) => {\n await client.auth.logout({ revokeToken: options?.revokeToken });\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n if (options?.returnTo) {\n window.location.href = options.returnTo;\n }\n },\n [client]\n );\n\n const getAccessToken = useCallback(() => client.auth.getAccessToken(), [client]);\n\n const handleCallback = useCallback(\n async (callbackUrl?: string): Promise<{ appState?: unknown }> => {\n const url = new URL(callbackUrl ?? window.location.href);\n const error = url.searchParams.get('error');\n\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error;\n const err = new Error(desc);\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const code = url.searchParams.get('code');\n const authState = url.searchParams.get('state');\n\n if (!code || !authState) {\n const err = new Error('Missing code or state in callback URL');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const stored = sessionStorage.getItem(`${PKCE_KEY_PREFIX}${authState}`);\n if (!stored) {\n const err = new Error('Auth session expired or state mismatch. Please sign in again.');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const { codeVerifier, appState } = JSON.parse(stored) as {\n codeVerifier: string;\n appState?: unknown;\n };\n sessionStorage.removeItem(`${PKCE_KEY_PREFIX}${authState}`);\n\n await client.auth.exchangeCode(code, codeVerifier, redirectUri);\n await syncAuthState();\n\n onRedirectCallback?.(appState);\n return { appState };\n },\n [client, redirectUri, syncAuthState, onRedirectCallback]\n );\n\n return (\n <AuthContext.Provider\n value={{\n ...state,\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n signIn,\n signOut,\n getAccessToken,\n handleCallback,\n }}\n >\n {children}\n </AuthContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport type { UserClaims } from '@acl-identity/types';\n\nexport interface SignInOptions {\n appState?: unknown;\n}\n\nexport interface SignOutOptions {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport interface AuthContextValue {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n client: ACLBrowserClient;\n clientId: string;\n redirectUri: string;\n orgSlug?: string;\n scopes?: string;\n signIn(options?: SignInOptions): Promise<void>;\n signOut(options?: SignOutOptions): Promise<void>;\n getAccessToken(): Promise<string | null>;\n handleCallback(callbackUrl?: string): Promise<{ appState?: unknown }>;\n}\n\nexport const AuthContext = createContext<AuthContextValue | null>(null);\n","import { useContext } from 'react';\nimport { AuthContext, type AuthContextValue } from '../context';\n\nexport function useAuth(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\n 'useAuth must be used within an <AuthProvider>. ' +\n 'Wrap your app (or the relevant subtree) with <AuthProvider client={...} clientId={...} redirectUri={...} />.'\n );\n }\n return ctx;\n}\n","import { type ButtonHTMLAttributes } from 'react';\nimport { useAuth } from '../hooks/use-auth';\nimport type { SignOutOptions } from '../context';\n\nexport interface SignInButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n appState?: unknown;\n}\n\nexport function SignInButton({ children = 'Sign in', appState, onClick, ...props }: SignInButtonProps) {\n const { signIn } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n signIn({ appState });\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n\nexport interface SignOutButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport function SignOutButton({\n children = 'Sign out',\n returnTo,\n revokeToken,\n onClick,\n ...props\n}: SignOutButtonProps) {\n const { signOut } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n const options: SignOutOptions = {};\n if (returnTo) options.returnTo = returnTo;\n if (revokeToken !== undefined) options.revokeToken = revokeToken;\n signOut(options);\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n","import { useEffect, useRef } from 'react';\nimport { useAuth } from '../hooks/use-auth';\n\nexport interface CallbackHandlerProps {\n /** Called after tokens are stored and auth state is updated. Use this to navigate away. */\n onSuccess?: (appState?: unknown) => void;\n /** Called if the code exchange fails. */\n onError?: (error: Error) => void;\n /** Rendered while the code exchange is in progress. */\n loading?: React.ReactNode;\n}\n\n/**\n * Place this component on the route that matches your `redirectUri`.\n * It reads the `?code=` and `?state=` from the URL, exchanges them for\n * tokens, then calls `onSuccess`. The URL is cleaned up automatically.\n *\n * @example\n * // app/callback/page.tsx\n * 'use client';\n * import { CallbackHandler } from '@acl-identity/react';\n * import { useRouter } from 'next/navigation';\n *\n * export default function CallbackPage() {\n * const router = useRouter();\n * return (\n * <CallbackHandler\n * onSuccess={(appState) => router.replace(appState?.returnTo ?? '/dashboard')}\n * onError={() => router.replace('/login?error=callback_failed')}\n * />\n * );\n * }\n */\nexport function CallbackHandler({ onSuccess, onError, loading = null }: CallbackHandlerProps) {\n const { handleCallback } = useAuth();\n const called = useRef(false);\n\n useEffect(() => {\n if (called.current) return;\n called.current = true;\n\n handleCallback()\n .then(({ appState }) => {\n window.history.replaceState({}, document.title, window.location.pathname);\n onSuccess?.(appState);\n })\n .catch((err: Error) => {\n onError?.(err);\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return <>{loading}</>;\n}\n","import { useEffect } from 'react';\n\n/**\n * Render this component (and nothing else) at the route matching your\n * `silentRedirectUri`. It posts the current URL back to the parent window\n * so the hidden iframe used by silent renew can complete the token exchange.\n *\n * The route must be registered as an allowed redirect URI on your OAuth client.\n *\n * @example\n * // app/silent-callback/page.tsx (Next.js App Router)\n * 'use client';\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n *\n * @example\n * // pages/silent-callback.tsx (Next.js Pages Router)\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n */\nexport function SilentCallbackPage() {\n useEffect(() => {\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'acl:silent_callback', url: window.location.href },\n window.location.origin\n );\n }\n }, []);\n\n return null;\n}\n","export { AuthProvider } from './provider';\nexport type { AuthProviderProps } from './provider';\n\nexport { useAuth } from './hooks/use-auth';\n\nexport { SignInButton, SignOutButton } from './components/sign-in-button';\nexport type { SignInButtonProps, SignOutButtonProps } from './components/sign-in-button';\n\nexport { CallbackHandler } from './components/callback-handler';\nexport type { CallbackHandlerProps } from './components/callback-handler';\n\nexport { SilentCallbackPage } from './components/silent-callback-page';\n\nexport type { AuthContextValue, SignInOptions, SignOutOptions } from './context';\n\n// Re-export the browser client so consumers need only one install\nexport { ACLBrowserClient } from '@acl-identity/browser';\nexport type { ACLBrowserClientConfig } from '@acl-identity/browser';\n// Re-export useful types\nexport type { UserClaims, OrgContext } from '@acl-identity/types';\n"]}
package/dist/index.mjs CHANGED
@@ -73,7 +73,7 @@ function AuthProvider({
73
73
  if (!token) return;
74
74
  const expiresAtMs = tokenExpiresAt(token);
75
75
  if (!expiresAtMs) return;
76
- const msUntilRenew = expiresAtMs - Date.now() - 3e4;
76
+ const msUntilRenew = expiresAtMs - Date.now() - 6e4;
77
77
  if (msUntilRenew <= 0) {
78
78
  handleSilentRenew();
79
79
  return;
@@ -82,6 +82,21 @@ function AuthProvider({
82
82
  });
83
83
  return () => clearTimeout(timer);
84
84
  }, [state.user, silentRedirectUri, handleSilentRenew, client]);
85
+ useEffect(() => {
86
+ if (!silentRedirectUri) return;
87
+ const onVisible = () => {
88
+ if (document.visibilityState !== "visible") return;
89
+ client.auth.getAccessToken().then((token) => {
90
+ if (!token) return;
91
+ const expiresAtMs = tokenExpiresAt(token);
92
+ if (expiresAtMs && Date.now() >= expiresAtMs - 6e4) {
93
+ handleSilentRenew();
94
+ }
95
+ });
96
+ };
97
+ document.addEventListener("visibilitychange", onVisible);
98
+ return () => document.removeEventListener("visibilitychange", onVisible);
99
+ }, [silentRedirectUri, client, handleSilentRenew]);
85
100
  const signIn = useCallback(
86
101
  async (options) => {
87
102
  const { url, state: authState, codeVerifier } = await client.auth.buildAuthorizationUrl({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.tsx","../src/context.ts","../src/hooks/use-auth.ts","../src/components/sign-in-button.tsx","../src/components/callback-handler.tsx","../src/components/silent-callback-page.tsx","../src/index.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport { tokenExpiresAt } from '@acl-identity/core';\nimport type { UserClaims } from '@acl-identity/types';\nimport { AuthContext, type SignInOptions, type SignOutOptions } from './context';\n\nexport interface AuthProviderProps {\n client: ACLBrowserClient;\n /** OAuth2 client_id — passed to buildAuthorizationUrl and silentRenew */\n clientId: string;\n /** Redirect URI registered with the auth server for the main auth flow */\n redirectUri: string;\n /** Organization slug for multi-tenant routing */\n orgSlug?: string;\n /** Space-separated OAuth2 scopes. Defaults to 'openid profile email' */\n scopes?: string;\n /**\n * Redirect URI for silent token renewal via hidden iframe.\n * Must be registered with the auth server. The page at this URI\n * must render <SilentCallbackPage />.\n */\n silentRedirectUri?: string;\n /**\n * Called after the OAuth2 redirect callback is processed.\n * Use this to navigate the user to their intended destination.\n */\n onRedirectCallback?: (appState?: unknown) => void;\n children: ReactNode;\n}\n\ninterface AuthState {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n}\n\nconst PKCE_KEY_PREFIX = 'acl:pkce:';\n\nexport function AuthProvider({\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n silentRedirectUri,\n onRedirectCallback,\n children,\n}: AuthProviderProps) {\n const [state, setState] = useState<AuthState>({\n user: null,\n isAuthenticated: false,\n isLoading: true,\n error: null,\n });\n\n const initialized = useRef(false);\n\n const syncAuthState = useCallback(async () => {\n const [user, isAuthenticated] = await Promise.all([\n client.auth.parseCurrentUser(),\n client.auth.isAuthenticated(),\n ]);\n setState({ user, isAuthenticated, isLoading: false, error: null });\n }, [client]);\n\n const handleSilentRenew = useCallback(async () => {\n if (!silentRedirectUri) {\n setState((s) => ({ ...s, isLoading: false }));\n return;\n }\n try {\n await client.auth.silentRenew(silentRedirectUri, clientId, { orgSlug, scope: scopes });\n await syncAuthState();\n } catch {\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n }\n }, [client, silentRedirectUri, clientId, orgSlug, scopes, syncAuthState]);\n\n // Initial session check — runs once on mount\n useEffect(() => {\n if (initialized.current) return;\n initialized.current = true;\n\n const url = new URL(window.location.href);\n const isCallbackUrl = url.searchParams.has('code') && url.searchParams.has('state');\n\n if (isCallbackUrl) {\n // Stay loading while CallbackHandler processes the code exchange\n return;\n }\n\n client.auth.isAuthenticated().then((isAuth) => {\n if (isAuth) {\n syncAuthState();\n } else {\n handleSilentRenew();\n }\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Schedule proactive silent renew before the current token expires\n useEffect(() => {\n if (!state.user || !silentRedirectUri) return;\n\n let timer: ReturnType<typeof setTimeout>;\n\n client.auth.getAccessToken().then((token) => {\n if (!token) return;\n const expiresAtMs = tokenExpiresAt(token);\n if (!expiresAtMs) return;\n\n const msUntilRenew = expiresAtMs - Date.now() - 30_000;\n if (msUntilRenew <= 0) {\n handleSilentRenew();\n return;\n }\n timer = setTimeout(handleSilentRenew, msUntilRenew);\n });\n\n return () => clearTimeout(timer);\n }, [state.user, silentRedirectUri, handleSilentRenew, client]);\n\n const signIn = useCallback(\n async (options?: SignInOptions) => {\n const { url, state: authState, codeVerifier } = await client.auth.buildAuthorizationUrl({\n clientId,\n redirectUri,\n orgSlug,\n scope: scopes,\n });\n sessionStorage.setItem(\n `${PKCE_KEY_PREFIX}${authState}`,\n JSON.stringify({ codeVerifier, appState: options?.appState ?? null })\n );\n window.location.href = url;\n },\n [client, clientId, redirectUri, orgSlug, scopes]\n );\n\n const signOut = useCallback(\n async (options?: SignOutOptions) => {\n await client.auth.logout({ revokeToken: options?.revokeToken });\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n if (options?.returnTo) {\n window.location.href = options.returnTo;\n }\n },\n [client]\n );\n\n const getAccessToken = useCallback(() => client.auth.getAccessToken(), [client]);\n\n const handleCallback = useCallback(\n async (callbackUrl?: string): Promise<{ appState?: unknown }> => {\n const url = new URL(callbackUrl ?? window.location.href);\n const error = url.searchParams.get('error');\n\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error;\n const err = new Error(desc);\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const code = url.searchParams.get('code');\n const authState = url.searchParams.get('state');\n\n if (!code || !authState) {\n const err = new Error('Missing code or state in callback URL');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const stored = sessionStorage.getItem(`${PKCE_KEY_PREFIX}${authState}`);\n if (!stored) {\n const err = new Error('Auth session expired or state mismatch. Please sign in again.');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const { codeVerifier, appState } = JSON.parse(stored) as {\n codeVerifier: string;\n appState?: unknown;\n };\n sessionStorage.removeItem(`${PKCE_KEY_PREFIX}${authState}`);\n\n await client.auth.exchangeCode(code, codeVerifier, redirectUri);\n await syncAuthState();\n\n onRedirectCallback?.(appState);\n return { appState };\n },\n [client, redirectUri, syncAuthState, onRedirectCallback]\n );\n\n return (\n <AuthContext.Provider\n value={{\n ...state,\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n signIn,\n signOut,\n getAccessToken,\n handleCallback,\n }}\n >\n {children}\n </AuthContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport type { UserClaims } from '@acl-identity/types';\n\nexport interface SignInOptions {\n appState?: unknown;\n}\n\nexport interface SignOutOptions {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport interface AuthContextValue {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n client: ACLBrowserClient;\n clientId: string;\n redirectUri: string;\n orgSlug?: string;\n scopes?: string;\n signIn(options?: SignInOptions): Promise<void>;\n signOut(options?: SignOutOptions): Promise<void>;\n getAccessToken(): Promise<string | null>;\n handleCallback(callbackUrl?: string): Promise<{ appState?: unknown }>;\n}\n\nexport const AuthContext = createContext<AuthContextValue | null>(null);\n","import { useContext } from 'react';\nimport { AuthContext, type AuthContextValue } from '../context';\n\nexport function useAuth(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\n 'useAuth must be used within an <AuthProvider>. ' +\n 'Wrap your app (or the relevant subtree) with <AuthProvider client={...} clientId={...} redirectUri={...} />.'\n );\n }\n return ctx;\n}\n","import { type ButtonHTMLAttributes } from 'react';\nimport { useAuth } from '../hooks/use-auth';\nimport type { SignOutOptions } from '../context';\n\nexport interface SignInButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n appState?: unknown;\n}\n\nexport function SignInButton({ children = 'Sign in', appState, onClick, ...props }: SignInButtonProps) {\n const { signIn } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n signIn({ appState });\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n\nexport interface SignOutButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport function SignOutButton({\n children = 'Sign out',\n returnTo,\n revokeToken,\n onClick,\n ...props\n}: SignOutButtonProps) {\n const { signOut } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n const options: SignOutOptions = {};\n if (returnTo) options.returnTo = returnTo;\n if (revokeToken !== undefined) options.revokeToken = revokeToken;\n signOut(options);\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n","import { useEffect, useRef } from 'react';\nimport { useAuth } from '../hooks/use-auth';\n\nexport interface CallbackHandlerProps {\n /** Called after tokens are stored and auth state is updated. Use this to navigate away. */\n onSuccess?: (appState?: unknown) => void;\n /** Called if the code exchange fails. */\n onError?: (error: Error) => void;\n /** Rendered while the code exchange is in progress. */\n loading?: React.ReactNode;\n}\n\n/**\n * Place this component on the route that matches your `redirectUri`.\n * It reads the `?code=` and `?state=` from the URL, exchanges them for\n * tokens, then calls `onSuccess`. The URL is cleaned up automatically.\n *\n * @example\n * // app/callback/page.tsx\n * 'use client';\n * import { CallbackHandler } from '@acl-identity/react';\n * import { useRouter } from 'next/navigation';\n *\n * export default function CallbackPage() {\n * const router = useRouter();\n * return (\n * <CallbackHandler\n * onSuccess={(appState) => router.replace(appState?.returnTo ?? '/dashboard')}\n * onError={() => router.replace('/login?error=callback_failed')}\n * />\n * );\n * }\n */\nexport function CallbackHandler({ onSuccess, onError, loading = null }: CallbackHandlerProps) {\n const { handleCallback } = useAuth();\n const called = useRef(false);\n\n useEffect(() => {\n if (called.current) return;\n called.current = true;\n\n handleCallback()\n .then(({ appState }) => {\n window.history.replaceState({}, document.title, window.location.pathname);\n onSuccess?.(appState);\n })\n .catch((err: Error) => {\n onError?.(err);\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return <>{loading}</>;\n}\n","import { useEffect } from 'react';\n\n/**\n * Render this component (and nothing else) at the route matching your\n * `silentRedirectUri`. It posts the current URL back to the parent window\n * so the hidden iframe used by silent renew can complete the token exchange.\n *\n * The route must be registered as an allowed redirect URI on your OAuth client.\n *\n * @example\n * // app/silent-callback/page.tsx (Next.js App Router)\n * 'use client';\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n *\n * @example\n * // pages/silent-callback.tsx (Next.js Pages Router)\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n */\nexport function SilentCallbackPage() {\n useEffect(() => {\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'acl:silent_callback', url: window.location.href },\n window.location.origin\n );\n }\n }, []);\n\n return null;\n}\n","export { AuthProvider } from './provider';\nexport type { AuthProviderProps } from './provider';\n\nexport { useAuth } from './hooks/use-auth';\n\nexport { SignInButton, SignOutButton } from './components/sign-in-button';\nexport type { SignInButtonProps, SignOutButtonProps } from './components/sign-in-button';\n\nexport { CallbackHandler } from './components/callback-handler';\nexport type { CallbackHandlerProps } from './components/callback-handler';\n\nexport { SilentCallbackPage } from './components/silent-callback-page';\n\nexport type { AuthContextValue, SignInOptions, SignOutOptions } from './context';\n\n// Re-export the browser client so consumers need only one install\nexport { ACLBrowserClient } from '@acl-identity/browser';\nexport type { ACLBrowserClientConfig } from '@acl-identity/browser';\n// Re-export useful types\nexport type { UserClaims, OrgContext } from '@acl-identity/types';\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP,SAAS,sBAAsB;;;ACR/B,SAAS,qBAAqB;AA6BvB,IAAM,cAAc,cAAuC,IAAI;;;AD8KlE;AAhKJ,IAAM,kBAAkB;AAEjB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAoB;AAAA,IAC5C,MAAM;AAAA,IACN,iBAAiB;AAAA,IACjB,WAAW;AAAA,IACX,OAAO;AAAA,EACT,CAAC;AAED,QAAM,cAAc,OAAO,KAAK;AAEhC,QAAM,gBAAgB,YAAY,YAAY;AAC5C,UAAM,CAAC,MAAM,eAAe,IAAI,MAAM,QAAQ,IAAI;AAAA,MAChD,OAAO,KAAK,iBAAiB;AAAA,MAC7B,OAAO,KAAK,gBAAgB;AAAA,IAC9B,CAAC;AACD,aAAS,EAAE,MAAM,iBAAiB,WAAW,OAAO,OAAO,KAAK,CAAC;AAAA,EACnE,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,oBAAoB,YAAY,YAAY;AAChD,QAAI,CAAC,mBAAmB;AACtB,eAAS,CAAC,OAAO,EAAE,GAAG,GAAG,WAAW,MAAM,EAAE;AAC5C;AAAA,IACF;AACA,QAAI;AACF,YAAM,OAAO,KAAK,YAAY,mBAAmB,UAAU,EAAE,SAAS,OAAO,OAAO,CAAC;AACrF,YAAM,cAAc;AAAA,IACtB,QAAQ;AACN,eAAS,EAAE,MAAM,MAAM,iBAAiB,OAAO,WAAW,OAAO,OAAO,KAAK,CAAC;AAAA,IAChF;AAAA,EACF,GAAG,CAAC,QAAQ,mBAAmB,UAAU,SAAS,QAAQ,aAAa,CAAC;AAGxE,YAAU,MAAM;AACd,QAAI,YAAY,QAAS;AACzB,gBAAY,UAAU;AAEtB,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,UAAM,gBAAgB,IAAI,aAAa,IAAI,MAAM,KAAK,IAAI,aAAa,IAAI,OAAO;AAElF,QAAI,eAAe;AAEjB;AAAA,IACF;AAEA,WAAO,KAAK,gBAAgB,EAAE,KAAK,CAAC,WAAW;AAC7C,UAAI,QAAQ;AACV,sBAAc;AAAA,MAChB,OAAO;AACL,0BAAkB;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAGL,YAAU,MAAM;AACd,QAAI,CAAC,MAAM,QAAQ,CAAC,kBAAmB;AAEvC,QAAI;AAEJ,WAAO,KAAK,eAAe,EAAE,KAAK,CAAC,UAAU;AAC3C,UAAI,CAAC,MAAO;AACZ,YAAM,cAAc,eAAe,KAAK;AACxC,UAAI,CAAC,YAAa;AAElB,YAAM,eAAe,cAAc,KAAK,IAAI,IAAI;AAChD,UAAI,gBAAgB,GAAG;AACrB,0BAAkB;AAClB;AAAA,MACF;AACA,cAAQ,WAAW,mBAAmB,YAAY;AAAA,IACpD,CAAC;AAED,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,MAAM,MAAM,mBAAmB,mBAAmB,MAAM,CAAC;AAE7D,QAAM,SAAS;AAAA,IACb,OAAO,YAA4B;AACjC,YAAM,EAAE,KAAK,OAAO,WAAW,aAAa,IAAI,MAAM,OAAO,KAAK,sBAAsB;AAAA,QACtF;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO;AAAA,MACT,CAAC;AACD,qBAAe;AAAA,QACb,GAAG,eAAe,GAAG,SAAS;AAAA,QAC9B,KAAK,UAAU,EAAE,cAAc,UAAU,SAAS,YAAY,KAAK,CAAC;AAAA,MACtE;AACA,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,IACA,CAAC,QAAQ,UAAU,aAAa,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,UAAU;AAAA,IACd,OAAO,YAA6B;AAClC,YAAM,OAAO,KAAK,OAAO,EAAE,aAAa,SAAS,YAAY,CAAC;AAC9D,eAAS,EAAE,MAAM,MAAM,iBAAiB,OAAO,WAAW,OAAO,OAAO,KAAK,CAAC;AAC9E,UAAI,SAAS,UAAU;AACrB,eAAO,SAAS,OAAO,QAAQ;AAAA,MACjC;AAAA,IACF;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,iBAAiB,YAAY,MAAM,OAAO,KAAK,eAAe,GAAG,CAAC,MAAM,CAAC;AAE/E,QAAM,iBAAiB;AAAA,IACrB,OAAO,gBAA0D;AAC/D,YAAM,MAAM,IAAI,IAAI,eAAe,OAAO,SAAS,IAAI;AACvD,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAE1C,UAAI,OAAO;AACT,cAAM,OAAO,IAAI,aAAa,IAAI,mBAAmB,KAAK;AAC1D,cAAM,MAAM,IAAI,MAAM,IAAI;AAC1B,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,KAAK,WAAW,MAAM,EAAE;AACxD,cAAM;AAAA,MACR;AAEA,YAAM,OAAO,IAAI,aAAa,IAAI,MAAM;AACxC,YAAM,YAAY,IAAI,aAAa,IAAI,OAAO;AAE9C,UAAI,CAAC,QAAQ,CAAC,WAAW;AACvB,cAAM,MAAM,IAAI,MAAM,uCAAuC;AAC7D,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,KAAK,WAAW,MAAM,EAAE;AACxD,cAAM;AAAA,MACR;AAEA,YAAM,SAAS,eAAe,QAAQ,GAAG,eAAe,GAAG,SAAS,EAAE;AACtE,UAAI,CAAC,QAAQ;AACX,cAAM,MAAM,IAAI,MAAM,+DAA+D;AACrF,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,KAAK,WAAW,MAAM,EAAE;AACxD,cAAM;AAAA,MACR;AAEA,YAAM,EAAE,cAAc,SAAS,IAAI,KAAK,MAAM,MAAM;AAIpD,qBAAe,WAAW,GAAG,eAAe,GAAG,SAAS,EAAE;AAE1D,YAAM,OAAO,KAAK,aAAa,MAAM,cAAc,WAAW;AAC9D,YAAM,cAAc;AAEpB,2BAAqB,QAAQ;AAC7B,aAAO,EAAE,SAAS;AAAA,IACpB;AAAA,IACA,CAAC,QAAQ,aAAa,eAAe,kBAAkB;AAAA,EACzD;AAEA,SACE;AAAA,IAAC,YAAY;AAAA,IAAZ;AAAA,MACC,OAAO;AAAA,QACL,GAAG;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;;;AE5NA,SAAS,kBAAkB;AAGpB,SAAS,UAA4B;AAC1C,QAAM,MAAM,WAAW,WAAW;AAClC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;;;ACOI,gBAAAA,YAAA;AAXG,SAAS,aAAa,EAAE,WAAW,WAAW,UAAU,SAAS,GAAG,MAAM,GAAsB;AACrG,QAAM,EAAE,OAAO,IAAI,QAAQ;AAE3B,QAAM,cAAc,CAAC,MAA2C;AAC9D,cAAU,CAAC;AACX,QAAI,CAAC,EAAE,kBAAkB;AACvB,aAAO,EAAE,SAAS,CAAC;AAAA,IACrB;AAAA,EACF;AAEA,SACE,gBAAAA,KAAC,YAAO,MAAK,UAAS,SAAS,aAAc,GAAG,OAC7C,UACH;AAEJ;AAOO,SAAS,cAAc;AAAA,EAC5B,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuB;AACrB,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAE5B,QAAM,cAAc,CAAC,MAA2C;AAC9D,cAAU,CAAC;AACX,QAAI,CAAC,EAAE,kBAAkB;AACvB,YAAM,UAA0B,CAAC;AACjC,UAAI,SAAU,SAAQ,WAAW;AACjC,UAAI,gBAAgB,OAAW,SAAQ,cAAc;AACrD,cAAQ,OAAO;AAAA,IACjB;AAAA,EACF;AAEA,SACE,gBAAAA,KAAC,YAAO,MAAK,UAAS,SAAS,aAAc,GAAG,OAC7C,UACH;AAEJ;;;ACtDA,SAAS,aAAAC,YAAW,UAAAC,eAAc;AAmDzB,0BAAAC,YAAA;AAlBF,SAAS,gBAAgB,EAAE,WAAW,SAAS,UAAU,KAAK,GAAyB;AAC5F,QAAM,EAAE,eAAe,IAAI,QAAQ;AACnC,QAAM,SAASC,QAAO,KAAK;AAE3B,EAAAC,WAAU,MAAM;AACd,QAAI,OAAO,QAAS;AACpB,WAAO,UAAU;AAEjB,mBAAe,EACZ,KAAK,CAAC,EAAE,SAAS,MAAM;AACtB,aAAO,QAAQ,aAAa,CAAC,GAAG,SAAS,OAAO,OAAO,SAAS,QAAQ;AACxE,kBAAY,QAAQ;AAAA,IACtB,CAAC,EACA,MAAM,CAAC,QAAe;AACrB,gBAAU,GAAG;AAAA,IACf,CAAC;AAAA,EACL,GAAG,CAAC,CAAC;AAEL,SAAO,gBAAAF,KAAA,YAAG,mBAAQ;AACpB;;;ACpDA,SAAS,aAAAG,kBAAiB;AAoBnB,SAAS,qBAAqB;AACnC,EAAAA,WAAU,MAAM;AACd,QAAI,OAAO,WAAW,QAAQ;AAC5B,aAAO,OAAO;AAAA,QACZ,EAAE,MAAM,uBAAuB,KAAK,OAAO,SAAS,KAAK;AAAA,QACzD,OAAO,SAAS;AAAA,MAClB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;;;ACfA,SAAS,wBAAwB;","names":["jsx","useEffect","useRef","jsx","useRef","useEffect","useEffect"]}
1
+ {"version":3,"sources":["../src/provider.tsx","../src/context.ts","../src/hooks/use-auth.ts","../src/components/sign-in-button.tsx","../src/components/callback-handler.tsx","../src/components/silent-callback-page.tsx","../src/index.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport { tokenExpiresAt } from '@acl-identity/core';\nimport type { UserClaims } from '@acl-identity/types';\nimport { AuthContext, type SignInOptions, type SignOutOptions } from './context';\n\nexport interface AuthProviderProps {\n client: ACLBrowserClient;\n /** OAuth2 client_id — passed to buildAuthorizationUrl and silentRenew */\n clientId: string;\n /** Redirect URI registered with the auth server for the main auth flow */\n redirectUri: string;\n /** Organization slug for multi-tenant routing */\n orgSlug?: string;\n /** Space-separated OAuth2 scopes. Defaults to 'openid profile email' */\n scopes?: string;\n /**\n * Redirect URI for silent token renewal via hidden iframe.\n * Must be registered with the auth server. The page at this URI\n * must render <SilentCallbackPage />.\n */\n silentRedirectUri?: string;\n /**\n * Called after the OAuth2 redirect callback is processed.\n * Use this to navigate the user to their intended destination.\n */\n onRedirectCallback?: (appState?: unknown) => void;\n children: ReactNode;\n}\n\ninterface AuthState {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n}\n\nconst PKCE_KEY_PREFIX = 'acl:pkce:';\n\nexport function AuthProvider({\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n silentRedirectUri,\n onRedirectCallback,\n children,\n}: AuthProviderProps) {\n const [state, setState] = useState<AuthState>({\n user: null,\n isAuthenticated: false,\n isLoading: true,\n error: null,\n });\n\n const initialized = useRef(false);\n\n const syncAuthState = useCallback(async () => {\n const [user, isAuthenticated] = await Promise.all([\n client.auth.parseCurrentUser(),\n client.auth.isAuthenticated(),\n ]);\n setState({ user, isAuthenticated, isLoading: false, error: null });\n }, [client]);\n\n const handleSilentRenew = useCallback(async () => {\n if (!silentRedirectUri) {\n setState((s) => ({ ...s, isLoading: false }));\n return;\n }\n try {\n await client.auth.silentRenew(silentRedirectUri, clientId, { orgSlug, scope: scopes });\n await syncAuthState();\n } catch {\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n }\n }, [client, silentRedirectUri, clientId, orgSlug, scopes, syncAuthState]);\n\n // Initial session check — runs once on mount\n useEffect(() => {\n if (initialized.current) return;\n initialized.current = true;\n\n const url = new URL(window.location.href);\n const isCallbackUrl = url.searchParams.has('code') && url.searchParams.has('state');\n\n if (isCallbackUrl) {\n // Stay loading while CallbackHandler processes the code exchange\n return;\n }\n\n client.auth.isAuthenticated().then((isAuth) => {\n if (isAuth) {\n syncAuthState();\n } else {\n handleSilentRenew();\n }\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Schedule proactive silent renew 60 s before the current token expires.\n useEffect(() => {\n if (!state.user || !silentRedirectUri) return;\n\n let timer: ReturnType<typeof setTimeout>;\n\n client.auth.getAccessToken().then((token) => {\n if (!token) return;\n const expiresAtMs = tokenExpiresAt(token);\n if (!expiresAtMs) return;\n\n const msUntilRenew = expiresAtMs - Date.now() - 60_000;\n if (msUntilRenew <= 0) {\n handleSilentRenew();\n return;\n }\n timer = setTimeout(handleSilentRenew, msUntilRenew);\n });\n\n return () => clearTimeout(timer);\n }, [state.user, silentRedirectUri, handleSilentRenew, client]);\n\n // Trigger silent renew when the tab regains focus — JS timers are throttled\n // while backgrounded, so the token may already be expired when the user returns.\n useEffect(() => {\n if (!silentRedirectUri) return;\n\n const onVisible = () => {\n if (document.visibilityState !== 'visible') return;\n client.auth.getAccessToken().then((token) => {\n if (!token) return;\n const expiresAtMs = tokenExpiresAt(token);\n if (expiresAtMs && Date.now() >= expiresAtMs - 60_000) {\n handleSilentRenew();\n }\n });\n };\n\n document.addEventListener('visibilitychange', onVisible);\n return () => document.removeEventListener('visibilitychange', onVisible);\n }, [silentRedirectUri, client, handleSilentRenew]);\n\n const signIn = useCallback(\n async (options?: SignInOptions) => {\n const { url, state: authState, codeVerifier } = await client.auth.buildAuthorizationUrl({\n clientId,\n redirectUri,\n orgSlug,\n scope: scopes,\n });\n sessionStorage.setItem(\n `${PKCE_KEY_PREFIX}${authState}`,\n JSON.stringify({ codeVerifier, appState: options?.appState ?? null })\n );\n window.location.href = url;\n },\n [client, clientId, redirectUri, orgSlug, scopes]\n );\n\n const signOut = useCallback(\n async (options?: SignOutOptions) => {\n await client.auth.logout({ revokeToken: options?.revokeToken });\n setState({ user: null, isAuthenticated: false, isLoading: false, error: null });\n if (options?.returnTo) {\n window.location.href = options.returnTo;\n }\n },\n [client]\n );\n\n const getAccessToken = useCallback(() => client.auth.getAccessToken(), [client]);\n\n const handleCallback = useCallback(\n async (callbackUrl?: string): Promise<{ appState?: unknown }> => {\n const url = new URL(callbackUrl ?? window.location.href);\n const error = url.searchParams.get('error');\n\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error;\n const err = new Error(desc);\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const code = url.searchParams.get('code');\n const authState = url.searchParams.get('state');\n\n if (!code || !authState) {\n const err = new Error('Missing code or state in callback URL');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const stored = sessionStorage.getItem(`${PKCE_KEY_PREFIX}${authState}`);\n if (!stored) {\n const err = new Error('Auth session expired or state mismatch. Please sign in again.');\n setState((s) => ({ ...s, error: err, isLoading: false }));\n throw err;\n }\n\n const { codeVerifier, appState } = JSON.parse(stored) as {\n codeVerifier: string;\n appState?: unknown;\n };\n sessionStorage.removeItem(`${PKCE_KEY_PREFIX}${authState}`);\n\n await client.auth.exchangeCode(code, codeVerifier, redirectUri);\n await syncAuthState();\n\n onRedirectCallback?.(appState);\n return { appState };\n },\n [client, redirectUri, syncAuthState, onRedirectCallback]\n );\n\n return (\n <AuthContext.Provider\n value={{\n ...state,\n client,\n clientId,\n redirectUri,\n orgSlug,\n scopes,\n signIn,\n signOut,\n getAccessToken,\n handleCallback,\n }}\n >\n {children}\n </AuthContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { ACLBrowserClient } from '@acl-identity/browser';\nimport type { UserClaims } from '@acl-identity/types';\n\nexport interface SignInOptions {\n appState?: unknown;\n}\n\nexport interface SignOutOptions {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport interface AuthContextValue {\n user: UserClaims | null;\n isAuthenticated: boolean;\n isLoading: boolean;\n error: Error | null;\n client: ACLBrowserClient;\n clientId: string;\n redirectUri: string;\n orgSlug?: string;\n scopes?: string;\n signIn(options?: SignInOptions): Promise<void>;\n signOut(options?: SignOutOptions): Promise<void>;\n getAccessToken(): Promise<string | null>;\n handleCallback(callbackUrl?: string): Promise<{ appState?: unknown }>;\n}\n\nexport const AuthContext = createContext<AuthContextValue | null>(null);\n","import { useContext } from 'react';\nimport { AuthContext, type AuthContextValue } from '../context';\n\nexport function useAuth(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\n 'useAuth must be used within an <AuthProvider>. ' +\n 'Wrap your app (or the relevant subtree) with <AuthProvider client={...} clientId={...} redirectUri={...} />.'\n );\n }\n return ctx;\n}\n","import { type ButtonHTMLAttributes } from 'react';\nimport { useAuth } from '../hooks/use-auth';\nimport type { SignOutOptions } from '../context';\n\nexport interface SignInButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n appState?: unknown;\n}\n\nexport function SignInButton({ children = 'Sign in', appState, onClick, ...props }: SignInButtonProps) {\n const { signIn } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n signIn({ appState });\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n\nexport interface SignOutButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n returnTo?: string;\n revokeToken?: boolean;\n}\n\nexport function SignOutButton({\n children = 'Sign out',\n returnTo,\n revokeToken,\n onClick,\n ...props\n}: SignOutButtonProps) {\n const { signOut } = useAuth();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n const options: SignOutOptions = {};\n if (returnTo) options.returnTo = returnTo;\n if (revokeToken !== undefined) options.revokeToken = revokeToken;\n signOut(options);\n }\n };\n\n return (\n <button type=\"button\" onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n","import { useEffect, useRef } from 'react';\nimport { useAuth } from '../hooks/use-auth';\n\nexport interface CallbackHandlerProps {\n /** Called after tokens are stored and auth state is updated. Use this to navigate away. */\n onSuccess?: (appState?: unknown) => void;\n /** Called if the code exchange fails. */\n onError?: (error: Error) => void;\n /** Rendered while the code exchange is in progress. */\n loading?: React.ReactNode;\n}\n\n/**\n * Place this component on the route that matches your `redirectUri`.\n * It reads the `?code=` and `?state=` from the URL, exchanges them for\n * tokens, then calls `onSuccess`. The URL is cleaned up automatically.\n *\n * @example\n * // app/callback/page.tsx\n * 'use client';\n * import { CallbackHandler } from '@acl-identity/react';\n * import { useRouter } from 'next/navigation';\n *\n * export default function CallbackPage() {\n * const router = useRouter();\n * return (\n * <CallbackHandler\n * onSuccess={(appState) => router.replace(appState?.returnTo ?? '/dashboard')}\n * onError={() => router.replace('/login?error=callback_failed')}\n * />\n * );\n * }\n */\nexport function CallbackHandler({ onSuccess, onError, loading = null }: CallbackHandlerProps) {\n const { handleCallback } = useAuth();\n const called = useRef(false);\n\n useEffect(() => {\n if (called.current) return;\n called.current = true;\n\n handleCallback()\n .then(({ appState }) => {\n window.history.replaceState({}, document.title, window.location.pathname);\n onSuccess?.(appState);\n })\n .catch((err: Error) => {\n onError?.(err);\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return <>{loading}</>;\n}\n","import { useEffect } from 'react';\n\n/**\n * Render this component (and nothing else) at the route matching your\n * `silentRedirectUri`. It posts the current URL back to the parent window\n * so the hidden iframe used by silent renew can complete the token exchange.\n *\n * The route must be registered as an allowed redirect URI on your OAuth client.\n *\n * @example\n * // app/silent-callback/page.tsx (Next.js App Router)\n * 'use client';\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n *\n * @example\n * // pages/silent-callback.tsx (Next.js Pages Router)\n * import { SilentCallbackPage } from '@acl-identity/react';\n * export default SilentCallbackPage;\n */\nexport function SilentCallbackPage() {\n useEffect(() => {\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'acl:silent_callback', url: window.location.href },\n window.location.origin\n );\n }\n }, []);\n\n return null;\n}\n","export { AuthProvider } from './provider';\nexport type { AuthProviderProps } from './provider';\n\nexport { useAuth } from './hooks/use-auth';\n\nexport { SignInButton, SignOutButton } from './components/sign-in-button';\nexport type { SignInButtonProps, SignOutButtonProps } from './components/sign-in-button';\n\nexport { CallbackHandler } from './components/callback-handler';\nexport type { CallbackHandlerProps } from './components/callback-handler';\n\nexport { SilentCallbackPage } from './components/silent-callback-page';\n\nexport type { AuthContextValue, SignInOptions, SignOutOptions } from './context';\n\n// Re-export the browser client so consumers need only one install\nexport { ACLBrowserClient } from '@acl-identity/browser';\nexport type { ACLBrowserClientConfig } from '@acl-identity/browser';\n// Re-export useful types\nexport type { UserClaims, OrgContext } from '@acl-identity/types';\n"],"mappings":"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","names":["jsx","useEffect","useRef","jsx","useRef","useEffect","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acl-identity/react",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "React hooks and provider for ACL Identity — Phase 2",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -24,9 +24,9 @@
24
24
  "react-dom": ">=18.0.0"
25
25
  },
26
26
  "dependencies": {
27
- "@acl-identity/browser": "0.1.6",
28
- "@acl-identity/core": "0.1.6",
29
- "@acl-identity/types": "0.1.6"
27
+ "@acl-identity/types": "0.2.0",
28
+ "@acl-identity/core": "0.2.0",
29
+ "@acl-identity/browser": "0.2.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/react": "^18.3.3",