From 69ed47969735299aaa642b0c2d27ae7eae917f59 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Jan 2026 21:29:34 +0000 Subject: [PATCH] fix: Build errors in inbox and gift wrap implementation Fixed TypeScript compilation errors and updated API usage: **Type Fixes:** - Changed `Signer` to `ISigner` (applesauce-signers v5 API) - Fixed InboxViewer to use proper state management methods - Removed unused imports (useEffect, Badge, db) **State Management:** - Added `setPrivateMessagesEnabled()` to Logic - Added `setAutoDecryptGiftWraps()` to Logic - Exposed both methods in useGrimoire hook - Updated InboxViewer to use new setters instead of setState **Relay Pool API:** - Replaced `pool.subscribe()` with `createTimelineLoader()` - Updated gift-wrap-loader to use applesauce-loaders pattern - Proper Observable subscription with next/error/complete **Test Fixes:** - Updated all test calls to match new `unwrapAndUnseal()` signature - Removed unused `beforeEach` import - Fixed all instances of 3-arg calls to 2-arg calls All changes maintain the same functionality while conforming to the correct applesauce v5 and TypeScript APIs. --- src/components/InboxViewer.tsx | 17 +++------- src/core/logic.ts | 26 +++++++++++++++ src/core/state.ts | 16 ++++++++++ src/services/gift-wrap-loader.ts | 45 +++++++++++++++----------- src/services/gift-wrap.test.ts | 54 ++++++++++++++++---------------- src/services/gift-wrap.ts | 10 +++--- 6 files changed, 106 insertions(+), 62 deletions(-) diff --git a/src/components/InboxViewer.tsx b/src/components/InboxViewer.tsx index dd8d141..86b4885 100644 --- a/src/components/InboxViewer.tsx +++ b/src/components/InboxViewer.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useLiveQuery } from "dexie-react-hooks"; import { Mail, @@ -14,15 +14,14 @@ import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; import { Button } from "./ui/button"; import { Label } from "./ui/label"; import { Switch } from "./ui/switch"; -import { Badge } from "./ui/badge"; import giftWrapLoader from "@/services/gift-wrap-loader"; -import db from "@/services/db"; import { toast } from "sonner"; import { use$ } from "applesauce-react/hooks"; import accounts from "@/services/accounts"; export function InboxViewer() { - const { state, setState } = useGrimoire(); + const { state, setPrivateMessagesEnabled, setAutoDecryptGiftWraps } = + useGrimoire(); const activeAccount = use$(accounts.active$); const [isDecrypting, setIsDecrypting] = useState(false); const [decryptResult, setDecryptResult] = useState<{ @@ -54,10 +53,7 @@ export function InboxViewer() { }, [activeAccount?.pubkey]); const handleTogglePrivateMessages = (enabled: boolean) => { - setState((prev) => ({ - ...prev, - privateMessagesEnabled: enabled, - })); + setPrivateMessagesEnabled(enabled); if (enabled) { toast.success("Private messages enabled"); @@ -67,10 +63,7 @@ export function InboxViewer() { }; const handleToggleAutoDecrypt = (enabled: boolean) => { - setState((prev) => ({ - ...prev, - autoDecryptGiftWraps: enabled, - })); + setAutoDecryptGiftWraps(enabled); if (enabled) { toast.success("Auto-decrypt enabled"); diff --git a/src/core/logic.ts b/src/core/logic.ts index 349a520..8ef9de6 100644 --- a/src/core/logic.ts +++ b/src/core/logic.ts @@ -526,3 +526,29 @@ export const clearActiveSpellbook = (state: GrimoireState): GrimoireState => { activeSpellbook: undefined, }; }; + +/** + * Enables or disables NIP-59 private messages (gift wraps). + */ +export const setPrivateMessagesEnabled = ( + state: GrimoireState, + enabled: boolean, +): GrimoireState => { + return { + ...state, + privateMessagesEnabled: enabled, + }; +}; + +/** + * Enables or disables auto-decrypt for gift wraps. + */ +export const setAutoDecryptGiftWraps = ( + state: GrimoireState, + enabled: boolean, +): GrimoireState => { + return { + ...state, + autoDecryptGiftWraps: enabled, + }; +}; diff --git a/src/core/state.ts b/src/core/state.ts index a23302b..584f494 100644 --- a/src/core/state.ts +++ b/src/core/state.ts @@ -319,6 +319,20 @@ export const useGrimoire = () => { [setState], ); + const setPrivateMessagesEnabled = useCallback( + (enabled: boolean) => { + setState((prev) => Logic.setPrivateMessagesEnabled(prev, enabled)); + }, + [setState], + ); + + const setAutoDecryptGiftWraps = useCallback( + (enabled: boolean) => { + setState((prev) => Logic.setAutoDecryptGiftWraps(prev, enabled)); + }, + [setState], + ); + const loadSpellbook = useCallback( (spellbook: ParsedSpellbook) => { setState((prev) => SpellbookManager.loadSpellbook(prev, spellbook)); @@ -366,6 +380,8 @@ export const useGrimoire = () => { updateWorkspaceLabel, reorderWorkspaces, setCompactModeKinds, + setPrivateMessagesEnabled, + setAutoDecryptGiftWraps, loadSpellbook, clearActiveSpellbook, switchToTemporary, diff --git a/src/services/gift-wrap-loader.ts b/src/services/gift-wrap-loader.ts index 93917ce..ac30746 100644 --- a/src/services/gift-wrap-loader.ts +++ b/src/services/gift-wrap-loader.ts @@ -17,7 +17,8 @@ import { BehaviorSubject, Observable } from "rxjs"; import type { NostrEvent } from "@/types/nostr"; -import type { Signer } from "applesauce-signers"; +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"; @@ -49,7 +50,7 @@ class GiftWrapLoader { }); private subscription?: { unsubscribe: () => void }; - private currentSigner?: Signer; + private currentSigner?: ISigner; /** * Observable state of the loader @@ -74,7 +75,7 @@ class GiftWrapLoader { */ async enable( recipientPubkey: string, - signer: Signer, + signer: ISigner, autoDecrypt = false, ): Promise { // Stop any existing subscription @@ -153,34 +154,42 @@ class GiftWrapLoader { `[GiftWrapLoader] Syncing from ${inboxRelays.length} inbox relays`, ); - // Subscribe to kind 1059 events for this user + // Subscribe to kind 1059 events for this user using timeline loader const filter = { - kinds: [1059], + kinds: [1059 as number], "#p": [state.recipientPubkey], // Optionally add since: to only get new messages // since: state.lastSync ? Math.floor(state.lastSync / 1000) : undefined, }; - // Store subscription for cleanup - this.subscription = pool.subscribe(inboxRelays, [filter], { - onevent: async (event: NostrEvent) => { - await this.handleGiftWrap(event); + // Create timeline loader for gift wraps + const timeline = createTimelineLoader(pool, { + eventStore, + relays: inboxRelays, + filters: [filter], + }); + + // Subscribe to timeline + this.subscription = timeline.subscribe({ + next: (event: NostrEvent) => { + void this.handleGiftWrap(event); }, - oneose: () => { - console.log("[GiftWrapLoader] EOSE received"); + error: (error: Error) => { + console.error("[GiftWrapLoader] Timeline error:", error); + this.state$.next({ + ...this.state$.value, + loading: false, + errorCount: this.state$.value.errorCount + 1, + }); + }, + complete: () => { + console.log("[GiftWrapLoader] Timeline complete"); this.state$.next({ ...this.state$.value, loading: false, lastSync: Date.now(), }); }, - onclose: (reason: string) => { - console.log(`[GiftWrapLoader] Subscription closed: ${reason}`); - this.state$.next({ - ...this.state$.value, - loading: false, - }); - }, }); // Process any pending gift wraps from database diff --git a/src/services/gift-wrap.test.ts b/src/services/gift-wrap.test.ts index e197ec9..6b395f9 100644 --- a/src/services/gift-wrap.test.ts +++ b/src/services/gift-wrap.test.ts @@ -2,13 +2,13 @@ * Tests for NIP-59 Gift Wrap Service */ -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { describe, it, expect, vi } from "vitest"; import { unwrapAndUnseal, GiftWrapError } from "./gift-wrap"; import type { NostrEvent } from "@/types/nostr"; -import type { Signer } from "applesauce-signers"; +import type { ISigner } from "applesauce-signers"; // Mock signer for testing -function createMockSigner(decryptResponses: Map): Signer & { +function createMockSigner(decryptResponses: Map): ISigner & { nip44Decrypt: (pubkey: string, ciphertext: string) => Promise; } { return { @@ -195,13 +195,13 @@ describe("unwrapAndUnseal", () => { const signer = createMockSigner(decryptResponses); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow(GiftWrapError); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + GiftWrapError, + ); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow("Expected seal kind 13"); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + "Expected seal kind 13", + ); }); it("should reject seal with empty content", async () => { @@ -231,13 +231,13 @@ describe("unwrapAndUnseal", () => { const signer = createMockSigner(decryptResponses); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow(GiftWrapError); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + GiftWrapError, + ); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow("Seal content is empty"); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + "Seal content is empty", + ); }); it("should reject invalid rumor structure", async () => { @@ -275,13 +275,13 @@ describe("unwrapAndUnseal", () => { const signer = createMockSigner(decryptResponses); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow(GiftWrapError); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + GiftWrapError, + ); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow("Rumor missing content"); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + "Rumor missing content", + ); }); it("should handle decryption failures", async () => { @@ -298,13 +298,13 @@ describe("unwrapAndUnseal", () => { // No responses configured - decryption will fail const signer = createMockSigner(new Map()); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow(GiftWrapError); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + GiftWrapError, + ); - await expect( - unwrapAndUnseal(giftWrap, "recipient-pubkey", signer), - ).rejects.toThrow("Failed to decrypt"); + await expect(unwrapAndUnseal(giftWrap, signer)).rejects.toThrow( + "Failed to decrypt", + ); }); }); }); diff --git a/src/services/gift-wrap.ts b/src/services/gift-wrap.ts index 4c1983a..a6ba0d5 100644 --- a/src/services/gift-wrap.ts +++ b/src/services/gift-wrap.ts @@ -13,7 +13,7 @@ */ import type { NostrEvent } from "@/types/nostr"; -import type { Signer } from "applesauce-signers"; +import type { ISigner } from "applesauce-signers"; import db, { GiftWrapEnvelope, DecryptedRumor, @@ -117,7 +117,7 @@ function validateRumor(event: any): NostrEvent { */ async function unwrapGiftWrap( giftWrap: NostrEvent, - signer: Signer, + signer: ISigner, ): Promise { validateGiftWrap(giftWrap); @@ -161,7 +161,7 @@ async function unwrapGiftWrap( */ async function unsealSeal( seal: NostrEvent, - signer: Signer, + signer: ISigner, ): Promise { validateSeal(seal); @@ -208,7 +208,7 @@ async function unsealSeal( */ export async function unwrapAndUnseal( giftWrap: NostrEvent, - signer: Signer, + signer: ISigner, ): Promise<{ seal: NostrEvent; rumor: NostrEvent }> { // Step 1: Unwrap gift wrap to get seal const seal = await unwrapGiftWrap(giftWrap, signer); @@ -230,7 +230,7 @@ export async function unwrapAndUnseal( export async function processGiftWrap( giftWrap: NostrEvent, recipientPubkey: string, - signer: Signer, + signer: ISigner, ): Promise { // Check if already processed const existing = await db.giftWraps.get(giftWrap.id);