/** * Service Worker client - communication bridge between main thread and Service Worker */ interface SWMessage { type: string data?: unknown } interface SWResponse { type: string data?: unknown } class ServiceWorkerClient { private registration: ServiceWorkerRegistration | null = null private messageHandlers: Map void>> = new Map() private readyPromise: Promise | null = null /** * Initialize and register the Service Worker */ async register(): Promise { if (this.readyPromise) { return this.readyPromise } if (typeof window === 'undefined' || !('serviceWorker' in navigator)) { throw new Error('Service Workers are not supported in this browser') } this.readyPromise = navigator.serviceWorker .register('/sw.js', { scope: '/' }) .then((registration) => { this.registration = registration console.warn('[SWClient] Service Worker registered:', registration.scope) // Listen for messages from Service Worker navigator.serviceWorker.addEventListener('message', (event) => { this.handleMessage(event.data) }) // Handle updates registration.addEventListener('updatefound', () => { const newWorker = registration.installing if (newWorker) { newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { console.warn('[SWClient] New Service Worker available, reloading...') window.location.reload() } }) } }) return registration }) .catch((error) => { console.error('[SWClient] Service Worker registration failed:', error) throw error }) return this.readyPromise } /** * Unregister the Service Worker */ async unregister(): Promise { if (!this.registration) { return false } const result = await this.registration.unregister() this.registration = null this.readyPromise = null return result } /** * Send message to Service Worker */ async sendMessage(message: SWMessage): Promise { if (!this.registration?.active) { await this.register() } if (!this.registration?.active) { throw new Error('Service Worker is not active') } this.registration.active.postMessage(message) } /** * Send message and wait for response */ async sendMessageWithResponse(message: SWMessage, timeout: number = 5000): Promise { if (!this.registration?.active) { await this.register() } if (!this.registration?.active) { throw new Error('Service Worker is not active') } return new Promise((resolve, reject) => { const messageChannel = new MessageChannel() const timeoutId = setTimeout(() => { reject(new Error('Service Worker response timeout')) }, timeout) messageChannel.port1.onmessage = (event) => { clearTimeout(timeoutId) resolve(event.data) } if (this.registration?.active) { this.registration.active.postMessage(message, [messageChannel.port2]) } }) } /** * Handle messages from Service Worker */ private handleMessage(message: SWResponse): void { const handlers = this.messageHandlers.get(message.type) if (handlers) { handlers.forEach((handler) => { try { handler(message.data) } catch (error) { console.error(`[SWClient] Error handling message ${message.type}:`, error) } }) } } /** * Register a message handler */ onMessage(type: string, handler: (data: unknown) => void): () => void { if (!this.messageHandlers.has(type)) { this.messageHandlers.set(type, []) } const handlers = this.messageHandlers.get(type) if (handlers) { handlers.push(handler) } // Return unsubscribe function return () => { const typeHandlers = this.messageHandlers.get(type) if (typeHandlers) { const index = typeHandlers.indexOf(handler) if (index > -1) { typeHandlers.splice(index, 1) } } } } /** * Start platform sync */ async startPlatformSync(): Promise { await this.sendMessage({ type: 'START_PLATFORM_SYNC' }) } /** * Stop platform sync */ async stopPlatformSync(): Promise { await this.sendMessage({ type: 'STOP_PLATFORM_SYNC' }) } /** * Start user content sync */ async startUserSync(userPubkey: string): Promise { await this.sendMessage({ type: 'START_USER_SYNC', data: { userPubkey } }) } /** * Start publish worker */ async startPublishWorker(): Promise { await this.sendMessage({ type: 'START_PUBLISH_WORKER' }) } /** * Stop publish worker */ async stopPublishWorker(): Promise { await this.sendMessage({ type: 'STOP_PUBLISH_WORKER' }) } /** * Start notification detector */ async startNotificationDetector(userPubkey: string): Promise { await this.sendMessage({ type: 'START_NOTIFICATION_DETECTOR', data: { userPubkey } }) } /** * Stop notification detector */ async stopNotificationDetector(): Promise { await this.sendMessage({ type: 'STOP_NOTIFICATION_DETECTOR' }) } /** * Check if Service Worker is ready */ async isReady(): Promise { try { await this.register() return this.registration?.active !== null } catch { return false } } } export const swClient = new ServiceWorkerClient()