150 lines
5.2 KiB
TypeScript
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()); |