227 lines
5.6 KiB
TypeScript
227 lines
5.6 KiB
TypeScript
/**
|
|
* 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<string, Array<(data: unknown) => void>> = new Map()
|
|
private readyPromise: Promise<ServiceWorkerRegistration> | null = null
|
|
|
|
/**
|
|
* Initialize and register the Service Worker
|
|
*/
|
|
async register(): Promise<ServiceWorkerRegistration> {
|
|
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<boolean> {
|
|
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<void> {
|
|
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<SWResponse> {
|
|
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<void> {
|
|
await this.sendMessage({ type: 'START_PLATFORM_SYNC' })
|
|
}
|
|
|
|
/**
|
|
* Stop platform sync
|
|
*/
|
|
async stopPlatformSync(): Promise<void> {
|
|
await this.sendMessage({ type: 'STOP_PLATFORM_SYNC' })
|
|
}
|
|
|
|
/**
|
|
* Start user content sync
|
|
*/
|
|
async startUserSync(userPubkey: string): Promise<void> {
|
|
await this.sendMessage({ type: 'START_USER_SYNC', data: { userPubkey } })
|
|
}
|
|
|
|
/**
|
|
* Start publish worker
|
|
*/
|
|
async startPublishWorker(): Promise<void> {
|
|
await this.sendMessage({ type: 'START_PUBLISH_WORKER' })
|
|
}
|
|
|
|
/**
|
|
* Stop publish worker
|
|
*/
|
|
async stopPublishWorker(): Promise<void> {
|
|
await this.sendMessage({ type: 'STOP_PUBLISH_WORKER' })
|
|
}
|
|
|
|
/**
|
|
* Start notification detector
|
|
*/
|
|
async startNotificationDetector(userPubkey: string): Promise<void> {
|
|
await this.sendMessage({ type: 'START_NOTIFICATION_DETECTOR', data: { userPubkey } })
|
|
}
|
|
|
|
/**
|
|
* Stop notification detector
|
|
*/
|
|
async stopNotificationDetector(): Promise<void> {
|
|
await this.sendMessage({ type: 'STOP_NOTIFICATION_DETECTOR' })
|
|
}
|
|
|
|
/**
|
|
* Check if Service Worker is ready
|
|
*/
|
|
async isReady(): Promise<boolean> {
|
|
try {
|
|
await this.register()
|
|
return this.registration?.active !== null
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
export const swClient = new ServiceWorkerClient()
|