65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
|
|
"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,
|
||
|
|
};
|
||
|
|
}
|