add real-time infrastructure (#137)
* setup infra for pusher service * setup client sdk for pusher service * fix: ping parse failure * fix: send pong back to client avoid disconnections every 2.5 minutes * increase replicas * feat: show presence and compose indicator
This commit was merged in pull request #137.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { usePusherClient } from "@/lib/pusher-provider";
|
||||
import type { ChannelMessage } from "@/lib/pusher-client";
|
||||
|
||||
interface UseChannelResult {
|
||||
/** Current set of humanIds present in the channel */
|
||||
presence: string[];
|
||||
/** Messages received on this channel (since the hook mounted) */
|
||||
messages: ChannelMessage[];
|
||||
/** Send a message to the channel */
|
||||
sendMessage: (payload: unknown) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a pusher channel. Manages presence tracking and message delivery.
|
||||
* Subscribes on mount, unsubscribes on unmount.
|
||||
*
|
||||
* @param channelId - The channel to subscribe to, or null to skip.
|
||||
*/
|
||||
export function useChannel(channelId: string | null): UseChannelResult {
|
||||
const client = usePusherClient();
|
||||
const [presence, setPresence] = useState<string[]>([]);
|
||||
const [messages, setMessages] = useState<ChannelMessage[]>([]);
|
||||
|
||||
// Keep a ref to avoid re-subscribing when sendMessage changes
|
||||
const clientRef = useRef(client);
|
||||
const channelRef = useRef(channelId);
|
||||
clientRef.current = client;
|
||||
channelRef.current = channelId;
|
||||
|
||||
useEffect(() => {
|
||||
if (!client || !channelId) {
|
||||
setPresence([]);
|
||||
setMessages([]);
|
||||
return;
|
||||
}
|
||||
|
||||
client.subscribe(channelId);
|
||||
|
||||
const onSubscribed = (msg: { presence?: string[] }) => {
|
||||
setPresence(msg.presence ?? []);
|
||||
};
|
||||
|
||||
const onJoin = (msg: { humanId?: string }) => {
|
||||
if (msg.humanId) {
|
||||
setPresence((prev) =>
|
||||
prev.includes(msg.humanId!) ? prev : [...prev, msg.humanId!],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onLeave = (msg: { humanId?: string }) => {
|
||||
if (msg.humanId) {
|
||||
setPresence((prev) => prev.filter((id) => id !== msg.humanId));
|
||||
}
|
||||
};
|
||||
|
||||
const onMessage = (msg: { humanId?: string; payload?: unknown }) => {
|
||||
if (msg.humanId) {
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{ humanId: msg.humanId!, payload: msg.payload },
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
client.on(channelId, "subscribed", onSubscribed);
|
||||
client.on(channelId, "join", onJoin);
|
||||
client.on(channelId, "leave", onLeave);
|
||||
client.on(channelId, "message", onMessage);
|
||||
|
||||
return () => {
|
||||
client.off(channelId, "subscribed", onSubscribed);
|
||||
client.off(channelId, "join", onJoin);
|
||||
client.off(channelId, "leave", onLeave);
|
||||
client.off(channelId, "message", onMessage);
|
||||
client.unsubscribe(channelId);
|
||||
};
|
||||
}, [client, channelId]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
(payload: unknown) => {
|
||||
if (clientRef.current && channelRef.current) {
|
||||
clientRef.current.sendMessage(channelRef.current, payload);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { presence, messages, sendMessage };
|
||||
}
|
||||
Reference in New Issue
Block a user