Compare commits

...

2 Commits

2 changed files with 346 additions and 305 deletions

View File

@ -6,7 +6,11 @@ import { splitPrivateData, isValid32ByteHex } from '../utils/service.utils';
import { MerkleProofResult } from '../../pkg/sdk_client'; import { MerkleProofResult } from '../../pkg/sdk_client';
export class IframeController { export class IframeController {
private static isInitialized = false; // <--- VERROU
static async init() { 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 // On ne lance l'écoute que si on est dans une iframe
if (window.self !== window.top) { if (window.self !== window.top) {
console.log('[IframeController] 📡 Mode Iframe détecté. Démarrage des listeners API...'); 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<void>) => {
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) --- // --- Définitions des gestionnaires (Handlers) ---
const handleRequestLink = async (event: MessageEvent) => { const handleRequestLink = async (event: MessageEvent) => {
@ -95,131 +110,121 @@ export class IframeController {
throw new Error('Device already paired — ignoring CREATE_PAIRING request'); throw new Error('Device already paired — ignoring CREATE_PAIRING request');
} }
const { accessToken } = event.data; await withToken(event, async () => {
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { console.log("[Router:API] 🚀 Démarrage du processus d'appairage...");
throw new Error('Invalid or expired session token');
}
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(); const pairingId = createPairingProcessReturn.updated_process?.process_id;
console.log('[Router:API] 1/7: Création du processus de pairing...'); const stateId = createPairingProcessReturn.updated_process?.current_process?.states[0]?.state_id as string;
const createPairingProcessReturn = await services.createPairingProcess('', [myAddress]); 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; console.log("[Router:API] 3/7: Enregistrement local de l'appareil...");
const stateId = createPairingProcessReturn.updated_process?.current_process?.states[0]?.state_id as string; services.pairDevice(pairingId, [myAddress]);
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..."); console.log('[Router:API] 4/7: Traitement du retour (handleApiReturn)...');
services.pairDevice(pairingId, [myAddress]); await services.handleApiReturn(createPairingProcessReturn);
console.log('[Router:API] 4/7: Traitement du retour (handleApiReturn)...'); console.log('[Router:API] 5/7: Création de la mise à jour PRD...');
await services.handleApiReturn(createPairingProcessReturn); const createPrdUpdateReturn = await services.createPrdUpdate(pairingId, stateId);
await services.handleApiReturn(createPrdUpdateReturn);
console.log('[Router:API] 5/7: Création de la mise à jour PRD...'); console.log('[Router:API] 6/7: Approbation du changement...');
const createPrdUpdateReturn = await services.createPrdUpdate(pairingId, stateId); const approveChangeReturn = await services.approveChange(pairingId, stateId);
await services.handleApiReturn(createPrdUpdateReturn); await services.handleApiReturn(approveChangeReturn);
console.log('[Router:API] 6/7: Approbation du changement...'); console.log('[Router:API] 7/7: Confirmation finale du pairing...');
const approveChangeReturn = await services.approveChange(pairingId, stateId); await services.confirmPairing();
await services.handleApiReturn(approveChangeReturn);
console.log('[Router:API] 7/7: Confirmation finale du pairing...'); console.log('[Router:API] 🎉 Appairage terminé avec succès !');
await services.confirmPairing();
console.log('[Router:API] 🎉 Appairage terminé avec succès !'); const successMsg = {
type: MessageType.PAIRING_CREATED,
const successMsg = { pairingId,
type: MessageType.PAIRING_CREATED, messageId: event.data.messageId,
pairingId, };
messageId: event.data.messageId, window.parent.postMessage(successMsg, event.origin);
}; });
window.parent.postMessage(successMsg, event.origin);
}; };
const handleGetMyProcesses = async (event: MessageEvent) => { const handleGetMyProcesses = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { accessToken } = event.data; await withToken(event, async () => {
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { const myProcesses = await services.getMyProcesses();
throw new Error('Invalid or expired session token');
}
const myProcesses = await services.getMyProcesses(); window.parent.postMessage(
{
window.parent.postMessage( type: MessageType.GET_MY_PROCESSES,
{ myProcesses,
type: MessageType.GET_MY_PROCESSES, messageId: event.data.messageId,
myProcesses, },
messageId: event.data.messageId, event.origin,
}, );
event.origin, });
);
}; };
const handleGetProcesses = async (event: MessageEvent) => { const handleGetProcesses = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { accessToken } = event.data; await withToken(event, async () => {
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { const processes = await services.getProcesses();
throw new Error('Invalid or expired session token');
}
const processes = await services.getProcesses(); window.parent.postMessage(
{
window.parent.postMessage( type: MessageType.PROCESSES_RETRIEVED,
{ processes,
type: MessageType.PROCESSES_RETRIEVED, messageId: event.data.messageId,
processes, },
messageId: event.data.messageId, event.origin,
}, );
event.origin, });
);
}; };
const handleDecryptState = async (event: MessageEvent) => { const handleDecryptState = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { processId, stateId, accessToken } = event.data; const { processId, stateId } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
const process = await services.getProcess(processId); await withToken(event, async () => {
if (!process) throw new Error("Can't find process"); const process = await services.getProcess(processId);
if (!process) throw new Error("Can't find process");
const state = services.getStateFromId(process, stateId); const state = services.getStateFromId(process, stateId);
if (!state) throw new Error(`Unknown state ${stateId} for process ${processId}`); if (!state) throw new Error(`Unknown state ${stateId} for process ${processId}`);
console.log(`[Router:API] 🔐 Démarrage du déchiffrement pour ${processId}`); console.log(`[Router:API] 🔐 Démarrage du déchiffrement pour ${processId}`);
await services.ensureConnections(process, stateId); await services.ensureConnections(process, stateId);
const res: Record<string, any> = {}; const res: Record<string, any> = {};
for (const attribute of Object.keys(state.pcd_commitment)) { for (const attribute of Object.keys(state.pcd_commitment)) {
if (attribute === 'roles' || (state.public_data && state.public_data[attribute])) { if (attribute === 'roles' || (state.public_data && state.public_data[attribute])) {
continue; continue;
}
const decryptedAttribute = await services.decryptAttribute(processId, state, attribute);
if (decryptedAttribute) {
res[attribute] = decryptedAttribute;
}
} }
const decryptedAttribute = await services.decryptAttribute(processId, state, attribute); console.log(`[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length} attribut(s) déchiffré(s).`);
if (decryptedAttribute) {
res[attribute] = decryptedAttribute;
}
}
console.log(`[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length} attribut(s) déchiffré(s).`);
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.DATA_RETRIEVED, type: MessageType.DATA_RETRIEVED,
data: res, data: res,
messageId: event.data.messageId, messageId: event.data.messageId,
}, },
event.origin, event.origin,
); );
});
}; };
const handleValidateToken = async (event: MessageEvent) => { const handleValidateToken = async (event: MessageEvent) => {
@ -295,214 +300,204 @@ export class IframeController {
throw new Error('Device not paired'); throw new Error('Device not paired');
} }
const { accessToken } = event.data; await withToken(event, async () => {
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { window.parent.postMessage(
throw new Error('Invalid or expired session token'); {
} type: MessageType.GET_PAIRING_ID,
userPairingId: pairingId,
window.parent.postMessage( messageId: event.data.messageId,
{ },
type: MessageType.GET_PAIRING_ID, event.origin,
userPairingId: pairingId, );
messageId: event.data.messageId, });
},
event.origin,
);
}; };
const handleCreateProcess = async (event: MessageEvent) => { const handleCreateProcess = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { processData, privateFields, roles, accessToken } = event.data; const { processData, privateFields, roles } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
console.log('[Router:API] 🚀 Démarrage de la création de processus standard...'); await withToken(event, async () => {
const { privateData, publicData } = splitPrivateData(processData, privateFields); 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...'); console.log('[Router:API] 1/2: Création du processus...');
const createProcessReturn = await services.createProcess(privateData, publicData, roles); const createProcessReturn = await services.createProcess(privateData, publicData, roles);
if (!createProcessReturn.updated_process) { if (!createProcessReturn.updated_process) {
throw new Error('Empty updated_process in createProcessReturn'); throw new Error('Empty updated_process in createProcessReturn');
} }
const processId = createProcessReturn.updated_process.process_id; const processId = createProcessReturn.updated_process.process_id;
const process = createProcessReturn.updated_process.current_process; const process = createProcessReturn.updated_process.current_process;
const stateId = process.states[0].state_id; const stateId = process.states[0].state_id;
console.log(`[Router:API] 2/2: Processus ${processId} créé. Traitement...`); console.log(`[Router:API] 2/2: Processus ${processId} créé. Traitement...`);
await services.handleApiReturn(createProcessReturn); await services.handleApiReturn(createProcessReturn);
console.log(`[Router:API] 🎉 Processus ${processId} créé.`); console.log(`[Router:API] 🎉 Processus ${processId} créé.`);
const res = { const res = {
processId, processId,
process, process,
processData, processData,
}; };
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.PROCESS_CREATED, type: MessageType.PROCESS_CREATED,
processCreated: res, processCreated: res,
messageId: event.data.messageId, messageId: event.data.messageId,
}, },
event.origin, event.origin,
); );
});
}; };
const handleNotifyUpdate = async (event: MessageEvent) => { const handleNotifyUpdate = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { processId, stateId, accessToken } = event.data; const { processId, stateId } = 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 res = await services.createPrdUpdate(processId, stateId); await withToken(event, async () => {
await services.handleApiReturn(res); if (!isValid32ByteHex(stateId)) throw new Error('Invalid state id');
window.parent.postMessage( const res = await services.createPrdUpdate(processId, stateId);
{ await services.handleApiReturn(res);
type: MessageType.UPDATE_NOTIFIED,
messageId: event.data.messageId, window.parent.postMessage(
}, {
event.origin, type: MessageType.UPDATE_NOTIFIED,
); messageId: event.data.messageId,
},
event.origin,
);
});
}; };
const handleValidateState = async (event: MessageEvent) => { const handleValidateState = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { processId, stateId, accessToken } = event.data; const { processId, stateId } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
const res = await services.approveChange(processId, stateId); await withToken(event, async () => {
await services.handleApiReturn(res); const res = await services.approveChange(processId, stateId);
await services.handleApiReturn(res);
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.STATE_VALIDATED, type: MessageType.STATE_VALIDATED,
validatedProcess: res.updated_process, validatedProcess: res.updated_process,
messageId: event.data.messageId, messageId: event.data.messageId,
}, },
event.origin, event.origin,
); );
});
}; };
const handleUpdateProcess = async (event: MessageEvent) => { const handleUpdateProcess = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { processId, newData, privateFields, roles, accessToken } = event.data; const { processId, newData, privateFields, roles } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
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. // 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); const res = await services.updateProcess(processId, newData, privateFields, roles);
// Nous appelons handleApiReturn ici, comme avant. // Nous appelons handleApiReturn ici, comme avant.
await services.handleApiReturn(res); await services.handleApiReturn(res);
// --- FIN DE LA MODIFICATION --- // --- FIN DE LA MODIFICATION ---
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.PROCESS_UPDATED, type: MessageType.PROCESS_UPDATED,
updatedProcess: res.updated_process, // res vient directement de l'appel service updatedProcess: res.updated_process, // res vient directement de l'appel service
messageId: event.data.messageId, messageId: event.data.messageId,
}, },
event.origin, event.origin,
); );
});
}; };
const handleDecodePublicData = async (event: MessageEvent) => { const handleDecodePublicData = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu`);
if (!services.isPaired()) throw new Error('Device not paired'); if (!services.isPaired()) throw new Error('Device not paired');
const { accessToken, encodedData } = event.data; const { encodedData } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
const decodedData = services.decodeValue(encodedData); await withToken(event, async () => {
window.parent.postMessage( const decodedData = services.decodeValue(encodedData);
{ window.parent.postMessage(
type: MessageType.PUBLIC_DATA_DECODED, {
decodedData, type: MessageType.PUBLIC_DATA_DECODED,
messageId: event.data.messageId, decodedData,
}, messageId: event.data.messageId,
event.origin, },
); event.origin,
);
});
}; };
const handleHashValue = async (event: MessageEvent) => { const handleHashValue = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.HASH_VALUE} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.HASH_VALUE} reçu`);
const { accessToken, commitedIn, label, fileBlob } = event.data; const { commitedIn, label, fileBlob } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
const hash = services.getHashForFile(commitedIn, label, fileBlob); await withToken(event, async () => {
window.parent.postMessage( const hash = services.getHashForFile(commitedIn, label, fileBlob);
{ window.parent.postMessage(
type: MessageType.VALUE_HASHED, {
hash, type: MessageType.VALUE_HASHED,
messageId: event.data.messageId, hash,
}, messageId: event.data.messageId,
event.origin, },
); event.origin,
);
});
}; };
const handleGetMerkleProof = async (event: MessageEvent) => { const handleGetMerkleProof = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.GET_MERKLE_PROOF} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.GET_MERKLE_PROOF} reçu`);
const { accessToken, processState, attributeName } = event.data; const { processState, attributeName } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
const proof = services.getMerkleProofForFile(processState, attributeName); await withToken(event, async () => {
window.parent.postMessage( const proof = services.getMerkleProofForFile(processState, attributeName);
{ window.parent.postMessage(
type: MessageType.MERKLE_PROOF_RETRIEVED, {
proof, type: MessageType.MERKLE_PROOF_RETRIEVED,
messageId: event.data.messageId, proof,
}, messageId: event.data.messageId,
event.origin, },
); event.origin,
);
});
}; };
const handleValidateMerkleProof = async (event: MessageEvent) => { const handleValidateMerkleProof = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_MERKLE_PROOF} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_MERKLE_PROOF} reçu`);
const { accessToken, merkleProof, documentHash } = event.data; const { merkleProof, documentHash } = event.data;
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
throw new Error('Invalid or expired session token');
}
let parsedMerkleProof: MerkleProofResult; await withToken(event, async () => {
try { let parsedMerkleProof: MerkleProofResult;
parsedMerkleProof = JSON.parse(merkleProof); try {
} catch (e) { parsedMerkleProof = JSON.parse(merkleProof);
throw new Error('Provided merkleProof is not a valid json object'); } catch (e) {
} throw new Error('Provided merkleProof is not a valid json object');
}
const res = services.validateMerkleProof(parsedMerkleProof, documentHash); const res = services.validateMerkleProof(parsedMerkleProof, documentHash);
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.MERKLE_PROOF_VALIDATED, type: MessageType.MERKLE_PROOF_VALIDATED,
isValid: res, isValid: res,
messageId: event.data.messageId, messageId: event.data.messageId,
}, },
event.origin, event.origin,
); );
});
}; };
// --- Le "Switchyard" : il reçoit tous les messages et les dispatche --- // --- Le "Switchyard" : il reçoit tous les messages et les dispatche ---

