UNPKG

@authava/react-client

Version:

React client library for seamless integration with Authava's white-label authentication service

195 lines 9.54 kB
import React, { createContext, useContext, useEffect, useState, useMemo, useRef } from 'react'; import { AuthavaClient, MockAuthavaClient, } from '@authava/client'; import { hasPermission, hasRole, isValidUser } from './utils/authUtils'; const AuthavaContext = createContext(undefined); export function AuthavaProvider({ config, children, sessionMode = 'protected', useMock = false, }) { const isProtected = sessionMode === 'protected'; const log = (...args) => { if (config.debug) { console.log(...args); } }; log('[AuthavaProvider] Initializing with config:', config); const [isLoading, setIsLoading] = useState(true); const [session, setSession] = useState(null); const hasLoggedOutRef = useRef(false); const [sessionState, setSessionState] = useState({ status: 'expired', user: null, }); // Flag to indicate that the initial session fetch (or a valid session update) has occurred. const hasInitialSessionResolvedRef = useRef(false); const lastValidSessionTimeRef = useRef(0); const hasFetchedSession = useRef(false); const lastRefetch = useRef(0); const client = useMemo(() => { const newClient = useMock ? new MockAuthavaClient(config) : new AuthavaClient(config); log('[AuthavaProvider] Created new AuthavaClient instance:', newClient); return newClient; }, [config]); useEffect(() => { // Only trigger logout automatically if we're in the protected mode if (!isProtected) { log('[AuthavaProvider] Session mode is public; skipping logout on session change.'); setSession(null); setIsLoading(false); return; } log('[AuthavaProvider] Setting up session change subscription.'); let isMounted = true; const handleSessionChange = (state) => { log('[AuthavaProvider] handleSessionChange called with state:', state); if (!isMounted) { log('[AuthavaProvider] Component unmounted. Ignoring session change.'); return; } setSessionState(state); log('[AuthavaProvider] Updated sessionState:', state); if (state.status === 'valid' && isValidUser(state.user)) { log('[AuthavaProvider] Received valid session. Setting session:', state.user); setSession({ user: state.user, redirect_url: '' }); lastValidSessionTimeRef.current = Date.now(); log('[AuthavaProvider] Recorded lastValidSessionTime:', lastValidSessionTimeRef.current); if (!hasInitialSessionResolvedRef.current) { hasInitialSessionResolvedRef.current = true; log('[AuthavaProvider] Marked initial session as resolved from onSessionChange callback.'); } } else if (state.status === 'expired' || state.status === 'error' || (state.status === 'valid' && !isValidUser(state.user))) { // Immediately trigger logout if initial session has been resolved. if (!hasInitialSessionResolvedRef.current) { log('[AuthavaProvider] Initial session not resolved; ignoring expired/error state.'); return; } if (!hasLoggedOutRef.current) { log('[AuthavaProvider] Triggering logout from session change handler.'); hasLoggedOutRef.current = true; setTimeout(() => { log('[AuthavaProvider] Executing deferred logout.'); client.logout(); }, 0); } log('[AuthavaProvider] Clearing session due to logout trigger.'); setSession(null); } }; const unsubscribe = client.onSessionChange(handleSessionChange); log('[AuthavaProvider] Subscribed to session changes.'); if (!hasFetchedSession.current) { log('[AuthavaProvider] Fetching initial session.'); hasFetchedSession.current = true; client.getSession().then((newSession) => { if (!isMounted) { log('[AuthavaProvider] Component unmounted before getSession completed.'); return; } if (!isProtected) { setSession(null); log('[AuthavaProvider] Session mode is public; skipping session update.'); return; } log('[AuthavaProvider] Initial getSession result:', newSession); setSession(newSession); if (newSession && isValidUser(newSession.user)) { lastValidSessionTimeRef.current = Date.now(); log('[AuthavaProvider] Recorded lastValidSessionTime from initial getSession:', lastValidSessionTimeRef.current); } else { // If getSession returns null (or invalid data), trigger logout. if (!hasLoggedOutRef.current) { log('[AuthavaProvider] getSession returned null or invalid session; triggering logout.'); hasLoggedOutRef.current = true; client.logout(); } } setIsLoading(false); hasInitialSessionResolvedRef.current = true; log('[AuthavaProvider] Set isLoading to false and marked initial session as resolved.'); }); } return () => { log('[AuthavaProvider] Cleaning up session change subscription.'); isMounted = false; unsubscribe(); }; }, [client, isProtected]); useEffect(() => { if (!isProtected) { log('[AuthavaProvider] Session mode is public; skipping visibility/focus event listeners.'); return; } log('[AuthavaProvider] Setting up visibility/focus event listeners.'); const handler = () => { log('[AuthavaProvider] Visibility/Focus event triggered.'); if (hasLoggedOutRef.current) { log('[AuthavaProvider] Logout already triggered. Skipping getSession on event.'); return; } if (!hasFetchedSession.current) { log('[AuthavaProvider] Session not fetched yet. Skipping getSession on event.'); return; } const timeSinceLastRefetch = Date.now() - lastRefetch.current; log('[AuthavaProvider] Time since last refetch (ms):', timeSinceLastRefetch); if (timeSinceLastRefetch < 10000) { log('[AuthavaProvider] Refetch throttled. Skipping getSession on event.'); return; } lastRefetch.current = Date.now(); log('[AuthavaProvider] Triggering getSession on visibility/focus event.'); client.getSession(); }; document.addEventListener('visibilitychange', handler); window.addEventListener('focus', handler); log('[AuthavaProvider] Event listeners added for visibilitychange and focus.'); return () => { log('[AuthavaProvider] Removing visibility/focus event listeners.'); document.removeEventListener('visibilitychange', handler); window.removeEventListener('focus', handler); }; }, [client, isProtected]); const logout = async () => { if (!isProtected) { log('[AuthavaProvider] Session mode is public; skipping logout.'); setSession(null); return; } log('[AuthavaProvider] logout called from provider.'); await client.logout(); }; const value = useMemo(() => ({ isLoading, session, sessionState, logout, login: (data) => client.login(data), register: (data) => client.register(data), forgotPassword: (data) => client.forgotPassword(data), resetPassword: (data) => client.resetPassword(data), verifyMfa: (data) => client.verifyMfa(data), sendMfaEmail: (data) => client.sendMfaEmail(data), getProfile: () => client.getProfile(), changeEmail: (data) => client.changeEmail(data), changePassword: (data) => client.changePassword(data), updateNotificationPreferences: (data) => client.updateNotificationPreferences(data), removeMfaMethod: (methodId) => client.removeMfaMethod(methodId), setupEmailMfa: (data) => client.setupEmailMfa(data), verifyEmailMfa: (data) => client.verifyEmailMfa(data), setupTotp: (data) => client.setupTotp(data), verifyTotp: (data) => client.verifyTotp(data), hasRole: (r) => (session?.user ? hasRole(session.user, r) : false), hasPermission: (p) => session?.user ? hasPermission(session.user, p) : false, }), [isLoading, session, sessionState, client]); log('[AuthavaProvider] Rendering provider with value:', value); return React.createElement(AuthavaContext.Provider, { value: value }, children); } export function useAuthava() { const context = useContext(AuthavaContext); if (context === undefined) { throw new Error('useAuthava must be used within an AuthavaProvider'); } return context; } //# sourceMappingURL=AuthavaContext.js.map