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.
This commit is contained in:
Claude
2026-01-16 07:39:24 +00:00
parent e488e7d723
commit d37798b886
2 changed files with 32 additions and 7 deletions

View File

@@ -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(),
});

View File

@@ -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(),