diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index 2f5601d..d256f57 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -126,3 +126,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772298906,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"6aa4dcd1b3d8a933710a6eb43321aa4faaba56598c735a634069c882c83b4f03","sig":"80ce253e890e8e84c8138e004bc2aaea402379d9aa67f62793ac7a4b344de6a7223f46fc733b240215a983a3a9b574ea8d0858a184f06df58ee66212ba58ee53"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772299137,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","more-muted replyt-to"]],"content":"Signed commit: more-muted replyt-to","id":"fc0a91b526083b640d8116592fcac064fcf3cec9625b48dbd41c3877b2fe5444","sig":"998273d70d827ffbb939b4c149ff88e11c9f3aae3c5ddee78d860710f7fbff42c5ceed9433367b530bdc2869f9d382eb449537f813cf745c49f1a87a36926502"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772299733,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","make relay timeouts more efficient\nsuppress diff on initial commit"]],"content":"Signed commit: make relay timeouts more efficient\nsuppress diff on initial commit","id":"5fbc2dfb13acab011df5a394a022267e69bbe908e696c4389e0c07ba83d58a0d","sig":"daf46d563c413e2481be2cbd2b00d3015cf601e19fe0a191ffbb18c2c07508b17e34ebda5c903a1391914f991cecd7a7a4e809fcba45e1f14ebab674117eb53c"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772300935,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"3618c494d594408165ebf461e290676817ab6cc8b0b076ccc02b35a487ae8da1","sig":"d9106ce1318e703df1c366835be69c0cab12fba3a9be0fed944b17e55fd3b44f3fc9d45b32366d1d237452f099ab3b07f8ad6199660972ce571ec23ae264e873"} diff --git a/src/app.html b/src/app.html index 4f01d56..df1ae5c 100644 --- a/src/app.html +++ b/src/app.html @@ -39,6 +39,100 @@ } // Other unhandled rejections will still be logged by the browser }); + + // Suppress WebSocket connection errors for nostr relays + // These errors occur when relays are down or unreachable, which is expected behavior + // SimplePool from nostr-tools will retry connections, causing repeated console errors + (function() { + // List of known nostr relay URLs to suppress errors for + const nostrRelayPatterns = [ + 'wss://orly-relay.imwald.eu', + 'wss://nostr.sovbit.host', + 'wss://nostr21.com', + 'wss://theforest.nostr1.com', + 'wss://nostr.land', + 'wss://relay.damus.io', + 'wss://thecitadel.nostr1.com', + 'wss://freelay.sovbit.host', + 'wss://bevos.nostr1.com', + 'wss://relay.primal.net', + 'wss://nostr.mom', + 'wss://relay.snort.social', + 'wss://aggr.nostr.land' + ]; + + // Helper function to check if an error message is a nostr relay connection error + function isNostrRelayConnectionError(message) { + if (typeof message !== 'string') return false; + + return nostrRelayPatterns.some(pattern => + message.includes(pattern) && ( + message.includes('Verbindung') || // German: "connection" + message.includes('connection') || + message.includes('kann keine Verbindung') || // German: "cannot establish connection" + message.includes('cannot establish') || + message.includes('WebSocket') || + message.includes('wss://') || + message.includes('aufbauen') // German: "establish" + ) + ); + } + + // Store original console methods + const originalConsoleError = console.error; + const originalConsoleWarn = console.warn; + + // Override console.error to filter out WebSocket connection errors for nostr relays + console.error = function(...args) { + const message = args.join(' '); + + // Suppress nostr relay connection errors (they're expected when relays are down) + if (isNostrRelayConnectionError(message)) { + // Optionally log at debug level instead of error level + if (typeof console.debug === 'function') { + console.debug('[Nostr Relay] Connection failed (expected):', ...args); + } + return; // Don't log the error + } + + // For all other errors, use the original console.error + originalConsoleError.apply(console, args); + }; + + // Also override console.warn for consistency + console.warn = function(...args) { + const message = args.join(' '); + + // Suppress nostr relay connection warnings too + if (isNostrRelayConnectionError(message)) { + if (typeof console.debug === 'function') { + console.debug('[Nostr Relay] Connection warning (expected):', ...args); + } + return; + } + + originalConsoleWarn.apply(console, args); + }; + + // Handle window.onerror for global error catching + const originalOnError = window.onerror; + window.onerror = function(message, source, lineno, colno, error) { + const errorMessage = String(message || ''); + + // Suppress nostr relay connection errors + if (isNostrRelayConnectionError(errorMessage)) { + // Return true to prevent default error handling + return true; + } + + // For other errors, call original handler if it exists + if (originalOnError) { + return originalOnError.call(this, message, source, lineno, colno, error); + } + + return false; + }; + })(); %sveltekit.head% diff --git a/src/lib/components/CommentRenderer.svelte b/src/lib/components/CommentRenderer.svelte index 4e43b09..9d58ab0 100644 --- a/src/lib/components/CommentRenderer.svelte +++ b/src/lib/components/CommentRenderer.svelte @@ -15,7 +15,7 @@ import EventCopyButton from '$lib/components/EventCopyButton.svelte'; import { processContentWithNostrLinks, - getReferencedEventFromDiscussion, + getReferencedEventWithTagType, formatDiscussionTime, type ProcessedContentPart } from '$lib/utils/nostr-links.js'; @@ -42,9 +42,16 @@ nested = false }: Props = $props(); - const referencedEvent = $derived(commentEvent - ? getReferencedEventFromDiscussion(commentEvent, eventCache) + const referencedEventWithTag = $derived(commentEvent + ? getReferencedEventWithTagType(commentEvent, eventCache) : undefined); + + const referencedEvent = $derived(referencedEventWithTag?.event); + const referencedTagType = $derived(referencedEventWithTag?.tagType); + + const referencedLabel = $derived( + referencedTagType === 'q' ? 'Quoting:' : 'Reply-To:' + ); const contentParts = $derived(processContentWithNostrLinks(comment.content, eventCache, profileCache)); @@ -68,7 +75,7 @@ {#if referencedEvent}
- Replying to: + {referencedLabel} {formatDiscussionTime(referencedEvent.created_at)}
diff --git a/src/lib/utils/nostr-links.ts b/src/lib/utils/nostr-links.ts index c394c39..664de58 100644 --- a/src/lib/utils/nostr-links.ts +++ b/src/lib/utils/nostr-links.ts @@ -381,11 +381,18 @@ export function getReferencedEventFromDiscussion( event: NostrEvent, eventCache: Map ): NostrEvent | undefined { + return getReferencedEventWithTagType(event, eventCache)?.event; +} + +export function getReferencedEventWithTagType( + event: NostrEvent, + eventCache: Map +): { event: NostrEvent; tagType: 'e' | 'a' | 'q' } | undefined { // Check e-tag const eTag = event.tags.find(t => t[0] === 'e' && t[1])?.[1]; if (eTag) { const referenced = eventCache.get(eTag); - if (referenced) return referenced; + if (referenced) return { event: referenced, tagType: 'e' }; } // Check a-tag @@ -396,18 +403,20 @@ export function getReferencedEventFromDiscussion( const kind = parseInt(parts[0]); const pubkey = parts[1]; const dTag = parts[2]; - return Array.from(eventCache.values()).find(e => + const referenced = Array.from(eventCache.values()).find(e => e.kind === kind && e.pubkey === pubkey && e.tags.find(t => t[0] === 'd' && t[1] === dTag) ); + if (referenced) return { event: referenced, tagType: 'a' }; } } // Check q-tag const qTag = event.tags.find(t => t[0] === 'q' && t[1])?.[1]; if (qTag) { - return eventCache.get(qTag); + const referenced = eventCache.get(qTag); + if (referenced) return { event: referenced, tagType: 'q' }; } return undefined; diff --git a/src/routes/users/[npub]/+page.svelte b/src/routes/users/[npub]/+page.svelte index 62e1040..51fd51f 100644 --- a/src/routes/users/[npub]/+page.svelte +++ b/src/routes/users/[npub]/+page.svelte @@ -15,6 +15,7 @@ import { combineRelays } from '$lib/config.js'; import { KIND, isEphemeralKind, isReplaceableKind } from '$lib/types/nostr.js'; import { hasUnlimitedAccess } from '$lib/utils/user-access.js'; + import { eventCache } from '$lib/services/nostr/event-cache.js'; const npub = ($page.params as { npub?: string }).npub || ''; @@ -60,6 +61,48 @@ return quotedEvents.find(e => e.id === eventId); }; + // Helper to get author name from pubkey + function getAuthorName(pubkey: string): string { + // Try to find profile event in nostrLinkEvents cache + // Check both by profile key and by iterating values + const profileByKey = nostrLinkEvents.get(`profile:${pubkey}`); + let profileEvent = profileByKey || Array.from(nostrLinkEvents.values()).find( + e => e.kind === 0 && e.pubkey === pubkey + ); + + // If not found in nostrLinkEvents, try global eventCache + if (!profileEvent) { + try { + const cachedProfile = eventCache.getProfile(pubkey); + if (cachedProfile) { + profileEvent = cachedProfile; + } + } catch { + // eventCache not available, continue + } + } + + if (profileEvent) { + try { + const profile = JSON.parse(profileEvent.content); + const name = profile.display_name || profile.name; + if (name && name.trim()) return name.trim(); + } catch { + // Try tags if JSON parse fails + const nameTag = profileEvent.tags.find(t => t[0] === 'name' || t[0] === 'display_name')?.[1]; + if (nameTag && nameTag.trim()) return nameTag.trim(); + } + } + + // Fallback to shortened pubkey + try { + const npub = nip19.npubEncode(pubkey); + return npub.slice(0, 12) + '...'; + } catch { + return pubkey.slice(0, 8) + '...'; + } + } + // Referenced events cache for activity (a-tags and e-tags) - use array for better reactivity let referencedEvents = $state([]); @@ -163,6 +206,7 @@ } // Fetch events + const loadedEvents: NostrEvent[] = []; if (eventIds.length > 0) { try { const events = await Promise.race([ @@ -172,6 +216,7 @@ for (const event of events) { nostrLinkEvents.set(event.id, event); + loadedEvents.push(event); } } catch { // Ignore fetch errors @@ -194,6 +239,7 @@ if (events.length > 0) { nostrLinkEvents.set(events[0].id, events[0]); + loadedEvents.push(events[0]); } } catch { // Ignore fetch errors @@ -201,6 +247,38 @@ } } } + + // Load profiles for authors of loaded events + if (loadedEvents.length > 0) { + const authorPubkeys = new Set(); + for (const event of loadedEvents) { + if (event.pubkey) { + authorPubkeys.add(event.pubkey); + } + } + + // Fetch profiles for all authors + if (authorPubkeys.size > 0) { + try { + const profiles = await Promise.race([ + nostrClient.fetchEvents([{ kinds: [0], authors: Array.from(authorPubkeys), limit: authorPubkeys.size }]), + new Promise((resolve) => setTimeout(() => resolve([]), 10000)) + ]); + + // Store profiles in cache (use a special key format: `profile:${pubkey}`) + for (const profile of profiles) { + // Store with a key that includes the pubkey so we can find it + nostrLinkEvents.set(`profile:${profile.pubkey}`, profile); + // Also store by ID if it has one + if (profile.id) { + nostrLinkEvents.set(profile.id, profile); + } + } + } catch { + // Ignore profile fetch errors + } + } + } } // Get event from nostr: link @@ -1925,7 +2003,8 @@ i * {#if quotedEvent}
- + Quoting: + {formatMessageTime(quotedEvent.created_at)}
{quotedEvent.content || '(No content)'}
@@ -1984,7 +2063,7 @@ i * {:else if part.type === 'event' && part.event}