View File

@ -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'; import Services from './service';
let ws: WebSocket; let ws: WebSocket | null = null;
let messageQueue: string[] = []; 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) { export async function initWebsocket(url: string) {
ws = new WebSocket(url); urlReference = url;
connect();
if (ws !== null) { }
ws.onopen = async (event) => {
console.log('WebSocket connection established'); function connect() {
if (isConnecting || (ws && ws.readyState === WebSocket.OPEN)) return;
while (messageQueue.length > 0) { isConnecting = true;
const message = messageQueue.shift();
if (message) { console.log(`[WS] 🔌 Tentative de connexion à ${urlReference}...`);
ws.send(message); ws = new WebSocket(urlReference);
}
} ws.onopen = async () => {
}; console.log('[WS] ✅ Connexion établie !');
isConnecting = false;
// Listen for messages reconnectInterval = 1000; // Reset du délai
ws.onmessage = (event) => {
const msgData = event.data; // Démarrer le Heartbeat (Ping pour garder la connexion vivante)
startHeartbeat();
// console.log("Received text message: ", msgData);
(async () => { // Vider la file d'attente (messages envoyés pendant la coupure)
if (typeof msgData === 'string') { while (messageQueue.length > 0) {
try { const message = messageQueue.shift();
const parsedMessage = JSON.parse(msgData); if (message) ws?.send(message);
const services = await Services.getInstance(); }
switch (parsedMessage.flag) { };
case 'Handshake':
await services.handleHandshakeMsg(url, parsedMessage.content); ws.onmessage = (event) => {
break; const msgData = event.data;
case 'NewTx': if (typeof msgData === 'string') {
await services.parseNewTx(parsedMessage.content); (async () => {
break; try {
case 'Cipher': const parsedMessage = JSON.parse(msgData);
await services.parseCipher(parsedMessage.content); const services = await Services.getInstance();
break;
case 'Commit': // Gestion des messages
// Basically if we see this it means we have an error switch (parsedMessage.flag) {
await services.handleCommitError(parsedMessage.content); case 'Handshake':
break; await services.handleHandshakeMsg(urlReference, parsedMessage.content);
} break;
} catch (error) { case 'NewTx':
console.error('Received an invalid message:', error); await services.parseNewTx(parsedMessage.content);
} break;
} else { case 'Cipher':
console.error('Received a non-string message'); await services.parseCipher(parsedMessage.content);
} break;
})(); case 'Commit':
}; await services.handleCommitError(parsedMessage.content);
break;
// Listen for possible errors // Ajoute d'autres cas si nécessaire
ws.onerror = (event) => { default:
console.error('WebSocket error:', event); // console.log('[WS] Message reçu:', parsedMessage.flag);
}; }
} catch (error) {
// Listen for when the connection is closed console.error('[WS] Erreur traitement message:', error);
ws.onclose = (event) => { }
console.log('WebSocket is closed now.'); })();
}; }
} };
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 { export function sendMessage(flag: AnkFlag, message: string): void {
if (ws.readyState === WebSocket.OPEN) { if (ws && ws.readyState === WebSocket.OPEN) {
const networkMessage = { const networkMessage = {
flag: flag, flag: flag,
content: message, content: message,
}; };
console.log('Sending message of type:', flag);
ws.send(JSON.stringify(networkMessage)); ws.send(JSON.stringify(networkMessage));
} else { } else {
console.error('WebSocket is not open. ReadyState:', ws.readyState); console.warn(`[WS] Pas connecté. Message '${flag}' mis en file d'attente.`);
messageQueue.push(message); 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 { export function getUrl(): string {
return ws.url; return urlReference;
} }
// Method to close the WebSocket connection
export function close(): void { export function close(): void {
ws.close(); if (ws) {
ws.onclose = null; // On évite la reconnexion auto si fermeture volontaire
stopHeartbeat();
ws.close();
}
} }