From c9e86d07679873858be8a4ebc160eacaf4b5bb64 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Jan 2026 22:23:25 +0000 Subject: [PATCH] fix: Properly connect to DM relays and handle gift wrap events This commit fixes critical performance and connectivity issues with gift wrap loading: **Problem:** - gift-wrap-loader was creating a timeline loader but not using it correctly - Events weren't being handled from the subscription (only error/complete) - Used inefficient eventStore.timeline() query instead of handling relay events - App never actually connected to DM relays to fetch gift wraps **Solution:** - Switched to pool.subscription() pattern (like chat adapters use) - Handle events directly in next() callback as they arrive from relays - Track EOSE from each relay to detect when loading is complete - Process gift wraps immediately when received (better performance) - Added detailed logging for debugging relay connections **What now works:** - App connects to your DM relays when private messages enabled - Fetches kind 1059 gift wraps from relays in real-time - Processes events as they arrive (not batched) - Console shows: relay connections, event count, EOSE tracking - Counts update reactively via Dexie's useLiveQuery **Console output you'll see:** - "[GiftWrapLoader] Subscribing to kind 1059 events on X relays" - "[GiftWrapLoader] Received gift wrap abc123... (X total)" - "[GiftWrapLoader] EOSE X/Y from relay" - "[GiftWrapLoader] All relays sent EOSE, received X gift wraps" This follows the same proven pattern used by chat adapters and ensures proper relay connectivity. --- src/services/gift-wrap-loader.ts | 77 +++++++++++++++++++------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/src/services/gift-wrap-loader.ts b/src/services/gift-wrap-loader.ts index b72eb8f..55a687b 100644 --- a/src/services/gift-wrap-loader.ts +++ b/src/services/gift-wrap-loader.ts @@ -18,7 +18,6 @@ import { BehaviorSubject, Observable } from "rxjs"; import type { NostrEvent } from "@/types/nostr"; import type { ISigner } from "applesauce-signers"; -import { createTimelineLoader } from "applesauce-loaders/loaders"; import pool from "./relay-pool"; import eventStore from "./event-store"; import { relayListCache } from "./relay-list-cache"; @@ -164,7 +163,7 @@ class GiftWrapLoader { relays: inboxRelays, }); - // Subscribe to kind 1059 events for this user using timeline loader + // Subscribe to kind 1059 events for this user const filter = { kinds: [1059 as number], "#p": [state.recipientPubkey], @@ -172,15 +171,53 @@ class GiftWrapLoader { // since: state.lastSync ? Math.floor(state.lastSync / 1000) : undefined, }; - // Create timeline loader for gift wraps - const loader = createTimelineLoader(pool, inboxRelays, [filter], { - eventStore, - }); + console.log( + `[GiftWrapLoader] Subscribing to kind 1059 events on ${inboxRelays.length} relays`, + ); - // Subscribe to timeline - this.subscription = loader().subscribe({ + // Use pool.subscription to connect to relays and fetch events + const obs = pool.subscription(inboxRelays, [filter], { eventStore }); + + let eventCount = 0; + let eoseCount = 0; + + this.subscription = obs.subscribe({ + next: (response) => { + if (typeof response === "string") { + // EOSE received from a relay + eoseCount++; + console.log( + `[GiftWrapLoader] EOSE ${eoseCount}/${inboxRelays.length} from relay`, + ); + + // When we've received EOSE from all relays, we're done loading + if (eoseCount >= inboxRelays.length) { + console.log( + `[GiftWrapLoader] All relays sent EOSE, received ${eventCount} gift wraps`, + ); + this.state$.next({ + ...this.state$.value, + loading: false, + lastSync: Date.now(), + }); + + // Process pending gift wraps if auto-decrypt is enabled + void this.processPendingGiftWraps(); + } + } else { + // Event received from relay + const event = response as NostrEvent; + eventCount++; + console.log( + `[GiftWrapLoader] Received gift wrap ${event.id.slice(0, 8)} (${eventCount} total)`, + ); + + // Process the gift wrap immediately + void this.handleGiftWrap(event); + } + }, error: (error: Error) => { - console.error("[GiftWrapLoader] Timeline error:", error); + console.error("[GiftWrapLoader] Subscription error:", error); this.state$.next({ ...this.state$.value, loading: false, @@ -188,7 +225,7 @@ class GiftWrapLoader { }); }, complete: () => { - console.log("[GiftWrapLoader] Timeline complete"); + console.log("[GiftWrapLoader] Subscription completed"); this.state$.next({ ...this.state$.value, loading: false, @@ -196,26 +233,6 @@ class GiftWrapLoader { }); }, }); - - // Handle events from timeline via eventStore subscription - // Timeline loader automatically adds events to eventStore - const eventSub = eventStore.timeline([filter]).subscribe((events) => { - events.forEach((event) => { - void this.handleGiftWrap(event); - }); - }); - - // Store both subscriptions for cleanup - const originalUnsub = this.subscription.unsubscribe.bind( - this.subscription, - ); - this.subscription.unsubscribe = () => { - originalUnsub(); - eventSub.unsubscribe(); - }; - - // Process any pending gift wraps from database - await this.processPendingGiftWraps(); } catch (error) { console.error("[GiftWrapLoader] Sync error:", error); this.state$.next({