160 lines
5.0 KiB
TypeScript
160 lines
5.0 KiB
TypeScript
/**
|
|
* 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<string[]> {
|
|
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<void> {
|
|
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[]>): 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>): 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<false | string[]> {
|
|
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<void> {
|
|
await writeService.updatePublished(objectType, id, published)
|
|
}
|
|
}
|
|
|
|
export const writeOrchestrator = new WriteOrchestrator()
|