/** * Write orchestrator service - manages writes/updates to WebSockets/API and to write Web Worker * Orchestrates communication between interface, network (Nostr/WebSockets) and write Web Worker */ import { websocketService } from './websocketService' import { writeService } from './writeService' import type { EventTemplate } from 'nostr-tools' import { finalizeEvent } from 'nostr-tools' import { hexToBytes } from 'nostr-tools/utils' import type { ObjectType } from './objectCache' import type { NostrEvent } from 'nostr-tools' interface WriteObjectParams { objectType: ObjectType hash: string event: NostrEvent parsed: unknown version: number hidden: boolean index?: number published?: false | string[] } class WriteOrchestrator { private privateKey: string | null = null /** * Set private key for signing events */ setPrivateKey(privateKey: string): void { this.privateKey = privateKey } /** * Write object to IndexedDB and publish to network * Orchestrates: WebSockets → Web Worker → IndexedDB * Écriture en parallèle réseau et local indépendamment * Si réseau échoue mais écriture locale réussit, rien (un autre service worker réessaiera) */ async writeAndPublish( params: WriteObjectParams, relays: string[] ): Promise<{ success: boolean; eventId: string; published: false | string[] }> { const localWrite = this.writeLocally(params) const networkPublish = this.publishToNetwork(params.event, relays) const [networkResult, localResult] = await Promise.allSettled([networkPublish, localWrite]) const publishedRelays = this.readPublishedRelays(networkResult) this.assertLocalWriteSucceeded(localResult) const published = await this.persistPublishedStatus(params.objectType, params.hash, publishedRelays) return { success: publishedRelays.length > 0, eventId: params.event.id, published } } private async publishToNetwork(event: NostrEvent, relays: string[]): Promise { const statuses = await websocketService.publishEvent(event, relays) return statuses .map((status, statusIndex) => (status.success ? relays[statusIndex] : null)) .filter((relay): relay is string => relay !== null) } private async writeLocally(params: WriteObjectParams): Promise { await writeService.writeObject({ objectType: params.objectType, hash: params.hash, event: params.event, parsed: params.parsed, version: params.version, hidden: params.hidden, ...(params.index !== undefined ? { index: params.index } : {}), published: false, }) } private readPublishedRelays(result: PromiseSettledResult): string[] { if (result.status === 'fulfilled') { return result.value } console.warn('[WriteOrchestrator] Network publish failed, will retry later:', result.reason) return [] } private assertLocalWriteSucceeded(result: PromiseSettledResult): void { if (result.status === 'fulfilled') { return } console.error('[WriteOrchestrator] Local write failed:', result.reason) throw new Error(`Failed to write to IndexedDB: ${String(result.reason)}`) } private async persistPublishedStatus( objectType: ObjectType, hash: string, publishedRelays: string[] ): Promise { const published: false | string[] = publishedRelays.length > 0 ? publishedRelays : false await writeService.updatePublished(objectType, hash, published) return published } /** * Create and publish event from template */ async createAndPublishEvent(params: { eventTemplate: EventTemplate relays: string[] objectType: ObjectType hash: string parsed: unknown version: number hidden: boolean index?: number }): Promise<{ success: boolean; event: NostrEvent; published: false | string[] }> { if (!this.privateKey) { throw new Error('Private key not set') } // Create event const unsignedEvent: EventTemplate = { ...params.eventTemplate, created_at: params.eventTemplate.created_at ?? Math.floor(Date.now() / 1000), } const secretKey = hexToBytes(this.privateKey) const finalizedEvent = finalizeEvent(unsignedEvent, secretKey) // Write and publish const result = await this.writeAndPublish( { objectType: params.objectType, hash: params.hash, event: finalizedEvent, parsed: params.parsed, version: params.version, hidden: params.hidden, ...(params.index !== undefined ? { index: params.index } : {}), }, params.relays ) return { success: result.success, event: finalizedEvent, published: result.published, } } /** * Update published status (for republishing) */ async updatePublishedStatus( objectType: ObjectType, id: string, published: false | string[] ): Promise { await writeService.updatePublished(objectType, id, published) } } export const writeOrchestrator = new WriteOrchestrator()