* 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)
| 444 | * sends hello immediately on accept. |
| 445 | */ |
| 446 | function readHelloLine(socket: net.Socket): Promise<DaemonHello> { |
| 447 | return new Promise((resolve, reject) => { |
| 448 | let buffer = ''; |
| 449 | const cleanup = () => { |
| 450 | socket.removeListener('data', onData); |
| 451 | socket.removeListener('error', onError); |
| 452 | socket.removeListener('close', onClose); |
| 453 | clearTimeout(timer); |
| 454 | }; |
| 455 | const onData = (chunk: string | Buffer) => { |
| 456 | buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8'); |
| 457 | const idx = buffer.indexOf('\n'); |
| 458 | if (idx === -1) { |
| 459 | if (buffer.length > MAX_HELLO_LINE_BYTES) { |
| 460 | cleanup(); |
| 461 | reject(new Error('daemon hello line exceeded size limit')); |
| 462 | } |
| 463 | return; |
| 464 | } |
| 465 | const line = buffer.slice(0, idx); |
| 466 | // Re-emit anything past the newline so the pipe-stage sees it. |
| 467 | const tail = buffer.slice(idx + 1); |
| 468 | cleanup(); |
| 469 | if (tail.length > 0) { |
| 470 | // Push back via unshift — Node's net.Socket supports it on readable streams. |
| 471 | socket.unshift(tail); |
| 472 | } |
| 473 | try { |
| 474 | const parsed = JSON.parse(line) as DaemonHello; |
| 475 | if (typeof parsed.codegraph !== 'string' || typeof parsed.pid !== 'number') { |
| 476 | reject(new Error('daemon hello missing required fields')); |
| 477 | return; |
| 478 | } |
| 479 | resolve(parsed); |
| 480 | } catch (err) { |
| 481 | reject(new Error(`daemon hello not JSON: ${err instanceof Error ? err.message : String(err)}`)); |
| 482 | } |
| 483 | }; |
| 484 | const onError = (err: Error) => { cleanup(); reject(err); }; |
| 485 | const onClose = () => { cleanup(); reject(new Error('daemon closed connection before hello')); }; |
| 486 | const timer = setTimeout(() => { |
| 487 | cleanup(); |
| 488 | reject(new Error('timed out waiting for daemon hello')); |
| 489 | }, 3000); |
| 490 | timer.unref?.(); |
| 491 | socket.on('data', onData); |
| 492 | socket.on('error', onError); |
| 493 | socket.on('close', onClose); |
| 494 | }); |
| 495 | } |
| 496 | |
| 497 | /** |
| 498 | * Pipe stdin → socket and socket → stdout. Resolves once either end closes |
no test coverage detected