bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
149 lines (148 loc) • 4.9 kB
JavaScript
"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,
};
}