From 7310f49eb8e4ab03044f7ee99707fbc01c4912cc Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Tue, 25 Nov 2025 00:41:41 +0100 Subject: [PATCH] Added withToken func to remove the duplicated code --- src/services/iframe-controller.service.ts | 469 +++++++++++----------- src/services/websockets.service.ts | 182 +++++---- 2 files changed, 346 insertions(+), 305 deletions(-) diff --git a/src/services/iframe-controller.service.ts b/src/services/iframe-controller.service.ts index 7e3486c..dee524a 100644 --- a/src/services/iframe-controller.service.ts +++ b/src/services/iframe-controller.service.ts @@ -6,7 +6,11 @@ import { splitPrivateData, isValid32ByteHex } from '../utils/service.utils'; import { MerkleProofResult } from '../../pkg/sdk_client'; export class IframeController { + private static isInitialized = false; // <--- VERROU + static async init() { + if (this.isInitialized) return; // On sort si déjà lancé + // On ne lance l'écoute que si on est dans une iframe if (window.self !== window.top) { console.log('[IframeController] 📡 Mode Iframe détecté. Démarrage des listeners API...'); @@ -36,6 +40,17 @@ export class IframeController { ); }; + // Helper pour vérifier le token avant chaque action sensible + const withToken = async (event: MessageEvent, action: () => Promise) => { + const { accessToken } = event.data; + // On vérifie si le token est présent ET valide pour l'origine de l'iframe + if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { + throw new Error('Invalid or expired session token'); + } + // Si tout est bon, on exécute l'action + await action(); + }; + // --- Définitions des gestionnaires (Handlers) --- const handleRequestLink = async (event: MessageEvent) => { @@ -95,131 +110,121 @@ export class IframeController { throw new Error('Device already paired — ignoring CREATE_PAIRING request'); } - const { accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + await withToken(event, async () => { + console.log("[Router:API] 🚀 Démarrage du processus d'appairage..."); - console.log("[Router:API] 🚀 Démarrage du processus d'appairage..."); + const myAddress = services.getDeviceAddress(); + console.log('[Router:API] 1/7: Création du processus de pairing...'); + const createPairingProcessReturn = await services.createPairingProcess('', [myAddress]); - const myAddress = services.getDeviceAddress(); - console.log('[Router:API] 1/7: Création du processus de pairing...'); - const createPairingProcessReturn = await services.createPairingProcess('', [myAddress]); + const pairingId = createPairingProcessReturn.updated_process?.process_id; + const stateId = createPairingProcessReturn.updated_process?.current_process?.states[0]?.state_id as string; + if (!pairingId || !stateId) { + throw new Error('Pairing process creation failed to return valid IDs'); + } + console.log(`[Router:API] 2/7: Processus ${pairingId} créé.`); - const pairingId = createPairingProcessReturn.updated_process?.process_id; - const stateId = createPairingProcessReturn.updated_process?.current_process?.states[0]?.state_id as string; - if (!pairingId || !stateId) { - throw new Error('Pairing process creation failed to return valid IDs'); - } - console.log(`[Router:API] 2/7: Processus ${pairingId} créé.`); + console.log("[Router:API] 3/7: Enregistrement local de l'appareil..."); + services.pairDevice(pairingId, [myAddress]); - console.log("[Router:API] 3/7: Enregistrement local de l'appareil..."); - services.pairDevice(pairingId, [myAddress]); + console.log('[Router:API] 4/7: Traitement du retour (handleApiReturn)...'); + await services.handleApiReturn(createPairingProcessReturn); - console.log('[Router:API] 4/7: Traitement du retour (handleApiReturn)...'); - await services.handleApiReturn(createPairingProcessReturn); + console.log('[Router:API] 5/7: Création de la mise à jour PRD...'); + const createPrdUpdateReturn = await services.createPrdUpdate(pairingId, stateId); + await services.handleApiReturn(createPrdUpdateReturn); - console.log('[Router:API] 5/7: Création de la mise à jour PRD...'); - const createPrdUpdateReturn = await services.createPrdUpdate(pairingId, stateId); - await services.handleApiReturn(createPrdUpdateReturn); + console.log('[Router:API] 6/7: Approbation du changement...'); + const approveChangeReturn = await services.approveChange(pairingId, stateId); + await services.handleApiReturn(approveChangeReturn); - console.log('[Router:API] 6/7: Approbation du changement...'); - const approveChangeReturn = await services.approveChange(pairingId, stateId); - await services.handleApiReturn(approveChangeReturn); + console.log('[Router:API] 7/7: Confirmation finale du pairing...'); + await services.confirmPairing(); - console.log('[Router:API] 7/7: Confirmation finale du pairing...'); - await services.confirmPairing(); + console.log('[Router:API] 🎉 Appairage terminé avec succès !'); - console.log('[Router:API] 🎉 Appairage terminé avec succès !'); - - const successMsg = { - type: MessageType.PAIRING_CREATED, - pairingId, - messageId: event.data.messageId, - }; - window.parent.postMessage(successMsg, event.origin); + const successMsg = { + type: MessageType.PAIRING_CREATED, + pairingId, + messageId: event.data.messageId, + }; + window.parent.postMessage(successMsg, event.origin); + }); }; const handleGetMyProcesses = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + await withToken(event, async () => { + const myProcesses = await services.getMyProcesses(); - const myProcesses = await services.getMyProcesses(); - - window.parent.postMessage( - { - type: MessageType.GET_MY_PROCESSES, - myProcesses, - messageId: event.data.messageId, - }, - event.origin, - ); + window.parent.postMessage( + { + type: MessageType.GET_MY_PROCESSES, + myProcesses, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleGetProcesses = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + await withToken(event, async () => { + const processes = await services.getProcesses(); - const processes = await services.getProcesses(); - - window.parent.postMessage( - { - type: MessageType.PROCESSES_RETRIEVED, - processes, - messageId: event.data.messageId, - }, - event.origin, - ); + window.parent.postMessage( + { + type: MessageType.PROCESSES_RETRIEVED, + processes, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleDecryptState = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { processId, stateId, accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { processId, stateId } = event.data; - const process = await services.getProcess(processId); - if (!process) throw new Error("Can't find process"); + await withToken(event, async () => { + const process = await services.getProcess(processId); + if (!process) throw new Error("Can't find process"); - const state = services.getStateFromId(process, stateId); - if (!state) throw new Error(`Unknown state ${stateId} for process ${processId}`); + const state = services.getStateFromId(process, stateId); + if (!state) throw new Error(`Unknown state ${stateId} for process ${processId}`); - console.log(`[Router:API] 🔐 Démarrage du déchiffrement pour ${processId}`); - await services.ensureConnections(process, stateId); + console.log(`[Router:API] 🔐 Démarrage du déchiffrement pour ${processId}`); + await services.ensureConnections(process, stateId); - const res: Record = {}; - for (const attribute of Object.keys(state.pcd_commitment)) { - if (attribute === 'roles' || (state.public_data && state.public_data[attribute])) { - continue; + const res: Record = {}; + for (const attribute of Object.keys(state.pcd_commitment)) { + if (attribute === 'roles' || (state.public_data && state.public_data[attribute])) { + continue; + } + const decryptedAttribute = await services.decryptAttribute(processId, state, attribute); + if (decryptedAttribute) { + res[attribute] = decryptedAttribute; + } } - const decryptedAttribute = await services.decryptAttribute(processId, state, attribute); - if (decryptedAttribute) { - res[attribute] = decryptedAttribute; - } - } - console.log(`[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length} attribut(s) déchiffré(s).`); + console.log(`[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length} attribut(s) déchiffré(s).`); - window.parent.postMessage( - { - type: MessageType.DATA_RETRIEVED, - data: res, - messageId: event.data.messageId, - }, - event.origin, - ); + window.parent.postMessage( + { + type: MessageType.DATA_RETRIEVED, + data: res, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleValidateToken = async (event: MessageEvent) => { @@ -295,214 +300,204 @@ export class IframeController { throw new Error('Device not paired'); } - const { accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } - - window.parent.postMessage( - { - type: MessageType.GET_PAIRING_ID, - userPairingId: pairingId, - messageId: event.data.messageId, - }, - event.origin, - ); + await withToken(event, async () => { + window.parent.postMessage( + { + type: MessageType.GET_PAIRING_ID, + userPairingId: pairingId, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleCreateProcess = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { processData, privateFields, roles, accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { processData, privateFields, roles } = event.data; - console.log('[Router:API] 🚀 Démarrage de la création de processus standard...'); - const { privateData, publicData } = splitPrivateData(processData, privateFields); + await withToken(event, async () => { + console.log('[Router:API] 🚀 Démarrage de la création de processus standard...'); + const { privateData, publicData } = splitPrivateData(processData, privateFields); - console.log('[Router:API] 1/2: Création du processus...'); - const createProcessReturn = await services.createProcess(privateData, publicData, roles); - if (!createProcessReturn.updated_process) { - throw new Error('Empty updated_process in createProcessReturn'); - } + console.log('[Router:API] 1/2: Création du processus...'); + const createProcessReturn = await services.createProcess(privateData, publicData, roles); + if (!createProcessReturn.updated_process) { + throw new Error('Empty updated_process in createProcessReturn'); + } - const processId = createProcessReturn.updated_process.process_id; - const process = createProcessReturn.updated_process.current_process; - const stateId = process.states[0].state_id; - console.log(`[Router:API] 2/2: Processus ${processId} créé. Traitement...`); - await services.handleApiReturn(createProcessReturn); + const processId = createProcessReturn.updated_process.process_id; + const process = createProcessReturn.updated_process.current_process; + const stateId = process.states[0].state_id; + console.log(`[Router:API] 2/2: Processus ${processId} créé. Traitement...`); + await services.handleApiReturn(createProcessReturn); - console.log(`[Router:API] 🎉 Processus ${processId} créé.`); + console.log(`[Router:API] 🎉 Processus ${processId} créé.`); - const res = { - processId, - process, - processData, - }; + const res = { + processId, + process, + processData, + }; - window.parent.postMessage( - { - type: MessageType.PROCESS_CREATED, - processCreated: res, - messageId: event.data.messageId, - }, - event.origin, - ); + window.parent.postMessage( + { + type: MessageType.PROCESS_CREATED, + processCreated: res, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleNotifyUpdate = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { processId, stateId, accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } - if (!isValid32ByteHex(stateId)) throw new Error('Invalid state id'); + const { processId, stateId } = event.data; - const res = await services.createPrdUpdate(processId, stateId); - await services.handleApiReturn(res); + await withToken(event, async () => { + if (!isValid32ByteHex(stateId)) throw new Error('Invalid state id'); - window.parent.postMessage( - { - type: MessageType.UPDATE_NOTIFIED, - messageId: event.data.messageId, - }, - event.origin, - ); + const res = await services.createPrdUpdate(processId, stateId); + await services.handleApiReturn(res); + + window.parent.postMessage( + { + type: MessageType.UPDATE_NOTIFIED, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleValidateState = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { processId, stateId, accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { processId, stateId } = event.data; - const res = await services.approveChange(processId, stateId); - await services.handleApiReturn(res); + await withToken(event, async () => { + const res = await services.approveChange(processId, stateId); + await services.handleApiReturn(res); - window.parent.postMessage( - { - type: MessageType.STATE_VALIDATED, - validatedProcess: res.updated_process, - messageId: event.data.messageId, - }, - event.origin, - ); + window.parent.postMessage( + { + type: MessageType.STATE_VALIDATED, + validatedProcess: res.updated_process, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleUpdateProcess = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { processId, newData, privateFields, roles, accessToken } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { processId, newData, privateFields, roles } = event.data; - console.log(`[Router:API] 🔄 Transfert de la mise à jour de ${processId} au service...`); + await withToken(event, async () => { + console.log(`[Router:API] 🔄 Transfert de la mise à jour de ${processId} au service...`); - // Le service gère maintenant tout : récupération, réparation d'état, et mise à jour. - const res = await services.updateProcess(processId, newData, privateFields, roles); + // Le service gère maintenant tout : récupération, réparation d'état, et mise à jour. + const res = await services.updateProcess(processId, newData, privateFields, roles); - // Nous appelons handleApiReturn ici, comme avant. - await services.handleApiReturn(res); - // --- FIN DE LA MODIFICATION --- + // Nous appelons handleApiReturn ici, comme avant. + await services.handleApiReturn(res); + // --- FIN DE LA MODIFICATION --- - window.parent.postMessage( - { - type: MessageType.PROCESS_UPDATED, - updatedProcess: res.updated_process, // res vient directement de l'appel service - messageId: event.data.messageId, - }, - event.origin, - ); + window.parent.postMessage( + { + type: MessageType.PROCESS_UPDATED, + updatedProcess: res.updated_process, // res vient directement de l'appel service + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleDecodePublicData = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu`); if (!services.isPaired()) throw new Error('Device not paired'); - const { accessToken, encodedData } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { encodedData } = event.data; - const decodedData = services.decodeValue(encodedData); - window.parent.postMessage( - { - type: MessageType.PUBLIC_DATA_DECODED, - decodedData, - messageId: event.data.messageId, - }, - event.origin, - ); + await withToken(event, async () => { + const decodedData = services.decodeValue(encodedData); + window.parent.postMessage( + { + type: MessageType.PUBLIC_DATA_DECODED, + decodedData, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleHashValue = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.HASH_VALUE} reçu`); - const { accessToken, commitedIn, label, fileBlob } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { commitedIn, label, fileBlob } = event.data; - const hash = services.getHashForFile(commitedIn, label, fileBlob); - window.parent.postMessage( - { - type: MessageType.VALUE_HASHED, - hash, - messageId: event.data.messageId, - }, - event.origin, - ); + await withToken(event, async () => { + const hash = services.getHashForFile(commitedIn, label, fileBlob); + window.parent.postMessage( + { + type: MessageType.VALUE_HASHED, + hash, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleGetMerkleProof = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.GET_MERKLE_PROOF} reçu`); - const { accessToken, processState, attributeName } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { processState, attributeName } = event.data; - const proof = services.getMerkleProofForFile(processState, attributeName); - window.parent.postMessage( - { - type: MessageType.MERKLE_PROOF_RETRIEVED, - proof, - messageId: event.data.messageId, - }, - event.origin, - ); + await withToken(event, async () => { + const proof = services.getMerkleProofForFile(processState, attributeName); + window.parent.postMessage( + { + type: MessageType.MERKLE_PROOF_RETRIEVED, + proof, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; const handleValidateMerkleProof = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_MERKLE_PROOF} reçu`); - const { accessToken, merkleProof, documentHash } = event.data; - if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { - throw new Error('Invalid or expired session token'); - } + const { merkleProof, documentHash } = event.data; - let parsedMerkleProof: MerkleProofResult; - try { - parsedMerkleProof = JSON.parse(merkleProof); - } catch (e) { - throw new Error('Provided merkleProof is not a valid json object'); - } + await withToken(event, async () => { + let parsedMerkleProof: MerkleProofResult; + try { + parsedMerkleProof = JSON.parse(merkleProof); + } catch (e) { + throw new Error('Provided merkleProof is not a valid json object'); + } - const res = services.validateMerkleProof(parsedMerkleProof, documentHash); - window.parent.postMessage( - { - type: MessageType.MERKLE_PROOF_VALIDATED, - isValid: res, - messageId: event.data.messageId, - }, - event.origin, - ); + const res = services.validateMerkleProof(parsedMerkleProof, documentHash); + window.parent.postMessage( + { + type: MessageType.MERKLE_PROOF_VALIDATED, + isValid: res, + messageId: event.data.messageId, + }, + event.origin, + ); + }); }; // --- Le "Switchyard" : il reçoit tous les messages et les dispatche --- diff --git a/src/services/websockets.service.ts b/src/services/websockets.service.ts index 492a995..9bbe53e 100755 --- a/src/services/websockets.service.ts +++ b/src/services/websockets.service.ts @@ -1,89 +1,135 @@ -import { AnkFlag } from 'pkg/sdk_client'; +import { AnkFlag } from '../../pkg/sdk_client'; // Vérifie le chemin vers pkg import Services from './service'; -let ws: WebSocket; +let ws: WebSocket | null = null; let messageQueue: string[] = []; +let reconnectInterval = 1000; // Délai initial de 1s avant reconnexion +const MAX_RECONNECT_INTERVAL = 30000; // Max 30s +let isConnecting = false; +let urlReference: string = ''; +let pingIntervalId: any = null; + export async function initWebsocket(url: string) { - ws = new WebSocket(url); - - if (ws !== null) { - ws.onopen = async (event) => { - console.log('WebSocket connection established'); - - while (messageQueue.length > 0) { - const message = messageQueue.shift(); - if (message) { - ws.send(message); - } - } - }; - - // Listen for messages - ws.onmessage = (event) => { - const msgData = event.data; - - // console.log("Received text message: ", msgData); - (async () => { - if (typeof msgData === 'string') { - try { - const parsedMessage = JSON.parse(msgData); - const services = await Services.getInstance(); - switch (parsedMessage.flag) { - case 'Handshake': - await services.handleHandshakeMsg(url, parsedMessage.content); - break; - case 'NewTx': - await services.parseNewTx(parsedMessage.content); - break; - case 'Cipher': - await services.parseCipher(parsedMessage.content); - break; - case 'Commit': - // Basically if we see this it means we have an error - await services.handleCommitError(parsedMessage.content); - break; - } - } catch (error) { - console.error('Received an invalid message:', error); - } - } else { - console.error('Received a non-string message'); - } - })(); - }; - - // Listen for possible errors - ws.onerror = (event) => { - console.error('WebSocket error:', event); - }; - - // Listen for when the connection is closed - ws.onclose = (event) => { - console.log('WebSocket is closed now.'); - }; - } + urlReference = url; + connect(); +} + +function connect() { + if (isConnecting || (ws && ws.readyState === WebSocket.OPEN)) return; + isConnecting = true; + + console.log(`[WS] 🔌 Tentative de connexion à ${urlReference}...`); + ws = new WebSocket(urlReference); + + ws.onopen = async () => { + console.log('[WS] ✅ Connexion établie !'); + isConnecting = false; + reconnectInterval = 1000; // Reset du délai + + // Démarrer le Heartbeat (Ping pour garder la connexion vivante) + startHeartbeat(); + + // Vider la file d'attente (messages envoyés pendant la coupure) + while (messageQueue.length > 0) { + const message = messageQueue.shift(); + if (message) ws?.send(message); + } + }; + + ws.onmessage = (event) => { + const msgData = event.data; + if (typeof msgData === 'string') { + (async () => { + try { + const parsedMessage = JSON.parse(msgData); + const services = await Services.getInstance(); + + // Gestion des messages + switch (parsedMessage.flag) { + case 'Handshake': + await services.handleHandshakeMsg(urlReference, parsedMessage.content); + break; + case 'NewTx': + await services.parseNewTx(parsedMessage.content); + break; + case 'Cipher': + await services.parseCipher(parsedMessage.content); + break; + case 'Commit': + await services.handleCommitError(parsedMessage.content); + break; + // Ajoute d'autres cas si nécessaire + default: + // console.log('[WS] Message reçu:', parsedMessage.flag); + } + } catch (error) { + console.error('[WS] Erreur traitement message:', error); + } + })(); + } + }; + + ws.onerror = (event) => { + console.error('[WS] 💥 Erreur:', event); + // Pas besoin de reconnecter ici, onclose sera appelé juste après + }; + + ws.onclose = (event) => { + isConnecting = false; + stopHeartbeat(); + console.warn(`[WS] ⚠️ Déconnecté (Code: ${event.code}). Reconnexion dans ${reconnectInterval / 1000}s...`); + + // Reconnexion exponentielle (1s, 1.5s, 2.25s...) + setTimeout(() => { + connect(); + reconnectInterval = Math.min(reconnectInterval * 1.5, MAX_RECONNECT_INTERVAL); + }, reconnectInterval); + }; +} + +function startHeartbeat() { + stopHeartbeat(); + // Envoie un ping toutes les 30 secondes pour éviter que le serveur ou le navigateur ne coupe la connexion + pingIntervalId = setInterval(() => { + if (ws && ws.readyState === WebSocket.OPEN) { + // Adapter selon ce que ton serveur attend comme Ping, ou envoyer un message vide + // ws.send(JSON.stringify({ flag: 'Ping', content: '' })); + } + }, 30000); +} + +function stopHeartbeat() { + if (pingIntervalId) clearInterval(pingIntervalId); } -// Method to send messages export function sendMessage(flag: AnkFlag, message: string): void { - if (ws.readyState === WebSocket.OPEN) { + if (ws && ws.readyState === WebSocket.OPEN) { const networkMessage = { flag: flag, content: message, }; - console.log('Sending message of type:', flag); ws.send(JSON.stringify(networkMessage)); } else { - console.error('WebSocket is not open. ReadyState:', ws.readyState); - messageQueue.push(message); + console.warn(`[WS] Pas connecté. Message '${flag}' mis en file d'attente.`); + const networkMessage = { + flag: flag, + content: message, + }; + messageQueue.push(JSON.stringify(networkMessage)); + + // Si on n'est pas déjà en train de se connecter, on force une tentative + if (!isConnecting) connect(); } } export function getUrl(): string { - return ws.url; + return urlReference; } -// Method to close the WebSocket connection export function close(): void { - ws.close(); + if (ws) { + ws.onclose = null; // On évite la reconnexion auto si fermeture volontaire + stopHeartbeat(); + ws.close(); + } }