"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { api } from "@/lib/trpc"; import type { ChatMessageView } from "./chat-message-list"; import { mergeChatMessages } from "./chat-utils"; import { createMutationRecovery } from "./mutation-recovery"; export function useChatMutationRecovery({ workspace, channelId, enabled }: { workspace: string; channelId?: string; enabled: boolean; }) { const utils = api.useUtils(); const identity = JSON.stringify([workspace, channelId, enabled]); const scope = useRef({ identity }); if (scope.current.identity !== identity) scope.current = { identity }; const token = scope.current; const [state, setState] = useState<{ token: typeof token; messages: ChatMessageView[]; error: unknown }>({ token, messages: [], error: null }); const cursors = useRef(new Map()); const run = useRef<(() => void) | undefined>(undefined); const cursorKey = JSON.stringify([workspace, channelId ?? ""]); const refresh = useCallback(() => { run.current?.(); }, []); useEffect(() => { if (!enabled || !channelId) return; let stopped = false; const valid = () => !stopped && scope.current === token; const cursorKey = JSON.stringify([workspace, channelId]); const recovery = createMutationRecovery( (cursor) => utils.chat.mutationCatchUp.fetch({ workspace, channelId, ...cursor, limit: 100 }), (messages) => { if (!valid()) return; setState((previous) => ({ token, messages: mergeChatMessages(previous.token === token ? previous.messages : [], messages), error: null })); }, cursors.current.get(cursorKey), (cursor) => cursors.current.set(cursorKey, cursor), ); const recover = () => { void recovery.run().catch((error: unknown) => { if (valid()) setState((previous) => ({ token, messages: previous.token === token ? previous.messages : [], error })); }); }; run.current = recover; recover(); const timer = setInterval(recover, 15000); window.addEventListener("online", recover); window.addEventListener("focus", recover); return () => { stopped = true; recovery.stop(); clearInterval(timer); window.removeEventListener("online", recover); window.removeEventListener("focus", recover); if (run.current === recover) run.current = undefined; }; }, [workspace, channelId, enabled, token, utils]); return { messages: state.token === token && enabled ? state.messages : [], error: state.token === token && enabled ? state.error : null, refresh, }; }