mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-16 17:48:34 +02:00
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.
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<void> {
|
||||
// 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
|
||||
|
||||
@@ -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<string, string>): Signer & {
|
||||
function createMockSigner(decryptResponses: Map<string, string>): ISigner & {
|
||||
nip44Decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
||||
} {
|
||||
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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<NostrEvent> {
|
||||
validateGiftWrap(giftWrap);
|
||||
|
||||
@@ -161,7 +161,7 @@ async function unwrapGiftWrap(
|
||||
*/
|
||||
async function unsealSeal(
|
||||
seal: NostrEvent,
|
||||
signer: Signer,
|
||||
signer: ISigner,
|
||||
): Promise<NostrEvent> {
|
||||
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<DecryptedRumor | null> {
|
||||
// Check if already processed
|
||||
const existing = await db.giftWraps.get(giftWrap.id);
|
||||
|
||||
Reference in New Issue
Block a user