* Read one CRLF/LF-terminated JSON line from the socket, parse it as the * daemon hello, and return it. Bounded to MAX_HELLO_LINE_BYTES so a * malicious or broken peer can't OOM us. Times out at 3s — a healthy daemon * sends hello immediately on accept.
(socket: net.Socket)
| 420 | * sends hello immediately on accept. |
| 421 | */ |
| 422 | function readHelloLine(socket: net.Socket): Promise<DaemonHello> { |
| 423 | return new Promise((resolve, reject) => { |
| 424 | let buffer = ''; |
| 425 | const cleanup = () => { |
| 426 | socket.removeListener('data', onData); |
| 427 | socket.removeListener('error', onError); |
| 428 | socket.removeListener('close', onClose); |
| 429 | clearTimeout(timer); |
| 430 | }; |
| 431 | const onData = (chunk: string | Buffer) => { |
| 432 | buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8'); |
| 433 | const idx = buffer.indexOf('\n'); |
| 434 | if (idx === -1) { |
| 435 | if (buffer.length > MAX_HELLO_LINE_BYTES) { |
| 436 | cleanup(); |
| 437 | reject(new Error('daemon hello line exceeded size limit')); |
| 438 | } |
| 439 | return; |
| 440 | } |
| 441 | const line = buffer.slice(0, idx); |
| 442 | // Re-emit anything past the newline so the pipe-stage sees it. |
| 443 | const tail = buffer.slice(idx + 1); |
| 444 | cleanup(); |
| 445 | if (tail.length > 0) { |
| 446 | // Push back via unshift — Node's net.Socket supports it on readable streams. |
| 447 | socket.unshift(tail); |
| 448 | } |
| 449 | try { |
| 450 | const parsed = JSON.parse(line) as DaemonHello; |
| 451 | if (typeof parsed.codegraph !== 'string' || typeof parsed.pid !== 'number') { |
| 452 | reject(new Error('daemon hello missing required fields')); |
| 453 | return; |
| 454 | } |
| 455 | resolve(parsed); |
| 456 | } catch (err) { |
| 457 | reject(new Error(`daemon hello not JSON: ${err instanceof Error ? err.message : String(err)}`)); |
| 458 | } |
| 459 | }; |
| 460 | const onError = (err: Error) => { cleanup(); reject(err); }; |
| 461 | const onClose = () => { cleanup(); reject(new Error('daemon closed connection before hello')); }; |
| 462 | const timer = setTimeout(() => { |
| 463 | cleanup(); |
| 464 | reject(new Error('timed out waiting for daemon hello')); |
| 465 | }, 3000); |
| 466 | timer.unref?.(); |
| 467 | socket.on('data', onData); |
| 468 | socket.on('error', onError); |
| 469 | socket.on('close', onClose); |
| 470 | }); |
| 471 | } |
| 472 | |
| 473 | /** |
| 474 | * Pipe stdin → socket and socket → stdout. Resolves once either end closes |
no test coverage detected