Browse Source

keep relay status out of published events

imwald
Silberengel 4 months ago
parent
commit
ca002f14ab
  1. 5
      src/components/PostEditor/PostContent.tsx
  2. 33
      src/providers/NostrProvider/index.tsx
  3. 14
      src/services/client.service.ts
  4. 32
      src/services/indexed-db.service.ts

5
src/components/PostEditor/PostContent.tsx

@ -320,7 +320,10 @@ export default function PostContent({
// Full success - clean up and close // Full success - clean up and close
postEditorCache.clearPostCache({ defaultContent, parentEvent }) postEditorCache.clearPostCache({ defaultContent, parentEvent })
deleteDraftEventCache(draftEvent) 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() close()
} catch (error) { } catch (error) {
console.error('Publishing error:', error) console.error('Publishing error:', error)

33
src/providers/NostrProvider/index.tsx

@ -830,10 +830,9 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
try { try {
const publishResult = await client.publishEvent(relays, event) const publishResult = await client.publishEvent(relays, event)
// Store relay status for display // Store relay status temporarily for display (but don't persist it on the event)
if (publishResult.relayStatuses.length > 0) { // This metadata is only for logging/feedback, not part of the actual event
(event as any).relayStatuses = publishResult.relayStatuses const relayStatuses = publishResult.relayStatuses.length > 0 ? publishResult.relayStatuses : undefined
}
// If publishing failed completely, throw an error so the form doesn't close // If publishing failed completely, throw an error so the form doesn't close
if (!publishResult.success) { if (!publishResult.success) {
@ -847,15 +846,35 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
throw error 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 return event
} catch (error) { } catch (error) {
// Check for authentication-related errors // Check for authentication-related errors
if (error instanceof AggregateError && (error as any).relayStatuses) { 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 // Check if any relay returned an "invalid key" error
const invalidKeyErrors = (error as any).relayStatuses.filter( const invalidKeyErrors = errorRelayStatuses.filter(
(status: any) => status.error && status.error.includes('invalid key') (status) => status.error && status.error.includes('invalid key')
) )
if (invalidKeyErrors.length > 0) { if (invalidKeyErrors.length > 0) {

14
src/services/client.service.ts

@ -901,12 +901,16 @@ class ClientService extends EventTarget {
} }
addEventToCache(event: NEvent) { addEventToCache(event: NEvent) {
this.eventDataLoader.prime(event.id, Promise.resolve(event)) // Remove relayStatuses before caching (it's metadata for logging, not part of the event)
if (isReplaceableEvent(event.kind)) { const cleanEvent = { ...event } as NEvent
const coordinate = getReplaceableCoordinateFromEvent(event) 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) const cachedEvent = this.replaceableEventCacheMap.get(coordinate)
if (!cachedEvent || compareEvents(event, cachedEvent) > 0) { if (!cachedEvent || compareEvents(cleanEvent, cachedEvent) > 0) {
this.replaceableEventCacheMap.set(coordinate, event) this.replaceableEventCacheMap.set(coordinate, cleanEvent)
} }
} }
} }

32
src/services/indexed-db.service.ts

@ -167,7 +167,11 @@ class IndexedDbService {
} }
async putReplaceableEvent(event: Event): Promise<Event> { async putReplaceableEvent(event: Event): Promise<Event> {
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) { if (!storeName) {
return Promise.reject('store name not found') return Promise.reject('store name not found')
} }
@ -191,23 +195,23 @@ class IndexedDbService {
if (!this.db.objectStoreNames.contains(storeName)) { if (!this.db.objectStoreNames.contains(storeName)) {
console.warn(`Store ${storeName} not found in database. Cannot save event.`) console.warn(`Store ${storeName} not found in database. Cannot save event.`)
// Return the event anyway (don't reject) - caching is optional // Return the event anyway (don't reject) - caching is optional
return resolve(event) return resolve(cleanEvent)
} }
const transaction = this.db.transaction(storeName, 'readwrite') const transaction = this.db.transaction(storeName, 'readwrite')
const store = transaction.objectStore(storeName) const store = transaction.objectStore(storeName)
const key = this.getReplaceableEventKeyFromEvent(event) const key = this.getReplaceableEventKeyFromEvent(cleanEvent)
const getRequest = store.get(key) const getRequest = store.get(key)
getRequest.onsuccess = () => { getRequest.onsuccess = () => {
const oldValue = getRequest.result as TValue<Event> | undefined const oldValue = getRequest.result as TValue<Event> | undefined
if (oldValue?.value && oldValue.value.created_at >= event.created_at) { if (oldValue?.value && oldValue.value.created_at >= cleanEvent.created_at) {
transaction.commit() transaction.commit()
return resolve(oldValue.value) return resolve(oldValue.value)
} }
const putRequest = store.put(this.formatValue(key, event)) const putRequest = store.put(this.formatValue(key, cleanEvent))
putRequest.onsuccess = () => { putRequest.onsuccess = () => {
transaction.commit() transaction.commit()
resolve(event) resolve(cleanEvent)
} }
putRequest.onerror = (event) => { putRequest.onerror = (event) => {
@ -541,7 +545,11 @@ class IndexedDbService {
} }
private async putReplaceableEventWithMaster(event: Event, masterKey: string): Promise<Event> { private async putReplaceableEventWithMaster(event: Event, masterKey: string): Promise<Event> {
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) { if (!storeName) {
return Promise.reject('store name not found') return Promise.reject('store name not found')
} }
@ -562,16 +570,16 @@ class IndexedDbService {
} }
if (!this.db.objectStoreNames.contains(storeName)) { if (!this.db.objectStoreNames.contains(storeName)) {
console.warn(`Store ${storeName} not found in database. Cannot save event.`) 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 transaction = this.db.transaction(storeName, 'readwrite')
const store = transaction.objectStore(storeName) const store = transaction.objectStore(storeName)
const key = this.getReplaceableEventKeyFromEvent(event) const key = this.getReplaceableEventKeyFromEvent(cleanEvent)
const getRequest = store.get(key) const getRequest = store.get(key)
getRequest.onsuccess = () => { getRequest.onsuccess = () => {
const oldValue = getRequest.result as TValue<Event> | undefined const oldValue = getRequest.result as TValue<Event> | 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 // Update master key link even if event is not newer
if (oldValue.masterPublicationKey !== masterKey) { if (oldValue.masterPublicationKey !== masterKey) {
const value = this.formatValue(key, oldValue.value) const value = this.formatValue(key, oldValue.value)
@ -582,12 +590,12 @@ class IndexedDbService {
return resolve(oldValue.value) return resolve(oldValue.value)
} }
// Store with master key link // Store with master key link
const value = this.formatValue(key, event) const value = this.formatValue(key, cleanEvent)
value.masterPublicationKey = masterKey value.masterPublicationKey = masterKey
const putRequest = store.put(value) const putRequest = store.put(value)
putRequest.onsuccess = () => { putRequest.onsuccess = () => {
transaction.commit() transaction.commit()
resolve(event) resolve(cleanEvent)
} }
putRequest.onerror = (event) => { putRequest.onerror = (event) => {

Loading…
Cancel
Save