Compare commits
No commits in common. "633fd57793062f52f397eaa34fe538209c80f5fa" and "77019896e58af3b98f5ad7109c306784aa7197d8" have entirely different histories.
633fd57793
...
77019896e5
@ -1,6 +1,7 @@
|
||||
import Services from "./service";
|
||||
|
||||
/**
|
||||
* Database service managing IndexedDB operations via Web Worker
|
||||
* Pure Data Store Layer
|
||||
* Database service managing IndexedDB operations via Web Worker and Service Worker
|
||||
*/
|
||||
export class Database {
|
||||
// ============================================
|
||||
@ -8,6 +9,8 @@ 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<
|
||||
@ -21,6 +24,7 @@ export class Database {
|
||||
|
||||
private constructor() {
|
||||
this.initIndexedDBWorker();
|
||||
this.initServiceWorker();
|
||||
}
|
||||
|
||||
public static async getInstance(): Promise<Database> {
|
||||
@ -77,6 +81,7 @@ 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);
|
||||
@ -86,6 +91,186 @@ 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
|
||||
// ============================================
|
||||
|
||||
@ -13,7 +13,6 @@ 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 {
|
||||
@ -56,27 +55,21 @@ export default class Services {
|
||||
|
||||
// 1. Initialiser le Core Worker
|
||||
await this.coreWorker.init();
|
||||
|
||||
// 2. Initialiser la Database (Main Thread)
|
||||
// 2. Initialiser la Database ici (Main Thread) pour lancer le SW
|
||||
await Database.getInstance();
|
||||
|
||||
// 3. Initialiser le Service Worker Controller
|
||||
const swController = await SWController.getInstance();
|
||||
await swController.init();
|
||||
|
||||
// 4. Configurer les Callbacks
|
||||
// 3. Configurer les Callbacks (Le worker pilote le main thread pour le réseau)
|
||||
await this.coreWorker.setCallbacks(
|
||||
Comlink.proxy(this.handleWorkerNotification.bind(this)),
|
||||
Comlink.proxy(this.handleWorkerNetworkSend.bind(this)),
|
||||
Comlink.proxy(this.handleWorkerRelayUpdate.bind(this)),
|
||||
Comlink.proxy(this.handleWorkerRelayRequest.bind(this))
|
||||
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
|
||||
);
|
||||
|
||||
// 5. Initialiser le Réseau
|
||||
// 3. Initialiser le Réseau (Network Worker via Proxy)
|
||||
await this.networkService.initRelays();
|
||||
|
||||
console.log(
|
||||
"[Services] ✅ Proxy connecté au CoreWorker, SWController et NetworkService."
|
||||
"[Services] ✅ Proxy connecté au CoreWorker et NetworkService."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,194 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user