diff --git a/src/components/InboxViewer.tsx b/src/components/InboxViewer.tsx index 86b4885..c362d20 100644 --- a/src/components/InboxViewer.tsx +++ b/src/components/InboxViewer.tsx @@ -12,7 +12,6 @@ import { import { useGrimoire } from "@/core/state"; import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; import { Button } from "./ui/button"; -import { Label } from "./ui/label"; import { Switch } from "./ui/switch"; import giftWrapLoader from "@/services/gift-wrap-loader"; import { toast } from "sonner"; @@ -172,9 +171,12 @@ export function InboxViewer() { {/* Enable Private Messages */}
-

Fetch and store encrypted gift wraps from DM relays

@@ -190,7 +192,12 @@ export function InboxViewer() { {privateMessagesEnabled && (
- +

Automatically decrypt gift wraps as they arrive

diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..7bb373c --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,47 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export interface SwitchProps { + id?: string; + checked?: boolean; + onCheckedChange?: (checked: boolean) => void; + disabled?: boolean; + className?: string; +} + +/** + * Simple toggle switch component + * Styled as a sliding toggle for boolean states + */ +export const Switch = React.forwardRef( + ( + { id, checked = false, onCheckedChange, disabled = false, className }, + ref, + ) => { + return ( + + ); + }, +); + +Switch.displayName = "Switch"; diff --git a/src/services/gift-wrap-loader.ts b/src/services/gift-wrap-loader.ts index ac30746..3a42420 100644 --- a/src/services/gift-wrap-loader.ts +++ b/src/services/gift-wrap-loader.ts @@ -163,17 +163,12 @@ class GiftWrapLoader { }; // Create timeline loader for gift wraps - const timeline = createTimelineLoader(pool, { + const loader = createTimelineLoader(pool, inboxRelays, [filter], { eventStore, - relays: inboxRelays, - filters: [filter], }); // Subscribe to timeline - this.subscription = timeline.subscribe({ - next: (event: NostrEvent) => { - void this.handleGiftWrap(event); - }, + this.subscription = loader().subscribe({ error: (error: Error) => { console.error("[GiftWrapLoader] Timeline error:", error); this.state$.next({ @@ -192,6 +187,23 @@ 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) { diff --git a/src/services/gift-wrap.test.ts b/src/services/gift-wrap.test.ts index 6b395f9..7d284a3 100644 --- a/src/services/gift-wrap.test.ts +++ b/src/services/gift-wrap.test.ts @@ -8,19 +8,20 @@ import type { NostrEvent } from "@/types/nostr"; import type { ISigner } from "applesauce-signers"; // Mock signer for testing -function createMockSigner(decryptResponses: Map): ISigner & { - nip44Decrypt: (pubkey: string, ciphertext: string) => Promise; -} { +function createMockSigner(decryptResponses: Map): ISigner { return { getPublicKey: vi.fn().mockResolvedValue("mock-pubkey"), signEvent: vi.fn(), - nip44Decrypt: vi.fn(async (pubkey: string, ciphertext: string) => { - const response = decryptResponses.get(`${pubkey}:${ciphertext}`); - if (!response) { - throw new Error("Mock decryption failed: no response configured"); - } - return response; - }), + nip44: { + encrypt: vi.fn(), + decrypt: vi.fn(async (pubkey: string, ciphertext: string) => { + const response = decryptResponses.get(`${pubkey}:${ciphertext}`); + if (!response) { + throw new Error("Mock decryption failed: no response configured"); + } + return response; + }), + }, }; } diff --git a/src/services/gift-wrap.ts b/src/services/gift-wrap.ts index a6ba0d5..e4224ea 100644 --- a/src/services/gift-wrap.ts +++ b/src/services/gift-wrap.ts @@ -121,7 +121,7 @@ async function unwrapGiftWrap( ): Promise { validateGiftWrap(giftWrap); - if (!signer.nip44Decrypt) { + if (!signer.nip44?.decrypt) { throw new GiftWrapError( "Signer does not support NIP-44 decryption", "NO_SIGNER", @@ -130,7 +130,7 @@ async function unwrapGiftWrap( try { // Decrypt using the gift wrap author's pubkey (ephemeral key) - const decryptedContent = await signer.nip44Decrypt( + const decryptedContent = await signer.nip44.decrypt( giftWrap.pubkey, giftWrap.content, ); @@ -165,7 +165,7 @@ async function unsealSeal( ): Promise { validateSeal(seal); - if (!signer.nip44Decrypt) { + if (!signer.nip44?.decrypt) { throw new GiftWrapError( "Signer does not support NIP-44 decryption", "NO_SIGNER", @@ -174,7 +174,7 @@ async function unsealSeal( try { // Decrypt using the seal author's pubkey (sender's real key) - const decryptedContent = await signer.nip44Decrypt( + const decryptedContent = await signer.nip44.decrypt( seal.pubkey, seal.content, );