@myrialabs/ptykit 0.0.1

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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -0
  3. package/dist/client/fit.d.ts +29 -0
  4. package/dist/client/fit.d.ts.map +1 -0
  5. package/dist/client/fit.js +45 -0
  6. package/dist/client/fit.js.map +1 -0
  7. package/dist/client/index.d.ts +10 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +9 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/persistence.d.ts +15 -0
  12. package/dist/client/persistence.d.ts.map +1 -0
  13. package/dist/client/persistence.js +47 -0
  14. package/dist/client/persistence.js.map +1 -0
  15. package/dist/client/pty-kit-client.d.ts +122 -0
  16. package/dist/client/pty-kit-client.d.ts.map +1 -0
  17. package/dist/client/pty-kit-client.js +245 -0
  18. package/dist/client/pty-kit-client.js.map +1 -0
  19. package/dist/client/terminal.d.ts +77 -0
  20. package/dist/client/terminal.d.ts.map +1 -0
  21. package/dist/client/terminal.js +112 -0
  22. package/dist/client/terminal.js.map +1 -0
  23. package/dist/client/ws-core.d.ts +88 -0
  24. package/dist/client/ws-core.d.ts.map +1 -0
  25. package/dist/client/ws-core.js +324 -0
  26. package/dist/client/ws-core.js.map +1 -0
  27. package/dist/core/backend.d.ts +52 -0
  28. package/dist/core/backend.d.ts.map +1 -0
  29. package/dist/core/backend.js +11 -0
  30. package/dist/core/backend.js.map +1 -0
  31. package/dist/core/detect.d.ts +21 -0
  32. package/dist/core/detect.d.ts.map +1 -0
  33. package/dist/core/detect.js +82 -0
  34. package/dist/core/detect.js.map +1 -0
  35. package/dist/core/env.d.ts +30 -0
  36. package/dist/core/env.d.ts.map +1 -0
  37. package/dist/core/env.js +68 -0
  38. package/dist/core/env.js.map +1 -0
  39. package/dist/core/index.d.ts +11 -0
  40. package/dist/core/index.d.ts.map +1 -0
  41. package/dist/core/index.js +10 -0
  42. package/dist/core/index.js.map +1 -0
  43. package/dist/core/pty-kit.d.ts +90 -0
  44. package/dist/core/pty-kit.d.ts.map +1 -0
  45. package/dist/core/pty-kit.js +187 -0
  46. package/dist/core/pty-kit.js.map +1 -0
  47. package/dist/core/scrollback.d.ts +43 -0
  48. package/dist/core/scrollback.d.ts.map +1 -0
  49. package/dist/core/scrollback.js +79 -0
  50. package/dist/core/scrollback.js.map +1 -0
  51. package/dist/core/session.d.ts +100 -0
  52. package/dist/core/session.d.ts.map +1 -0
  53. package/dist/core/session.js +264 -0
  54. package/dist/core/session.js.map +1 -0
  55. package/dist/core/shell.d.ts +24 -0
  56. package/dist/core/shell.d.ts.map +1 -0
  57. package/dist/core/shell.js +55 -0
  58. package/dist/core/shell.js.map +1 -0
  59. package/dist/index.d.ts +11 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +11 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/server/connection.d.ts +38 -0
  64. package/dist/server/connection.d.ts.map +1 -0
  65. package/dist/server/connection.js +67 -0
  66. package/dist/server/connection.js.map +1 -0
  67. package/dist/server/ids.d.ts +2 -0
  68. package/dist/server/ids.d.ts.map +1 -0
  69. package/dist/server/ids.js +7 -0
  70. package/dist/server/ids.js.map +1 -0
  71. package/dist/server/index.d.ts +6 -0
  72. package/dist/server/index.d.ts.map +1 -0
  73. package/dist/server/index.js +6 -0
  74. package/dist/server/index.js.map +1 -0
  75. package/dist/server/pty-kit-server.d.ts +101 -0
  76. package/dist/server/pty-kit-server.d.ts.map +1 -0
  77. package/dist/server/pty-kit-server.js +361 -0
  78. package/dist/server/pty-kit-server.js.map +1 -0
  79. package/dist/server/transport-bun.d.ts +26 -0
  80. package/dist/server/transport-bun.d.ts.map +1 -0
  81. package/dist/server/transport-bun.js +79 -0
  82. package/dist/server/transport-bun.js.map +1 -0
  83. package/dist/server/transport-node.d.ts +20 -0
  84. package/dist/server/transport-node.d.ts.map +1 -0
  85. package/dist/server/transport-node.js +77 -0
  86. package/dist/server/transport-node.js.map +1 -0
  87. package/dist/shared/index.d.ts +260 -0
  88. package/dist/shared/index.d.ts.map +1 -0
  89. package/dist/shared/index.js +85 -0
  90. package/dist/shared/index.js.map +1 -0
  91. package/package.json +108 -0
  92. package/src/client/svelte/PtyTerminal.svelte +146 -0
  93. package/src/client/svelte/index.d.ts +84 -0
  94. package/src/client/svelte/index.js +4 -0
  95. package/src/client/svelte/svelte-compile.test.ts +11 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * `PtyKitClient` — the browser client.
