From 3b614c5d058d523d066f7a1f9bdcfae74d69ad7f Mon Sep 17 00:00:00 2001 From: jeanotx32 Date: Tue, 11 Aug 2026 21:37:52 +0200 Subject: [PATCH] fix : fail to connect agent --- packages/agent/src/doctor.ts | 82 +++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/packages/agent/src/doctor.ts b/packages/agent/src/doctor.ts index 38b05ec..6300976 100644 --- a/packages/agent/src/doctor.ts +++ b/packages/agent/src/doctor.ts @@ -1,6 +1,7 @@ import { execFile } from 'node:child_process'; import net from 'node:net'; import { promisify } from 'node:util'; +import { WebSocket } from 'ws'; import { CONFIG_PATH, type AgentConfig } from './config.ts'; const run = promisify(execFile); @@ -30,6 +31,67 @@ function probeTcp(host: string, port: number, timeoutMs = 4000): Promise { + return new Promise((resolve) => { + let socket: WebSocket; + try { + socket = new WebSocket(config.serverUrl, { + headers: { authorization: `Bearer ${config.token}` }, + rejectUnauthorized: !config.insecureTls, + handshakeTimeout: timeoutMs, + }); + } catch (err) { + resolve({ ok: false, detail: err instanceof Error ? err.message : String(err) }); + return; + } + + const finish = (result: { ok: boolean; detail: string }) => { + clearTimeout(timer); + try { + socket.close(); + } catch { + /* ignore */ + } + resolve(result); + }; + + const timer = setTimeout( + () => finish({ ok: false, detail: 'aucune réponse à la poignée de main' }), + timeoutMs + 1000, + ); + + socket.once('open', () => finish({ ok: true, detail: 'jeton accepté' })); + + socket.once('error', (err: Error) => { + const message = err.message; + if (message.includes('401')) { + finish({ + ok: false, + detail: + 'JETON REFUSÉ (401). Le serveur ne reconnaît pas ce jeton : il a été régénéré, ' + + "ou l'agent a été supprimé. Régénère-le dans le dashboard puis réinstalle avec --reset-identity.", + }); + return; + } + if (message.includes('certificate') || message.includes('self-signed')) { + finish({ ok: false, detail: `certificat TLS rejeté (${message})` }); + return; + } + finish({ ok: false, detail: message }); + }); + }); +} + async function commandExists(command: string, args: string[]): Promise { try { await run(command, args, { timeout: 8000, windowsHide: true }); @@ -71,12 +133,20 @@ export async function runDiagnostics(config: AgentConfig): Promise { if (serverUrl) { const port = Number(serverUrl.port) || (serverUrl.protocol === 'wss:' ? 443 : 80); - const error = await probeTcp(serverUrl.hostname, port); - results.push( - error - ? line('fail', 'serveur', `${serverUrl.host} injoignable (${error})`) - : line('ok', 'serveur', `${serverUrl.host} joignable`), - ); + const tcpError = await probeTcp(serverUrl.hostname, port); + + if (tcpError) { + results.push(line('fail', 'serveur', `${serverUrl.host} injoignable (${tcpError})`)); + } else { + // Le port ouvert ne prouve rien : un jeton refusé produit exactement la + // même réponse TCP. On tente donc la vraie poignée de main. + const handshake = await probeHandshake(config); + results.push( + handshake.ok + ? line('ok', 'serveur', `${serverUrl.host} — jeton accepté`) + : line('fail', 'serveur', `${serverUrl.host} — ${handshake.detail}`), + ); + } if (serverUrl.protocol === 'ws:' && !isLoopback(serverUrl.hostname)) { line('warn', 'transport', 'ws:// non chiffré — à réserver à un réseau privé'); }