diff --git a/src/components/InboxViewer.tsx b/src/components/InboxViewer.tsx index 91df48e..ea6a283 100644 --- a/src/components/InboxViewer.tsx +++ b/src/components/InboxViewer.tsx @@ -7,8 +7,10 @@ import { CheckCircle2, Trash2, } from "lucide-react"; +import { firstValueFrom } from "rxjs"; import giftWrapManager from "@/services/gift-wrap"; import accountManager from "@/services/accounts"; +import eventStore from "@/services/event-store"; import db, { DecryptedGiftWrap } from "@/services/db"; import { getInboxes } from "applesauce-core/helpers"; @@ -37,7 +39,7 @@ export default function InboxViewer({ action }: InboxViewerProps) { .limit(50) .toArray() .then(setDecrypted); - }, [pubkey, page, syncState.decryptedCount]); + }, [pubkey, page, syncState?.decryptedCount]); // Initial sync on mount useEffect(() => { @@ -46,11 +48,15 @@ export default function InboxViewer({ action }: InboxViewerProps) { const syncGiftWraps = async () => { setLoading(true); try { - // Get inbox relays from user's relay list - const inboxRelays = Array.from( - await getInboxes(pubkey).then((set) => set || new Set()), + // Get inbox relays from user's kind 10002 relay list event + const relayListEvent = await firstValueFrom( + eventStore.replaceable({ kind: 10002, pubkey }), ); + const inboxRelays = relayListEvent + ? Array.from(getInboxes(relayListEvent)) + : []; + // Fallback to default relays if no inbox relays const relays = inboxRelays.length > 0 @@ -100,14 +106,15 @@ export default function InboxViewer({ action }: InboxViewerProps) { setDecrypting(true); try { - // Get pending gift wrap IDs - const pending = await new Promise((resolve) => { - giftWrapManager.getPendingCount(pubkey).subscribe((set) => { - // Get IDs from EventStore - const ids = Array.from(set as any).map((e: any) => e.id); - resolve(ids); - }); - }); + // Get pending gift wrap events (returns array) + const pendingEvents = await firstValueFrom( + giftWrapManager.getPendingGiftWraps(pubkey), + ); + + // Extract IDs + const pending = Array.isArray(pendingEvents) + ? pendingEvents.map((e) => e.id) + : []; if (pending.length === 0) { console.log("[InboxViewer] No pending gift wraps to decrypt"); @@ -190,20 +197,22 @@ export default function InboxViewer({ action }: InboxViewerProps) {
- {syncState.pendingCount} + {syncState?.pendingCount ?? 0} Pending
- {syncState.decryptedCount} + + {syncState?.decryptedCount ?? 0} + Decrypted
- {syncState.failedCount > 0 && ( + {(syncState?.failedCount ?? 0) > 0 && (
- {syncState.failedCount} + {syncState?.failedCount} Failed
)} @@ -213,7 +222,7 @@ export default function InboxViewer({ action }: InboxViewerProps) {
- {syncState.failedCount > 0 && ( + {(syncState?.failedCount ?? 0) > 0 && ( )} - {syncState.decryptedCount > 0 && ( + {(syncState?.decryptedCount ?? 0) > 0 && ( )}
@@ -317,9 +326,9 @@ export default function InboxViewer({ action }: InboxViewerProps) {
{/* Footer with sync info */} - {syncState.lastSyncAt > 0 && ( + {(syncState?.lastSyncAt ?? 0) > 0 && (
- Last synced: {new Date(syncState.lastSyncAt).toLocaleString()} + Last synced: {new Date(syncState!.lastSyncAt).toLocaleString()}
)} diff --git a/src/services/gift-wrap.ts b/src/services/gift-wrap.ts index c9b7875..a6bb534 100644 --- a/src/services/gift-wrap.ts +++ b/src/services/gift-wrap.ts @@ -1,14 +1,12 @@ -import { BehaviorSubject, map, Subscription } from "rxjs"; +import { BehaviorSubject, Subscription, firstValueFrom } from "rxjs"; import { createTimelineLoader } from "applesauce-loaders/loaders"; -import { onlyEvents, mapEventsToStore } from "applesauce-core"; import { unlockGiftWrap, getGiftWrapSeal } from "applesauce-common/helpers"; import { GiftWrapsModel } from "applesauce-common/models"; -import type { Signer } from "applesauce-signers"; +import type { ISigner } from "applesauce-signers"; import type { NostrEvent } from "@/types/nostr"; import eventStore from "./event-store"; import pool from "./relay-pool"; import db from "./db"; -import { getEventsForFilters } from "nostr-idb"; /** * Gift wrap sync state @@ -66,14 +64,13 @@ class GiftWrapManager { ); try { - // Create timeline loader with cache fallback + // Create timeline loader const timeline = createTimelineLoader( pool, relays, { kinds: [1059], "#p": [pubkey], limit: 100 }, { eventStore, - cache: (filters) => getEventsForFilters(await db.open(), filters), }, ); @@ -125,29 +122,32 @@ class GiftWrapManager { console.log(`[GiftWrap] Subscribing to new gift wraps for ${pubkey}`); - // Subscribe to each relay - const subs = relays.map((relay) => - pool - .relay(relay) - .subscription({ - kinds: [1059], - "#p": [pubkey], - since: Math.floor(Date.now() / 1000), - }) - .pipe(onlyEvents(), mapEventsToStore(eventStore)) - .subscribe({ - next: (event) => { - console.log("[GiftWrap] New gift wrap received:", event.id); - this.updateCounts(pubkey); + // Subscribe via pool.subscription (auto-adds to eventStore) + const sub = pool + .subscription( + relays, + [ + { + kinds: [1059], + "#p": [pubkey], + since: Math.floor(Date.now() / 1000), }, - error: (err) => console.error("[GiftWrap] Subscription error:", err), - }), - ); + ], + { eventStore }, + ) + .subscribe({ + next: (response) => { + if (typeof response !== "string") { + // It's an event + console.log("[GiftWrap] New gift wrap received:", response.id); + this.updateCounts(pubkey); + } + }, + error: (err) => console.error("[GiftWrap] Subscription error:", err), + }); - // Store combined subscription - const combined = new Subscription(); - subs.forEach((sub) => combined.add(sub)); - this.subscriptions.set(key, combined); + // Store subscription + this.subscriptions.set(key, sub); } /** @@ -163,12 +163,11 @@ class GiftWrapManager { */ async updateCounts(pubkey: string): Promise { // Get pending count from applesauce model - const pending = await new Promise((resolve) => { - eventStore - .model(GiftWrapsModel, pubkey, true) - .pipe(map((set) => set.size)) - .subscribe(resolve); - }); + const pendingEvents = await firstValueFrom( + eventStore.model(GiftWrapsModel, pubkey, true), + ); + // GiftWrapsModel returns an array of events + const pending = Array.isArray(pendingEvents) ? pendingEvents.length : 0; // Get decrypted count from Dexie const decrypted = await db.decryptedGiftWraps.count(); @@ -188,19 +187,17 @@ class GiftWrapManager { } /** - * Get observable of pending gift wrap count + * Get observable of pending gift wraps */ - getPendingCount(pubkey: string) { - return eventStore - .model(GiftWrapsModel, pubkey, true) - .pipe(map((set) => set.size)); + getPendingGiftWraps(pubkey: string) { + return eventStore.model(GiftWrapsModel, pubkey, true); } /** * Decrypt a single gift wrap * Returns cached result if already decrypted */ - async decryptOne(giftWrapId: string, signer: Signer): Promise { + async decryptOne(giftWrapId: string, signer: ISigner): Promise { // Check cache first const cached = await db.decryptedGiftWraps.get(giftWrapId); if (cached) { @@ -216,8 +213,8 @@ class GiftWrapManager { ); } - // Get gift wrap from EventStore - const gift = eventStore.event(giftWrapId); + // Get gift wrap from EventStore (returns Observable) + const gift = await firstValueFrom(eventStore.event(giftWrapId)); if (!gift) { throw new Error(`Gift wrap not found: ${giftWrapId}`); } @@ -230,14 +227,14 @@ class GiftWrapManager { await db.decryptedGiftWraps.add({ giftWrapId: gift.id, rumorId: rumor.id, - rumor, + rumor: rumor as NostrEvent, // Rumor extends NostrEvent but without sig sealPubkey: getGiftWrapSeal(gift)?.pubkey || "", decryptedAt: Math.floor(Date.now() / 1000), receivedAt: gift.created_at, }); console.log("[GiftWrap] Decrypted successfully:", giftWrapId); - return rumor; + return rumor as NostrEvent; } catch (err) { const errorMessage = String(err); console.error("[GiftWrap] Decryption failed:", giftWrapId, errorMessage); @@ -259,7 +256,7 @@ class GiftWrapManager { */ async *decryptBatch( giftWrapIds: string[], - signer: Signer, + signer: ISigner, ): AsyncGenerator<{ id: string; status: "success" | "error"; diff --git a/tsconfig.node.tsbuildinfo b/tsconfig.node.tsbuildinfo index 5e39d3d..75ea001 100644 --- a/tsconfig.node.tsbuildinfo +++ b/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"root":["./vite.config.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file +{"root":["./vite.config.ts"],"version":"5.6.3"} \ No newline at end of file