3
+ *
4
+ * One `WsCore` socket multiplexes every session in a namespace. Server output
5
+ * broadcasts to the room; this client filters by `sessionId` and dedups by
6
+ * `seq` (R5). Replayed frames (no `seq`) are passed through `stripReportRequests`
7
+ * so the terminal does not answer color/cursor queries into an idle prompt (R17).
8
+ *
9
+ * Reconnect is ON by default; on reconnect every known session is re-attached
10
+ * (idempotent `create-session`) so the room subscription and scrollback recover
11
+ * (R7/R14).
12
+ */
13
+ import { stripReportRequests } from '../shared/index.js';
14
+ import { WsCore, } from './ws-core.js';
15
+ import { defaultPersistence } from './persistence.js';
16
+ /** A handle to one PTY session over the shared socket. */
17
+ export class ClientSession {
18
+ client;
19
+ sessionId;
20
+ namespace;
21
+ /** @internal — the request used to (re)attach on reconnect. */
22
+ createRequest;
23
+ lastSeq = 0;
24
+ /** Output that arrived before the first `onData` listener (e.g. reattach replay). */
25
+ pending = [];
26
+ pendingBytes = 0;
27
+ static PENDING_CAP = 1_000_000; // 1MB backlog ceiling
28
+ dataCbs = new Set();
29
+ exitCbs = new Set();
30
+ dirCbs = new Set();
31
+ errCbs = new Set();
32
+ constructor(client, request) {
33
+ this.client = client;
34
+ this.sessionId = request.sessionId;
35
+ this.namespace = request.namespace;
36
+ this.createRequest = request;
37
+ }
38
+ /** Subscribe to terminal output (deduped by seq; replay frames stripped). */
39
+ onData(cb) {
40
+ this.dataCbs.add(cb);
41
+ // Flush output that arrived before any listener (the reattach replay
42
+ // frame is unicast during `attach`, before the caller subscribes).
43
+ if (this.pending.length) {
44
+ const buffered = this.pending;
45
+ this.pending = [];
46
+ this.pendingBytes = 0;
47
+ for (const chunk of buffered)
48
+ cb(chunk);
49
+ }
50
+ return () => this.dataCbs.delete(cb);
51
+ }
52
+ onExit(cb) {
53
+ this.exitCbs.add(cb);
54
+ return () => this.exitCbs.delete(cb);
55
+ }
56
+ onDirectory(cb) {
57
+ this.dirCbs.add(cb);
58
+ return () => this.dirCbs.delete(cb);
59
+ }
60
+ onError(cb) {
61
+ this.errCbs.add(cb);
62
+ return () => this.errCbs.delete(cb);
63
+ }
64
+ /** @internal Route an output event for this session. */
65
+ _handleOutput(content, seq) {
66
+ let deliver;
67
+ if (seq === undefined) {
68
+ // Replay frame — strip report-requests so xterm doesn't answer into the
69
+ // idle prompt (R17). No seq tracking; replay is a full screen frame.
70
+ deliver = stripReportRequests(content);
71
+ }
72
+ else {
73
+ if (seq <= this.lastSeq)
74
+ return; // dedup live output (R5)
75
+ this.lastSeq = seq;
76
+ deliver = content;
77
+ }
78
+ if (this.dataCbs.size === 0) {
79
+ this.pending.push(deliver);
80
+ this.pendingBytes += deliver.length;
81
+ while (this.pendingBytes > ClientSession.PENDING_CAP && this.pending.length > 1) {
82
+ this.pendingBytes -= this.pending.shift().length;
83
+ }
84
+ return;
85
+ }
86
+ for (const cb of this.dataCbs)
87
+ cb(deliver);
88
+ }
89
+ /** @internal */
90
+ _handleExit(exitCode) {
91
+ for (const cb of this.exitCbs)
92
+ cb(exitCode);
93
+ }
94
+ /** @internal */
95
+ _handleDirectory(directory) {
96
+ for (const cb of this.dirCbs)
97
+ cb(directory);
98
+ }
99
+ /** @internal */
100
+ _handleError(error) {
101
+ for (const cb of this.errCbs)
102
+ cb(error);
103
+ }
104
+ /** Send raw keystrokes (fire-and-forget pass-through, R16). */
105
+ write(data) {
106
+ this.client._emitInput(this.sessionId, data);
107
+ }
108
+ resize(cols, rows) {
109
+ return this.client._resize(this.sessionId, cols, rows);
110
+ }
111
+ cancel() {
112
+ return this.client._cancel(this.sessionId);
113
+ }
114
+ clear() {
115
+ return this.client._clear(this.sessionId);
116
+ }
117
+ /** Kill the session on the server. */
118
+ kill() {
119
+ return this.client._kill(this.sessionId);
120
+ }
121
+ /** Stop receiving locally; does NOT kill the server-side session. */
122
+ detach() {
123
+ this.dataCbs.clear();
124
+ this.exitCbs.clear();
125
+ this.dirCbs.clear();
126
+ this.errCbs.clear();
127
+ this.client._forget(this.sessionId);
128
+ }
129
+ }
130
+ export class PtyKitClient {
131
+ core;
132
+ persistence;
133
+ defaultNamespace;
134
+ sessions = new Map();
135
+ status = 'reconnecting';
136
+ statusCbs = new Set();
137
+ constructor(options) {
138
+ this.persistence = options.persistence ?? defaultPersistence();
139
+ this.defaultNamespace = options.namespace;
140
+ this.core = new WsCore({
141
+ url: options.url,
142
+ reconnect: options.reconnect,
143
+ WebSocketImpl: options.WebSocketImpl,
144
+ requestTimeoutMs: options.requestTimeoutMs,
145
+ onStatus: (s) => {
146
+ this.status = s;
147
+ for (const cb of this.statusCbs)
148
+ cb(s);
149
+ },
150
+ onReconnect: () => this.reattachAll(),
151
+ });
152
+ // Route broadcast events to the matching session.
153
+ this.core.on('output', (p) => this.sessions.get(p?.sessionId)?._handleOutput(p.content, p.seq));
154
+ this.core.on('exit', (p) => this.sessions.get(p?.sessionId)?._handleExit(p.exitCode));
155
+ this.core.on('directory', (p) => this.sessions.get(p?.sessionId)?._handleDirectory(p.newDirectory));
156
+ this.core.on('error', (p) => this.sessions.get(p?.sessionId)?._handleError(p.error));
157
+ }
158
+ /** Subscribe to connection status. Fires immediately with the current value. */
159
+ onStatus(cb) {
160
+ this.statusCbs.add(cb);
161
+ cb(this.status);
162
+ return () => this.statusCbs.delete(cb);
163
+ }
164
+ connected() {
165
+ return this.core.connected();
166
+ }
167
+ resolveNamespace(ns) {
168
+ const namespace = ns ?? this.defaultNamespace;
169
+ if (!namespace)
170
+ throw new Error('ptykit: a namespace is required (set it on the client or the call)');
171
+ return namespace;
172
+ }
173
+ generateId(namespace) {
174
+ return `${namespace}-terminal-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
175
+ }
176
+ /** Create a new session (or attach if the id already exists — server is idempotent). */
177
+ async create(options = {}) {
178
+ const namespace = this.resolveNamespace(options.namespace);
179
+ const sessionId = options.sessionId ?? this.generateId(namespace);
180
+ this.persistence.save(namespace, sessionId);
181
+ return this.open({ sessionId, namespace, cwd: options.cwd, cols: options.cols, rows: options.rows, shell: options.shell });
182
+ }
183
+ /** Attach to an existing session (replays serialized scrollback). */
184
+ async attach(sessionId, options = {}) {
185
+ const namespace = this.resolveNamespace(options.namespace);
186
+ const id = sessionId ?? this.persistence.load(namespace);
187
+ if (!id)
188
+ throw new Error('ptykit: no sessionId provided and none persisted for this namespace');
189
+ this.persistence.save(namespace, id);
190
+ return this.open({ sessionId: id, namespace, cwd: options.cwd, cols: options.cols, rows: options.rows, shell: options.shell });
191
+ }
192
+ async open(request) {
193
+ let session = this.sessions.get(request.sessionId);
194
+ if (!session) {
195
+ session = new ClientSession(this, request);
196
+ this.sessions.set(request.sessionId, session);
197
+ }
198
+ // Listener is registered (above) before the request is sent, so the replay
199
+ // frame the server unicasts is captured by this session.
200
+ await this.core.http('create-session', request);
201
+ return session;
202
+ }
203
+ reattachAll() {
204
+ for (const session of this.sessions.values()) {
205
+ this.core.http('create-session', session.createRequest).catch(() => {
206
+ /* best-effort re-attach; next user action will retry */
207
+ });
208
+ }
209
+ }
210
+ /** List sessions a namespace currently has on the server. */
211
+ listSessions(namespace) {
212
+ return this.core.http('list-sessions', { namespace: this.resolveNamespace(namespace) });
213
+ }
214
+ disconnect() {
215
+ this.core.disconnect();
216
+ }
217
+ // ---- internal session plumbing -----------------------------------------
218
+ /** @internal */
219
+ _emitInput(sessionId, data) {
220
+ this.core.emit('input', { sessionId, data });
221
+ }
222
+ /** @internal */
223
+ _resize(sessionId, cols, rows) {
224
+ return this.core.http('resize', { sessionId, cols, rows }).then(() => undefined);
225
+ }
226
+ /** @internal */
227
+ _cancel(sessionId) {
228
+ return this.core.http('cancel', { sessionId }).then(() => undefined);
229
+ }
230
+ /** @internal */
231
+ _clear(sessionId) {
232
+ return this.core.http('clear', { sessionId }).then(() => undefined);
233
+ }
234
+ /** @internal */
235
+ _kill(sessionId) {
236
+ return this.core.http('kill-session', { sessionId }).then(() => {
237
+ this.sessions.delete(sessionId);
238
+ });
239
+ }
240
+ /** @internal */
241
+ _forget(sessionId) {
242
+ this.sessions.delete(sessionId);
243
+ }
244
+ }
245
+ //# sourceMappingURL=pty-kit-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pty-kit-client.js","sourceRoot":"","sources":["../../src/client/pty-kit-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * `mountTerminal` — a ready-to-use xterm.js terminal wired to a PtyKit session.
3
+ *
4
+ * The framework-agnostic counterpart to `<PtyTerminal/>`: hand it a container
5
+ * element and a `url` and you get back a live, fitted terminal — no manual
6
+ * `new Terminal()`, `loadAddon`, `open`, or `onData` plumbing. It stays fully
7
+ * configurable (appearance, theme, an existing client, callbacks) and returns a
8
+ * handle with the underlying `terminal`/`fitAddon`/`session` for advanced use.
9
+ *
10
+ * xterm and the FitAddon are imported dynamically (they are optional peer deps),
11
+ * so non-browser/headless consumers of `ptykit/client` never pull them in, and
12
+ * the call is SSR-safe — it only touches the DOM when actually invoked.
13
+ */
14
+ import { PtyKitClient, type ClientSession } from './pty-kit-client.js';
15
+ import type { ReconnectOptions, WSStatus } from './ws-core.js';
16
+ import type { SessionPersistence } from './persistence.js';
17
+ export interface MountTerminalOptions {
18
+ /** WebSocket endpoint, e.g. `/pty` or `wss://host/pty`. */
19
+ url: string;
20
+ /** Session to attach to / create. Omit when creating to auto-generate one. */
21
+ sessionId?: string;
22
+ /** Room/namespace. Required (here or on a passed-in `client`). */
23
+ namespace?: string;
24
+ /** Create a new session instead of attaching. Default `false`. */
25
+ create?: boolean;
26
+ /** Reuse an existing client (e.g. to share one socket across terminals). */
27
+ client?: PtyKitClient;
28
+ /** Reconnect tuning for the internally-created client. */
29
+ reconnect?: ReconnectOptions;
30
+ /** sessionId persistence override. */
31
+ persistence?: SessionPersistence;
32
+ /** RPC timeout (ms). */
33
+ requestTimeoutMs?: number;
34
+ cols?: number;
35
+ rows?: number;
36
+ cwd?: string;
37
+ shell?: string;
38
+ scrollback?: number;
39
+ fontSize?: number;
40
+ fontFamily?: string;
41
+ lineHeight?: number;
42
+ cursorBlink?: boolean;
43
+ cursorStyle?: 'block' | 'underline' | 'bar';
44
+ /** An xterm `ITheme` object. */
45
+ theme?: Record<string, unknown>;
46
+ /** Extra/override xterm `Terminal` options. */
47
+ terminalOptions?: Record<string, unknown>;
48
+ /** Attach a FitAddon + ResizeObserver. Default `true`. */
49
+ fit?: boolean;
50
+ fitDebounceMs?: number;
51
+ onData?: (chunk: string) => void;
52
+ onExit?: (exitCode: number) => void;
53
+ onError?: (error: unknown) => void;
54
+ onStatus?: (status: WSStatus) => void;
55
+ onDirectory?: (directory: string) => void;
56
+ }
57
+ export interface TerminalHandle {
58
+ /** The (possibly shared) client driving the socket. */
59
+ client: PtyKitClient;
60
+ /** The attached/created session. */
61
+ session: ClientSession;
62
+ /** The xterm `Terminal` instance (typed loosely to avoid a hard xterm dep). */
63
+ terminal: any;
64
+ /** The xterm `FitAddon` instance (`undefined` when `fit: false`). */
65
+ fitAddon: any;
66
+ /** Tear everything down: detach listeners, dispose the terminal, and — if the
67
+ * client was created internally — disconnect it. Idempotent. */
68
+ dispose(): void;
69
+ }
70
+ /**
71
+ * Mount an xterm terminal into `target`, attach/create a PtyKit session, and
72
+ * wire output⇄input + fit. Resolves once the session is open; rejects (and
73
+ * calls `onError`) if attach/create fails — so a backend/connection failure
74
+ * surfaces instead of leaving a silently blank terminal.
75
+ */
76
+ export declare function mountTerminal(target: HTMLElement, options: MountTerminalOptions): Promise<TerminalHandle>;
77
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/client/terminal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IAEpC,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,sCAAsC;IACtC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,wBAAwB;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,KAAK,CAAC;IAC5C,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAG1C,0DAA0D;IAC1D,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,cAAc;IAC9B,uDAAuD;IACvD,MAAM,EAAE,YAAY,CAAC;IACrB,oCAAoC;IACpC,OAAO,EAAE,aAAa,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,EAAE,GAAG,CAAC;IACd,qEAAqE;IACrE,QAAQ,EAAE,GAAG,CAAC;IACd;qEACiE;IACjE,OAAO,IAAI,IAAI,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAClC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAyFzB"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * `mountTerminal` — a ready-to-use xterm.js terminal wired to a PtyKit session.
3
+ *
4
+ * The framework-agnostic counterpart to `<PtyTerminal/>`: hand it a container
5
+ * element and a `url` and you get back a live, fitted terminal — no manual
6
+ * `new Terminal()`, `loadAddon`, `open`, or `onData` plumbing. It stays fully
7
+ * configurable (appearance, theme, an existing client, callbacks) and returns a
8
+ * handle with the underlying `terminal`/`fitAddon`/`session` for advanced use.
9
+ *
10
+ * xterm and the FitAddon are imported dynamically (they are optional peer deps),
11
+ * so non-browser/headless consumers of `ptykit/client` never pull them in, and
12
+ * the call is SSR-safe — it only touches the DOM when actually invoked.
13
+ */
14
+ import { PtyKitClient } from './pty-kit-client.js';
15
+ import { attachFit } from './fit.js';
16
+ /**
17
+ * Mount an xterm terminal into `target`, attach/create a PtyKit session, and
18
+ * wire output⇄input + fit. Resolves once the session is open; rejects (and
19
+ * calls `onError`) if attach/create fails — so a backend/connection failure
20
+ * surfaces instead of leaving a silently blank terminal.
21
+ */
22
+ export async function mountTerminal(target, options) {
23
+ // Optional peer deps, loaded lazily so the core client stays xterm-free.
24
+ const [{ Terminal }, { FitAddon }] = (await Promise.all([
25
+ import('@xterm/xterm'),
26
+ import('@xterm/addon-fit'),
27
+ ]));
28
+ const terminal = new Terminal({
29
+ scrollback: options.scrollback ?? 5000,
30
+ fontSize: options.fontSize ?? 13,
31
+ fontFamily: options.fontFamily ?? 'ui-monospace, SFMono-Regular, Menlo, monospace',
32
+ lineHeight: options.lineHeight ?? 1.0,
33
+ cursorBlink: options.cursorBlink ?? true,
34
+ cursorStyle: options.cursorStyle ?? 'block',
35
+ allowProposedApi: true,
36
+ ...(options.theme ? { theme: options.theme } : {}),
37
+ ...(options.terminalOptions ?? {}),
38
+ });
39
+ const fitAddon = options.fit === false ? undefined : new FitAddon();
40
+ if (fitAddon)
41
+ terminal.loadAddon(fitAddon);
42
+ terminal.open(target);
43
+ const ownClient = !options.client;
44
+ const client = options.client ??
45
+ new PtyKitClient({
46
+ url: options.url,
47
+ namespace: options.namespace,
48
+ reconnect: options.reconnect,
49
+ persistence: options.persistence,
50
+ requestTimeoutMs: options.requestTimeoutMs,
51
+ });
52
+ const unsubs = [];
53
+ if (options.onStatus)
54
+ unsubs.push(client.onStatus(options.onStatus));
55
+ const open = {
56
+ sessionId: options.sessionId,
57
+ namespace: options.namespace,
58
+ cols: options.cols ?? terminal.cols,
59
+ rows: options.rows ?? terminal.rows,
60
+ cwd: options.cwd,
61
+ shell: options.shell,
62
+ };
63
+ let session;
64
+ try {
65
+ session = options.create
66
+ ? await client.create(open)
67
+ : await client.attach(options.sessionId, open);
68
+ }
69
+ catch (err) {
70
+ for (const u of unsubs)
71
+ u();
72
+ terminal.dispose();
73
+ if (ownClient)
74
+ client.disconnect();
75
+ options.onError?.(err);
76
+ throw err;
77
+ }
78
+ unsubs.push(session.onData((chunk) => {
79
+ terminal.write(chunk);
80
+ options.onData?.(chunk);
81
+ }));
82
+ if (options.onExit)
83
+ unsubs.push(session.onExit(options.onExit));
84
+ if (options.onError)
85
+ unsubs.push(session.onError(options.onError));
86
+ if (options.onDirectory)
87
+ unsubs.push(session.onDirectory(options.onDirectory));
88
+ terminal.onData((data) => session.write(data));
89
+ const detachFit = fitAddon
90
+ ? attachFit(session, terminal, fitAddon, { debounceMs: options.fitDebounceMs ?? 100 })
91
+ : undefined;
92
+ let disposed = false;
93
+ return {
94
+ client,
95
+ session,
96
+ terminal,
97
+ fitAddon,
98
+ dispose() {
99
+ if (disposed)
100
+ return;
101
+ disposed = true;
102
+ detachFit?.();
103
+ for (const u of unsubs)
104
+ u();
105
+ session.detach();
106
+ terminal.dispose();
107
+ if (ownClient)
108
+ client.disconnect();
109
+ },
110
+ };
111
+ }
112
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/client/terminal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Resilient WebSocket client core — the battle-tested pieces of a production
3
+ * WebSocket client:
4
+ * - exponential backoff reconnect (default 1s→30s, max 5 attempts),
5
+ * - **heal-reconnect** for "open but dead" sockets (a stalled read forces one
6
+ * reconnect + retry before failing),
7
+ * - **idempotency-aware resend**: reads resend freely; mutations resend only if
8
+ * never actually delivered,
9
+ * - `onReconnect` fires BEFORE the queue flush so room re-subscription happens
10
+ * first,
11
+ * - connection status surfaced via `onStatus`.
12
+ *
13
+ * Text/JSON frames only (terminal I/O is text). The WebSocket implementation is
14
+ * injectable for testing.
15
+ */
16
+ import { type Seq } from '../shared/index.js';
17
+ export type WSStatus = 'connected' | 'reconnecting' | 'disconnected';
18
+ /** Minimal structural WebSocket shape (browser `WebSocket` satisfies it). */
19
+ export interface WebSocketLike {
20
+ readyState: number;
21
+ send(data: string): void;
22
+ close(): void;
23
+ onopen: ((ev?: any) => void) | null;
24
+ onmessage: ((ev: {
25
+ data: any;
26
+ }) => void) | null;
27
+ onclose: ((ev?: any) => void) | null;
28
+ onerror: ((ev?: any) => void) | null;
29
+ }
30
+ export type WebSocketFactory = (url: string) => WebSocketLike;
31
+ export interface ReconnectOptions {
32
+ enabled?: boolean;
33
+ baseDelayMs?: number;
34
+ maxDelayMs?: number;
35
+ /** 0 = infinite. Default 5. */
36
+ maxAttempts?: number;
37
+ }
38
+ export interface WsCoreOptions {
39
+ url: string;
40
+ reconnect?: ReconnectOptions;
41
+ onStatus?: (status: WSStatus, attempts: number) => void;
42
+ /** Fired on every reconnect (not the first connect), before the queue flush. */
43
+ onReconnect?: () => void;
44
+ /** Injectable WebSocket constructor (defaults to global `WebSocket`). */
45
+ WebSocketImpl?: WebSocketFactory;
46
+ /** RPC timeout (ms). Default 30000. */
47
+ requestTimeoutMs?: number;
48
+ }
49
+ export declare class WsCore {
50
+ private ws;
51
+ private readonly url;
52
+ private readonly factory;
53
+ private readonly reconnectOpts;
54
+ private readonly requestTimeoutMs;
55
+ private readonly onStatus?;
56
+ private readonly onReconnect?;
57
+ private reconnectAttempts;
58
+ private reconnectTimer;
59
+ private isConnected;
60
+ private shouldReconnect;
61
+ private hasConnectedBefore;
62
+ private healing;
63
+ private readonly listeners;
64
+ private messageQueue;
65
+ private readonly pending;
66
+ constructor(options: WsCoreOptions);
67
+ private connect;
68
+ private scheduleReconnect;
69
+ private dispatch;
70
+ private isSocketOpen;
71
+ private sendRaw;
72
+ /** Fire-and-forget event to the server (queued if not connected). */
73
+ emit(action: string, payload: unknown): void;
74
+ /** Subscribe to a server event. Returns an unsubscribe function. */
75
+ on(action: string, callback: (payload: any) => void): () => void;
76
+ /**
77
+ * RPC request/response. Resolves with the unwrapped `data` on success,
78
+ * rejects on error/timeout. Survives a transport hiccup (resend + heal).
79
+ */
80
+ http<TData = any>(action: string, data?: unknown, timeout?: number): Promise<TData>;
81
+ connected(): boolean;
82
+ /** Force a reconnect, preserving listeners and the pending/queue state. */
83
+ reconnect(): void;
84
+ /** Permanently disconnect and fail any in-flight requests. */
85
+ disconnect(): void;
86
+ }
87
+ export type { Seq };
88
+ //# sourceMappingURL=ws-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-core.d.ts","sourceRoot":"","sources":["../../src/client/ws-core.ts"],"names":[],"mappings":"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"}