/** * Write service - manages all write operations to IndexedDB * Routes writes through Web Worker for IndexedDB operations * Routes network operations through WebSocket service */ import type { NostrEvent } from 'nostr-tools' import type { ObjectType } from './objectCache' class WriteService { private writeWorker: Worker | null = null private initPromise: Promise | null = null /** * Initialize the write worker */ private async init(): Promise { if (this.writeWorker) { return } if (this.initPromise) { return this.initPromise } this.initPromise = this.createWorker() try { await this.initPromise } catch (error) { this.initPromise = null throw error } } private createWorker(): Promise { return new Promise((resolve, reject) => { if (typeof window === 'undefined' || !window.Worker) { // Fallback: write directly if Worker not available console.warn('[WriteService] Web Workers not available, using direct writes') resolve() return } try { // Worker dans public/ pour Next.js this.writeWorker = new Worker('/writeWorker.js', { type: 'classic' }) this.writeWorker.addEventListener('message', (event) => { const { type, data } = event.data if (type === 'ERROR') { console.error('[WriteService] Worker error:', data) } }) this.writeWorker.addEventListener('error', (error) => { console.error('[WriteService] Worker error:', error) // Ne pas rejeter, utiliser fallback console.warn('[WriteService] Falling back to direct writes') this.writeWorker = null resolve() }) // Attendre que le worker soit prêt const readyTimeout = setTimeout(() => { console.warn('[WriteService] Worker ready timeout, using direct writes') if (this.writeWorker) { this.writeWorker.terminate() this.writeWorker = null } resolve() }, 2000) // Le worker est prêt quand il répond const readyHandler = (event: MessageEvent): void => { if (event.data?.type === 'WORKER_READY') { clearTimeout(readyTimeout) this.writeWorker?.removeEventListener('message', readyHandler) resolve() } } this.writeWorker.addEventListener('message', readyHandler) } catch (error) { console.warn('[WriteService] Failed to create worker, using direct writes:', error) resolve() // Fallback to direct writes } }) } /** * Write object to IndexedDB (via Web Worker) */ async writeObject( objectType: ObjectType, hash: string, event: NostrEvent, parsed: unknown, version: number, hidden: boolean, index?: number, published: false | string[] = false ): Promise { try { await this.init() if (this.writeWorker) { // Send to worker return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Write operation timeout')) }, 10000) const handler = (event: MessageEvent): void => { const { type, data } = event.data if (type === 'WRITE_OBJECT_SUCCESS' && data.hash === hash) { clearTimeout(timeout) this.writeWorker?.removeEventListener('message', handler) resolve() } else if (type === 'ERROR' && data.originalType === 'WRITE_OBJECT') { clearTimeout(timeout) this.writeWorker?.removeEventListener('message', handler) reject(new Error(data.error)) } } this.writeWorker!.addEventListener('message', handler) this.writeWorker!.postMessage({ type: 'WRITE_OBJECT', data: { objectType, hash, event, parsed, version, hidden, index, published, }, }) }) } // Fallback: direct write const { objectCache } = await import('./objectCache') await objectCache.set(objectType, hash, event, parsed, version, hidden, index, published) } catch (error) { console.error('[WriteService] Error writing object:', error) throw error } } /** * Update published status (via Web Worker) */ async updatePublished( objectType: ObjectType, id: string, published: false | string[] ): Promise { try { await this.init() if (this.writeWorker) { // Send to worker return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Update published operation timeout')) }, 10000) const handler = (event: MessageEvent): void => { const { type, data } = event.data if (type === 'UPDATE_PUBLISHED_SUCCESS' && data.id === id) { clearTimeout(timeout) this.writeWorker?.removeEventListener('message', handler) resolve() } else if (type === 'ERROR' && (data.originalType === 'UPDATE_PUBLISHED' || data.taskId?.startsWith('UPDATE_PUBLISHED'))) { clearTimeout(timeout) this.writeWorker?.removeEventListener('message', handler) reject(new Error(data.error)) } } this.writeWorker!.addEventListener('message', handler) this.writeWorker!.postMessage({ type: 'UPDATE_PUBLISHED', data: { objectType, id, published }, }) }) } // Fallback: direct write const { objectCache } = await import('./objectCache') await objectCache.updatePublished(objectType, id, published) } catch (error) { console.error('[WriteService] Error updating published status:', error) throw error } } /** * Create notification (via Web Worker) */ async createNotification( type: string, objectType: string, objectId: string, eventId: string, data?: Record ): Promise { try { await this.init() if (this.writeWorker) { // Send to worker return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Create notification operation timeout')) }, 10000) const handler = (event: MessageEvent): void => { const { type: responseType, data: responseData } = event.data if (responseType === 'CREATE_NOTIFICATION_SUCCESS' && responseData.eventId === eventId) { clearTimeout(timeout) this.writeWorker?.removeEventListener('message', handler) resolve() } else if (responseType === 'ERROR' && (responseData.originalType === 'CREATE_NOTIFICATION' || responseData.taskId?.startsWith('CREATE_NOTIFICATION'))) { clearTimeout(timeout) this.writeWorker?.removeEventListener('message', handler) reject(new Error(responseData.error)) } } this.writeWorker!.addEventListener('message', handler) this.writeWorker!.postMessage({ type: 'CREATE_NOTIFICATION', data: { type, objectType, objectId, eventId, notificationData: data }, }) }) } // Fallback: direct write const { notificationService } = await import('./notificationService') await notificationService.createNotificationDirect( type as Parameters[0], objectType, objectId, eventId, data ) } catch (error) { console.error('[WriteService] Error creating notification:', error) throw error } } /** * Log publication (via Web Worker) */ async logPublication( eventId: string, relayUrl: string, success: boolean, error?: string, objectType?: string, objectId?: string ): Promise { try { await this.init() if (this.writeWorker) { // Send to worker this.writeWorker.postMessage({ type: 'LOG_PUBLICATION', data: { eventId, relayUrl, success, error, objectType, objectId }, }) // Don't wait for response for logs (fire and forget) } else { // Fallback: direct write const { publishLog } = await import('./publishLog') await publishLog.logPublicationDirect(eventId, relayUrl, success, error, objectType, objectId) } } catch (error) { console.error('[WriteService] Error logging publication:', error) // Don't throw for logs } } /** * Terminate the worker */ terminate(): void { if (this.writeWorker) { this.writeWorker.terminate() this.writeWorker = null this.initPromise = null } } } export const writeService = new WriteService()