Files
PacketStation/frontend/src/auth/useEvents.ts
T

61 lines
1.9 KiB
TypeScript
Raw Normal View History

import { useEffect, useRef, useState } from "react";
import type { PresenceUser } from "./types";
interface EventHandlers {
/** Called when the server revokes this browser's own session. */
onRevoked: () => void;
/** Called for every `changed` event. Later phases refresh their data from it. */
onChanged?: (payload: unknown) => void;
}
function parse(data: unknown): unknown {
if (typeof data !== "string") return null;
try {
return JSON.parse(data) as unknown;
} catch {
return null;
}
}
/**
* Opens /api/events with EventSource while `sessionId` is set.
* A `session` event with this session id and action "revoked" calls onRevoked.
* `presence` is kept in state for later phases.
*/
export function useEvents(sessionId: string | null, handlers: EventHandlers): PresenceUser[] {
const [presence, setPresence] = useState<PresenceUser[]>([]);
const ref = useRef(handlers);
useEffect(() => {
ref.current = handlers;
});
useEffect(() => {
if (!sessionId || typeof EventSource === "undefined") return;
const es = new EventSource("/api/events", { withCredentials: true });
es.addEventListener("session", (ev) => {
const p = parse((ev as MessageEvent).data);
if (p && typeof p === "object") {
const o = p as { session_id?: unknown; action?: unknown };
if (o.session_id === sessionId && o.action === "revoked") ref.current.onRevoked();
}
});
es.addEventListener("presence", (ev) => {
const p = parse((ev as MessageEvent).data);
if (p && typeof p === "object" && Array.isArray((p as { users?: unknown }).users)) {
setPresence((p as { users: PresenceUser[] }).users);
}
});
es.addEventListener("changed", (ev) => {
ref.current.onChanged?.(parse((ev as MessageEvent).data));
});
return () => {
es.close();
setPresence([]);
};
}, [sessionId]);
return presence;
}