diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx index 40f84b3..df3f9d9 100644 --- a/src/components/PostEditor/PostContent.tsx +++ b/src/components/PostEditor/PostContent.tsx @@ -320,7 +320,10 @@ export default function PostContent({ // Full success - clean up and close postEditorCache.clearPostCache({ defaultContent, parentEvent }) deleteDraftEventCache(draftEvent) - addReplies([newEvent]) + // Remove relayStatuses before storing the event (it's only for UI feedback) + const cleanEvent = { ...newEvent } + delete (cleanEvent as any).relayStatuses + addReplies([cleanEvent]) close() } catch (error) { console.error('Publishing error:', error) diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 11535c7..6d1a833 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -830,10 +830,9 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { try { const publishResult = await client.publishEvent(relays, event) - // Store relay status for display - if (publishResult.relayStatuses.length > 0) { - (event as any).relayStatuses = publishResult.relayStatuses - } + // Store relay status temporarily for display (but don't persist it on the event) + // This metadata is only for logging/feedback, not part of the actual event + const relayStatuses = publishResult.relayStatuses.length > 0 ? publishResult.relayStatuses : undefined // If publishing failed completely, throw an error so the form doesn't close if (!publishResult.success) { @@ -847,15 +846,35 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { throw error } + // Attach relayStatuses only temporarily for UI feedback, then remove it + // This prevents it from being included in the event when serialized + if (relayStatuses) { + (event as any).relayStatuses = relayStatuses + // Remove it immediately after return so it's not persisted + // The components that need it will read it synchronously + setTimeout(() => { + delete (event as any).relayStatuses + }, 0) + } + return event } catch (error) { // Check for authentication-related errors if (error instanceof AggregateError && (error as any).relayStatuses) { - (event as any).relayStatuses = (error as any).relayStatuses + // Attach relayStatuses temporarily for UI feedback + const errorRelayStatuses = (error as any).relayStatuses as Array<{ url: string; success: boolean; error?: string }> + + // Attach to event temporarily for UI feedback + (event as any).relayStatuses = errorRelayStatuses + + // Remove it after a brief delay to allow UI components to read it + setTimeout(() => { + delete (event as any).relayStatuses + }, 100) // Check if any relay returned an "invalid key" error - const invalidKeyErrors = (error as any).relayStatuses.filter( - (status: any) => status.error && status.error.includes('invalid key') + const invalidKeyErrors = errorRelayStatuses.filter( + (status) => status.error && status.error.includes('invalid key') ) if (invalidKeyErrors.length > 0) { diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 92be61a..c1503b2 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -901,12 +901,16 @@ class ClientService extends EventTarget { } addEventToCache(event: NEvent) { - this.eventDataLoader.prime(event.id, Promise.resolve(event)) - if (isReplaceableEvent(event.kind)) { - const coordinate = getReplaceableCoordinateFromEvent(event) + // Remove relayStatuses before caching (it's metadata for logging, not part of the event) + const cleanEvent = { ...event } as NEvent + delete (cleanEvent as any).relayStatuses + + this.eventDataLoader.prime(cleanEvent.id, Promise.resolve(cleanEvent)) + if (isReplaceableEvent(cleanEvent.kind)) { + const coordinate = getReplaceableCoordinateFromEvent(cleanEvent) const cachedEvent = this.replaceableEventCacheMap.get(coordinate) - if (!cachedEvent || compareEvents(event, cachedEvent) > 0) { - this.replaceableEventCacheMap.set(coordinate, event) + if (!cachedEvent || compareEvents(cleanEvent, cachedEvent) > 0) { + this.replaceableEventCacheMap.set(coordinate, cleanEvent) } } } diff --git a/src/services/indexed-db.service.ts b/src/services/indexed-db.service.ts index f9df77c..1823bec 100644 --- a/src/services/indexed-db.service.ts +++ b/src/services/indexed-db.service.ts @@ -167,7 +167,11 @@ class IndexedDbService { } async putReplaceableEvent(event: Event): Promise { - const storeName = this.getStoreNameByKind(event.kind) + // Remove relayStatuses before storing (it's metadata for logging, not part of the event) + const cleanEvent = { ...event } + delete (cleanEvent as any).relayStatuses + + const storeName = this.getStoreNameByKind(cleanEvent.kind) if (!storeName) { return Promise.reject('store name not found') } @@ -191,23 +195,23 @@ class IndexedDbService { if (!this.db.objectStoreNames.contains(storeName)) { console.warn(`Store ${storeName} not found in database. Cannot save event.`) // Return the event anyway (don't reject) - caching is optional - return resolve(event) + return resolve(cleanEvent) } const transaction = this.db.transaction(storeName, 'readwrite') const store = transaction.objectStore(storeName) - const key = this.getReplaceableEventKeyFromEvent(event) + const key = this.getReplaceableEventKeyFromEvent(cleanEvent) const getRequest = store.get(key) getRequest.onsuccess = () => { const oldValue = getRequest.result as TValue | undefined - if (oldValue?.value && oldValue.value.created_at >= event.created_at) { + if (oldValue?.value && oldValue.value.created_at >= cleanEvent.created_at) { transaction.commit() return resolve(oldValue.value) } - const putRequest = store.put(this.formatValue(key, event)) + const putRequest = store.put(this.formatValue(key, cleanEvent)) putRequest.onsuccess = () => { transaction.commit() - resolve(event) + resolve(cleanEvent) } putRequest.onerror = (event) => { @@ -541,7 +545,11 @@ class IndexedDbService { } private async putReplaceableEventWithMaster(event: Event, masterKey: string): Promise { - const storeName = this.getStoreNameByKind(event.kind) + // Remove relayStatuses before storing (it's metadata for logging, not part of the event) + const cleanEvent = { ...event } + delete (cleanEvent as any).relayStatuses + + const storeName = this.getStoreNameByKind(cleanEvent.kind) if (!storeName) { return Promise.reject('store name not found') } @@ -562,16 +570,16 @@ class IndexedDbService { } if (!this.db.objectStoreNames.contains(storeName)) { console.warn(`Store ${storeName} not found in database. Cannot save event.`) - return resolve(event) + return resolve(cleanEvent) } const transaction = this.db.transaction(storeName, 'readwrite') const store = transaction.objectStore(storeName) - const key = this.getReplaceableEventKeyFromEvent(event) + const key = this.getReplaceableEventKeyFromEvent(cleanEvent) const getRequest = store.get(key) getRequest.onsuccess = () => { const oldValue = getRequest.result as TValue | undefined - if (oldValue?.value && oldValue.value.created_at >= event.created_at) { + if (oldValue?.value && oldValue.value.created_at >= cleanEvent.created_at) { // Update master key link even if event is not newer if (oldValue.masterPublicationKey !== masterKey) { const value = this.formatValue(key, oldValue.value) @@ -582,12 +590,12 @@ class IndexedDbService { return resolve(oldValue.value) } // Store with master key link - const value = this.formatValue(key, event) + const value = this.formatValue(key, cleanEvent) value.masterPublicationKey = masterKey const putRequest = store.put(value) putRequest.onsuccess = () => { transaction.commit() - resolve(event) + resolve(cleanEvent) } putRequest.onerror = (event) => {