import * as Comlink from 'comlink'; import { APP_CONFIG } from '../config/constants'; // On redéfinit le type localement pour éviter d'importer tout le SDK WASM ici type AnkFlag = 'Handshake' | 'NewTx' | 'Cipher' | 'Commit' | 'Faucet' | 'Ping'; export class NetworkBackend { private sockets: Map = new Map(); private relayAddresses: Map = new Map(); // wsUrl -> spAddress private messageQueue: string[] = []; // Callback pour notifier le Main Thread private msgCallback: ((flag: string, content: string, url: string) => void) | null = null; private statusCallback: ((url: string, status: 'OPEN' | 'CLOSED', spAddress?: string) => void) | null = null; // Timers pour la gestion des reconnexions private reconnectTimers: Map = new Map(); private heartbeatInterval: any = null; constructor() { this.startHeartbeat(); } public setCallbacks( msgCb: (flag: string, content: string, url: string) => void, statusCb: (url: string, status: 'OPEN' | 'CLOSED', spAddress?: string) => void ) { this.msgCallback = msgCb; this.statusCallback = statusCb; } public async connect(url: string) { if (this.sockets.has(url) && this.sockets.get(url)?.readyState === WebSocket.OPEN) return; console.log(`[NetworkWorker] 🔌 Connexion à ${url}...`); const ws = new WebSocket(url); ws.onopen = () => { console.log(`[NetworkWorker] ✅ Connecté à ${url}`); this.sockets.set(url, ws); // Reset timer reconnexion si existant if (this.reconnectTimers.has(url)) { clearTimeout(this.reconnectTimers.get(url)); this.reconnectTimers.delete(url); } // Vider la file d'attente (si message en attente pour ce socket ou broadcast) this.flushQueue(); if (this.statusCallback) this.statusCallback(url, 'OPEN'); }; ws.onmessage = (event) => { try { const msg = JSON.parse(event.data); // Si c'est un Handshake, on met à jour la map locale if (msg.flag === 'Handshake' && msg.content) { const handshake = JSON.parse(msg.content); if (handshake.sp_address) { this.relayAddresses.set(url, handshake.sp_address); if (this.statusCallback) this.statusCallback(url, 'OPEN', handshake.sp_address); } } // On remonte TOUT au Main Thread (qui passera au Core) if (this.msgCallback) this.msgCallback(msg.flag, msg.content, url); } catch (e) { console.error('[NetworkWorker] Erreur parsing message:', e); } }; ws.onerror = (e) => { // console.error(`[NetworkWorker] Erreur sur ${url}`, e); }; ws.onclose = () => { console.warn(`[NetworkWorker] ❌ Déconnecté de ${url}.`); this.sockets.delete(url); this.relayAddresses.set(url, ''); // Reset spAddress if (this.statusCallback) this.statusCallback(url, 'CLOSED'); this.scheduleReconnect(url); }; } public sendMessage(flag: AnkFlag, content: string) { const msgStr = JSON.stringify({ flag, content }); // Stratégie simple : On envoie à TOUS les relais connectés (Broadcast) // Ou on pourrait cibler un relais spécifique si besoin. let sent = false; for (const [url, ws] of this.sockets) { if (ws.readyState === WebSocket.OPEN) { ws.send(msgStr); sent = true; } } if (!sent) { // console.warn(`[NetworkWorker] Pas de connexion. Message ${flag} mis en file.`); this.messageQueue.push(msgStr); } } public getAvailableRelay(): string | null { // Retourne l'adresse SP d'un relais connecté for (const sp of this.relayAddresses.values()) { if (sp && sp !== '') return sp; } return null; } public getAllRelays() { return Object.fromEntries(this.relayAddresses); } // --- INTERNES --- private flushQueue() { while (this.messageQueue.length > 0) { const msg = this.messageQueue.shift(); if (!msg) break; for (const ws of this.sockets.values()) { if (ws.readyState === WebSocket.OPEN) ws.send(msg); } } } private scheduleReconnect(url: string) { if (this.reconnectTimers.has(url)) return; console.log(`[NetworkWorker] ⏳ Reconnexion à ${url} dans 3s...`); const timer = setTimeout(() => { this.reconnectTimers.delete(url); this.connect(url); }, 3000); // Délai fixe ou APP_CONFIG.TIMEOUTS.RETRY_DELAY this.reconnectTimers.set(url, timer); } private startHeartbeat() { this.heartbeatInterval = setInterval(() => { // Envoi d'un ping léger ou gestion du keep-alive // this.sendMessage('Ping', ''); }, 30000); } } Comlink.expose(new NetworkBackend());