diff --git a/src/service-workers/network.sw.ts b/src/service-workers/network.sw.ts index 68211c3..434686d 100644 --- a/src/service-workers/network.sw.ts +++ b/src/service-workers/network.sw.ts @@ -3,8 +3,6 @@ /// -declare const self: ServiceWorkerGlobalScope; - type AnkFlag = 'Handshake' | 'NewTx' | 'Cipher' | 'Commit' | 'Faucet' | 'Ping'; interface ServiceWorkerMessage { @@ -24,14 +22,14 @@ let heartbeatInterval: any = null; // SERVICE WORKER LIFECYCLE // ========================================== -self.addEventListener('install', (event: ExtendableEvent) => { +(self as unknown as ServiceWorkerGlobalScope).addEventListener('install', (event: ExtendableEvent) => { console.log('[NetworkSW] Installing...'); - event.waitUntil(self.skipWaiting()); + event.waitUntil((self as unknown as ServiceWorkerGlobalScope).skipWaiting()); }); -self.addEventListener('activate', (event: ExtendableEvent) => { +(self as unknown as ServiceWorkerGlobalScope).addEventListener('activate', (event: ExtendableEvent) => { console.log('[NetworkSW] Activating...'); - event.waitUntil(self.clients.claim()); + event.waitUntil((self as unknown as ServiceWorkerGlobalScope).clients.claim()); startHeartbeat(); }); @@ -39,7 +37,7 @@ self.addEventListener('activate', (event: ExtendableEvent) => { // MESSAGE HANDLING // ========================================== -self.addEventListener('message', async (event: ExtendableMessageEvent) => { +(self as unknown as ServiceWorkerGlobalScope).addEventListener('message', async (event: ExtendableMessageEvent) => { const { type, payload, id } = event.data; console.log(`[NetworkSW] Received message: ${type} (id: ${id})`); @@ -227,9 +225,9 @@ function respondToClient(client: Client | null, message: any) { console.error('[NetworkSW] Error sending message to client:', error); // Fallback: try to get the client by ID or use broadcast if (client.id) { - self.clients.get(client.id).then((c) => { + (self as unknown as ServiceWorkerGlobalScope).clients.get(client.id).then((c: Client | undefined) => { if (c) c.postMessage(message); - }).catch((err) => { + }).catch((err: any) => { console.error('[NetworkSW] Failed to get client by ID:', err); }); } @@ -237,8 +235,8 @@ function respondToClient(client: Client | null, message: any) { } async function broadcastToClients(message: any) { - const clients = await self.clients.matchAll({ includeUncontrolled: true }); - clients.forEach((client) => { + const clients = await (self as unknown as ServiceWorkerGlobalScope).clients.matchAll({ includeUncontrolled: true }); + clients.forEach((client: Client) => { client.postMessage(message); }); } diff --git a/src/services/core/sdk.service.ts b/src/services/core/sdk.service.ts deleted file mode 100644 index 0a0949f..0000000 --- a/src/services/core/sdk.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ApiReturn, Device } from '../../../pkg/sdk_client'; - -export class SdkService { - private client: any; - - async init() { - this.client = await import('../../../pkg/sdk_client'); - this.client.setup(); - } - - public getClient(): any { - if (!this.client) throw new Error('SDK not initialized'); - return this.client; - } - - // Méthodes utilitaires directes du SDK - public encodeJson(data: any): any { - return this.client.encode_json(data); - } - public encodeBinary(data: any): any { - return this.client.encode_binary(data); - } - public decodeValue(value: number[]): any { - return this.client.decode_value(value); - } -} diff --git a/src/services/domain/crypto.service.ts b/src/services/crypto.service.ts similarity index 59% rename from src/services/domain/crypto.service.ts rename to src/services/crypto.service.ts index c04a459..29714bc 100644 --- a/src/services/domain/crypto.service.ts +++ b/src/services/crypto.service.ts @@ -1,8 +1,14 @@ -import { MerkleProofResult, ProcessState } from '../../../pkg/sdk_client'; -import { SdkService } from '../core/sdk.service'; +import { MerkleProofResult, ProcessState } from '../../pkg/sdk_client'; + +// Type for WasmService proxy (passed from Core Worker) +type WasmServiceProxy = { + hashValue(fileBlob: { type: string; data: Uint8Array }, commitedIn: string, label: string): Promise; + getMerkleProof(processState: any, attributeName: string): Promise; + validateMerkleProof(proof: any, hash: string): Promise; +}; export class CryptoService { - constructor(private sdk: SdkService) {} + constructor(private wasm: WasmServiceProxy) {} public hexToBlob(hexString: string): Blob { const uint8Array = this.hexToUInt8Array(hexString); @@ -26,16 +32,16 @@ export class CryptoService { .join(''); } - public getHashForFile(commitedIn: string, label: string, fileBlob: { type: string; data: Uint8Array }): string { - return this.sdk.getClient().hash_value(fileBlob, commitedIn, label); + public async getHashForFile(commitedIn: string, label: string, fileBlob: { type: string; data: Uint8Array }): Promise { + return await this.wasm.hashValue(fileBlob, commitedIn, label); } - public getMerkleProofForFile(processState: ProcessState, attributeName: string): MerkleProofResult { - return this.sdk.getClient().get_merkle_proof(processState, attributeName); + public async getMerkleProofForFile(processState: ProcessState, attributeName: string): Promise { + return await this.wasm.getMerkleProof(processState, attributeName); } - public validateMerkleProof(proof: MerkleProofResult, hash: string): boolean { - return this.sdk.getClient().validate_merkle_proof(proof, hash); + public async validateMerkleProof(proof: MerkleProofResult, hash: string): Promise { + return await this.wasm.validateMerkleProof(proof, hash); } public splitData(obj: Record) { diff --git a/src/services/domain/wallet.service.ts b/src/services/domain/wallet.service.ts deleted file mode 100644 index b7e26b6..0000000 --- a/src/services/domain/wallet.service.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Device } from '../../../pkg/sdk_client'; -import { SdkService } from '../core/sdk.service'; -import Database from '../database.service'; - -export class WalletService { - constructor(private sdk: SdkService, private db: Database) {} - - public isPaired(): boolean { - try { - return this.sdk.getClient().is_paired(); - } catch (e) { - return false; - } - } - - public getAmount(): BigInt { - return this.sdk.getClient().get_available_amount(); - } - - public getDeviceAddress(): string { - return this.sdk.getClient().get_address(); - } - - public getPairingProcessId(): string { - return this.sdk.getClient().get_pairing_process_id(); - } - - public async createNewDevice(chainTip: number): Promise { - const spAddress = await this.sdk.getClient().create_new_device(0, 'signet'); - const device = this.dumpDeviceFromMemory(); - if (device.sp_wallet.birthday === 0) { - device.sp_wallet.birthday = chainTip; - device.sp_wallet.last_scan = chainTip; - this.sdk.getClient().restore_device(device); - } - await this.saveDeviceInDatabase(device); - return spAddress; - } - - public dumpDeviceFromMemory(): Device { - return this.sdk.getClient().dump_device(); - } - - public dumpNeuteredDevice(): Device | null { - try { - return this.sdk.getClient().dump_neutered_device(); - } catch (e) { - return null; - } - } - - public async dumpWallet(): Promise { - return await this.sdk.getClient().dump_wallet(); - } - - public async getMemberFromDevice(): Promise { - try { - const device = await this.getDeviceFromDatabase(); - if (device) { - const pairedMember = device['paired_member']; - return pairedMember.sp_addresses; - } else { - return null; - } - } catch (e) { - throw new Error(`[WalletService] Échec: ${e}`); - } - } - - public async saveDeviceInDatabase(device: Device): Promise { - await this.db.saveDevice(device); - } - - public async getDeviceFromDatabase(): Promise { - const db = await Database.getInstance(); - const res = await db.getObject('wallet', '1'); - return res ? res['device'] : null; - } - - public restoreDevice(device: Device) { - this.sdk.getClient().restore_device(device); - } - - public pairDevice(processId: string, spAddressList: string[]): void { - this.sdk.getClient().pair_device(processId, spAddressList); - } - - public async unpairDevice(): Promise { - this.sdk.getClient().unpair_device(); - const newDevice = this.dumpDeviceFromMemory(); - await this.saveDeviceInDatabase(newDevice); - } -} diff --git a/src/services/core/network.service.ts b/src/services/network.service.ts similarity index 95% rename from src/services/core/network.service.ts rename to src/services/network.service.ts index 17cd1ad..85d3e6f 100644 --- a/src/services/core/network.service.ts +++ b/src/services/network.service.ts @@ -1,4 +1,4 @@ -import Services from "../service"; +import Services from "./service"; interface ServiceWorkerMessage { type: string; @@ -160,7 +160,16 @@ export class NetworkService { } // Wait for the Service Worker to be ready (installed and activated) - await this.serviceWorkerRegistration.ready; + if (this.serviceWorkerRegistration.installing) { + const installing = this.serviceWorkerRegistration.installing; + await new Promise((resolve) => { + installing.addEventListener('statechange', () => { + if (installing.state === 'installed') { + resolve(); + } + }); + }); + } // Also ensure it's active if (this.serviceWorkerRegistration.active) { @@ -234,18 +243,18 @@ export class NetworkService { }; // Listen on both the serviceWorker and controller - navigator.serviceWorker.addEventListener("message", messageHandler); + navigator.serviceWorker.addEventListener("message", messageHandler as EventListener); // Also listen on the controller if it exists if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.addEventListener("message", messageHandler); + navigator.serviceWorker.controller.addEventListener("message", messageHandler as EventListener); } // Listen for controller changes navigator.serviceWorker.addEventListener("controllerchange", () => { console.log("[NetworkService] Service Worker controller changed"); if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.addEventListener("message", messageHandler); + navigator.serviceWorker.controller.addEventListener("message", messageHandler as EventListener); } }); } diff --git a/src/services/domain/process.service.ts b/src/services/process.service.ts similarity index 79% rename from src/services/domain/process.service.ts rename to src/services/process.service.ts index 7f97b76..dadd67e 100644 --- a/src/services/domain/process.service.ts +++ b/src/services/process.service.ts @@ -1,6 +1,12 @@ -import { Process, ProcessState, RoleDefinition } from '../../../pkg/sdk_client'; -import { SdkService } from '../core/sdk.service'; -import Database from '../database.service'; +import { Process, ProcessState, RoleDefinition } from '../../pkg/sdk_client'; + +// Type for Database proxy (passed from Core Worker) +type DatabaseServiceProxy = { + getProcess(processId: string): Promise; + getAllProcesses(): Promise>; + saveProcess(processId: string, process: Process): Promise; + saveProcessesBatch(processes: Record): Promise; +}; const EMPTY32BYTES = String('').padStart(64, '0'); @@ -8,7 +14,7 @@ export class ProcessService { private processesCache: Record = {}; private myProcesses: Set = new Set(); - constructor(private sdk: SdkService, private db: Database) {} + constructor(private db: any) {} public async getProcess(processId: string): Promise { if (this.processesCache[processId]) return this.processesCache[processId]; @@ -39,17 +45,17 @@ export class ProcessService { public getLastCommitedState(process: Process): ProcessState | null { if (process.states.length === 0) return null; const processTip = process.states[process.states.length - 1].commited_in; - return process.states.findLast((state) => state.commited_in !== processTip) || null; + return process.states.findLast((state: ProcessState) => state.commited_in !== processTip) || null; } public getUncommitedStates(process: Process): ProcessState[] { if (process.states.length === 0) return []; const processTip = process.states[process.states.length - 1].commited_in; - return process.states.filter((state) => state.commited_in === processTip).filter((state) => state.state_id !== EMPTY32BYTES); + return process.states.filter((state: ProcessState) => state.commited_in === processTip).filter((state: ProcessState) => state.state_id !== EMPTY32BYTES); } public getStateFromId(process: Process, stateId: string): ProcessState | null { - return process.states.find((state) => state.state_id === stateId) || null; + return process.states.find((state: ProcessState) => state.state_id === stateId) || null; } public getRoles(process: Process): Record | null { diff --git a/src/services/service.ts b/src/services/service.ts index f0dc928..dd0a3f3 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -11,9 +11,10 @@ import { } from "../../pkg/sdk_client"; import { BackUp } from "../types/index"; import { APP_CONFIG } from "../config/constants"; -import { NetworkService } from "./core/network.service"; +import { NetworkService } from "./network.service"; import type { CoreBackend } from "../workers/core.worker"; import Database from "./database.service"; +import WasmService from "./wasm.service"; export default class Services { private static instance: Services; @@ -53,13 +54,20 @@ export default class Services { public async init(): Promise { console.log("[Services] 🚀 Démarrage Proxy..."); - // 1. Initialiser le Core Worker + // 1. Initialiser les Services dans le Main Thread (Hub) + const db = await Database.getInstance(); + const wasmService = await WasmService.getInstance(); + + // 2. Passer les Services au Core Worker via Comlink proxy (BEFORE init) + await this.coreWorker.setServices( + Comlink.proxy(wasmService), + Comlink.proxy(db) + ); + + // 3. Initialiser le Core Worker (after services are set) await this.coreWorker.init(); - // 2. Initialiser la Database (Main Thread) - await Database.getInstance(); - - // 3. Configurer les Callbacks + // 4. Configurer les Callbacks await this.coreWorker.setCallbacks( Comlink.proxy(this.handleWorkerNotification.bind(this)), Comlink.proxy(this.handleWorkerNetworkSend.bind(this)), @@ -67,11 +75,11 @@ export default class Services { Comlink.proxy(this.handleWorkerRelayRequest.bind(this)) ); - // 4. Initialiser le Réseau + // 5. Initialiser le Réseau await this.networkService.initRelays(); console.log( - "[Services] ✅ Proxy connecté au CoreWorker et NetworkService." + "[Services] ✅ Proxy connecté au CoreWorker, WASM Service, Database Service et NetworkService." ); } @@ -176,9 +184,6 @@ export default class Services { public async dumpDeviceFromMemory() { return await this.coreWorker.dumpDeviceFromMemory(); } - public async dumpNeuteredDevice() { - return await this.coreWorker.dumpNeuteredDevice(); - } public async getPairingProcessId() { return await this.coreWorker.getPairingProcessId(); } @@ -339,9 +344,6 @@ export default class Services { public async resetDevice() { await this.coreWorker.resetDevice(); } - public async handleApiReturn(res: ApiReturn) { - await this.coreWorker.handleApiReturn(res); - } public async saveDiffsToDb(diffs: UserDiff[]) { await this.coreWorker.saveDiffsToDb(diffs); } @@ -349,6 +351,10 @@ export default class Services { await this.coreWorker.handleCommitError(res); } + public async handleApiReturn(res: ApiReturn) { + await this.coreWorker.handleApiReturn(res); + } + public async rolesContainsUs(roles: any) { return await this.coreWorker.rolesContainsUs(roles); } diff --git a/src/services/wallet.service.ts b/src/services/wallet.service.ts new file mode 100644 index 0000000..9ff1a65 --- /dev/null +++ b/src/services/wallet.service.ts @@ -0,0 +1,134 @@ +import { Device } from '../../pkg/sdk_client'; +import Database from './database.service'; + +// Type for WasmService proxy (passed from Core Worker) +type WasmServiceProxy = { + isPaired(): Promise; + getAvailableAmount(): Promise; + getAddress(): Promise; + getPairingProcessId(): Promise; + createNewDevice(birthday: number, network: string): Promise; + dumpDevice(): Promise; + dumpNeuteredDevice(): Promise; + dumpWallet(): Promise; + restoreDevice(device: any): Promise; + pairDevice(processId: string, spAddresses: string[]): Promise; + unpairDevice(): Promise; +}; + +type DatabaseServiceProxy = { + getStoreList(): Promise<{ [key: string]: string }>; + addObject(payload: { storeName: string; object: any; key: any }): Promise; + batchWriting(payload: { storeName: string; objects: { key: any; object: any }[] }): Promise; + getObject(storeName: string, key: string): Promise; + dumpStore(storeName: string): Promise>; + deleteObject(storeName: string, key: string): Promise; + clearStore(storeName: string): Promise; + requestStoreByIndex(storeName: string, indexName: string, request: string): Promise; + clearMultipleStores(storeNames: string[]): Promise; + saveDevice(device: any): Promise; + getDevice(): Promise; + saveProcess(processId: string, process: any): Promise; + saveProcessesBatch(processes: Record): Promise; + getProcess(processId: string): Promise; + getAllProcesses(): Promise>; + saveBlob(hash: string, data: Blob): Promise; + getBlob(hash: string): Promise; + saveDiffs(diffs: any[]): Promise; + getDiff(hash: string): Promise; + getAllDiffs(): Promise>; + getSharedSecret(address: string): Promise; + saveSecretsBatch(unconfirmedSecrets: any[], sharedSecrets: { key: string; value: any }[]): Promise; + getAllSecrets(): Promise<{ shared_secrets: Record; unconfirmed_secrets: any[] }>; +}; + + +export class WalletService { + constructor(private wasm: WasmServiceProxy, private db: DatabaseServiceProxy) {} + + public async isPaired(): Promise { + try { + return await this.wasm.isPaired(); + } catch (e) { + return false; + } + } + + public async getAmount(): Promise { + return await this.wasm.getAvailableAmount(); + } + + public async getDeviceAddress(): Promise { + return await this.wasm.getAddress(); + } + + public async getPairingProcessId(): Promise { + return await this.wasm.getPairingProcessId(); + } + + public async createNewDevice(chainTip: number): Promise { + const spAddress = await this.wasm.createNewDevice(0, 'signet'); + const device = await this.dumpDeviceFromMemory(); + if (device.sp_wallet.birthday === 0) { + device.sp_wallet.birthday = chainTip; + device.sp_wallet.last_scan = chainTip; + await this.wasm.restoreDevice(device); + } + await this.saveDeviceInDatabase(device); + return spAddress; + } + + public async dumpDeviceFromMemory(): Promise { + return await this.wasm.dumpDevice(); + } + + public async dumpNeuteredDevice(): Promise { + try { + return await this.wasm.dumpNeuteredDevice(); + } catch (e) { + return null; + } + } + + public async dumpWallet(): Promise { + return await this.wasm.dumpWallet(); + } + + public async getMemberFromDevice(): Promise { + try { + const device = await this.getDeviceFromDatabase(); + if (device) { + const pairedMember = device['paired_member']; + return pairedMember.sp_addresses; + } else { + return null; + } + } catch (e) { + throw new Error(`[WalletService] Échec: ${e}`); + } + } + + public async saveDeviceInDatabase(device: Device): Promise { + await this.db.saveDevice(device); + } + + public async getDeviceFromDatabase(): Promise { + const db = await Database.getInstance(); + const res = await db.getObject('wallet', '1'); + return res ? res['device'] : null; + } + + public async restoreDevice(device: Device): Promise { + await this.wasm.restoreDevice(device); + } + + public async pairDevice(processId: string, spAddressList: string[]): Promise { + await this.wasm.pairDevice(processId, spAddressList); + } + + public async unpairDevice(): Promise { + await this.wasm.unpairDevice(); + const newDevice = await this.dumpDeviceFromMemory(); + await this.saveDeviceInDatabase(newDevice); + } +} diff --git a/src/workers/core.worker.ts b/src/workers/core.worker.ts index 0405eb2..b083483 100644 --- a/src/workers/core.worker.ts +++ b/src/workers/core.worker.ts @@ -19,18 +19,93 @@ import { APP_CONFIG } from "../config/constants"; import { splitPrivateData } from "../utils/service.utils"; // Services internes au worker -import { SdkService } from "../services/core/sdk.service"; -import { WalletService } from "../services/domain/wallet.service"; -import { ProcessService } from "../services/domain/process.service"; -import { CryptoService } from "../services/domain/crypto.service"; +import { WalletService } from "../services/wallet.service"; +import { ProcessService } from "../services/process.service"; +import { CryptoService } from "../services/crypto.service"; + +// Types for services passed from main thread +type WasmServiceProxy = { + callMethod(method: string, ...args: any[]): Promise; + parseCipher(msg: string, membersList: any, processes: any): Promise; + parseNewTx(msg: string, blockHeight: number, membersList: any): Promise; + createTransaction(addresses: string[], feeRate: number): Promise; + signTransaction(partialTx: any): Promise; + getTxid(transaction: string): Promise; + getOpReturn(transaction: string): Promise; + getPrevouts(transaction: string): Promise; + createProcess(privateData: any, publicData: any, roles: any, membersList: any): Promise; + createNewProcess(privateData: any, roles: any, publicData: any, relayAddress: string, feeRate: number, membersList: any): Promise; + updateProcess(process: any, privateData: any, publicData: any, roles: any, membersList: any): Promise; + createUpdateMessage(process: any, stateId: string, membersList: any): Promise; + createPrdResponse(process: any, stateId: string, membersList: any): Promise; + validateState(process: any, stateId: string, membersList: any): Promise; + refuseState(process: any, stateId: string): Promise; + requestData(processId: string, stateIds: string[], roles: any, membersList: any): Promise; + processCommitNewState(process: any, newStateId: string, newTip: string): Promise; + encodeJson(data: any): Promise; + encodeBinary(data: any): Promise; + decodeValue(value: number[]): Promise; + createFaucetMessage(): Promise; + isChildRole(parent: any, child: any): Promise; + // Wallet methods + isPaired(): Promise; + getAvailableAmount(): Promise; + getAddress(): Promise; + getPairingProcessId(): Promise; + createNewDevice(birthday: number, network: string): Promise; + dumpDevice(): Promise; + dumpNeuteredDevice(): Promise; + dumpWallet(): Promise; + restoreDevice(device: any): Promise; + pairDevice(processId: string, spAddresses: string[]): Promise; + unpairDevice(): Promise; + resetDevice(): Promise; + // Crypto methods + hashValue(fileBlob: { type: string; data: Uint8Array }, commitedIn: string, label: string): Promise; + getMerkleProof(processState: any, attributeName: string): Promise; + validateMerkleProof(proof: any, hash: string): Promise; + decryptData(key: Uint8Array, data: Uint8Array): Promise; + // Secrets management + setSharedSecrets(secretsJson: string): Promise; + // Blockchain scanning + scanBlocks(tipHeight: number, blindbitUrl: string): Promise; +}; + +type DatabaseServiceProxy = { + getStoreList(): Promise<{ [key: string]: string }>; + addObject(payload: { storeName: string; object: any; key: any }): Promise; + batchWriting(payload: { storeName: string; objects: { key: any; object: any }[] }): Promise; + getObject(storeName: string, key: string): Promise; + dumpStore(storeName: string): Promise>; + deleteObject(storeName: string, key: string): Promise; + clearStore(storeName: string): Promise; + requestStoreByIndex(storeName: string, indexName: string, request: string): Promise; + clearMultipleStores(storeNames: string[]): Promise; + saveDevice(device: any): Promise; + getDevice(): Promise; + saveProcess(processId: string, process: any): Promise; + saveProcessesBatch(processes: Record): Promise; + getProcess(processId: string): Promise; + getAllProcesses(): Promise>; + saveBlob(hash: string, data: Blob): Promise; + getBlob(hash: string): Promise; + saveDiffs(diffs: any[]): Promise; + getDiff(hash: string): Promise; + getAllDiffs(): Promise>; + getSharedSecret(address: string): Promise; + saveSecretsBatch(unconfirmedSecrets: any[], sharedSecrets: { key: string; value: any }[]): Promise; + getAllSecrets(): Promise<{ shared_secrets: Record; unconfirmed_secrets: any[] }>; +}; export class CoreBackend { - // Services - private sdkService: SdkService; + // Services (passed from main thread via Comlink) + private wasmService!: WasmServiceProxy; + private db!: DatabaseServiceProxy; + + // Domain services private walletService!: WalletService; private processService!: ProcessService; - private cryptoService: CryptoService; - private db!: Database; + private cryptoService!: CryptoService; // État (State) private processId: string | null = null; @@ -54,28 +129,39 @@ export class CoreBackend { private relayGetter: (() => Promise) | null = null; constructor() { - this.sdkService = new SdkService(); - this.cryptoService = new CryptoService(this.sdkService); - // Initialisation temporaire - this.walletService = new WalletService(this.sdkService, null as any); - this.processService = new ProcessService(this.sdkService, null as any); + // Services will be set via setServices() from main thread } public async init(): Promise { if (this.isInitialized) return; console.log("[CoreWorker] ⚙️ Initialisation du Backend..."); - await this.sdkService.init(); // Charge le WASM - this.db = await Database.getInstance(); // Lance le Database Worker + + // Services must be set before init + if (!this.wasmService || !this.db) { + throw new Error("Services must be set via setServices() before init()"); + } - this.walletService = new WalletService(this.sdkService, this.db); - this.processService = new ProcessService(this.sdkService, this.db); + // Initialize domain services with WASM and Database proxies + this.cryptoService = new CryptoService(this.wasmService); + this.walletService = new WalletService(this.wasmService, this.db); + // @ts-ignore - ProcessService accepts DatabaseServiceProxy via Comlink but TypeScript sees Database type + this.processService = new ProcessService(this.db); this.notifications = this.getNotifications(); this.isInitialized = true; console.log("[CoreWorker] ✅ Backend prêt."); } + // --- CONFIGURATION DES SERVICES (depuis Main Thread) --- + public setServices( + wasmService: WasmServiceProxy, + db: DatabaseServiceProxy + ) { + this.wasmService = wasmService; + this.db = db; + } + // --- CONFIGURATION DES CALLBACKS --- public setCallbacks( notifier: (event: string, data?: any) => void, @@ -120,32 +206,32 @@ export class CoreBackend { // ========================================== // WALLET PROXY // ========================================== - public isPaired() { - return this.walletService.isPaired(); + public async isPaired() { + return await this.walletService.isPaired(); } - public getAmount() { - return this.walletService.getAmount(); + public async getAmount() { + return await this.walletService.getAmount(); } - public getDeviceAddress() { - return this.walletService.getDeviceAddress(); + public async getDeviceAddress() { + return await this.walletService.getDeviceAddress(); } - public dumpDeviceFromMemory() { - return this.walletService.dumpDeviceFromMemory(); + public async dumpDeviceFromMemory() { + return await this.walletService.dumpDeviceFromMemory(); } - public dumpNeuteredDevice() { - return this.walletService.dumpNeuteredDevice(); + public async dumpNeuteredDevice() { + return await this.walletService.dumpNeuteredDevice(); } - public getPairingProcessId() { - return this.walletService.getPairingProcessId(); + public async getPairingProcessId() { + return await this.walletService.getPairingProcessId(); } public async getDeviceFromDatabase() { return this.walletService.getDeviceFromDatabase(); } - public restoreDevice(d: Device) { - this.walletService.restoreDevice(d); + public async restoreDevice(d: Device) { + await this.walletService.restoreDevice(d); } - public pairDevice(pid: string, list: string[]) { - this.walletService.pairDevice(pid, list); + public async pairDevice(pid: string, list: string[]) { + await this.walletService.pairDevice(pid, list); } public async unpairDevice() { await this.walletService.unpairDevice(); @@ -199,8 +285,8 @@ export class CoreBackend { // ========================================== // CRYPTO HELPERS // ========================================== - public decodeValue(val: number[]) { - return this.sdkService.decodeValue(val); + public async decodeValue(val: number[]) { + return await this.wasmService.decodeValue(val); } public hexToBlob(hex: string) { return this.cryptoService.hexToBlob(hex); @@ -211,14 +297,14 @@ export class CoreBackend { public async blobToHex(blob: Blob) { return this.cryptoService.blobToHex(blob); } - public getHashForFile(c: string, l: string, f: any) { - return this.cryptoService.getHashForFile(c, l, f); + public async getHashForFile(c: string, l: string, f: any) { + return await this.cryptoService.getHashForFile(c, l, f); } - public getMerkleProofForFile(s: ProcessState, a: string) { - return this.cryptoService.getMerkleProofForFile(s, a); + public async getMerkleProofForFile(s: ProcessState, a: string) { + return await this.cryptoService.getMerkleProofForFile(s, a); } - public validateMerkleProof(p: MerkleProofResult, h: string) { - return this.cryptoService.validateMerkleProof(p, h); + public async validateMerkleProof(p: MerkleProofResult, h: string) { + return await this.cryptoService.validateMerkleProof(p, h); } private splitData(obj: Record) { return this.cryptoService.splitData(obj); @@ -257,16 +343,13 @@ export class CoreBackend { // ========================================== // UTILITAIRES DIVERS // ========================================== - public createFaucetMessage() { - return this.sdkService.getClient().create_faucet_msg(); + public async createFaucetMessage() { + return await this.wasmService.createFaucetMessage(); } - public isChildRole(parent: any, child: any): boolean { + public async isChildRole(parent: any, child: any): Promise { try { - this.sdkService - .getClient() - .is_child_role(JSON.stringify(parent), JSON.stringify(child)); - return true; + return await this.wasmService.isChildRole(parent, child); } catch (e) { console.error(e); return false; @@ -285,7 +368,7 @@ export class CoreBackend { } this.currentBlockHeight = handshakeMsg.chain_tip; - if (!this.isPaired()) { + if (!(await this.isPaired())) { console.log(`[CoreWorker] ⏳ Non pairé. Attente appairage...`); } @@ -315,16 +398,14 @@ export class CoreBackend { device.sp_wallet.birthday = this.currentBlockHeight; device.sp_wallet.last_scan = this.currentBlockHeight; await this.walletService.saveDeviceInDatabase(device); - this.walletService.restoreDevice(device); + await this.walletService.restoreDevice(device); } else if (device.sp_wallet.last_scan < this.currentBlockHeight) { console.log( `[CoreWorker] Scan requis de ${device.sp_wallet.last_scan} à ${this.currentBlockHeight}` ); try { - await this.sdkService - .getClient() - .scan_blocks(this.currentBlockHeight, APP_CONFIG.URLS.BLINDBIT); - const updatedDevice = this.walletService.dumpDeviceFromMemory(); + await this.wasmService.scanBlocks(this.currentBlockHeight, APP_CONFIG.URLS.BLINDBIT); + const updatedDevice = await this.walletService.dumpDeviceFromMemory(); await this.walletService.saveDeviceInDatabase(updatedDevice); } catch (e) { console.error("Scan error", e); @@ -371,7 +452,7 @@ export class CoreBackend { existing.states.push(existingLast); toSave[processId] = existing; hasChanged = true; - if (this.rolesContainsUs(state.roles)) { + if (await this.rolesContainsUs(state.roles)) { newStates.push(state.state_id); newRoles.push(state.roles); } @@ -387,7 +468,7 @@ export class CoreBackend { (!existingState.keys || Object.keys(existingState.keys).length === 0) ) { - if (this.rolesContainsUs(state.roles)) { + if (await this.rolesContainsUs(state.roles)) { newStates.push(state.state_id); newRoles.push(state.roles); // Ici on ne marque pas forcément hasChanged pour la DB, mais on demande les clés @@ -429,7 +510,10 @@ export class CoreBackend { // ========================================== public async getMyProcesses(): Promise { try { - const pid = this.getPairingProcessId(); + if (!(await this.isPaired())) { + return null; + } + const pid = await this.getPairingProcessId(); return await this.processService.getMyProcesses(pid); } catch (e) { return null; @@ -470,7 +554,7 @@ export class CoreBackend { } } if (publicData && publicData["pairedAddresses"]) { - const decoded = this.decodeValue(publicData["pairedAddresses"]); + const decoded = await this.decodeValue(publicData["pairedAddresses"]); if (decoded) members.add({ sp_addresses: decoded }); } } @@ -478,7 +562,7 @@ export class CoreBackend { if (members.size === 0) return; const unconnected = new Set(); - const myAddress = this.getDeviceAddress(); + const myAddress = await this.getDeviceAddress(); for (const member of Array.from(members)) { if (!member.sp_addresses) continue; for (const address of member.sp_addresses) { @@ -502,13 +586,11 @@ export class CoreBackend { if (addresses.length === 0) return null; const feeRate = APP_CONFIG.FEE_RATE; try { - return this.sdkService.getClient().create_transaction(addresses, feeRate); + return await this.wasmService.createTransaction(addresses, feeRate); } catch (error: any) { if (String(error).includes("Insufficient funds")) { await this.getTokensFromFaucet(); - return this.sdkService - .getClient() - .create_transaction(addresses, feeRate); + return await this.wasmService.createTransaction(addresses, feeRate); } else { throw error; } @@ -517,15 +599,15 @@ export class CoreBackend { private async getTokensFromFaucet(): Promise { console.log("[CoreWorker] 🚰 Demande Faucet..."); - const availableAmt = this.getAmount(); + const availableAmt = await this.getAmount(); const target: BigInt = APP_CONFIG.DEFAULT_AMOUNT * BigInt(10); if (availableAmt < target) { - const msg = this.sdkService.getClient().create_faucet_msg(); + const msg = await this.wasmService.createFaucetMessage(); if (this.networkSender) this.networkSender("Faucet", msg); let attempts = 3; while (attempts > 0) { - if (this.getAmount() >= target) return; + if ((await this.getAmount()) >= target) return; attempts--; await new Promise((r) => setTimeout(r, APP_CONFIG.TIMEOUTS.RETRY_DELAY) @@ -539,8 +621,8 @@ export class CoreBackend { userName: string, pairWith: string[] ): Promise { - if (this.isPaired()) throw new Error("Déjà appairé"); - const myAddress = this.getDeviceAddress(); + if (await this.isPaired()) throw new Error("Déjà appairé"); + const myAddress = await this.getDeviceAddress(); pairWith.push(myAddress); const privateData = { description: "pairing", counter: 0 }; const publicData = { @@ -610,9 +692,7 @@ export class CoreBackend { fee: number, members: any ): Promise { - const res = this.sdkService - .getClient() - .create_new_process(priv, roles, pub, relay, fee, members); + const res = await this.wasmService.createNewProcess(priv, roles, pub, relay, fee, members); if (res.updated_process) { await this.ensureConnections(res.updated_process.current_process); } @@ -633,7 +713,7 @@ export class CoreBackend { if (!lastState) { const first = process.states[0]; - if (this.rolesContainsUs(first.roles)) { + if (await this.rolesContainsUs(first.roles)) { const appRes = await this.approveChange(processId, first.state_id); await this.handleApiReturn(appRes); const prdRes = await this.createPrdUpdate(processId, first.state_id); @@ -678,15 +758,13 @@ export class CoreBackend { const { encodedPrivateData, encodedPublicData } = await this.prepareProcessData(privateData, publicData); - const res = this.sdkService - .getClient() - .update_process( - currentProcess, - encodedPrivateData, - finalRoles, - encodedPublicData, - this.membersList - ); + const res = await this.wasmService.updateProcess( + currentProcess, + encodedPrivateData, + encodedPublicData, + finalRoles, + this.membersList + ); if (res.updated_process) await this.ensureConnections(res.updated_process.current_process); return res; @@ -697,12 +775,12 @@ export class CoreBackend { const p2 = this.splitData(pub); return { encodedPrivateData: { - ...this.sdkService.getClient().encode_json(p1.jsonCompatibleData), - ...this.sdkService.getClient().encode_binary(p1.binaryData), + ...(await this.wasmService.encodeJson(p1.jsonCompatibleData)), + ...(await this.wasmService.encodeBinary(p1.binaryData)), }, encodedPublicData: { - ...this.sdkService.getClient().encode_json(p2.jsonCompatibleData), - ...this.sdkService.getClient().encode_binary(p2.binaryData), + ...(await this.wasmService.encodeJson(p2.jsonCompatibleData)), + ...(await this.wasmService.encodeBinary(p2.binaryData)), }, }; } @@ -713,38 +791,30 @@ export class CoreBackend { public async createPrdUpdate(pid: string, sid: string) { const p = await this.getProcess(pid); await this.ensureConnections(p!); - return this.sdkService - .getClient() - .create_update_message(p, sid, this.membersList); + return await this.wasmService.createUpdateMessage(p, sid, this.membersList); } public async createPrdResponse(pid: string, sid: string) { const p = await this.getProcess(pid); - return this.sdkService - .getClient() - .create_response_prd(p, sid, this.membersList); + return await this.wasmService.createPrdResponse(p, sid, this.membersList); } public async approveChange(pid: string, sid: string) { const p = await this.getProcess(pid); - const res = this.sdkService - .getClient() - .validate_state(p, sid, this.membersList); + const res = await this.wasmService.validateState(p, sid, this.membersList); if (res.updated_process) await this.ensureConnections(res.updated_process.current_process); return res; } public async rejectChange(pid: string, sid: string) { const p = await this.getProcess(pid); - return this.sdkService.getClient().refuse_state(p, sid); + return await this.wasmService.refuseState(p, sid); } public async requestDataFromPeers(pid: string, sids: string[], roles: any) { - const res = this.sdkService - .getClient() - .request_data(pid, sids, roles, this.membersList); + const res = await this.wasmService.requestData(pid, sids, roles, this.membersList); await this.handleApiReturn(res); } public async resetDevice() { - this.sdkService.getClient().reset_device(); + await this.wasmService.resetDevice(); await this.db.clearMultipleStores([ "wallet", "shared_secrets", @@ -782,8 +852,8 @@ export class CoreBackend { private async handlePartialTx(partialTx: any): Promise { try { - return this.sdkService.getClient().sign_transaction(partialTx) - .new_tx_to_send; + const result = await this.wasmService.signTransaction(partialTx); + return result.new_tx_to_send; } catch (e) { return null; } @@ -871,11 +941,19 @@ export class CoreBackend { } } - public rolesContainsUs(roles: any) { - return this.processService.rolesContainsMember( - roles, - this.getPairingProcessId() - ); + public async rolesContainsUs(roles: any) { + try { + if (!(await this.isPaired())) { + return false; + } + return this.processService.rolesContainsMember( + roles, + await this.getPairingProcessId() + ); + } catch (e) { + console.error("RolesContainsUs Error:", e); + return false; + } } public async getSecretForAddress(address: string): Promise { @@ -946,9 +1024,7 @@ export class CoreBackend { // ========================================== async parseCipher(msg: string) { try { - const res = this.sdkService - .getClient() - .parse_cipher(msg, this.membersList, await this.getProcesses()); + const res = await this.wasmService.parseCipher(msg, this.membersList, await this.getProcesses()); await this.handleApiReturn(res); } catch (e) { console.error("Cipher Error", e); @@ -959,27 +1035,19 @@ export class CoreBackend { const parsed = JSON.parse(msg); if (parsed.error) return; - const prevouts = this.sdkService - .getClient() - .get_prevouts(parsed.transaction); + const prevouts = await this.wasmService.getPrevouts(parsed.transaction); for (const p of Object.values(await this.getProcesses())) { const tip = p.states[p.states.length - 1].commited_in; if (prevouts.includes(tip)) { - const newTip = this.sdkService.getClient().get_txid(parsed.transaction); - const newStateId = this.sdkService - .getClient() - .get_opreturn(parsed.transaction); - this.sdkService - .getClient() - .process_commit_new_state(p, newStateId, newTip); + const newTip = await this.wasmService.getTxid(parsed.transaction); + const newStateId = await this.wasmService.getOpReturn(parsed.transaction); + await this.wasmService.processCommitNewState(p, newStateId, newTip); break; } } try { - const res = this.sdkService - .getClient() - .parse_new_tx(msg, 0, this.membersList); + const res = await this.wasmService.parseNewTx(msg, 0, this.membersList); if ( res && (res.partial_tx || @@ -988,7 +1056,7 @@ export class CoreBackend { res.updated_process) ) { await this.handleApiReturn(res); - const d = this.dumpDeviceFromMemory(); + const d = await this.dumpDeviceFromMemory(); const old = await this.getDeviceFromDatabase(); if (old && old.pairing_process_commitment) d.pairing_process_commitment = old.pairing_process_commitment; @@ -1003,7 +1071,7 @@ export class CoreBackend { public async importJSON(backup: BackUp) { await this.resetDevice(); await this.walletService.saveDeviceInDatabase(backup.device); - this.walletService.restoreDevice(backup.device); + await this.walletService.restoreDevice(backup.device); await this.processService.batchSaveProcesses(backup.processes); await this.restoreSecretsFromBackUp(backup.secrets); } @@ -1019,9 +1087,7 @@ export class CoreBackend { } public async restoreSecretsFromDB() { const secretsStore = await this.db.getAllSecrets(); - this.sdkService - .getClient() - .set_shared_secrets(JSON.stringify(secretsStore)); + await this.wasmService.setSharedSecrets(JSON.stringify(secretsStore)); console.log("[CoreWorker] 🔐 Secrets restaurés depuis la DB"); } public async createBackUp() { @@ -1047,9 +1113,13 @@ export class CoreBackend { ); try { + if (!(await this.isPaired())) { + console.warn(`⚠️ Device is not paired. Cannot decrypt attribute.`); + return null; + } let hash: string | null | undefined = state.pcd_commitment[attribute]; let key: string | null | undefined = state.keys[attribute]; - const pairingProcessId = this.getPairingProcessId(); + const pairingProcessId = await this.getPairingProcessId(); if (!hash) { console.warn(`⚠️ L'attribut n'existe pas (pas de hash).`); @@ -1080,12 +1150,10 @@ export class CoreBackend { const cipher = new Uint8Array(buf); const keyUIntArray = this.hexToUInt8Array(key); - const clear = this.sdkService - .getClient() - .decrypt_data(keyUIntArray, cipher); + const clear = await this.wasmService.decryptData(keyUIntArray, cipher); if (!clear) throw new Error("decrypt_data returned null"); - const decoded = this.sdkService.getClient().decode_value(clear); + const decoded = await this.wasmService.decodeValue(Array.from(clear)); console.log(`✅ Attribut '${attribute}' déchiffré avec succès.`); return decoded; } catch (e) { diff --git a/src/workers/wasm.worker.ts b/src/workers/wasm.worker.ts new file mode 100644 index 0000000..1ed36a3 --- /dev/null +++ b/src/workers/wasm.worker.ts @@ -0,0 +1,112 @@ +/** + * WASM Worker - Handles all WASM operations in background + * Single source of truth for WASM module lifecycle and method calls + */ + +/// + +interface WasmWorkerMessage { + id: string; + type: 'WASM_METHOD' | 'INIT'; + method?: string; + args?: any[]; +} + +interface WasmWorkerResponse { + id: string; + type: 'SUCCESS' | 'ERROR'; + result?: any; + error?: string; +} + +let wasmClient: any = null; +let isInitialized = false; + +// ============================================ +// WASM INITIALIZATION +// ============================================ + +async function initWasm(): Promise { + if (isInitialized && wasmClient) { + return; + } + + console.log('[WasmWorker] 🔄 Initializing WASM module...'); + wasmClient = await import('../../pkg/sdk_client'); + wasmClient.setup(); + isInitialized = true; + console.log('[WasmWorker] ✅ WASM module initialized'); +} + +// ============================================ +// WASM METHOD CALL HANDLER +// ============================================ + +async function callWasmMethod(method: string, args: any[]): Promise { + if (!wasmClient) { + throw new Error('WASM client not initialized'); + } + + if (typeof wasmClient[method] !== 'function') { + throw new Error(`WASM method '${method}' does not exist`); + } + + try { + return await wasmClient[method](...args); + } catch (error) { + console.error(`[WasmWorker] Error calling WASM method '${method}':`, error); + throw error; + } +} + +// ============================================ +// MESSAGE HANDLER +// ============================================ + +self.addEventListener('message', async (event: MessageEvent) => { + const { id, type, method, args = [] } = event.data; + + try { + let result: any; + + switch (type) { + case 'INIT': + await initWasm(); + result = { success: true }; + break; + + case 'WASM_METHOD': + if (!method) { + throw new Error('Method name is required'); + } + if (!isInitialized) { + await initWasm(); + } + result = await callWasmMethod(method, args); + break; + + default: + throw new Error(`Unknown message type: ${type}`); + } + + self.postMessage({ + id, + type: 'SUCCESS', + result, + } as WasmWorkerResponse); + } catch (error) { + self.postMessage({ + id, + type: 'ERROR', + error: error instanceof Error ? error.message : String(error), + } as WasmWorkerResponse); + } +}); + +// ============================================ +// INITIALIZATION +// ============================================ + +console.log('[WasmWorker] 🔄 Worker script loaded'); + +