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 && (
-
Auto-Decrypt Messages
+
+ Auto-Decrypt Messages
+
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,
);