mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 23:47:12 +02:00
fix: Add decrypt button to NIP-17 chat and load cached gift wraps
- Add decrypt button to ChatViewer header for NIP-17 conversations - Show pending count with unlock icon, allows decryption directly from chat - Load cached gift wraps from Dexie on cold start (not just EventStore) - EventStore is in-memory only, so on app reload we need to query Dexie - Add events back to EventStore after loading from Dexie for consistency - This fixes cold start scenarios where chat $me shows nothing
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
AlertTriangle,
|
||||
RefreshCw,
|
||||
Paperclip,
|
||||
Unlock,
|
||||
} from "lucide-react";
|
||||
import { getZapRequest } from "applesauce-common/helpers/zap";
|
||||
import { toast } from "sonner";
|
||||
@@ -378,6 +379,42 @@ export function ChatViewer({
|
||||
}
|
||||
}, [protocol]);
|
||||
|
||||
// NIP-17 decrypt state
|
||||
const [isDecrypting, setIsDecrypting] = useState(false);
|
||||
const pendingCount =
|
||||
use$(
|
||||
() =>
|
||||
protocol === "nip-17" ? nip17Adapter.getPendingCount$() : undefined,
|
||||
[protocol],
|
||||
) ?? 0;
|
||||
|
||||
// Handle decrypt for NIP-17
|
||||
const handleDecrypt = useCallback(async () => {
|
||||
if (protocol !== "nip-17") return;
|
||||
setIsDecrypting(true);
|
||||
try {
|
||||
const result = await nip17Adapter.decryptPending();
|
||||
console.log(
|
||||
`[Chat] Decrypted ${result.success} messages, ${result.failed} failed`,
|
||||
);
|
||||
if (result.success > 0) {
|
||||
toast.success(
|
||||
`Decrypted ${result.success} message${result.success !== 1 ? "s" : ""}`,
|
||||
);
|
||||
}
|
||||
if (result.failed > 0) {
|
||||
toast.warning(
|
||||
`${result.failed} message${result.failed !== 1 ? "s" : ""} failed to decrypt`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Chat] Decrypt error:", error);
|
||||
toast.error("Failed to decrypt messages");
|
||||
} finally {
|
||||
setIsDecrypting(false);
|
||||
}
|
||||
}, [protocol]);
|
||||
|
||||
// State for retry trigger
|
||||
const [retryCount, setRetryCount] = useState(0);
|
||||
|
||||
@@ -810,6 +847,28 @@ export function ChatViewer({
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground p-1">
|
||||
<MembersDropdown participants={derivedParticipants} />
|
||||
<RelaysDropdown conversation={conversation} />
|
||||
{/* NIP-17 decrypt button */}
|
||||
{protocol === "nip-17" && pendingCount > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 gap-1 text-xs px-2"
|
||||
onClick={handleDecrypt}
|
||||
disabled={isDecrypting}
|
||||
>
|
||||
{isDecrypting ? (
|
||||
<>
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
<span className="hidden sm:inline">Decrypting...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Unlock className="size-3" />
|
||||
<span>{pendingCount}</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{(conversation.type === "group" ||
|
||||
conversation.type === "live-chat" ||
|
||||
conversation.type === "dm") && (
|
||||
|
||||
@@ -34,6 +34,7 @@ import accountManager from "@/services/accounts";
|
||||
import { hub } from "@/services/hub";
|
||||
import { relayListCache } from "@/services/relay-list-cache";
|
||||
import { AGGREGATOR_RELAYS } from "@/services/loaders";
|
||||
import { getEventsForFilters } from "@/services/event-cache";
|
||||
import { isNip05, resolveNip05 } from "@/lib/nip05";
|
||||
import { getDisplayName } from "@/lib/nostr-utils";
|
||||
import { isValidHexPubkey } from "@/lib/nostr-validation";
|
||||
@@ -660,6 +661,10 @@ export class Nip17Adapter extends ChatProtocolAdapter {
|
||||
|
||||
this.subscriptionActive = true;
|
||||
|
||||
// First, load any cached gift wraps from EventStore (persisted to Dexie)
|
||||
// This is critical for cold start scenarios
|
||||
await this.loadCachedGiftWraps(pubkey);
|
||||
|
||||
// Subscribe to eventStore.insert$ to catch gift wraps added locally (e.g., after sending)
|
||||
// This is critical for immediate display of sent messages
|
||||
const insertSub = eventStore.insert$.subscribe((event) => {
|
||||
@@ -724,6 +729,37 @@ export class Nip17Adapter extends ChatProtocolAdapter {
|
||||
this.subscriptions.set(conversationId, relaySub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cached gift wraps from Dexie (persistent storage)
|
||||
* This is called on cold start to restore previously received gift wraps
|
||||
* We query Dexie directly because EventStore is in-memory and empty on cold start
|
||||
*/
|
||||
private async loadCachedGiftWraps(pubkey: string): Promise<void> {
|
||||
try {
|
||||
// Query Dexie directly for cached gift wraps addressed to this user
|
||||
// EventStore is in-memory only, so on cold start it's empty
|
||||
const cachedGiftWraps = await getEventsForFilters([
|
||||
{ kinds: [GIFT_WRAP_KIND], "#p": [pubkey] },
|
||||
]);
|
||||
|
||||
if (cachedGiftWraps.length > 0) {
|
||||
console.log(
|
||||
`[NIP-17] Loading ${cachedGiftWraps.length} cached gift wrap(s) from Dexie`,
|
||||
);
|
||||
for (const giftWrap of cachedGiftWraps) {
|
||||
// Add to EventStore so other parts of the app can access it
|
||||
eventStore.add(giftWrap);
|
||||
// Handle in adapter state
|
||||
this.handleGiftWrap(giftWrap);
|
||||
}
|
||||
} else {
|
||||
console.log("[NIP-17] No cached gift wraps found in Dexie");
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[NIP-17] Failed to load cached gift wraps:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a received or sent gift wrap
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user