61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
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;
|
||
|
|
}
|