UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

149 lines (148 loc) 4.9 kB
"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { useMetaLensProvider } from "../components/MetaLensProvider.js"; import { useMetaLens } from "./useMetaLens.js"; /** * Hook for real-time MetaLens comment streaming * * Based on patterns from: * - MetaLens browser extension EventSource implementation * - /Users/satchmo/code/metalens.app/CLAUDE.md (real-time features) * * @example * ```tsx * const { comments, loading, error } = useMetaLensStream('url', 'https://example.com'); * * // Comments will update in real-time as new ones are posted * ``` */ export function useMetaLensStream(context, value, options) { const [comments, setComments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { getComments, subscribeToComments } = useMetaLens(); const { config } = useMetaLensProvider(); const mountedRef = useRef(true); // Helper function to update comment reactions const updateCommentReaction = useCallback((comments, reaction) => { return comments.map((comment) => { if (comment.txid === reaction.commentId) { return { ...comment, reactions: { ...comment.reactions, [reaction.emoji]: (comment.reactions?.[reaction.emoji] || 0) + (reaction.removed ? -1 : 1), }, }; } // Check replies recursively if (comment.replies) { return { ...comment, replies: updateCommentReaction(comment.replies, reaction), }; } return comment; }); }, []); // Helper function to add reply to comment tree const addReplyToComment = useCallback((comments, reply) => { return comments.map((comment) => { if (comment.txid === reply.metadata?.thread) { return { ...comment, replies: [...(comment.replies || []), reply], }; } // Check replies recursively if (comment.replies) { return { ...comment, replies: addReplyToComment(comment.replies, reply), }; } return comment; }); }, []); useEffect(() => { mountedRef.current = true; // Fetch initial comments const fetchInitialComments = async () => { try { setLoading(true); setError(null); const initialComments = await getComments(context, value); if (mountedRef.current) { setComments(initialComments); } } catch (err) { if (mountedRef.current) { setError(err); } } finally { if (mountedRef.current) { setLoading(false); } } }; // Set up real-time subscription let unsubscribe = null; if (config.enableRealTime && options?.autoConnect !== false) { // Subscribe to new comments unsubscribe = subscribeToComments(context, value, (newComment) => { if (mountedRef.current) { setComments((prev) => { // Check if it's a reply if (newComment.metadata?.thread) { return addReplyToComment(prev, newComment); } // New root comment - add to top return [newComment, ...prev]; }); } }); } // Fetch initial data if autoConnect is true (default) if (options?.autoConnect !== false) { fetchInitialComments(); } // Cleanup return () => { mountedRef.current = false; if (unsubscribe) { unsubscribe(); } }; }, [ context, value, getComments, subscribeToComments, config.enableRealTime, options?.autoConnect, addReplyToComment, ]); const refetch = useCallback(async () => { setLoading(true); setError(null); try { const freshComments = await getComments(context, value); setComments(freshComments); } catch (err) { setError(err); } finally { setLoading(false); } }, [context, value, getComments]); return { comments, loading, error, refetch, }; }