Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions apps/cli/src/cli-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,35 @@ function requestConnection(handshake: CliHandshake, timeoutMs: number): Promise<
fn();
};

sock.setEncoding("utf8");
sock.setTimeout(timeoutMs, () =>
settle(() => reject(new Error("Timed out waiting for the app to accept the connection"))),
);
sock.on("connect", () => sock.end(JSON.stringify(handshake)));
sock.on("data", (chunk) => {
buf += chunk;
});
sock.on("end", () => {
const settleReply = (raw: string) => {
let reply: CliHandshakeReply;
try {
reply = JSON.parse(buf) as CliHandshakeReply;
reply = JSON.parse(raw) as CliHandshakeReply;
} catch (e) {
settle(() => reject(e instanceof Error ? e : new Error(String(e))));
return;
}
if (reply.ok) settle(resolve);
else settle(() => reject(new Error(reply.error)));
};

sock.setEncoding("utf8");
sock.setTimeout(timeoutMs, () =>
settle(() => reject(new Error("Timed out waiting for the app to accept the connection"))),
);
// Windows named pipes do not support half-open: an `end()` here tears the
// pipe down before the app can reply. Frame the handshake with a newline
// instead and keep the socket open until the (newline-framed) reply lands.
sock.on("connect", () => sock.write(JSON.stringify(handshake) + "\n"));
sock.on("data", (chunk) => {
buf += chunk;
const nl = buf.indexOf("\n");
if (nl !== -1) settleReply(buf.slice(0, nl));
});
sock.on("end", () => {
// Unix servers may still close without a trailing newline.
if (buf.length > 0) settleReply(buf);
else settle(() => reject(new Error("App closed the handshake socket without replying")));
});
sock.on("error", (err) => settle(() => reject(err)));
});
Expand Down
29 changes: 20 additions & 9 deletions apps/desktop/src/cli-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async function deliverHandshake(handshake: CliHandshake, sock: Socket): Promise<
} catch (err) {
reply = { ok: false, error: (err as Error).message };
}
if (!sock.destroyed) sock.end(JSON.stringify(reply));
if (!sock.destroyed) sock.end(JSON.stringify(reply) + "\n");
}

export function startCliServer() {
Expand All @@ -107,24 +107,35 @@ export function startCliServer() {
cliServer = createServer({ allowHalfOpen: true }, (sock: Socket) => {
enableHeadless();
let buf = "";
sock.setEncoding("utf8");
sock.setTimeout(60000, () => sock.destroy());
sock.on("data", (chunk) => {
buf += chunk;
});
sock.on("end", async () => {
let handled = false;
const handle = async (raw: string) => {
if (handled) return;
handled = true;
sock.setTimeout(0);
let handshake: CliHandshake;
try {
handshake = JSON.parse(buf) as CliHandshake;
handshake = JSON.parse(raw) as CliHandshake;
if (typeof handshake.port !== "number" || typeof handshake.token !== "string") {
throw new Error("Malformed handshake");
}
} catch {
sock.end(JSON.stringify({ ok: false, error: "Invalid handshake" }));
sock.end(JSON.stringify({ ok: false, error: "Invalid handshake" }) + "\n");
return;
}
await deliverHandshake(handshake, sock);
};
sock.setEncoding("utf8");
sock.setTimeout(60000, () => sock.destroy());
// Newline-framed clients (required on Windows, where named pipes cannot
// half-open) are answered as soon as the first line arrives; legacy
// clients that frame by half-closing are handled on "end".
sock.on("data", (chunk) => {
buf += chunk;
const nl = buf.indexOf("\n");
if (nl !== -1) void handle(buf.slice(0, nl));
});
sock.on("end", () => {
void handle(buf);
});
sock.on("error", () => {
// Client hung up; nothing to do.
Expand Down