2025-02-22 01:16:33 -05:00
|
|
|
// lib/hooks/useSubscribe.ts
|
2025-03-06 16:34:50 -05:00
|
|
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
|
import { NDKEvent, NDKFilter, NDKSubscription, NDKSubscriptionOptions } from '@nostr-dev-kit/ndk-mobile';
|
2025-02-22 01:16:33 -05:00
|
|
|
import { useNDK } from './useNDK';
|
|
|
|
|
2025-03-06 16:34:50 -05:00
|
|
|
interface UseSubscribeOptions extends Partial<NDKSubscriptionOptions> {
|
2025-02-22 01:16:33 -05:00
|
|
|
enabled?: boolean;
|
2025-03-03 22:40:11 -05:00
|
|
|
deduplicate?: boolean;
|
2025-02-22 01:16:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
export function useSubscribe(
|
2025-03-06 16:34:50 -05:00
|
|
|
filters: NDKFilter[] | false,
|
2025-02-22 01:16:33 -05:00
|
|
|
options: UseSubscribeOptions = {}
|
|
|
|
) {
|
|
|
|
const { ndk } = useNDK();
|
|
|
|
const [events, setEvents] = useState<NDKEvent[]>([]);
|
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const [eose, setEose] = useState(false);
|
2025-03-03 22:40:11 -05:00
|
|
|
const subscriptionRef = useRef<NDKSubscription | null>(null);
|
2025-02-22 01:16:33 -05:00
|
|
|
|
|
|
|
// Default options
|
2025-03-03 22:40:11 -05:00
|
|
|
const {
|
|
|
|
enabled = true,
|
|
|
|
closeOnEose = false,
|
2025-03-06 16:34:50 -05:00
|
|
|
deduplicate = true,
|
|
|
|
...subscriptionOptions
|
2025-03-03 22:40:11 -05:00
|
|
|
} = options;
|
2025-02-22 01:16:33 -05:00
|
|
|
|
2025-03-06 16:34:50 -05:00
|
|
|
// Function to clear all events
|
|
|
|
const clearEvents = useCallback(() => {
|
|
|
|
setEvents([]);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
// Function to manually resubscribe
|
|
|
|
const resubscribe = useCallback(() => {
|
2025-03-03 22:40:11 -05:00
|
|
|
if (subscriptionRef.current) {
|
|
|
|
subscriptionRef.current.stop();
|
|
|
|
subscriptionRef.current = null;
|
|
|
|
}
|
|
|
|
setEvents([]);
|
|
|
|
setEose(false);
|
2025-03-06 16:34:50 -05:00
|
|
|
setIsLoading(true);
|
|
|
|
}, []);
|
|
|
|
|
2025-03-16 21:31:38 -04:00
|
|
|
// Direct fetch function for manual fetching
|
|
|
|
const manualFetch = useCallback(async () => {
|
|
|
|
if (!ndk || !filters) return;
|
|
|
|
|
|
|
|
try {
|
|
|
|
console.log('[useSubscribe] Manual fetch triggered');
|
|
|
|
setIsLoading(true);
|
|
|
|
|
|
|
|
const fetchedEvents = await ndk.fetchEvents(filters);
|
|
|
|
const eventsArray = Array.from(fetchedEvents);
|
|
|
|
|
|
|
|
setEvents(prev => {
|
|
|
|
if (deduplicate) {
|
|
|
|
const existingIds = new Set(prev.map(e => e.id));
|
|
|
|
const newEvents = eventsArray.filter(e => !existingIds.has(e.id));
|
|
|
|
return [...prev, ...newEvents];
|
|
|
|
}
|
|
|
|
return [...prev, ...eventsArray];
|
|
|
|
});
|
|
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
setEose(true);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('[useSubscribe] Manual fetch error:', err);
|
|
|
|
setIsLoading(false);
|
|
|
|
}
|
|
|
|
}, [ndk, filters, deduplicate]);
|
|
|
|
|
|
|
|
// Only run the subscription effect when dependencies change
|
|
|
|
const filtersKey = filters ? JSON.stringify(filters) : 'none';
|
|
|
|
const optionsKey = JSON.stringify(subscriptionOptions);
|
|
|
|
|
2025-03-06 16:34:50 -05:00
|
|
|
useEffect(() => {
|
2025-02-22 01:16:33 -05:00
|
|
|
if (!ndk || !filters || !enabled) {
|
|
|
|
setIsLoading(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-16 21:31:38 -04:00
|
|
|
// Clean up any existing subscription
|
|
|
|
if (subscriptionRef.current) {
|
|
|
|
subscriptionRef.current.stop();
|
|
|
|
subscriptionRef.current = null;
|
|
|
|
}
|
|
|
|
|
2025-02-22 01:16:33 -05:00
|
|
|
setIsLoading(true);
|
2025-03-06 16:34:50 -05:00
|
|
|
setEose(false);
|
2025-02-22 01:16:33 -05:00
|
|
|
|
|
|
|
try {
|
2025-03-16 21:31:38 -04:00
|
|
|
console.log('[useSubscribe] Creating new subscription');
|
|
|
|
|
|
|
|
// Create subscription with NDK
|
2025-03-06 16:34:50 -05:00
|
|
|
const subscription = ndk.subscribe(filters, {
|
|
|
|
closeOnEose,
|
|
|
|
...subscriptionOptions
|
|
|
|
});
|
2025-03-03 22:40:11 -05:00
|
|
|
|
|
|
|
subscriptionRef.current = subscription;
|
2025-02-22 01:16:33 -05:00
|
|
|
|
2025-03-16 21:31:38 -04:00
|
|
|
// Event handler - use a function reference to avoid recreating
|
|
|
|
const handleEvent = (event: NDKEvent) => {
|
2025-02-22 01:16:33 -05:00
|
|
|
setEvents(prev => {
|
2025-03-03 22:40:11 -05:00
|
|
|
if (deduplicate && prev.some(e => e.id === event.id)) {
|
2025-02-22 01:16:33 -05:00
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
return [...prev, event];
|
|
|
|
});
|
2025-03-16 21:31:38 -04:00
|
|
|
};
|
2025-02-22 01:16:33 -05:00
|
|
|
|
2025-03-16 21:31:38 -04:00
|
|
|
// EOSE handler
|
|
|
|
const handleEose = () => {
|
2025-02-22 01:16:33 -05:00
|
|
|
setIsLoading(false);
|
|
|
|
setEose(true);
|
2025-03-16 21:31:38 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
subscription.on('event', handleEvent);
|
|
|
|
subscription.on('eose', handleEose);
|
|
|
|
|
|
|
|
// Clean up on unmount or when dependencies change
|
|
|
|
return () => {
|
|
|
|
if (subscription) {
|
|
|
|
subscription.off('event', handleEvent);
|
|
|
|
subscription.off('eose', handleEose);
|
|
|
|
subscription.stop();
|
|
|
|
}
|
|
|
|
subscriptionRef.current = null;
|
|
|
|
};
|
2025-02-22 01:16:33 -05:00
|
|
|
} catch (error) {
|
2025-03-16 21:31:38 -04:00
|
|
|
console.error('[useSubscribe] Subscription error:', error);
|
2025-02-22 01:16:33 -05:00
|
|
|
setIsLoading(false);
|
|
|
|
}
|
2025-03-16 21:31:38 -04:00
|
|
|
}, [ndk, enabled, filtersKey, optionsKey, closeOnEose, deduplicate]);
|
2025-02-22 01:16:33 -05:00
|
|
|
|
2025-03-03 22:40:11 -05:00
|
|
|
return {
|
|
|
|
events,
|
|
|
|
isLoading,
|
2025-03-06 16:34:50 -05:00
|
|
|
eose,
|
|
|
|
clearEvents,
|
|
|
|
resubscribe,
|
2025-03-16 21:31:38 -04:00
|
|
|
fetchEvents: manualFetch // Function to trigger manual fetch
|
2025-03-03 22:40:11 -05:00
|
|
|
};
|
2025-02-22 01:16:33 -05:00
|
|
|
}
|