@thenewlabs/entangle-serve 2.6.2 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ipc.d.ts ADDED
@@ -0,0 +1,103 @@
1
+ import type { Duplex } from 'stream';
2
+ import type { WindowStateBody } from '@thenewlabs/entangle-protocol';
3
+ /** Bytes of the length prefix that precedes every JSON payload. */
4
+ export declare const HEADER_BYTES = 4;
5
+ /**
6
+ * Maximum accepted payload size. A frame declaring a larger payload is rejected
7
+ * (the decoder errors) rather than buffering an unbounded amount of memory for
8
+ * a payload that may never fully arrive. 8 MB comfortably fits a full-screen
9
+ * replay while capping a hostile/framed-wrong peer.
10
+ */
11
+ export declare const MAX_FRAME_BYTES: number;
12
+ /** A window operation requested by the client (tmux-style window control). */
13
+ export type WindowOp = 'new' | 'next' | 'prev' | 'select' | 'close';
14
+ /** Messages sent from an attached terminal client to the daemon. */
15
+ export type ClientToDaemon = {
16
+ t: 'hello';
17
+ cols: number;
18
+ rows: number;
19
+ }
20
+ /** Keystrokes / raw terminal input, base64-encoded. */
21
+ | {
22
+ t: 'input';
23
+ data: string;
24
+ } | {
25
+ t: 'resize';
26
+ cols: number;
27
+ rows: number;
28
+ } | {
29
+ t: 'win';
30
+ op: WindowOp;
31
+ index?: number;
32
+ } | {
33
+ t: 'detach';
34
+ };
35
+ /** Messages sent from the daemon to an attached terminal client. */
36
+ export type DaemonToClient =
37
+ /** Live terminal output, base64-encoded. */
38
+ {
39
+ t: 'data';
40
+ chunk: string;
41
+ }
42
+ /** Scrollback replay sent right after attach, base64-encoded. */
43
+ | {
44
+ t: 'replay';
45
+ chunk: string;
46
+ } | {
47
+ t: 'window-state';
48
+ state: WindowStateBody;
49
+ }
50
+ /** Number of currently attached viewers. */
51
+ | {
52
+ t: 'viewers';
53
+ n: number;
54
+ } | {
55
+ t: 'log';
56
+ line: string;
57
+ } | {
58
+ t: 'url';
59
+ url: string;
60
+ } | {
61
+ t: 'exit';
62
+ code: number | null;
63
+ };
64
+ /** Any framed IPC message, in either direction. */
65
+ export type IpcMessage = ClientToDaemon | DaemonToClient;
66
+ /** Encode a binary terminal chunk as a base64 string for the JSON wire. */
67
+ export declare function encodeChunk(data: Uint8Array): string;
68
+ /** Decode a base64 chunk string back into a Buffer of terminal bytes. */
69
+ export declare function decodeChunk(chunk: string): Buffer;
70
+ /**
71
+ * Serialize a message to a length-prefixed frame: a 4-byte big-endian payload
72
+ * length followed by the UTF-8 JSON payload.
73
+ *
74
+ * @throws if the resulting payload exceeds {@link MAX_FRAME_BYTES}.
75
+ */
76
+ export declare function encodeMessage(msg: IpcMessage): Buffer;
77
+ /** Encode and write a single message to a socket/stream. */
78
+ export declare function writeMessage(socket: Duplex, msg: IpcMessage): boolean;
79
+ /**
80
+ * Incrementally decodes length-prefixed frames from a byte stream, tolerating
81
+ * partial reads (a frame split across chunks) and coalesced reads (many frames
82
+ * in one chunk). A frame whose declared length exceeds {@link MAX_FRAME_BYTES}
83
+ * makes {@link push} throw rather than buffering unbounded memory — callers
84
+ * should treat that as a fatal protocol error and close the socket.
85
+ */
86
+ export declare class FrameDecoder {
87
+ private buffer;
88
+ /**
89
+ * Feed received bytes; returns every message that became fully available.
90
+ * @throws if a frame declares a payload larger than {@link MAX_FRAME_BYTES}
91
+ * or if a completed payload is not valid JSON.
92
+ */
93
+ push(chunk: Uint8Array): IpcMessage[];
94
+ }
95
+ /**
96
+ * Attach a {@link FrameDecoder} to a socket and invoke `onMessage` for each
97
+ * decoded message. A decode error (oversize frame or malformed JSON) is routed
98
+ * to `onError` if provided, otherwise emitted on the socket as an `'error'`.
99
+ *
100
+ * @returns a detach function that removes the installed `'data'` listener.
101
+ */
102
+ export declare function createMessageReader(socket: Duplex, onMessage: (msg: IpcMessage) => void, onError?: (err: Error) => void): () => void;
103
+ //# sourceMappingURL=ipc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.d.ts","sourceRoot":"","sources":["../src/ipc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAgBrE,mEAAmE;AACnE,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAkB,CAAC;AAI/C,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEpE,oEAAoE;AACpE,MAAM,MAAM,cAAc,GACtB;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC5C,uDAAuD;GACrD;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,CAAC,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,CAAC,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEpB,oEAAoE;AACpE,MAAM,MAAM,cAAc;AACxB,4CAA4C;AAC1C;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC9B,iEAAiE;GAC/D;IAAE,CAAC,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,CAAC,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE;AAC/C,4CAA4C;GAC1C;IAAE,CAAC,EAAE,SAAS,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,CAAC,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEvC,mDAAmD;AACnD,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,cAAc,CAAC;AAIzD,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEpD;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAID;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CASrD;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAErE;AAID;;;;;;GAMG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA2B;IAEzC;;;;OAIG;IACH,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE;CAoBtC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,EACpC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAC7B,MAAM,IAAI,CAgBZ"}
package/dist/ipc.js ADDED
@@ -0,0 +1,115 @@
1
+ // Local unix-socket IPC framing between the `entangle serve` daemon and an
2
+ // attached terminal client. This is a *local* transport (a filesystem socket
3
+ // secured by directory permissions — see session-registry.ts), so there is no
4
+ // encryption here: confidentiality is the registry's job.
5
+ //
6
+ // Framing: a 4-byte big-endian (network order) unsigned length prefix followed
7
+ // by exactly that many bytes of a UTF-8 JSON payload. Terminal data (which is
8
+ // binary) travels as base64 strings inside the JSON so the wire is always valid
9
+ // JSON and we never have to deal with mixed binary/text framing.
10
+ //
11
+ // +----------------+----------------------------+
12
+ // | uint32 BE len | JSON payload (len bytes) |
13
+ // +----------------+----------------------------+
14
+ /** Bytes of the length prefix that precedes every JSON payload. */
15
+ export const HEADER_BYTES = 4;
16
+ /**
17
+ * Maximum accepted payload size. A frame declaring a larger payload is rejected
18
+ * (the decoder errors) rather than buffering an unbounded amount of memory for
19
+ * a payload that may never fully arrive. 8 MB comfortably fits a full-screen
20
+ * replay while capping a hostile/framed-wrong peer.
21
+ */
22
+ export const MAX_FRAME_BYTES = 8 * 1024 * 1024;
23
+ // --- base64 helpers for binary chunks --------------------------------------
24
+ /** Encode a binary terminal chunk as a base64 string for the JSON wire. */
25
+ export function encodeChunk(data) {
26
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64');
27
+ }
28
+ /** Decode a base64 chunk string back into a Buffer of terminal bytes. */
29
+ export function decodeChunk(chunk) {
30
+ return Buffer.from(chunk, 'base64');
31
+ }
32
+ // --- Encoding --------------------------------------------------------------
33
+ /**
34
+ * Serialize a message to a length-prefixed frame: a 4-byte big-endian payload
35
+ * length followed by the UTF-8 JSON payload.
36
+ *
37
+ * @throws if the resulting payload exceeds {@link MAX_FRAME_BYTES}.
38
+ */
39
+ export function encodeMessage(msg) {
40
+ const payload = Buffer.from(JSON.stringify(msg), 'utf8');
41
+ if (payload.length > MAX_FRAME_BYTES) {
42
+ throw new Error(`IPC frame too large to encode: ${payload.length} > ${MAX_FRAME_BYTES}`);
43
+ }
44
+ const frame = Buffer.allocUnsafe(HEADER_BYTES + payload.length);
45
+ frame.writeUInt32BE(payload.length, 0);
46
+ payload.copy(frame, HEADER_BYTES);
47
+ return frame;
48
+ }
49
+ /** Encode and write a single message to a socket/stream. */
50
+ export function writeMessage(socket, msg) {
51
+ return socket.write(encodeMessage(msg));
52
+ }
53
+ // --- Decoding --------------------------------------------------------------
54
+ /**
55
+ * Incrementally decodes length-prefixed frames from a byte stream, tolerating
56
+ * partial reads (a frame split across chunks) and coalesced reads (many frames
57
+ * in one chunk). A frame whose declared length exceeds {@link MAX_FRAME_BYTES}
58
+ * makes {@link push} throw rather than buffering unbounded memory — callers
59
+ * should treat that as a fatal protocol error and close the socket.
60
+ */
61
+ export class FrameDecoder {
62
+ buffer = Buffer.alloc(0);
63
+ /**
64
+ * Feed received bytes; returns every message that became fully available.
65
+ * @throws if a frame declares a payload larger than {@link MAX_FRAME_BYTES}
66
+ * or if a completed payload is not valid JSON.
67
+ */
68
+ push(chunk) {
69
+ const messages = [];
70
+ this.buffer = this.buffer.length === 0
71
+ ? Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)
72
+ : Buffer.concat([this.buffer, Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)]);
73
+ while (this.buffer.length >= HEADER_BYTES) {
74
+ const length = this.buffer.readUInt32BE(0);
75
+ if (length > MAX_FRAME_BYTES) {
76
+ throw new Error(`IPC frame too large: ${length} > ${MAX_FRAME_BYTES}`);
77
+ }
78
+ if (this.buffer.length < HEADER_BYTES + length)
79
+ break; // wait for more bytes
80
+ const payload = this.buffer.subarray(HEADER_BYTES, HEADER_BYTES + length);
81
+ messages.push(JSON.parse(payload.toString('utf8')));
82
+ this.buffer = this.buffer.subarray(HEADER_BYTES + length);
83
+ }
84
+ return messages;
85
+ }
86
+ }
87
+ /**
88
+ * Attach a {@link FrameDecoder} to a socket and invoke `onMessage` for each
89
+ * decoded message. A decode error (oversize frame or malformed JSON) is routed
90
+ * to `onError` if provided, otherwise emitted on the socket as an `'error'`.
91
+ *
92
+ * @returns a detach function that removes the installed `'data'` listener.
93
+ */
94
+ export function createMessageReader(socket, onMessage, onError) {
95
+ const decoder = new FrameDecoder();
96
+ const onData = (chunk) => {
97
+ let messages;
98
+ try {
99
+ messages = decoder.push(chunk);
100
+ }
101
+ catch (err) {
102
+ const error = err instanceof Error ? err : new Error(String(err));
103
+ if (onError)
104
+ onError(error);
105
+ else
106
+ socket.emit('error', error);
107
+ return;
108
+ }
109
+ for (const msg of messages)
110
+ onMessage(msg);
111
+ };
112
+ socket.on('data', onData);
113
+ return () => socket.off('data', onData);
114
+ }
115
+ //# sourceMappingURL=ipc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.js","sourceRoot":"","sources":["../src/ipc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import type { Socket } from 'net';
2
+ import type { WindowStateBody } from '@thenewlabs/entangle-protocol';
3
+ import type { HostSession } from './host-session.js';
4
+ /**
5
+ * The socket-backed {@link HostSession}: the CLIENT half of the daemon/client
6
+ * split. It renders the SAME host UI (host-terminal.ts) that {@link LocalHostSession}
7
+ * does, but every read is a cached value fed by the daemon over a local unix
8
+ * socket and every action is a framed message written back to it.
9
+ *
10
+ * On construction it sends a `hello` (so the daemon learns the client's terminal
11
+ * size) and starts decoding {@link DaemonToClient} frames: live output, the
12
+ * post-attach replay, window-state, viewer count, captured logs, the session URL
13
+ * and the terminal exit. State the UI reads synchronously ({@link getReplay},
14
+ * {@link windowState}, {@link viewerCount}, {@link getUrl}, {@link getLogBuffer})
15
+ * is cached from those frames; the daemon sends the current state immediately on
16
+ * connect so these populate before the first paint.
17
+ */
18
+ export declare class RemoteHostSession implements HostSession {
19
+ private readonly socket;
20
+ private replay;
21
+ private winState;
22
+ private viewers;
23
+ private url;
24
+ private readonly logBuf;
25
+ private hostDataCb?;
26
+ private windowStateCb?;
27
+ private viewersCb?;
28
+ private logCb?;
29
+ private urlCb?;
30
+ private exitCb?;
31
+ private exited;
32
+ private closed;
33
+ private pendingExitCode;
34
+ constructor(socket: Socket, initial: {
35
+ cols: number;
36
+ rows: number;
37
+ });
38
+ private dispatch;
39
+ /** Fire the one-shot exit callback (idempotent). */
40
+ private fireExit;
41
+ /** Socket gone: fire exit (with a clean code if an `exit` frame preceded it). */
42
+ private handleClose;
43
+ /** Write a framed message, swallowing sends after the socket has closed. */
44
+ private send;
45
+ onHostData(cb: (chunk: Buffer) => void): void;
46
+ write(data: Uint8Array | string): void;
47
+ resize(cols: number, rows: number): void;
48
+ getReplay(): Uint8Array;
49
+ onWindowState(cb: (s: WindowStateBody) => void): void;
50
+ windowState(): WindowStateBody;
51
+ newWindow(): void;
52
+ nextWindow(): void;
53
+ prevWindow(): void;
54
+ selectWindow(i: number): void;
55
+ closeWindow(i: number): void;
56
+ onViewersChange(cb: (n: number) => void): void;
57
+ viewerCount(): number;
58
+ onLog(cb: (line: string) => void): void;
59
+ getLogBuffer(): readonly string[];
60
+ getUrl(): string | null;
61
+ onUrl(cb: (url: string) => void): void;
62
+ onExit(cb: (code: number | null, signal: string | null) => void): void;
63
+ /**
64
+ * Detach without ending the daemon session: tell the daemon to drop this
65
+ * client (it keeps running), end the socket, then run the local exit path so
66
+ * the host UI tears its terminal down (host-terminal's restore() runs on exit).
67
+ */
68
+ detach(): void;
69
+ /** Tear down: end the socket (no exit fired — the caller is disposing us). */
70
+ dispose(): void;
71
+ }
72
+ //# sourceMappingURL=remote-host-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-host-session.d.ts","sourceRoot":"","sources":["../src/remote-host-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAClC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqBrD;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,YAAW,WAAW;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAGhC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,GAAG,CAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IAIvC,OAAO,CAAC,UAAU,CAAC,CAA0B;IAC7C,OAAO,CAAC,aAAa,CAAC,CAA+B;IACrD,OAAO,CAAC,SAAS,CAAC,CAAsB;IACxC,OAAO,CAAC,KAAK,CAAC,CAAyB;IACvC,OAAO,CAAC,KAAK,CAAC,CAAwB;IACtC,OAAO,CAAC,MAAM,CAAC,CAAuD;IAKtE,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,eAAe,CAAuB;gBAElC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAgBnE,OAAO,CAAC,QAAQ;IAyChB,oDAAoD;IACpD,OAAO,CAAC,QAAQ;IAMhB,iFAAiF;IACjF,OAAO,CAAC,WAAW;IAOnB,4EAA4E;IAC5E,OAAO,CAAC,IAAI;IAWZ,UAAU,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAC7C,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI;IAItC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IACxC,SAAS,IAAI,UAAU;IAGvB,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IACrD,WAAW,IAAI,eAAe;IAC9B,SAAS,IAAI,IAAI;IACjB,UAAU,IAAI,IAAI;IAClB,UAAU,IAAI,IAAI;IAClB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAC7B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAG5B,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAC9C,WAAW,IAAI,MAAM;IAGrB,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IACvC,YAAY,IAAI,SAAS,MAAM,EAAE;IAGjC,MAAM,IAAI,MAAM,GAAG,IAAI;IACvB,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAGtC,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;IAEtE;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAOd,8EAA8E;IAC9E,OAAO,IAAI,IAAI;CAIhB"}
@@ -0,0 +1,170 @@
1
+ import { createMessageReader, decodeChunk, encodeChunk, writeMessage, } from './ipc.js';
2
+ /** Cap on the client-side mirror of the captured-log ring buffer. */
3
+ const DEBUG_MAX_LINES = 1000;
4
+ /** Empty window-state used before the daemon's first window-state frame arrives. */
5
+ const EMPTY_WINDOW_STATE = {
6
+ v: 1,
7
+ kind: 'window-state',
8
+ windows: [],
9
+ activeIndex: 0,
10
+ };
11
+ /**
12
+ * The socket-backed {@link HostSession}: the CLIENT half of the daemon/client
13
+ * split. It renders the SAME host UI (host-terminal.ts) that {@link LocalHostSession}
14
+ * does, but every read is a cached value fed by the daemon over a local unix
15
+ * socket and every action is a framed message written back to it.
16
+ *
17
+ * On construction it sends a `hello` (so the daemon learns the client's terminal
18
+ * size) and starts decoding {@link DaemonToClient} frames: live output, the
19
+ * post-attach replay, window-state, viewer count, captured logs, the session URL
20
+ * and the terminal exit. State the UI reads synchronously ({@link getReplay},
21
+ * {@link windowState}, {@link viewerCount}, {@link getUrl}, {@link getLogBuffer})
22
+ * is cached from those frames; the daemon sends the current state immediately on
23
+ * connect so these populate before the first paint.
24
+ */
25
+ export class RemoteHostSession {
26
+ socket;
27
+ // Cached state fed by daemon frames; read synchronously by the host UI.
28
+ replay = new Uint8Array(0);
29
+ winState = EMPTY_WINDOW_STATE;
30
+ viewers = 0;
31
+ url = null;
32
+ logBuf = [];
33
+ // The host UI registers exactly one callback per event; mirror the
34
+ // SharedWorkspace single-callback surface it binds against.
35
+ hostDataCb;
36
+ windowStateCb;
37
+ viewersCb;
38
+ logCb;
39
+ urlCb;
40
+ exitCb;
41
+ // Guards: `exited` makes the onExit path fire at most once (a clean `exit`
42
+ // frame or an unexpected socket close, whichever comes first); `closed` stops
43
+ // any send after the socket is gone.
44
+ exited = false;
45
+ closed = false;
46
+ pendingExitCode = null;
47
+ constructor(socket, initial) {
48
+ this.socket = socket;
49
+ createMessageReader(socket, (msg) => this.dispatch(msg), () => this.handleClose(null));
50
+ socket.on('close', () => this.handleClose(this.pendingExitCode));
51
+ socket.on('error', () => { });
52
+ this.send({ t: 'hello', cols: initial.cols, rows: initial.rows });
53
+ }
54
+ // --- inbound daemon → client ---------------------------------------------
55
+ dispatch(msg) {
56
+ // Only DaemonToClient frames arrive on a client socket; the daemon never
57
+ // sends ClientToDaemon frames back, so narrow by the discriminants we own.
58
+ const m = msg;
59
+ switch (m.t) {
60
+ case 'data':
61
+ this.hostDataCb?.(decodeChunk(m.chunk));
62
+ break;
63
+ case 'replay':
64
+ // Cached for getReplay(); NOT fed to onHostData — the host UI writes the
65
+ // replay itself once, via getReplay(), at go-live.
66
+ this.replay = decodeChunk(m.chunk);
67
+ break;
68
+ case 'window-state':
69
+ this.winState = m.state;
70
+ this.windowStateCb?.(m.state);
71
+ break;
72
+ case 'viewers':
73
+ this.viewers = m.n;
74
+ this.viewersCb?.(m.n);
75
+ break;
76
+ case 'log':
77
+ this.logBuf.push(m.line);
78
+ if (this.logBuf.length > DEBUG_MAX_LINES) {
79
+ this.logBuf.splice(0, this.logBuf.length - DEBUG_MAX_LINES);
80
+ }
81
+ this.logCb?.(m.line);
82
+ break;
83
+ case 'url':
84
+ this.url = m.url;
85
+ this.urlCb?.(m.url);
86
+ break;
87
+ case 'exit':
88
+ // Record the clean exit code so a following socket 'close' reports it,
89
+ // then fire the exit path now.
90
+ this.pendingExitCode = m.code;
91
+ this.fireExit(m.code, null);
92
+ break;
93
+ }
94
+ }
95
+ /** Fire the one-shot exit callback (idempotent). */
96
+ fireExit(code, signal) {
97
+ if (this.exited)
98
+ return;
99
+ this.exited = true;
100
+ this.exitCb?.(code, signal);
101
+ }
102
+ /** Socket gone: fire exit (with a clean code if an `exit` frame preceded it). */
103
+ handleClose(code) {
104
+ this.closed = true;
105
+ this.fireExit(code, null);
106
+ }
107
+ // --- outbound client → daemon --------------------------------------------
108
+ /** Write a framed message, swallowing sends after the socket has closed. */
109
+ send(msg) {
110
+ if (this.closed)
111
+ return;
112
+ try {
113
+ writeMessage(this.socket, msg);
114
+ }
115
+ catch {
116
+ // A write on a half-closed socket can throw; treat as closed.
117
+ this.closed = true;
118
+ }
119
+ }
120
+ // --- terminal ------------------------------------------------------------
121
+ onHostData(cb) { this.hostDataCb = cb; }
122
+ write(data) {
123
+ const bytes = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
124
+ this.send({ t: 'input', data: encodeChunk(bytes) });
125
+ }
126
+ resize(cols, rows) { this.send({ t: 'resize', cols, rows }); }
127
+ getReplay() { return this.replay; }
128
+ // --- windows -------------------------------------------------------------
129
+ onWindowState(cb) { this.windowStateCb = cb; }
130
+ windowState() { return this.winState; }
131
+ newWindow() { this.send({ t: 'win', op: 'new' }); }
132
+ nextWindow() { this.send({ t: 'win', op: 'next' }); }
133
+ prevWindow() { this.send({ t: 'win', op: 'prev' }); }
134
+ selectWindow(i) { this.send({ t: 'win', op: 'select', index: i }); }
135
+ closeWindow(i) { this.send({ t: 'win', op: 'close', index: i }); }
136
+ // --- viewers -------------------------------------------------------------
137
+ onViewersChange(cb) { this.viewersCb = cb; }
138
+ viewerCount() { return this.viewers; }
139
+ // --- logs (debug tab) ----------------------------------------------------
140
+ onLog(cb) { this.logCb = cb; }
141
+ getLogBuffer() { return this.logBuf; }
142
+ // --- url -----------------------------------------------------------------
143
+ getUrl() { return this.url; }
144
+ onUrl(cb) { this.urlCb = cb; }
145
+ // --- lifecycle -----------------------------------------------------------
146
+ onExit(cb) { this.exitCb = cb; }
147
+ /**
148
+ * Detach without ending the daemon session: tell the daemon to drop this
149
+ * client (it keeps running), end the socket, then run the local exit path so
150
+ * the host UI tears its terminal down (host-terminal's restore() runs on exit).
151
+ */
152
+ detach() {
153
+ this.send({ t: 'detach' });
154
+ this.closed = true;
155
+ try {
156
+ this.socket.end();
157
+ }
158
+ catch { /* already ending */ }
159
+ this.fireExit(null, null);
160
+ }
161
+ /** Tear down: end the socket (no exit fired — the caller is disposing us). */
162
+ dispose() {
163
+ this.closed = true;
164
+ try {
165
+ this.socket.end();
166
+ }
167
+ catch { /* already ending */ }
168
+ }
169
+ }
170
+ //# sourceMappingURL=remote-host-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-host-session.js","sourceRoot":"","sources":["../src/remote-host-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /** A registered, discoverable daemon session. */
2
+ export interface SessionInfo {
3
+ name: string;
4
+ socket: string;
5
+ logFile: string;
6
+ pid: number;
7
+ capId: string;
8
+ url: string;
9
+ createdAt: number;
10
+ }
11
+ /**
12
+ * Resolve the runtime directory holding sockets, logs and the registry.
13
+ *
14
+ * Precedence:
15
+ * 1. `ENTANGLE_RUN_DIR` — explicit override (used by tests, and handy for the
16
+ * daemon to pin a location);
17
+ * 2. `$XDG_RUNTIME_DIR/entangle` — the standard per-user runtime dir;
18
+ * 3. `~/.entangle/run` — fallback when XDG is unset.
19
+ */
20
+ export declare function resolveRunDir(): string;
21
+ /** Ensure the runtime directory exists with 0700 perms, returning its path. */
22
+ export declare function ensureRunDir(): string;
23
+ /** Reduce an arbitrary name to a filesystem-safe token ([A-Za-z0-9._-]). */
24
+ export declare function sanitizeName(name: string): string;
25
+ /** Absolute path of the unix socket for a session. */
26
+ export declare function socketPath(name: string): string;
27
+ /** Absolute path of the log file for a session. */
28
+ export declare function logPath(name: string): string;
29
+ /**
30
+ * Stable short session name derived from a capability id, so an unnamed session
31
+ * is keyed on its capability. Deterministic: the same capId always yields the
32
+ * same name. Uses the sanitized leading characters of the capId (capIds are
33
+ * already url-safe base-ish tokens) to stay human-recognizable.
34
+ */
35
+ export declare function defaultSessionName(capId: string): string;
36
+ /** Read the registry. A missing or corrupt file is treated as empty. */
37
+ export declare function listSessions(): SessionInfo[];
38
+ /** Add (or replace by name) a session in the registry. */
39
+ export declare function addSession(info: SessionInfo): void;
40
+ /** Remove a session from the registry by name. No-op if absent. */
41
+ export declare function removeSession(name: string): void;
42
+ /** Find a registered session by name. */
43
+ export declare function findSession(name: string): SessionInfo | undefined;
44
+ /** True if the pid is alive AND the session's socket file still exists. */
45
+ export declare function isAlive(info: SessionInfo): boolean;
46
+ /**
47
+ * Prune dead state: drop registry entries whose pid is gone, and unlink any
48
+ * `.sock` file in the run dir that no live registered session owns.
49
+ */
50
+ export declare function cleanupStale(): void;
51
+ //# sourceMappingURL=session-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"AAeA,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAID;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAMtC;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,mDAAmD;AACnD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGxD;AAsBD,wEAAwE;AACxE,wBAAgB,YAAY,IAAI,WAAW,EAAE,CAc5C;AAaD,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAIlD;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAIhD;AAED,yCAAyC;AACzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEjE;AAID,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAElD;AAcD;;;GAGG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAoBnC"}