/** * ProxmoxPanel — WebSocket Service Worker * Maintient les connexions WS en vie entre les navigations Swup. * Les pages s'abonnent/désabonnent via postMessage. */ 'use strict' // channel → { ws: WebSocket|null, url: string, clientIds: Set, retryDelay: number } const connections = new Map() self.addEventListener('install', () => self.skipWaiting()) self.addEventListener('activate', e => e.waitUntil(self.clients.claim()) ) self.addEventListener('message', event => { const { type, channel, url, payload } = event.data || {} if (!channel) return const clientId = event.source?.id if (!clientId) return switch (type) { case 'WS_SUBSCRIBE': { let conn = connections.get(channel) if (!conn) { conn = { ws: null, url: null, clientIds: new Set(), retryDelay: 2000 } connections.set(channel, conn) } conn.clientIds.add(clientId) if (url) conn.url = url ensureWS(channel) break } case 'WS_UNSUBSCRIBE': { const conn = connections.get(channel) if (conn) conn.clientIds.delete(clientId) break } case 'WS_SEND': { const conn = connections.get(channel) if (conn?.ws?.readyState === 1) conn.ws.send(payload) break } } }) function ensureWS(channel) { const conn = connections.get(channel) if (!conn?.url) return if (conn.ws && conn.ws.readyState < 2) return // CONNECTING (0) ou OPEN (1) const ws = new WebSocket(conn.url) conn.ws = ws conn.retryDelay = 2000 notify(channel, 'WS_STATUS', { status: 'connecting' }) ws.onopen = () => { conn.retryDelay = 2000 notify(channel, 'WS_STATUS', { status: 'connected' }) } ws.onmessage = e => notify(channel, 'WS_MESSAGE', { data: e.data }) ws.onerror = () => notify(channel, 'WS_STATUS', { status: 'error' }) ws.onclose = () => { notify(channel, 'WS_STATUS', { status: 'disconnected' }) // Backoff exponentiel plafonné à 30s const delay = conn.retryDelay conn.retryDelay = Math.min(conn.retryDelay * 1.5, 30000) setTimeout(() => ensureWS(channel), delay) } } async function notify(channel, type, extra) { const conn = connections.get(channel) if (!conn || conn.clientIds.size === 0) return const allClients = await self.clients.matchAll({ type: 'window' }) for (const client of allClients) { if (conn.clientIds.has(client.id)) { client.postMessage({ channel, type, ...extra }) } } }