Submatter/apps/web/components/chat/use-chat-mutation-recovery.tsx

65 lines
2.6 KiB
TypeScript
Raw Normal View History

"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<string, { after: string; through?: string }>());
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,
};
}