774ee48f19
node-pty ships prebuilt binaries for darwin/win32 only — on Linux it needs a native build via its install script. The plugin's --ignore-scripts install (scripts/plugin-bootstrap.js, deliberately skipped to avoid requiring a build toolchain) silently left node-pty unusable: server/lib/pty-attach.js's require() threw "Cannot find module './prebuilds/linux-x64//pty.node'" the moment a terminal was attached, leaving TerminalView permanently blank with no visible error. @lydell/node-pty is an API-compatible fork that ships each platform's binary as a regular optionalDependency instead of a postinstall build step, so a plain --ignore-scripts install resolves a working native binding on Linux with no compiler needed. Verified by installing with the exact `npm install --omit=dev --ignore-scripts` invocation the plugin bootstrap uses and confirming require() succeeds.
113 lines
3.7 KiB
JavaScript
113 lines
3.7 KiB
JavaScript
/**
|
|
* @file pty-attach.js
|
|
* @description Bridges one WebSocket connection to an `@lydell/node-pty`-backed
|
|
* `tmux attach-session` process. Binary WS frames carry raw PTY bytes in
|
|
* both directions; text WS frames carry small JSON control messages
|
|
* (`resize`, and an outbound `exit` sent once when the pane process/tmux
|
|
* session ends). Multiple browser tabs each get their own PTY attach
|
|
* process — tmux itself is what keeps them all in sync, this module does no
|
|
* cross-connection coordination.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const RUN_ID_RE = /^ccam-lane-\d+$/;
|
|
|
|
/**
|
|
* Reject anything that isn't exactly `ccam-lane-<digits>` before it can ever
|
|
* reach a tmux/PTY command — the trust boundary for this WS path, since a
|
|
* validated runId is the only thing standing between an authenticated WS
|
|
* client and naming an arbitrary session on the host.
|
|
*/
|
|
function validateRunId(runId) {
|
|
if (typeof runId !== "string" || !RUN_ID_RE.test(runId)) {
|
|
const err = new Error(`EBADRUNID: invalid runId: ${runId}`);
|
|
err.code = "EBADRUNID";
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// Test seam — real implementation set in Step 4.
|
|
let spawnImpl = null;
|
|
function __setSpawnImpl(fn) {
|
|
spawnImpl = fn;
|
|
}
|
|
|
|
/**
|
|
* Attach `ws` to the tmux session `runId`. Spawns one PTY-backed
|
|
* `tmux attach-session -t <runId>` per call.
|
|
*/
|
|
function attach(ws, runId, { cols, rows }) {
|
|
validateRunId(runId);
|
|
const pty = spawnImpl("tmux", ["attach-session", "-t", runId], {
|
|
name: "xterm-256color",
|
|
cols: cols || 80,
|
|
rows: rows || 24,
|
|
});
|
|
|
|
pty.onData((data) => {
|
|
try {
|
|
ws.send(data, { binary: true });
|
|
} catch {
|
|
/* client gone between data event and send — safe to ignore */
|
|
}
|
|
});
|
|
|
|
pty.onExit(({ exitCode }) => {
|
|
try {
|
|
ws.send(JSON.stringify({ type: "exit", code: exitCode }), { binary: false });
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
try {
|
|
ws.close();
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
});
|
|
|
|
ws.on("message", (data, isBinary) => {
|
|
// node-pty attach processes for `tmux attach` are already tolerant of
|
|
// resize mid-stream; the `ws` lib passes isBinary either as the second
|
|
// callback arg (newer) or via `data.binary` on some transports — this
|
|
// helper's own tests exercise the `{binary}` option shape used above.
|
|
const binary = typeof isBinary === "boolean" ? isBinary : !!(isBinary && isBinary.binary);
|
|
if (binary) {
|
|
pty.write(data.toString("utf8"));
|
|
return;
|
|
}
|
|
let msg;
|
|
try {
|
|
msg = JSON.parse(data.toString("utf8"));
|
|
} catch {
|
|
return;
|
|
}
|
|
if (msg && msg.type === "resize" && Number.isFinite(msg.cols) && Number.isFinite(msg.rows)) {
|
|
pty.resize(msg.cols, msg.rows);
|
|
}
|
|
});
|
|
|
|
ws.on("close", () => {
|
|
try {
|
|
pty.kill();
|
|
} catch {
|
|
/* already gone */
|
|
}
|
|
});
|
|
|
|
return pty;
|
|
}
|
|
|
|
// Real spawn implementation — lazy-required so unit tests never load the
|
|
// native PTY addon unless they explicitly opt in. Uses @lydell/node-pty (a
|
|
// drop-in-API-compatible fork of node-pty) rather than node-pty itself:
|
|
// node-pty ships prebuilt binaries for darwin/win32 only, so on Linux it
|
|
// needs a native build via its install script — but the plugin install path
|
|
// runs `npm install --ignore-scripts` deliberately (see plugin-bootstrap.js)
|
|
// to avoid requiring a build toolchain on the user's machine. @lydell/node-pty
|
|
// instead ships the platform binary as a regular optionalDependency
|
|
// (@lydell/node-pty-linux-x64 etc.), so a plain --ignore-scripts install still
|
|
// resolves a working native binding with no compiler needed.
|
|
__setSpawnImpl((...args) => require("@lydell/node-pty").spawn(...args));
|
|
|
|
module.exports = { attach, validateRunId, __setSpawnImpl };
|