Stop opening duplicate WebSocket connections
The real problem: nostr.relay() and nostr.group() each open brand new
WebSocket connections outside the pool. Every hook that called these
was creating duplicate connections to relays the pool already had open.
Fixed by routing all queries and subscriptions through the pool (nostr)
directly, which reuses its internal connections:
- useStreamPosts: nostr.relay('wss://relay.ditto.pub') → nostr
- useStreamKind: nostr.relay('wss://relay.ditto.pub') → nostr
- useSearchProfiles: nostr.relay('wss://relay.ditto.pub') → nostr
- useFollowActions: nostr.group([...]) → nostr
- FollowPackDetailContent: nostr.group([...]) → nostr
Also reverted the hacky relay cleanup code in NostrProvider since
it was trying to solve the wrong problem.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
@@ -15,7 +15,6 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useFollowList, useFollowActions } from '@/hooks/useFollowActions';
|
||||
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
|
||||
/** Parse a follow pack / starter pack event into structured data. */
|
||||
@@ -37,7 +36,6 @@ export function FollowPackDetailContent({ event }: { event: NostrEvent }) {
|
||||
const { toast } = useToast();
|
||||
const { nostr } = useNostr();
|
||||
const { user } = useCurrentUser();
|
||||
const { config } = useAppContext();
|
||||
const { data: followList } = useFollowList();
|
||||
const { mutateAsync: publishEvent } = useNostrPublish();
|
||||
|
||||
@@ -71,21 +69,9 @@ export function FollowPackDetailContent({ event }: { event: NostrEvent }) {
|
||||
|
||||
setIsFollowingAll(true);
|
||||
try {
|
||||
const discoveryRelays = [
|
||||
'wss://relay.damus.io',
|
||||
'wss://nos.lol',
|
||||
'wss://relay.primal.net',
|
||||
'wss://purplepag.es',
|
||||
];
|
||||
const userRelays = config.relayMetadata.relays
|
||||
.filter((r) => r.read)
|
||||
.map((r) => r.url);
|
||||
const allRelayUrls = [...new Set([...discoveryRelays, ...userRelays])];
|
||||
|
||||
const signal = AbortSignal.timeout(10_000);
|
||||
const relayGroup = nostr.group(allRelayUrls);
|
||||
|
||||
const followEvents = await relayGroup.query(
|
||||
const followEvents = await nostr.query(
|
||||
[{ kinds: [3], authors: [user.pubkey], limit: 1 }],
|
||||
{ signal },
|
||||
);
|
||||
@@ -123,7 +109,7 @@ export function FollowPackDetailContent({ event }: { event: NostrEvent }) {
|
||||
} finally {
|
||||
setIsFollowingAll(false);
|
||||
}
|
||||
}, [user, pubkeys, nostr, config, publishEvent, toast]);
|
||||
}, [user, pubkeys, nostr, publishEvent, toast]);
|
||||
|
||||
const handleCopyLink = useCallback(() => {
|
||||
const dTag = event.tags.find(([n]) => n === 'd')?.[1] ?? '';
|
||||
|
||||
@@ -21,9 +21,6 @@ const NostrProvider: React.FC<NostrProviderProps> = (props) => {
|
||||
// Use refs so the pool always has the latest data
|
||||
const effectiveRelays = useRef(getEffectiveRelays(config.relayMetadata, config.useAppRelays));
|
||||
|
||||
// Track previous relay URLs to detect changes
|
||||
const prevRelayUrlsRef = useRef<string>('');
|
||||
|
||||
// Update effective relays ref and invalidate all queries when relays change,
|
||||
// since any cached query may have been fetched from a different set of relays.
|
||||
useEffect(() => {
|
||||
@@ -33,31 +30,7 @@ const NostrProvider: React.FC<NostrProviderProps> = (props) => {
|
||||
// Only invalidate if the relay URLs actually changed
|
||||
const prevUrls = prev.relays.map(r => r.url).sort().join(',');
|
||||
const nextUrls = effectiveRelays.current.relays.map(r => r.url).sort().join(',');
|
||||
|
||||
if (prevUrls !== nextUrls) {
|
||||
// Close connections to relays that are no longer in the list
|
||||
if (pool.current && prevRelayUrlsRef.current) {
|
||||
const prevSet = new Set(prevUrls.split(',').filter(Boolean));
|
||||
const nextSet = new Set(nextUrls.split(',').filter(Boolean));
|
||||
|
||||
// Find relays that were removed
|
||||
const removedRelays = [...prevSet].filter(url => !nextSet.has(url));
|
||||
|
||||
// Close removed relay connections
|
||||
for (const url of removedRelays) {
|
||||
try {
|
||||
const relay = (pool.current as any).relays?.get?.(url);
|
||||
if (relay) {
|
||||
relay.close();
|
||||
(pool.current as any).relays?.delete?.(url);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevRelayUrlsRef.current = nextUrls;
|
||||
queryClient.invalidateQueries();
|
||||
}
|
||||
}, [config.relayMetadata, config.useAppRelays, queryClient]);
|
||||
|
||||
@@ -2,37 +2,20 @@ import { useCallback, useState } from 'react';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCurrentUser } from './useCurrentUser';
|
||||
import { useAppContext } from './useAppContext';
|
||||
import { useNostrPublish } from './useNostrPublish';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
/**
|
||||
* Discovery relays used to find the user's latest kind 3 event.
|
||||
* We cast a wide net to avoid acting on stale data.
|
||||
*/
|
||||
const DISCOVERY_RELAYS = [
|
||||
'wss://relay.damus.io',
|
||||
'wss://nos.lol',
|
||||
'wss://relay.primal.net',
|
||||
'wss://purplepag.es',
|
||||
'wss://relay.snort.social',
|
||||
];
|
||||
|
||||
/**
|
||||
* Fetches the absolute freshest kind 3 follow list from multiple relays.
|
||||
* This prevents stale-data mutations that could accidentally wipe follows.
|
||||
* Fetches the absolute freshest kind 3 follow list via the pool.
|
||||
* The pool already routes to all configured read relays.
|
||||
*/
|
||||
async function fetchFreshFollowEvent(
|
||||
nostr: ReturnType<typeof useNostr>['nostr'],
|
||||
pubkey: string,
|
||||
userRelayUrls: string[],
|
||||
): Promise<NostrEvent | null> {
|
||||
const allRelayUrls = [...new Set([...DISCOVERY_RELAYS, ...userRelayUrls])];
|
||||
|
||||
const signal = AbortSignal.timeout(10_000);
|
||||
const relayGroup = nostr.group(allRelayUrls);
|
||||
|
||||
const followEvents = await relayGroup.query(
|
||||
const followEvents = await nostr.query(
|
||||
[{ kinds: [3], authors: [pubkey], limit: 1 }],
|
||||
{ signal },
|
||||
);
|
||||
@@ -109,25 +92,19 @@ export interface UseFollowActionsReturn {
|
||||
export function useFollowActions(): UseFollowActionsReturn {
|
||||
const { nostr } = useNostr();
|
||||
const { user } = useCurrentUser();
|
||||
const { config } = useAppContext();
|
||||
const { mutateAsync: publishEvent } = useNostrPublish();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
/** User's read-enabled relay URLs (from NIP-65 config). */
|
||||
const userRelayUrls = config.relayMetadata.relays
|
||||
.filter((r) => r.read)
|
||||
.map((r) => r.url);
|
||||
|
||||
const mutateFollowList = useCallback(
|
||||
async (targetPubkey: string, action: 'follow' | 'unfollow') => {
|
||||
if (!user) throw new Error('Not logged in');
|
||||
setIsPending(true);
|
||||
|
||||
try {
|
||||
// ① Fetch the freshest kind 3 event from a wide relay set
|
||||
const latestEvent = await fetchFreshFollowEvent(nostr, user.pubkey, userRelayUrls);
|
||||
// ① Fetch the freshest kind 3 event via pool
|
||||
const latestEvent = await fetchFreshFollowEvent(nostr, user.pubkey);
|
||||
|
||||
// ② Separate tags into `p` tags (follow entries) and everything else
|
||||
const existingTags = latestEvent?.tags ?? [];
|
||||
@@ -163,7 +140,7 @@ export function useFollowActions(): UseFollowActionsReturn {
|
||||
setIsPending(false);
|
||||
}
|
||||
},
|
||||
[nostr, user, userRelayUrls, publishEvent, queryClient],
|
||||
[nostr, user, publishEvent, queryClient],
|
||||
);
|
||||
|
||||
const follow = useCallback(
|
||||
|
||||
@@ -18,10 +18,8 @@ export function useSearchProfiles(query: string) {
|
||||
queryFn: async ({ signal }) => {
|
||||
if (!query.trim()) return [];
|
||||
|
||||
// Use relay.ditto.pub specifically for NIP-50 profile search
|
||||
const relay = nostr.relay('wss://relay.ditto.pub');
|
||||
|
||||
const events = await relay.query(
|
||||
// NIP-50 profile search (uses pool, reuses existing connections)
|
||||
const events = await nostr.query(
|
||||
[{ kinds: [0], search: query.trim(), limit: 10 }],
|
||||
{ signal: AbortSignal.any([signal, AbortSignal.timeout(5000)]) },
|
||||
);
|
||||
|
||||
@@ -67,13 +67,12 @@ export function useStreamKind(kind: number | number[]) {
|
||||
setEvents(Array.from(eventMap.values()).sort((a, b) => b.created_at - a.created_at));
|
||||
}
|
||||
|
||||
const relay = nostr.relay('wss://relay.ditto.pub');
|
||||
const filter = { kinds };
|
||||
|
||||
// 1. Fetch initial batch
|
||||
// 1. Fetch initial batch (uses pool, reuses existing connections)
|
||||
(async () => {
|
||||
try {
|
||||
const results = await relay.query(
|
||||
const results = await nostr.query(
|
||||
[{ ...filter, limit: 40 }],
|
||||
{ signal: ac.signal },
|
||||
);
|
||||
@@ -86,11 +85,11 @@ export function useStreamKind(kind: number | number[]) {
|
||||
if (alive) setIsLoading(false);
|
||||
})();
|
||||
|
||||
// 2. Stream new events
|
||||
// 2. Stream new events (uses pool, reuses existing connections)
|
||||
(async () => {
|
||||
try {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
for await (const msg of relay.req(
|
||||
for await (const msg of nostr.req(
|
||||
[{ ...filter, since: now, limit: 100 }],
|
||||
{ signal: ac.signal },
|
||||
)) {
|
||||
|
||||
@@ -95,8 +95,6 @@ export function useStreamPosts(query: string, options: StreamPostsOptions) {
|
||||
setAllEvents(Array.from(eventMap.values()).sort((a, b) => b.created_at - a.created_at));
|
||||
}
|
||||
|
||||
const relay = nostr.relay('wss://relay.ditto.pub');
|
||||
|
||||
// Build the kinds list: either vines-only or kind 1 + enabled extras
|
||||
const kinds: number[] = isVines
|
||||
? [34236]
|
||||
@@ -108,10 +106,10 @@ export function useStreamPosts(query: string, options: StreamPostsOptions) {
|
||||
baseFilter.search = query.trim();
|
||||
}
|
||||
|
||||
// 1. Fetch initial batch
|
||||
// 1. Fetch initial batch (uses pool, reuses existing connections)
|
||||
(async () => {
|
||||
try {
|
||||
const events = await relay.query(
|
||||
const events = await nostr.query(
|
||||
[{ ...baseFilter, limit: 40 }],
|
||||
{ signal: ac.signal },
|
||||
);
|
||||
@@ -124,11 +122,11 @@ export function useStreamPosts(query: string, options: StreamPostsOptions) {
|
||||
if (alive) setIsLoading(false);
|
||||
})();
|
||||
|
||||
// 2. Stream new events
|
||||
// 2. Stream new events (uses pool, reuses existing connections)
|
||||
(async () => {
|
||||
try {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
for await (const msg of relay.req(
|
||||
for await (const msg of nostr.req(
|
||||
[{ ...baseFilter, since: now, limit: 100 }],
|
||||
{ signal: ac.signal },
|
||||
)) {
|
||||
|
||||
Reference in New Issue
Block a user