Compare commits

...

3 Commits

3 changed files with 211 additions and 195 deletions

View File

@ -1,7 +1,6 @@
import Services from "./service";
/**
* Database service managing IndexedDB operations via Web Worker and Service Worker
* Database service managing IndexedDB operations via Web Worker
* Pure Data Store Layer
*/
export class Database {
// ============================================
@ -9,8 +8,6 @@ export class Database {
// ============================================
private static instance: Database;
private serviceWorkerRegistration: ServiceWorkerRegistration | null = null;
private serviceWorkerCheckIntervalId: number | null = null;
private indexedDBWorker: Worker | null = null;
private messageIdCounter: number = 0;
private pendingMessages: Map<
@ -24,7 +21,6 @@ export class Database {
private constructor() {
this.initIndexedDBWorker();
this.initServiceWorker();
}
public static async getInstance(): Promise<Database> {
@ -81,7 +77,6 @@ export class Database {
this.indexedDBWorker.postMessage({ type, payload, id });
// Timeout de sécurité (30 secondes)
setTimeout(() => {
if (this.pendingMessages.has(id)) {
this.pendingMessages.delete(id);
@ -91,186 +86,6 @@ export class Database {
});
}
// ============================================
// SERVICE WORKER
// ============================================
private initServiceWorker(): void {
this.registerServiceWorker("/data.worker.js");
}
private async registerServiceWorker(path: string): Promise<void> {
if (!("serviceWorker" in navigator)) return;
console.log("[Database] Initializing Service Worker:", path);
try {
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(`[Database] 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("[Database] Registering new Service Worker");
this.serviceWorkerRegistration = await navigator.serviceWorker.register(
path,
{ type: "module", scope: "/" }
);
} else {
console.log("[Database] Service Worker already active");
this.serviceWorkerRegistration = existingValidWorker;
await this.serviceWorkerRegistration.update();
}
navigator.serviceWorker.addEventListener("message", async (event) => {
await this.handleServiceWorkerMessage(event.data);
});
if (this.serviceWorkerCheckIntervalId)
clearInterval(this.serviceWorkerCheckIntervalId);
this.serviceWorkerCheckIntervalId = window.setInterval(async () => {
const activeWorker =
this.serviceWorkerRegistration?.active ||
(await this.waitForServiceWorkerActivation(
this.serviceWorkerRegistration!
));
const service = await Services.getInstance();
const payload = await service.getMyProcesses();
if (payload && payload.length != 0) {
activeWorker?.postMessage({ type: "SCAN", payload });
}
}, 5000);
} catch (error) {
console.error("[Database] Service Worker error:", error);
}
}
private async waitForServiceWorkerActivation(
registration: ServiceWorkerRegistration
): Promise<ServiceWorker | null> {
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);
}
});
}
private async checkForUpdates(): Promise<void> {
if (this.serviceWorkerRegistration) {
try {
await this.serviceWorkerRegistration.update();
if (this.serviceWorkerRegistration.waiting) {
this.serviceWorkerRegistration.waiting.postMessage({
type: "SKIP_WAITING",
});
}
} catch (error) {
console.error("Error checking for service worker updates:", error);
}
}
}
// ============================================
// SERVICE WORKER 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(
"Unknown message type received from service worker:",
message
);
}
}
private async handleDiffsToCreate(diffs: any[]): Promise<void> {
console.log(
`[Database] Creating ${diffs.length} diffs from Service Worker scan`
);
try {
await this.saveDiffs(diffs);
console.log("[Database] Diffs created successfully");
} catch (error) {
console.error("[Database] Error creating diffs:", error);
}
}
private async handleDownloadList(downloadList: string[]): Promise<void> {
let requestedStateId: string[] = [];
const service = await Services.getInstance();
for (const hash of downloadList) {
const diff = await service.getDiffByValue(hash);
if (!diff) {
console.warn(`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("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);
}
}
}
// ============================================
// GENERIC INDEXEDDB OPERATIONS
// ============================================

View File

@ -13,6 +13,7 @@ import { BackUp } from "../types/index";
import { APP_CONFIG } from "../config/constants";
import { NetworkService } from "./core/network.service";
import type { CoreBackend } from "../workers/core.worker";
import { SWController } from "./sw-controller.service";
import Database from "./database.service";
export default class Services {
@ -55,21 +56,27 @@ export default class Services {
// 1. Initialiser le Core Worker
await this.coreWorker.init();
// 2. Initialiser la Database ici (Main Thread) pour lancer le SW
// 2. Initialiser la Database (Main Thread)
await Database.getInstance();
// 3. Configurer les Callbacks (Le worker pilote le main thread pour le réseau)
// 3. Initialiser le Service Worker Controller
const swController = await SWController.getInstance();
await swController.init();
// 4. Configurer les Callbacks
await this.coreWorker.setCallbacks(
Comlink.proxy(this.handleWorkerNotification.bind(this)), // notifier
Comlink.proxy(this.handleWorkerNetworkSend.bind(this)), // networkSender
Comlink.proxy(this.handleWorkerRelayUpdate.bind(this)), // relayUpdater
Comlink.proxy(this.handleWorkerRelayRequest.bind(this)) // relayGetter
Comlink.proxy(this.handleWorkerNotification.bind(this)),
Comlink.proxy(this.handleWorkerNetworkSend.bind(this)),
Comlink.proxy(this.handleWorkerRelayUpdate.bind(this)),
Comlink.proxy(this.handleWorkerRelayRequest.bind(this))
);
// 3. Initialiser le Réseau (Network Worker via Proxy)
// 5. Initialiser le Réseau
await this.networkService.initRelays();
console.log(
"[Services] ✅ Proxy connecté au CoreWorker et NetworkService."
"[Services] ✅ Proxy connecté au CoreWorker, SWController et NetworkService."
);
}

View File

@ -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<SWController> {
if (!SWController.instance) {
SWController.instance = new SWController();
}
return SWController.instance;
}
public async init(): Promise<void> {
await this.registerServiceWorker("/data.worker.js");
}
private async registerServiceWorker(path: string): Promise<void> {
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<ServiceWorker | null> {
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<void> {
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<void> {
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);
}
}
}
}