diff --git a/src/services/sw-controller.service.ts b/src/services/sw-controller.service.ts new file mode 100644 index 0000000..f697c15 --- /dev/null +++ b/src/services/sw-controller.service.ts @@ -0,0 +1,194 @@ +import Services from "./service"; +import Database from "./database.service"; + +/** + * Service Worker Controller - Manages SW registration and communication + */ +export class SWController { + private static instance: SWController; + private serviceWorkerRegistration: ServiceWorkerRegistration | null = null; + private serviceWorkerCheckIntervalId: number | null = null; + + private constructor() { + // Singleton + } + + public static async getInstance(): Promise { + if (!SWController.instance) { + SWController.instance = new SWController(); + } + return SWController.instance; + } + + public async init(): Promise { + await this.registerServiceWorker("/data.worker.js"); + } + + private async registerServiceWorker(path: string): Promise { + if (!("serviceWorker" in navigator)) return; + console.log("[SWController] Initializing Service Worker:", path); + + try { + // Nettoyage des anciens workers si nécessaire (logique conservée) + const registrations = await navigator.serviceWorker.getRegistrations(); + for (const registration of registrations) { + const scriptURL = + registration.active?.scriptURL || + registration.installing?.scriptURL || + registration.waiting?.scriptURL; + const scope = registration.scope; + + if ( + scope.includes("/src/service-workers/") || + (scriptURL && scriptURL.includes("/src/service-workers/")) + ) { + console.warn(`[SWController] Removing old Service Worker (${scope})`); + await registration.unregister(); + } + } + + const existingValidWorker = registrations.find((r) => { + const url = + r.active?.scriptURL || + r.installing?.scriptURL || + r.waiting?.scriptURL; + return url && url.endsWith(path.replace(/^\//, "")); + }); + + if (!existingValidWorker) { + console.log("[SWController] Registering new Service Worker"); + this.serviceWorkerRegistration = await navigator.serviceWorker.register( + path, + { type: "module", scope: "/" } + ); + } else { + console.log("[SWController] Service Worker already active"); + this.serviceWorkerRegistration = existingValidWorker; + await this.serviceWorkerRegistration.update(); + } + + navigator.serviceWorker.addEventListener("message", async (event) => { + await this.handleServiceWorkerMessage(event.data); + }); + + // Boucle de scan périodique + if (this.serviceWorkerCheckIntervalId) + clearInterval(this.serviceWorkerCheckIntervalId); + + this.serviceWorkerCheckIntervalId = window.setInterval(async () => { + const activeWorker = + this.serviceWorkerRegistration?.active || + (await this.waitForServiceWorkerActivation( + this.serviceWorkerRegistration! + )); + + // On récupère les processus via le proxy Services + const service = await Services.getInstance(); + const payload = await service.getMyProcesses(); + + if (payload && Object.keys(payload).length !== 0) { + activeWorker?.postMessage({ type: "SCAN", payload }); + } + }, 5000); + } catch (error) { + console.error("[SWController] Service Worker error:", error); + } + } + + private async waitForServiceWorkerActivation( + registration: ServiceWorkerRegistration + ): Promise { + return new Promise((resolve) => { + if (registration.active) { + resolve(registration.active); + } else { + const listener = () => { + if (registration.active) { + navigator.serviceWorker.removeEventListener( + "controllerchange", + listener + ); + resolve(registration.active); + } + }; + navigator.serviceWorker.addEventListener("controllerchange", listener); + } + }); + } + + // ============================================ + // MESSAGE HANDLERS + // ============================================ + + private async handleServiceWorkerMessage(message: any) { + switch (message.type) { + case "TO_DOWNLOAD": + await this.handleDownloadList(message.data); + break; + case "DIFFS_TO_CREATE": + await this.handleDiffsToCreate(message.data); + break; + default: + console.warn("[SWController] Unknown message type received:", message); + } + } + + private async handleDiffsToCreate(diffs: any[]): Promise { + console.log( + `[SWController] Creating ${diffs.length} diffs from Service Worker scan` + ); + try { + const db = await Database.getInstance(); + await db.saveDiffs(diffs); + console.log("[SWController] Diffs created successfully"); + } catch (error) { + console.error("[SWController] Error creating diffs:", error); + } + } + + private async handleDownloadList(downloadList: string[]): Promise { + let requestedStateId: string[] = []; + + // On a besoin de Services pour la logique métier (fetch, network) + const service = await Services.getInstance(); + + for (const hash of downloadList) { + const diff = await service.getDiffByValue(hash); + if (!diff) { + console.warn(`[SWController] Missing a diff for hash ${hash}`); + continue; + } + + const processId = diff.process_id; + const stateId = diff.state_id; + const roles = diff.roles; + + try { + const valueBytes = await service.fetchValueFromStorage(hash); + if (valueBytes) { + const blob = new Blob([valueBytes], { + type: "application/octet-stream", + }); + + await service.saveBlobToDb(hash, blob); + + document.dispatchEvent( + new CustomEvent("newDataReceived", { + detail: { processId, stateId, hash }, + }) + ); + } else { + console.log( + "[SWController] Request data from managers of the process" + ); + if (!requestedStateId.includes(stateId)) { + await service.requestDataFromPeers(processId, [stateId], [roles]); + requestedStateId.push(stateId); + } + } + } catch (e) { + console.error(e); + } + } + } +}