From d37798b8866b00cb89eb95a7fc67f550062766ca Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Jan 2026 07:39:24 +0000 Subject: [PATCH] fix: Strip Symbol properties before storing events in Dexie CRITICAL FIX for "can't serialize event with wrong or missing properties" error. The Problem: - Applesauce attaches Symbol properties to events for caching (SealSymbol, RumorSymbol, etc.) - These Symbols cannot be serialized by Dexie/IndexedDB - Attempting to store events with Symbols causes serialization errors The Solution: - Created serializableEvent() helper that creates plain object copies - Strips all Symbols and non-enumerable properties - Handles rumors (unsigned events without sig field) by using empty string - Applied to all event storage points: * Gift wrap events in envelopes * Seal events in DecryptedRumor * Rumor events in DecryptedRumor * Pending gift wraps stored by loader This ensures Dexie only receives plain, serializable objects while applesauce can still use its Symbol-based caching in memory. --- src/services/gift-wrap-loader.ts | 9 +++++++-- src/services/gift-wrap.ts | 30 +++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/services/gift-wrap-loader.ts b/src/services/gift-wrap-loader.ts index 7d22342..7960295 100644 --- a/src/services/gift-wrap-loader.ts +++ b/src/services/gift-wrap-loader.ts @@ -22,7 +22,11 @@ import pool from "./relay-pool"; import eventStore from "./event-store"; import { relayListCache } from "./relay-list-cache"; import { dmRelayListCache } from "./dm-relay-list-cache"; -import { processGiftWrap, getPendingGiftWraps } from "./gift-wrap"; +import { + processGiftWrap, + getPendingGiftWraps, + serializableEvent, +} from "./gift-wrap"; import db from "./db"; /** @@ -235,10 +239,11 @@ class GiftWrapLoader { await processGiftWrap(event, state.recipientPubkey, this.currentSigner); } else { // Otherwise, just store the envelope as pending + // Use serializableEvent to strip Symbol properties before storing await db.giftWraps.put({ id: event.id, recipientPubkey: state.recipientPubkey, - event, + event: serializableEvent(event), status: "pending", receivedAt: Date.now(), }); diff --git a/src/services/gift-wrap.ts b/src/services/gift-wrap.ts index 7566e32..baf834b 100644 --- a/src/services/gift-wrap.ts +++ b/src/services/gift-wrap.ts @@ -42,6 +42,26 @@ export class GiftWrapError extends Error { } } +/** + * Creates a plain serializable copy of an event object + * Strips Symbols and non-enumerable properties added by applesauce + * + * CRITICAL: Applesauce attaches Symbol properties to event objects for caching. + * These Symbols cannot be serialized by Dexie (IndexedDB). Always use this function + * before storing events in the database. + */ +export function serializableEvent(event: any): NostrEvent { + return { + id: event.id, + pubkey: event.pubkey, + created_at: event.created_at, + kind: event.kind, + tags: event.tags, + content: event.content, + sig: event.sig || "", // Rumors don't have sig, use empty string + }; +} + /** * Unwraps a gift wrap and unseals to extract the rumor (full process) * Uses applesauce-common unlockGiftWrap helper @@ -110,11 +130,11 @@ export async function processGiftWrap( // Applesauce caches seal/rumor on the event object via symbols eventStore.add(giftWrap); - // Store gift wrap envelope + // Store gift wrap envelope (use plain object to avoid Symbol serialization issues) const envelope: GiftWrapEnvelope = { id: giftWrap.id, recipientPubkey, - event: giftWrap, + event: serializableEvent(giftWrap), status: "pending", receivedAt: existing?.receivedAt || Date.now(), processedAt: Date.now(), @@ -124,13 +144,13 @@ export async function processGiftWrap( // Unwrap and unseal const { seal, rumor } = await unwrapAndUnseal(giftWrap, signer); - // Store decrypted rumor + // Store decrypted rumor (use plain objects to avoid Symbol serialization issues) const decryptedRumor: DecryptedRumor = { giftWrapId: giftWrap.id, recipientPubkey, senderPubkey: seal.pubkey, - seal, - rumor, + seal: serializableEvent(seal), + rumor: serializableEvent(rumor), rumorCreatedAt: rumor.created_at, rumorKind: rumor.kind, decryptedAt: Date.now(),