UNPKG

@daveyplate/better-auth-persistent

Version:

Persistent session management for Better Auth

235 lines (224 loc) 5.99 kB
import { useStore } from '@nanostores/react'; import { useMemo, useEffect, useSyncExternalStore } from 'react'; import { persistentAtom } from '@nanostores/persistent'; import SuperJSON from 'superjson'; import { atom } from 'nanostores'; import { multiSessionClient } from 'better-auth/client/plugins'; import { createAuthClient } from 'better-auth/react'; // src/hooks/use-list-device-sessions.ts // src/empty-result.ts var emptyResult = { data: null, isPending: true, isRefetching: false, error: null, refetch: async () => { } }; var $authClient = atom(null); // src/stores/device-sessions.ts var $deviceSessions = persistentAtom( "device-sessions", { ...emptyResult, isPending: false }, { encode: SuperJSON.stringify, decode: (value) => { const parsed = SuperJSON.parse(value); parsed.refetch = refetchDeviceSessions; return parsed; } } ); var refetchDeviceSessions = async () => { const authClient2 = $authClient.get(); if (!authClient2) { console.warn("$authClient not found"); return; } $deviceSessions.set({ ...$deviceSessions.get(), isRefetching: true }); const result = await authClient2.multiSession.listDeviceSessions(); if (result.error) { $deviceSessions.set({ ...$deviceSessions.get(), isRefetching: false, isPending: false, error: result.error }); } else { $deviceSessions.set({ ...$deviceSessions.get(), data: result.data, isRefetching: false, isPending: false, error: null }); } }; function subscribe() { return () => { }; } function useIsHydrated() { return useSyncExternalStore( subscribe, () => true, () => false ); } // src/hooks/use-list-device-sessions.ts function useListDeviceSessions(_) { const isHydrated = useIsHydrated(); const store = useStore($deviceSessions); const result = useMemo(() => { return isHydrated ? { ...store, data: store.data } : emptyResult; }, [isHydrated, store]); return result; } var $persistentSession = persistentAtom( "session", { ...emptyResult, isPending: false }, { encode: SuperJSON.stringify, decode: (value) => { const parsed = SuperJSON.parse(value); parsed.refetch = refetchSession; return parsed; } } ); var refetchSession = async () => { const authClient2 = $authClient.get(); if (!authClient2) { console.warn("$authClient not found"); return; } $persistentSession.set({ ...$persistentSession.get(), isRefetching: true }); const result = await authClient2.getSession(); if (result.error) { $persistentSession.set({ ...$persistentSession.get(), isRefetching: false, isPending: false, error: result.error }); } else { $persistentSession.set({ ...$persistentSession.get(), optimistic: false, data: result.data, isRefetching: false, isPending: false, error: null }); } }; // src/init-persist-session.ts function initPersistSession(authClient2) { if ($authClient.get()) return; $authClient.set(authClient2); const sessionResult = $persistentSession.get(); if (!sessionResult.data) return; sessionResult.refetch(); } // src/hooks/use-session.ts function useSession(authClient2) { const isHydrated = useIsHydrated(); const store = useStore($persistentSession); useEffect(() => { if (authClient2) initPersistSession(authClient2); }, [authClient2]); const result = useMemo(() => { return isHydrated ? { ...store, data: store.data } : emptyResult; }, [isHydrated, store]); return result; } // src/subscribe-device-sessions.ts function subscribeDeviceSessions() { const unsubscribe = $persistentSession.subscribe( ({ isPending, isRefetching, optimistic, data }) => { if (isPending || isRefetching || optimistic) return; const deviceSessions = $deviceSessions.get(); if (data) { deviceSessions.refetch(); } else { $deviceSessions.set({ ...deviceSessions, data: null, isPending: false, isRefetching: false, error: null }); } } ); const checkActiveSession = () => { const persistentSession = $persistentSession.get(); if (!persistentSession.optimistic || !persistentSession.data) return; const authClient2 = $authClient.get(); if (!authClient2) return; authClient2.multiSession.setActive({ sessionToken: persistentSession.data.session.token }).then((result) => { if (result.error) return; persistentSession.refetch(); }); }; checkActiveSession(); window.addEventListener("online", checkActiveSession); return () => { unsubscribe(); window.removeEventListener("online", checkActiveSession); }; } // src/hooks/use-subscribe-device-sessions.ts function useSubscribeDeviceSessions() { useEffect(() => { return subscribeDeviceSessions(); }, []); } var authClient = createAuthClient({ plugins: [multiSessionClient()] }); // src/set-active-session.ts async function setActiveSession({ sessionToken }) { var _a; const deviceSessions = $deviceSessions.get(); const session = (_a = deviceSessions.data) == null ? void 0 : _a.find( (session2) => session2.session.token === sessionToken ); if (!session) throw { error: { message: "Invalid session token", code: "INVALID_SESSION_TOKEN" } }; const persistentSession = $persistentSession.get(); $persistentSession.set({ ...persistentSession, optimistic: true, data: session, isPending: false, isRefetching: false, error: null }); authClient.multiSession.setActive({ sessionToken }).then((result) => { if (result.error) return; persistentSession.refetch(); }); } export { $authClient, $persistentSession, setActiveSession, subscribeDeviceSessions, useListDeviceSessions, useSession, useSubscribeDeviceSessions };