Browse Source

speed up performance

master
Silberengel 1 month ago
parent
commit
d0c9666304
  1. 69
      src/lib/modules/comments/CommentThread.svelte
  2. 91
      src/lib/modules/reactions/FeedReactionButtons.svelte
  3. 108
      src/lib/services/nostr/auth-handler.ts
  4. 129
      src/lib/services/nostr/relay-manager.ts

69
src/lib/modules/comments/CommentThread.svelte

@ -238,13 +238,13 @@
const allRelays = relayManager.getProfileReadRelays(); const allRelays = relayManager.getProfileReadRelays();
const replyFilters: any[] = [ const replyFilters: any[] = [
{ kinds: [KIND.COMMENT], '#e': [threadId] }, { kinds: [KIND.COMMENT], '#e': [threadId], limit: 500 },
{ kinds: [KIND.COMMENT], '#E': [threadId] }, { kinds: [KIND.COMMENT], '#E': [threadId], limit: 500 },
{ kinds: [KIND.COMMENT], '#a': [threadId] }, { kinds: [KIND.COMMENT], '#a': [threadId], limit: 500 },
{ kinds: [KIND.COMMENT], '#A': [threadId] }, { kinds: [KIND.COMMENT], '#A': [threadId], limit: 500 },
{ kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId] }, { kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: 500 },
{ kinds: [KIND.VOICE_REPLY], '#e': [threadId] }, { kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: 500 },
{ kinds: [KIND.ZAP_RECEIPT], '#e': [threadId] } { kinds: [KIND.ZAP_RECEIPT], '#e': [threadId], limit: 500 }
]; ];
// fetchEvents with useCache:true returns cached data immediately if available, // fetchEvents with useCache:true returns cached data immediately if available,
@ -319,13 +319,16 @@
nestedSubscriptionActive = true; nestedSubscriptionActive = true;
// Limit reply IDs to prevent massive subscriptions
const limitedReplyIds = Array.from(allReplyIds).slice(0, 100);
// Use a single subscription that covers all reply IDs // Use a single subscription that covers all reply IDs
const nestedFilters: any[] = [ const nestedFilters: any[] = [
{ kinds: [KIND.COMMENT], '#e': Array.from(allReplyIds) }, { kinds: [KIND.COMMENT], '#e': limitedReplyIds, limit: 200 },
{ kinds: [KIND.COMMENT], '#E': Array.from(allReplyIds) }, { kinds: [KIND.COMMENT], '#E': limitedReplyIds, limit: 200 },
{ kinds: [KIND.SHORT_TEXT_NOTE], '#e': Array.from(allReplyIds) }, { kinds: [KIND.SHORT_TEXT_NOTE], '#e': limitedReplyIds, limit: 200 },
{ kinds: [KIND.VOICE_REPLY], '#e': Array.from(allReplyIds) }, { kinds: [KIND.VOICE_REPLY], '#e': limitedReplyIds, limit: 200 },
{ kinds: [KIND.ZAP_RECEIPT], '#e': Array.from(allReplyIds) } { kinds: [KIND.ZAP_RECEIPT], '#e': limitedReplyIds, limit: 200 }
]; ];
nostrClient.fetchEvents( nostrClient.fetchEvents(
@ -347,37 +350,41 @@
const allRelays = relayManager.getProfileReadRelays(); const allRelays = relayManager.getProfileReadRelays();
let hasNewReplies = true; let hasNewReplies = true;
let iterations = 0; let iterations = 0;
const maxIterations = 10; const maxIterations = 3; // Reduced from 10 to prevent excessive fetching
const maxReplyIdsPerIteration = 100; // Limit number of reply IDs to check per iteration
while (hasNewReplies && iterations < maxIterations) { while (hasNewReplies && iterations < maxIterations) {
iterations++; iterations++;
hasNewReplies = false; hasNewReplies = false;
const allReplyIds = new Set([ const allReplyIds = Array.from(new Set([
...comments.map(c => c.id), ...comments.map(c => c.id),
...kind1Replies.map(r => r.id), ...kind1Replies.map(r => r.id),
...yakBacks.map(y => y.id), ...yakBacks.map(y => y.id),
...zapReceipts.map(z => z.id) ...zapReceipts.map(z => z.id)
]); ]));
// Limit the number of reply IDs to prevent massive queries
const limitedReplyIds = allReplyIds.slice(0, maxReplyIdsPerIteration);
if (allReplyIds.size > 0) { if (limitedReplyIds.length > 0) {
const nestedFilters: any[] = [ const nestedFilters: any[] = [
// Fetch nested kind 1111 comments - check both e/E and a/A tags // Fetch nested kind 1111 comments - check both e/E and a/A tags
{ kinds: [KIND.COMMENT], '#e': Array.from(allReplyIds) }, { kinds: [KIND.COMMENT], '#e': limitedReplyIds, limit: 200 },
{ kinds: [KIND.COMMENT], '#E': Array.from(allReplyIds) }, { kinds: [KIND.COMMENT], '#E': limitedReplyIds, limit: 200 },
{ kinds: [KIND.COMMENT], '#a': Array.from(allReplyIds) }, { kinds: [KIND.COMMENT], '#a': limitedReplyIds, limit: 200 },
{ kinds: [KIND.COMMENT], '#A': Array.from(allReplyIds) }, { kinds: [KIND.COMMENT], '#A': limitedReplyIds, limit: 200 },
// Fetch nested kind 1 replies // Fetch nested kind 1 replies
{ kinds: [KIND.SHORT_TEXT_NOTE], '#e': Array.from(allReplyIds) }, { kinds: [KIND.SHORT_TEXT_NOTE], '#e': limitedReplyIds, limit: 200 },
// Fetch nested yak backs // Fetch nested yak backs
{ kinds: [KIND.VOICE_REPLY], '#e': Array.from(allReplyIds) }, { kinds: [KIND.VOICE_REPLY], '#e': limitedReplyIds, limit: 200 },
// Fetch nested zap receipts // Fetch nested zap receipts
{ kinds: [KIND.ZAP_RECEIPT], '#e': Array.from(allReplyIds) } { kinds: [KIND.ZAP_RECEIPT], '#e': limitedReplyIds, limit: 200 }
]; ];
const nestedReplies = await nostrClient.fetchEvents( const nestedReplies = await nostrClient.fetchEvents(
nestedFilters, nestedFilters,
allRelays, allRelays,
{ useCache: true, cacheResults: true } { useCache: true, cacheResults: true, timeout: 5000 }
); );
// Add new replies by type // Add new replies by type
@ -537,21 +544,21 @@
// Always fetch kind 1111 comments - check both e and E tags, and a and A tags // Always fetch kind 1111 comments - check both e and E tags, and a and A tags
replyFilters.push( replyFilters.push(
{ kinds: [KIND.COMMENT], '#e': [threadId] }, // Lowercase e tag { kinds: [KIND.COMMENT], '#e': [threadId], limit: 500 }, // Lowercase e tag
{ kinds: [KIND.COMMENT], '#E': [threadId] }, // Uppercase E tag (NIP-22) { kinds: [KIND.COMMENT], '#E': [threadId], limit: 500 }, // Uppercase E tag (NIP-22)
{ kinds: [KIND.COMMENT], '#a': [threadId] }, // Lowercase a tag (some clients use wrong tags) { kinds: [KIND.COMMENT], '#a': [threadId], limit: 500 }, // Lowercase a tag (some clients use wrong tags)
{ kinds: [KIND.COMMENT], '#A': [threadId] } // Uppercase A tag (NIP-22 for addressable events) { kinds: [KIND.COMMENT], '#A': [threadId], limit: 500 } // Uppercase A tag (NIP-22 for addressable events)
); );
// For kind 1 events, fetch kind 1 replies // For kind 1 events, fetch kind 1 replies
// Also fetch kind 1 replies for any event (some apps use kind 1 for everything) // Also fetch kind 1 replies for any event (some apps use kind 1 for everything)
replyFilters.push({ kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId] }); replyFilters.push({ kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: 500 });
// Fetch yak backs (kind 1244) - voice replies // Fetch yak backs (kind 1244) - voice replies
replyFilters.push({ kinds: [KIND.VOICE_REPLY], '#e': [threadId] }); replyFilters.push({ kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: 500 });
// Fetch zap receipts (kind 9735) // Fetch zap receipts (kind 9735)
replyFilters.push({ kinds: [KIND.ZAP_RECEIPT], '#e': [threadId] }); replyFilters.push({ kinds: [KIND.ZAP_RECEIPT], '#e': [threadId], limit: 500 });
// Don't use cache when reloading after publishing - we want fresh data // Don't use cache when reloading after publishing - we want fresh data
const allReplies = await nostrClient.fetchEvents( const allReplies = await nostrClient.fetchEvents(

91
src/lib/modules/reactions/FeedReactionButtons.svelte

@ -32,8 +32,15 @@
let loadingReactions = $state(false); let loadingReactions = $state(false);
let lastEventId = $state<string | null>(null); let lastEventId = $state<string | null>(null);
let isMounted = $state(false);
onMount(() => { onMount(() => {
// Set lastEventId immediately to prevent $effect from running during mount
if (event.id) {
lastEventId = event.id;
}
isMounted = true;
nostrClient.initialize().then(async () => { nostrClient.initialize().then(async () => {
if (event.id) { if (event.id) {
// Use pre-loaded reactions if available, otherwise fetch // Use pre-loaded reactions if available, otherwise fetch
@ -50,24 +57,27 @@
}); });
}); });
// Reload reactions when event changes (but prevent duplicate loads) // Reload reactions when event changes (but prevent duplicate loads and initial mount)
$effect(() => { $effect(() => {
if (event.id && event.id !== lastEventId && !loadingReactions) { // Only run after mount and when event.id actually changes
lastEventId = event.id; if (!isMounted || !event.id || event.id === lastEventId || loadingReactions) {
// Clear previous reactions map when event changes return;
allReactionsMap.clear(); }
// Use pre-loaded reactions if available, otherwise fetch lastEventId = event.id;
if (preloadedReactions && preloadedReactions.length > 0) { // Clear previous reactions map when event changes
for (const r of preloadedReactions) { allReactionsMap.clear();
allReactionsMap.set(r.id, r);
} // Use pre-loaded reactions if available, otherwise fetch
filterDeletedReactions(preloadedReactions).then(filtered => { if (preloadedReactions && preloadedReactions.length > 0) {
processReactions(filtered); for (const r of preloadedReactions) {
}); allReactionsMap.set(r.id, r);
} else {
loadReactions();
} }
filterDeletedReactions(preloadedReactions).then(filtered => {
processReactions(filtered);
});
} else {
loadReactions();
} }
}); });
@ -112,14 +122,14 @@
allReactionsMap.clear(); allReactionsMap.clear();
const reactionsWithLowerE = await nostrClient.fetchEvents( const reactionsWithLowerE = await nostrClient.fetchEvents(
[{ kinds: [KIND.REACTION], '#e': [event.id] }], [{ kinds: [KIND.REACTION], '#e': [event.id], limit: 100 }],
reactionRelays, reactionRelays,
{ useCache: true, cacheResults: true, onUpdate: handleReactionUpdate } { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate, timeout: 5000 }
); );
const reactionsWithUpperE = await nostrClient.fetchEvents( const reactionsWithUpperE = await nostrClient.fetchEvents(
[{ kinds: [KIND.REACTION], '#E': [event.id] }], [{ kinds: [KIND.REACTION], '#E': [event.id], limit: 100 }],
reactionRelays, reactionRelays,
{ useCache: true, cacheResults: true, onUpdate: handleReactionUpdate } { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate, timeout: 5000 }
); );
console.debug(`[FeedReactionButtons] Reactions fetched:`, { console.debug(`[FeedReactionButtons] Reactions fetched:`, {
@ -190,12 +200,21 @@
async function filterDeletedReactions(reactions: NostrEvent[]): Promise<NostrEvent[]> { async function filterDeletedReactions(reactions: NostrEvent[]): Promise<NostrEvent[]> {
if (reactions.length === 0) return reactions; if (reactions.length === 0) return reactions;
// Fetch deletion events (kind 5) to filter out deleted reactions // Optimize: Instead of fetching all deletion events for all users,
// fetch deletion events that reference the specific reaction IDs we have
// This is much more efficient and limits memory usage
const reactionRelays = relayManager.getProfileReadRelays(); const reactionRelays = relayManager.getProfileReadRelays();
const reactionIds = reactions.map(r => r.id);
// Limit to first 100 reactions to avoid massive queries
const limitedReactionIds = reactionIds.slice(0, 100);
// Fetch deletion events that reference these specific reaction IDs
// This is much more efficient than fetching all deletion events from all users
const deletionEvents = await nostrClient.fetchEvents( const deletionEvents = await nostrClient.fetchEvents(
[{ kinds: [KIND.EVENT_DELETION], authors: Array.from(new Set(reactions.map(r => r.pubkey))) }], [{ kinds: [KIND.EVENT_DELETION], '#e': limitedReactionIds, limit: 100 }],
reactionRelays, reactionRelays,
{ useCache: true } { useCache: true, timeout: 5000 }
); );
console.debug(`[FeedReactionButtons] Deletion events fetched:`, { console.debug(`[FeedReactionButtons] Deletion events fetched:`, {
@ -208,40 +227,24 @@
})) }))
}); });
// Build a set of deleted reaction event IDs (keyed by pubkey) // Build a set of deleted reaction event IDs (more efficient - just a Set)
const deletedReactionIdsByPubkey = new Map<string, Set<string>>(); const deletedReactionIds = new Set<string>();
for (const deletionEvent of deletionEvents) { for (const deletionEvent of deletionEvents) {
const pubkey = deletionEvent.pubkey;
if (!deletedReactionIdsByPubkey.has(pubkey)) {
deletedReactionIdsByPubkey.set(pubkey, new Set());
}
// Kind 5 events have 'e' tags pointing to deleted events // Kind 5 events have 'e' tags pointing to deleted events
for (const tag of deletionEvent.tags) { for (const tag of deletionEvent.tags) {
if (tag[0] === 'e' && tag[1]) { if (tag[0] === 'e' && tag[1]) {
deletedReactionIdsByPubkey.get(pubkey)!.add(tag[1]); deletedReactionIds.add(tag[1]);
} }
} }
} }
console.debug(`[FeedReactionButtons] Deleted reaction IDs by pubkey:`, console.debug(`[FeedReactionButtons] Deleted reaction IDs by pubkey:`,
Array.from(deletedReactionIdsByPubkey.entries()).map(([pubkey, ids]) => ({ Array.from(deletedReactionIds).slice(0, 10).map(id => id.substring(0, 16) + '...')
pubkey: pubkey.substring(0, 16) + '...',
deletedIds: Array.from(ids).map(id => id.substring(0, 16) + '...')
}))
); );
// Filter out deleted reactions // Filter out deleted reactions - much simpler now
const filtered = reactions.filter(reaction => { const filtered = reactions.filter(reaction => {
const deletedIds = deletedReactionIdsByPubkey.get(reaction.pubkey); const isDeleted = deletedReactionIds.has(reaction.id);
const isDeleted = deletedIds && deletedIds.has(reaction.id);
if (isDeleted) {
console.debug(`[FeedReactionButtons] Filtering out deleted reaction:`, {
id: reaction.id.substring(0, 16) + '...',
pubkey: reaction.pubkey.substring(0, 16) + '...',
content: reaction.content,
fullEvent: reaction
});
}
return !isDeleted; return !isDeleted;
}); });

108
src/lib/services/nostr/auth-handler.ts

@ -33,8 +33,14 @@ export async function authenticateWithNIP07(): Promise<string> {
createdAt: Date.now() createdAt: Date.now()
}, {}); // No metadata needed for NIP-07 }, {}); // No metadata needed for NIP-07
// Fetch user relay lists and mute list // Fetch user relay lists and mute list in background with timeout
await loadUserPreferences(pubkey); // Don't block login if relays are slow or unavailable
Promise.race([
loadUserPreferences(pubkey),
new Promise<void>((resolve) => setTimeout(resolve, 5000)) // 5 second timeout
]).catch(() => {
// Silently fail - preference loading errors shouldn't break login
});
// Fetch and cache user's own profile (background-update if already cached) // Fetch and cache user's own profile (background-update if already cached)
fetchProfile(pubkey).catch(() => { fetchProfile(pubkey).catch(() => {
@ -269,42 +275,72 @@ export async function authenticateAsAnonymous(password: string): Promise<string>
* Load user preferences (relay lists, mute list, blocked relays) * Load user preferences (relay lists, mute list, blocked relays)
*/ */
async function loadUserPreferences(pubkey: string): Promise<void> { async function loadUserPreferences(pubkey: string): Promise<void> {
// Fetch relay lists and load into relay manager try {
await relayManager.loadUserPreferences(pubkey); // Fetch relay lists and load into relay manager with timeout
await Promise.race([
// Fetch mute list (kind 10000) relayManager.loadUserPreferences(pubkey),
const muteEvents = await nostrClient.fetchEvents( new Promise<void>((_, reject) =>
[{ kinds: [KIND.MUTE_LIST], authors: [pubkey], limit: 1 }], setTimeout(() => reject(new Error('Relay list fetch timeout')), 5000)
relayManager.getProfileReadRelays(), )
{ useCache: true, cacheResults: true } ]);
); } catch (error) {
// If relay list fetch fails, continue with default relays
if (muteEvents.length > 0) { console.debug('[auth-handler] Failed to load user relay preferences, using defaults');
const mutedPubkeys = muteEvents[0].tags }
.filter((t) => t[0] === 'p')
.map((t) => t[1]) try {
.filter(Boolean) as string[]; // Fetch mute list (kind 10000) with timeout
muteList.clear(); const muteEvents = await Promise.race([
mutedPubkeys.forEach(pk => muteList.add(pk)); nostrClient.fetchEvents(
[{ kinds: [KIND.MUTE_LIST], authors: [pubkey], limit: 1 }],
relayManager.getProfileReadRelays(),
{ useCache: true, cacheResults: true, timeout: 5000 }
),
new Promise<NostrEvent[]>((resolve) =>
setTimeout(() => resolve([]), 5000)
)
]);
if (muteEvents.length > 0) {
const mutedPubkeys = muteEvents[0].tags
.filter((t) => t[0] === 'p')
.map((t) => t[1])
.filter(Boolean) as string[];
muteList.clear();
mutedPubkeys.forEach(pk => muteList.add(pk));
}
} catch (error) {
// Silently fail - mute list fetch errors shouldn't break login
console.debug('[auth-handler] Failed to load mute list');
} }
// Fetch blocked relays (kind 10006) try {
const blockedRelayEvents = await nostrClient.fetchEvents( // Fetch blocked relays (kind 10006) with timeout
[{ kinds: [KIND.BLOCKED_RELAYS], authors: [pubkey], limit: 1 }], const blockedRelayEvents = await Promise.race([
relayManager.getProfileReadRelays(), nostrClient.fetchEvents(
{ useCache: true, cacheResults: true } [{ kinds: [KIND.BLOCKED_RELAYS], authors: [pubkey], limit: 1 }],
); relayManager.getProfileReadRelays(),
{ useCache: true, cacheResults: true, timeout: 5000 }
if (blockedRelayEvents.length > 0) { ),
const blocked = blockedRelayEvents[0].tags new Promise<NostrEvent[]>((resolve) =>
.filter((t) => t[0] === 'relay') setTimeout(() => resolve([]), 5000)
.map((t) => t[1]) )
.filter(Boolean) as string[]; ]);
blockedRelays.clear();
blocked.forEach(r => blockedRelays.add(r)); if (blockedRelayEvents.length > 0) {
const blocked = blockedRelayEvents[0].tags
// Update relay manager with blocked relays .filter((t) => t[0] === 'relay')
relayManager.updateBlockedRelays(blockedRelays); .map((t) => t[1])
.filter(Boolean) as string[];
blockedRelays.clear();
blocked.forEach(r => blockedRelays.add(r));
// Update relay manager with blocked relays
relayManager.updateBlockedRelays(blockedRelays);
}
} catch (error) {
// Silently fail - blocked relay fetch errors shouldn't break login
console.debug('[auth-handler] Failed to load blocked relays');
} }
} }

129
src/lib/services/nostr/relay-manager.ts

@ -9,6 +9,7 @@ import { config } from './config.js';
import { sessionManager } from '../auth/session-manager.js'; import { sessionManager } from '../auth/session-manager.js';
import { nostrClient } from './nostr-client.js'; import { nostrClient } from './nostr-client.js';
import { KIND } from '../../types/kind-lookup.js'; import { KIND } from '../../types/kind-lookup.js';
import type { NostrEvent } from '../../types/nostr.js';
class RelayManager { class RelayManager {
private userInbox: string[] = []; private userInbox: string[] = [];
@ -21,67 +22,89 @@ class RelayManager {
* Load user relay preferences * Load user relay preferences
*/ */
async loadUserPreferences(pubkey: string): Promise<void> { async loadUserPreferences(pubkey: string): Promise<void> {
// Fetch relay lists (includes both kind 10002 and 10432) try {
const { inbox, outbox } = await fetchRelayLists(pubkey); // Fetch relay lists (includes both kind 10002 and 10432) with timeout
this.userInbox = inbox; const { inbox, outbox } = await Promise.race([
this.userOutbox = outbox; fetchRelayLists(pubkey),
new Promise<{ inbox: string[]; outbox: string[] }>((resolve) =>
// Also fetch local relays separately to track read/write indicators setTimeout(() => resolve({ inbox: [], outbox: [] }), 5000)
// Local relays are used as external cache )
const relayList = [ ]);
...config.defaultRelays, this.userInbox = inbox;
...config.profileRelays this.userOutbox = outbox;
]; } catch (error) {
const localRelayEvents = await nostrClient.fetchEvents( // If relay list fetch fails, use empty lists (default relays will be used)
[{ kinds: [KIND.LOCAL_RELAYS], authors: [pubkey], limit: 1 }], this.userInbox = [];
relayList, this.userOutbox = [];
{ useCache: true, cacheResults: true } }
);
try {
const localRelaysRead: string[] = []; // Also fetch local relays separately to track read/write indicators
const localRelaysWrite: string[] = []; // Local relays are used as external cache
const relayList = [
for (const event of localRelayEvents) { ...config.defaultRelays,
for (const tag of event.tags) { ...config.profileRelays
if (tag[0] === 'r' && tag[1]) { ];
const url = tag[1].trim(); const localRelayEvents = await Promise.race([
if (!url) continue; nostrClient.fetchEvents(
[{ kinds: [KIND.LOCAL_RELAYS], authors: [pubkey], limit: 1 }],
const markers = tag.slice(2); relayList,
{ useCache: true, cacheResults: true, timeout: 5000 }
// If no markers, relay is both read and write ),
if (markers.length === 0) { new Promise<NostrEvent[]>((resolve) =>
if (!localRelaysRead.includes(url)) { setTimeout(() => resolve([]), 5000)
localRelaysRead.push(url); )
} ]);
if (!localRelaysWrite.includes(url)) {
localRelaysWrite.push(url); const localRelaysRead: string[] = [];
const localRelaysWrite: string[] = [];
for (const event of localRelayEvents) {
for (const tag of event.tags) {
if (tag[0] === 'r' && tag[1]) {
const url = tag[1].trim();
if (!url) continue;
const markers = tag.slice(2);
// If no markers, relay is both read and write
if (markers.length === 0) {
if (!localRelaysRead.includes(url)) {
localRelaysRead.push(url);
}
if (!localRelaysWrite.includes(url)) {
localRelaysWrite.push(url);
}
continue;
} }
continue;
}
// Check for explicit markers // Check for explicit markers
const hasRead = markers.includes('read'); const hasRead = markers.includes('read');
const hasWrite = markers.includes('write'); const hasWrite = markers.includes('write');
// Determine read/write permissions // Determine read/write permissions
// If only 'read' marker: read=true, write=false // If only 'read' marker: read=true, write=false
// If only 'write' marker: read=false, write=true // If only 'write' marker: read=false, write=true
// If both or neither: both true (default behavior) // If both or neither: both true (default behavior)
const read = hasRead || (!hasRead && !hasWrite); const read = hasRead || (!hasRead && !hasWrite);
const write = hasWrite || (!hasRead && !hasWrite); const write = hasWrite || (!hasRead && !hasWrite);
if (read && !localRelaysRead.includes(url)) { if (read && !localRelaysRead.includes(url)) {
localRelaysRead.push(url); localRelaysRead.push(url);
} }
if (write && !localRelaysWrite.includes(url)) { if (write && !localRelaysWrite.includes(url)) {
localRelaysWrite.push(url); localRelaysWrite.push(url);
}
} }
} }
} }
this.userLocalRelaysRead = localRelaysRead;
this.userLocalRelaysWrite = localRelaysWrite;
} catch (error) {
// If local relay fetch fails, use empty lists
this.userLocalRelaysRead = [];
this.userLocalRelaysWrite = [];
} }
this.userLocalRelaysRead = localRelaysRead;
this.userLocalRelaysWrite = localRelaysWrite;
// Get blocked relays // Get blocked relays
this.blockedRelays = getBlockedRelays(); this.blockedRelays = getBlockedRelays();

Loading…
Cancel
Save