ihm_client/src/workers/network.worker.ts

150 lines
5.2 KiB
TypeScript

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<string, WebSocket> = new Map();
private relayAddresses: Map<string, string> = 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<string, any> = 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());