@daveyplate/better-auth-persistent
Version:
Persistent session management for Better Auth
235 lines (224 loc) • 5.99 kB
JavaScript
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 };