1"use client";
2
3
14import { useEffect, useMemo, useState } from "react";
15
16const EMPTY_PRESENCE: Record<string, Presence> = {};
17import { useQueryClient } from "@tanstack/react-query";
18import { authStore } from "@/lib/auth-store";
19import { channelForPage, realtime, type RealtimeStatus } from "@/lib/realtime";
20import { useBlockPatchStore } from "@/stores/block-patch-store";
21import type { Block, BlockPatch, Presence } from "@/lib/types";
22import { handlePageRealtimeFrame } from "./realtime-frame-handler";
23
24export interface PageRealtime {
25 status: RealtimeStatus;
26 presence: Presence[];
27}
28
29export function usePageRealtime(pageId: string | null): PageRealtime {
30 const [status, setStatus] = useState<RealtimeStatus>(realtime.connectionStatus);
31
32
33 const presenceMap = useBlockPatchStore((s) =>
34 pageId ? (s.presence[pageId] ?? EMPTY_PRESENCE) : EMPTY_PRESENCE,
35 );
36 const presence = useMemo(() => Object.values(presenceMap), [presenceMap]);
37 const qc = useQueryClient();
38
39 useEffect(() => {
40 const offStatus = realtime.onStatus(setStatus);
41
42
43 realtime.connect(() => authStore.accessToken);
44
45 if (!pageId) return;
46 const channel = channelForPage(pageId);
47 realtime.subscribe(channel);
48
49
50
51
52
53 const actions = {
54 upsert: (pid: string, blocks: Block[]) => useBlockPatchStore.getState().upsert(pid, blocks),
55 delete: (pid: string, blockIds: string[]) =>
56 useBlockPatchStore.getState().delete(pid, blockIds),
57 patch: (pid: string, blockId: string, p: BlockPatch) =>
58 useBlockPatchStore.getState().patch(pid, blockId, p),
59 upsertPresence: (pid: string, peer: Presence) =>
60 useBlockPatchStore.getState().upsertPresence(pid, peer),
61 removePresence: (pid: string, userId: string) =>
62 useBlockPatchStore.getState().removePresence(pid, userId),
63 clearBlocks: (pid: string) => useBlockPatchStore.getState().clearBlocks(pid),
64 invalidateBlocks: (pid: string) =>
65 qc.invalidateQueries({ queryKey: ["pages", pid, "blocks"] }),
66 };
67 const frameTypes = [
68 "presence",
69 "presence-leave",
70 "block:upsert",
71 "block:delete",
72 "block:move",
73 "block:changed",
74 ] as const;
75 const offs = frameTypes.map((t) =>
76 realtime.on(t, (event) => handlePageRealtimeFrame(event, actions)),
77 );
78
79 return () => {
80 for (const off of offs) off();
81 offStatus();
82 realtime.unsubscribe(channel);
83
84
85 useBlockPatchStore.getState().reset(pageId);
86 };
87 }, [pageId, qc]);
88
89 return useMemo(() => ({ status, presence }), [status, presence]);
90}
91