UNPKG

@replyke/core

Version:

Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.

65 lines 2.88 kB
import { useCallback, useEffect, useRef } from "react"; import { useReplykeSelector } from "../../store/hooks"; import { selectTypingUsers } from "../../store/slices/chatSlice"; import { useChatContext } from "../../context/chat-context"; /** * Manages the typing indicator for a conversation. * * Sender side protocol (§6.5 of design): * 1. On first keystroke after idle: emit `typing:start` immediately. * 2. Re-emit `typing:start` every 2 s as a keep-alive while typing continues. * 3. On send / blur / input clear: cancel keep-alive, emit `typing:stop`. */ function useTypingIndicator({ conversationId, }) { const { socket } = useChatContext(); // Array of other users currently typing (current user already excluded by ChatProvider) const typingUsers = useReplykeSelector(selectTypingUsers(conversationId)); // Whether we (the current user) are currently emitting typing events const isTypingRef = useRef(false); // Keep-alive interval: re-emits typing:start every 2 s const keepAliveRef = useRef(null); const stopKeepAlive = useCallback(() => { if (keepAliveRef.current !== null) { clearInterval(keepAliveRef.current); keepAliveRef.current = null; } }, []); const startTyping = useCallback(() => { if (!socket || !conversationId) return; if (!isTypingRef.current) { // First keystroke after idle — emit immediately isTypingRef.current = true; socket.emit("typing:start", { conversationId }); // Start the 2-second keep-alive keepAliveRef.current = setInterval(() => { socket.emit("typing:start", { conversationId }); }, 2000); } // Subsequent keystrokes while already typing: keep-alive handles re-emitting }, [socket, conversationId]); const stopTyping = useCallback(() => { if (!socket || !conversationId) return; if (!isTypingRef.current) return; // Cancel keep-alive BEFORE emitting stop to avoid a scheduled keep-alive // firing after the stop and briefly re-adding the user to receivers' typing lists stopKeepAlive(); isTypingRef.current = false; socket.emit("typing:stop", { conversationId }); }, [socket, conversationId, stopKeepAlive]); // Clean up on unmount or conversation change useEffect(() => { return () => { if (isTypingRef.current && socket && conversationId) { stopKeepAlive(); socket.emit("typing:stop", { conversationId }); isTypingRef.current = false; } }; }, [socket, conversationId, stopKeepAlive]); return { typingUsers, startTyping, stopTyping }; } export default useTypingIndicator; //# sourceMappingURL=useTypingIndicator.js.map