import { type JSX, createContext, createEffect, on, onCleanup, onMount, useContext, } from "solid-js"; import { Channel, Client, Message } from "stoat.js"; import { useClientLifecycle } from "@revolt/client"; import { State } from "@revolt/client/Controller"; type ChannelState = { messages: Message[]; atStart: boolean; atEnd: boolean; scrollTop?: number; }; const CacheContext = createContext<{ manage(channel: Channel, state: ChannelState): void; unmanage(channel: Channel): ChannelState | void; // preload(channel: Channel): void; :: future optimisation feature }>(); /** * Persistent message & channel state cache */ export function MessageCache(props: { client: Client; children: JSX.Element }) { const lifecycle = useClientLifecycle(); const cache: Record = {}; /** * Handle incoming messages * @param message Message object */ function onMessage(message: Message) { const entry = cache[message.channelId]; if (entry?.atEnd) { entry.messages = [message, ...entry.messages].slice(0, 50); } } /** * Handle deleted messages */ function onMessageDelete(message: { id: string; channelId: string }) { const entry = cache[message.channelId]; if (entry) { entry.messages = entry.messages.filter((msg) => msg.id !== message.id); } } // Add listener for messages onMount(() => { props.client.addListener("messageCreate", onMessage); props.client.addListener("messageDelete", onMessageDelete); }); onCleanup(() => { props.client.removeListener("messageCreate", onMessage); props.client.removeListener("messageDelete", onMessageDelete); }); // Clear cache when we reconnect createEffect( on( () => lifecycle.lifecycle.state(), (state) => { if (state === State.Connected) { for (const key of Object.keys(cache)) { delete cache[key]; } } }, { defer: true }, ), ); return ( {props.children} ); } export function useMessageCache() { return useContext(CacheContext); }