@cloudflare/sandbox 0.13.0-next.776.1 → 1.0.0-rc.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/README.md +62 -219
- package/dist/index.d.mts +548 -0
- package/dist/index.mjs +2452 -0
- package/package.json +17 -118
- package/Dockerfile +0 -368
- package/dist/bridge/index.d.ts +0 -181
- package/dist/bridge/index.d.ts.map +0 -1
- package/dist/bridge/index.js +0 -3053
- package/dist/bridge/index.js.map +0 -1
- package/dist/contexts-1EsLHByO.d.ts +0 -463
- package/dist/contexts-1EsLHByO.d.ts.map +0 -1
- package/dist/dist-Duor5GbS.js +0 -752
- package/dist/dist-Duor5GbS.js.map +0 -1
- package/dist/errors/index.d.ts +0 -4
- package/dist/errors/index.js +0 -4
- package/dist/errors-CXR0xBpw.js +0 -285
- package/dist/errors-CXR0xBpw.js.map +0 -1
- package/dist/errors-QYlSkVGz.js +0 -893
- package/dist/errors-QYlSkVGz.js.map +0 -1
- package/dist/extensions/index.d.ts +0 -4
- package/dist/extensions/index.js +0 -6
- package/dist/extensions-CFB2xHqY.js +0 -1023
- package/dist/extensions-CFB2xHqY.js.map +0 -1
- package/dist/filesystem-BWAZCZER.d.ts +0 -732
- package/dist/filesystem-BWAZCZER.d.ts.map +0 -1
- package/dist/git/index.d.ts +0 -63
- package/dist/git/index.d.ts.map +0 -1
- package/dist/git/index.js +0 -338
- package/dist/git/index.js.map +0 -1
- package/dist/index-Bs4bqXDR.d.ts +0 -438
- package/dist/index-Bs4bqXDR.d.ts.map +0 -1
- package/dist/index-HNYBk-az.d.ts +0 -444
- package/dist/index-HNYBk-az.d.ts.map +0 -1
- package/dist/index.d.ts +0 -576
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -33
- package/dist/index.js.map +0 -1
- package/dist/interpreter/index.d.ts +0 -311
- package/dist/interpreter/index.d.ts.map +0 -1
- package/dist/interpreter/index.js +0 -292
- package/dist/interpreter/index.js.map +0 -1
- package/dist/openai/index.d.ts +0 -68
- package/dist/openai/index.d.ts.map +0 -1
- package/dist/openai/index.js +0 -367
- package/dist/openai/index.js.map +0 -1
- package/dist/opencode/index.d.ts +0 -182
- package/dist/opencode/index.d.ts.map +0 -1
- package/dist/opencode/index.js +0 -454
- package/dist/opencode/index.js.map +0 -1
- package/dist/process-types-GStiZ8f8.d.ts +0 -73
- package/dist/process-types-GStiZ8f8.d.ts.map +0 -1
- package/dist/sandbox-BbAabq93.d.ts +0 -42
- package/dist/sandbox-BbAabq93.d.ts.map +0 -1
- package/dist/sandbox-cmlgGVYX.js +0 -10056
- package/dist/sandbox-cmlgGVYX.js.map +0 -1
- package/dist/sidecar/index.d.ts +0 -77
- package/dist/sidecar/index.d.ts.map +0 -1
- package/dist/sidecar/index.js +0 -201
- package/dist/sidecar/index.js.map +0 -1
- package/dist/xterm/index.d.ts +0 -93
- package/dist/xterm/index.d.ts.map +0 -1
- package/dist/xterm/index.js +0 -220
- package/dist/xterm/index.js.map +0 -1
package/dist/sidecar/index.d.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { RpcTarget, RpcTransport } from "capnweb";
|
|
2
|
-
import net, { Socket } from "node:net";
|
|
3
|
-
|
|
4
|
-
//#region src/sidecar/socket-transport.d.ts
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* capnweb `RpcTransport` over a unix-domain socket, mirroring the host-side
|
|
8
|
-
* transport in `@repo/sandbox-container`. The wire format is a 4-byte
|
|
9
|
-
* big-endian length prefix followed by UTF-8 JSON, identical on both sides.
|
|
10
|
-
*
|
|
11
|
-
* Exported from `@cloudflare/sandbox/sidecar` so a sidecar process can run
|
|
12
|
-
* its own capnweb session without copy-pasting the framing logic.
|
|
13
|
-
*/
|
|
14
|
-
declare class SocketTransport implements RpcTransport {
|
|
15
|
-
#private;
|
|
16
|
-
constructor(socket: Socket);
|
|
17
|
-
send(message: string): Promise<void>;
|
|
18
|
-
receive(): Promise<string>;
|
|
19
|
-
abort(reason: unknown): void;
|
|
20
|
-
}
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/sidecar/index.d.ts
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Base class for a sidecar's main capnweb RpcTarget. Subclasses add their
|
|
26
|
-
* own methods \u2014 these become the typed remote main visible to the SDK.
|
|
27
|
-
*
|
|
28
|
-
* `__ping__` is reserved for host health probes; do not override it.
|
|
29
|
-
*/
|
|
30
|
-
declare abstract class SandboxSidecar extends RpcTarget {
|
|
31
|
-
/**
|
|
32
|
-
* Health probe \u2014 the container's `ExtensionHost.health()` calls this and
|
|
33
|
-
* expects the literal string `'pong'`. The host bounds the call with its
|
|
34
|
-
* own timeout so a misbehaving sidecar cannot hang the probe.
|
|
35
|
-
*/
|
|
36
|
-
__ping__(): 'pong';
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Options for {@link serveSandboxSidecar}. Mostly defaults; the socket path
|
|
40
|
-
* is always taken from `process.env.EXT_SOCKET` (set by the container's
|
|
41
|
-
* `ExtensionHost`) so authors do not have to plumb it themselves.
|
|
42
|
-
*/
|
|
43
|
-
interface ServeSidecarOptions {
|
|
44
|
-
/**
|
|
45
|
-
* Override the diagnostic log line printed to stdout after the sidecar opens
|
|
46
|
-
* its socket. Readiness is determined by the host's successful capnweb
|
|
47
|
-
* connection, not by this line. Defaults to `sandbox sidecar listening`.
|
|
48
|
-
*/
|
|
49
|
-
readyMessage?: string;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Open the sidecar's unix socket and serve capnweb sessions backed by
|
|
53
|
-
* `target`. Each inbound connection gets its own `RpcSession`; the `target`
|
|
54
|
-
* instance is shared across all sessions, so authors can keep per-sidecar
|
|
55
|
-
* state on it without sync.
|
|
56
|
-
*
|
|
57
|
-
* Idiomatic usage:
|
|
58
|
-
*
|
|
59
|
-
* ```ts
|
|
60
|
-
* import {
|
|
61
|
-
* SandboxSidecar,
|
|
62
|
-
* serveSandboxSidecar
|
|
63
|
-
* } from '@cloudflare/sandbox/sidecar';
|
|
64
|
-
*
|
|
65
|
-
* class MyApi extends SandboxSidecar {
|
|
66
|
-
* async isOdd(n: number): Promise<boolean> {
|
|
67
|
-
* return n % 2 === 1;
|
|
68
|
-
* }
|
|
69
|
-
* }
|
|
70
|
-
*
|
|
71
|
-
* serveSandboxSidecar(new MyApi());
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
declare function serveSandboxSidecar(target: SandboxSidecar, options?: ServeSidecarOptions): net.Server;
|
|
75
|
-
//#endregion
|
|
76
|
-
export { SandboxSidecar, ServeSidecarOptions, SocketTransport, serveSandboxSidecar };
|
|
77
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/sidecar/socket-transport.ts","../../src/sidecar/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AAgBA;;;;;;cAAa,eAAA,YAA2B;;sBAWlB;ECEA,IAAA,CAAA,OAAA,EAAA,MAAe,CAAA,EDKN,OCLc,CAAA,IAAA,CAAA;EAgB5B,OAAA,CAAA,CAAA,EDgCJ,OChCI,CAAA,MAAmB,CAAA;EAgCpB,KAAA,CAAA,MAAA,EAAA,OAAA,CAAmB,EAAA,IAAA;;;;;;;;;;;uBAhDb,cAAA,SAAuB,SAAA;;;;;;;;;;;;;UAgB5B,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCD,mBAAA,SACN,0BACC,sBACR,GAAA,CAAI"}
|
package/dist/sidecar/index.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { RpcSession, RpcTarget } from "capnweb";
|
|
2
|
-
import net from "node:net";
|
|
3
|
-
|
|
4
|
-
//#region src/sidecar/socket-transport.ts
|
|
5
|
-
const HEADER_BYTES = 4;
|
|
6
|
-
const MAX_FRAME_BYTES = 16 * 1024 * 1024;
|
|
7
|
-
const MAX_QUEUED_MESSAGES = 128;
|
|
8
|
-
const RESUME_QUEUED_MESSAGES = 64;
|
|
9
|
-
/**
|
|
10
|
-
* capnweb `RpcTransport` over a unix-domain socket, mirroring the host-side
|
|
11
|
-
* transport in `@repo/sandbox-container`. The wire format is a 4-byte
|
|
12
|
-
* big-endian length prefix followed by UTF-8 JSON, identical on both sides.
|
|
13
|
-
*
|
|
14
|
-
* Exported from `@cloudflare/sandbox/sidecar` so a sidecar process can run
|
|
15
|
-
* its own capnweb session without copy-pasting the framing logic.
|
|
16
|
-
*/
|
|
17
|
-
var SocketTransport = class {
|
|
18
|
-
#socket;
|
|
19
|
-
#buffer = Buffer.alloc(0);
|
|
20
|
-
#queue = [];
|
|
21
|
-
#waiters = [];
|
|
22
|
-
#error = null;
|
|
23
|
-
#paused = false;
|
|
24
|
-
constructor(socket) {
|
|
25
|
-
this.#socket = socket;
|
|
26
|
-
socket.on("data", (chunk) => this.#onData(chunk));
|
|
27
|
-
socket.on("error", (err) => this.#fail(err));
|
|
28
|
-
socket.on("close", () => this.#fail(/* @__PURE__ */ new Error("Extension socket closed")));
|
|
29
|
-
}
|
|
30
|
-
async send(message) {
|
|
31
|
-
if (this.#error) throw this.#error;
|
|
32
|
-
const body = Buffer.from(message, "utf8");
|
|
33
|
-
if (body.length > MAX_FRAME_BYTES) throw new Error(`Extension capnweb message exceeds maximum size (${body.length} > ${MAX_FRAME_BYTES} bytes)`);
|
|
34
|
-
const header = Buffer.allocUnsafe(HEADER_BYTES);
|
|
35
|
-
header.writeUInt32BE(body.length, 0);
|
|
36
|
-
if (!this.#socket.write(Buffer.concat([header, body]))) await this.#awaitDrain();
|
|
37
|
-
}
|
|
38
|
-
#awaitDrain() {
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
const onDrain = () => {
|
|
41
|
-
cleanup();
|
|
42
|
-
resolve();
|
|
43
|
-
};
|
|
44
|
-
const onError = (err) => {
|
|
45
|
-
cleanup();
|
|
46
|
-
reject(err);
|
|
47
|
-
};
|
|
48
|
-
const onClose = () => {
|
|
49
|
-
cleanup();
|
|
50
|
-
reject(this.#error ?? /* @__PURE__ */ new Error("Extension socket closed"));
|
|
51
|
-
};
|
|
52
|
-
const cleanup = () => {
|
|
53
|
-
this.#socket.off("drain", onDrain);
|
|
54
|
-
this.#socket.off("error", onError);
|
|
55
|
-
this.#socket.off("close", onClose);
|
|
56
|
-
};
|
|
57
|
-
if (this.#error) {
|
|
58
|
-
reject(this.#error);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
this.#socket.once("drain", onDrain);
|
|
62
|
-
this.#socket.once("error", onError);
|
|
63
|
-
this.#socket.once("close", onClose);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
receive() {
|
|
67
|
-
if (this.#queue.length > 0) {
|
|
68
|
-
const message = this.#queue.shift();
|
|
69
|
-
this.#updateReadFlow();
|
|
70
|
-
return Promise.resolve(message);
|
|
71
|
-
}
|
|
72
|
-
if (this.#error) return Promise.reject(this.#error);
|
|
73
|
-
return new Promise((resolve, reject) => {
|
|
74
|
-
this.#waiters.push({
|
|
75
|
-
resolve,
|
|
76
|
-
reject
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
abort(reason) {
|
|
81
|
-
this.#fail(reason instanceof Error ? reason : new Error(String(reason)));
|
|
82
|
-
this.#socket.destroy();
|
|
83
|
-
}
|
|
84
|
-
#onData(chunk) {
|
|
85
|
-
this.#buffer = this.#buffer.length === 0 ? chunk : Buffer.concat([this.#buffer, chunk]);
|
|
86
|
-
while (this.#buffer.length >= HEADER_BYTES) {
|
|
87
|
-
const length = this.#buffer.readUInt32BE(0);
|
|
88
|
-
if (length > MAX_FRAME_BYTES) {
|
|
89
|
-
this.#fail(/* @__PURE__ */ new Error(`Extension capnweb message exceeds maximum size (${length} > ${MAX_FRAME_BYTES} bytes)`));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (this.#buffer.length < HEADER_BYTES + length) break;
|
|
93
|
-
const message = this.#buffer.subarray(HEADER_BYTES, HEADER_BYTES + length).toString("utf8");
|
|
94
|
-
this.#buffer = this.#buffer.subarray(HEADER_BYTES + length);
|
|
95
|
-
const waiter = this.#waiters.shift();
|
|
96
|
-
if (waiter) waiter.resolve(message);
|
|
97
|
-
else {
|
|
98
|
-
this.#queue.push(message);
|
|
99
|
-
this.#updateReadFlow();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
#updateReadFlow() {
|
|
104
|
-
if (!this.#paused && this.#queue.length >= MAX_QUEUED_MESSAGES) {
|
|
105
|
-
this.#socket.pause();
|
|
106
|
-
this.#paused = true;
|
|
107
|
-
} else if (this.#paused && this.#queue.length <= RESUME_QUEUED_MESSAGES) {
|
|
108
|
-
this.#socket.resume();
|
|
109
|
-
this.#paused = false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
#fail(err) {
|
|
113
|
-
if (this.#error) return;
|
|
114
|
-
this.#error = err;
|
|
115
|
-
const waiters = this.#waiters.splice(0);
|
|
116
|
-
for (const waiter of waiters) waiter.reject(err);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
//#endregion
|
|
121
|
-
//#region src/sidecar/index.ts
|
|
122
|
-
/**
|
|
123
|
-
* Helpers for authoring a Cloudflare Sandbox SDK extension's **sidecar**
|
|
124
|
-
* process \u2014 the program the container spawns inside the sandbox and that
|
|
125
|
-
* exchanges capnweb RPC with the host over a unix socket.
|
|
126
|
-
*
|
|
127
|
-
* This subpath is loaded only inside the sidecar (it depends on Node-style
|
|
128
|
-
* built-ins like `node:net`); the SDK-side `@cloudflare/sandbox/extensions`
|
|
129
|
-
* surface never imports it.
|
|
130
|
-
*
|
|
131
|
-
* Authoring contract:
|
|
132
|
-
* - Extend {@link SandboxSidecar}. The base implements `__ping__` so the
|
|
133
|
-
* host's health probe round-trips without per-sidecar boilerplate.
|
|
134
|
-
* - Pass an instance to {@link serveSandboxSidecar}. The helper opens the
|
|
135
|
-
* unix socket at `EXT_SOCKET` and serves a fresh capnweb session per
|
|
136
|
-
* connection.
|
|
137
|
-
*/
|
|
138
|
-
/**
|
|
139
|
-
* Base class for a sidecar's main capnweb RpcTarget. Subclasses add their
|
|
140
|
-
* own methods \u2014 these become the typed remote main visible to the SDK.
|
|
141
|
-
*
|
|
142
|
-
* `__ping__` is reserved for host health probes; do not override it.
|
|
143
|
-
*/
|
|
144
|
-
var SandboxSidecar = class extends RpcTarget {
|
|
145
|
-
/**
|
|
146
|
-
* Health probe \u2014 the container's `ExtensionHost.health()` calls this and
|
|
147
|
-
* expects the literal string `'pong'`. The host bounds the call with its
|
|
148
|
-
* own timeout so a misbehaving sidecar cannot hang the probe.
|
|
149
|
-
*/
|
|
150
|
-
__ping__() {
|
|
151
|
-
return "pong";
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
/**
|
|
155
|
-
* Open the sidecar's unix socket and serve capnweb sessions backed by
|
|
156
|
-
* `target`. Each inbound connection gets its own `RpcSession`; the `target`
|
|
157
|
-
* instance is shared across all sessions, so authors can keep per-sidecar
|
|
158
|
-
* state on it without sync.
|
|
159
|
-
*
|
|
160
|
-
* Idiomatic usage:
|
|
161
|
-
*
|
|
162
|
-
* ```ts
|
|
163
|
-
* import {
|
|
164
|
-
* SandboxSidecar,
|
|
165
|
-
* serveSandboxSidecar
|
|
166
|
-
* } from '@cloudflare/sandbox/sidecar';
|
|
167
|
-
*
|
|
168
|
-
* class MyApi extends SandboxSidecar {
|
|
169
|
-
* async isOdd(n: number): Promise<boolean> {
|
|
170
|
-
* return n % 2 === 1;
|
|
171
|
-
* }
|
|
172
|
-
* }
|
|
173
|
-
*
|
|
174
|
-
* serveSandboxSidecar(new MyApi());
|
|
175
|
-
* ```
|
|
176
|
-
*/
|
|
177
|
-
function serveSandboxSidecar(target, options = {}) {
|
|
178
|
-
const socketPath = process.env.EXT_SOCKET;
|
|
179
|
-
if (!socketPath) throw new Error("EXT_SOCKET is not set — sidecars are spawned by the container ExtensionHost, which injects this env var");
|
|
180
|
-
const server = net.createServer((socket) => {
|
|
181
|
-
new RpcSession(new SocketTransport(socket), target);
|
|
182
|
-
});
|
|
183
|
-
server.once("error", (error) => {
|
|
184
|
-
process.stderr.write(`sandbox sidecar failed to listen on ${socketPath}: ${error.message}\n`);
|
|
185
|
-
process.exit(1);
|
|
186
|
-
});
|
|
187
|
-
server.listen(socketPath, () => {
|
|
188
|
-
const message = options.readyMessage ?? "sandbox sidecar listening";
|
|
189
|
-
process.stdout.write(`${message}\n`);
|
|
190
|
-
});
|
|
191
|
-
const onSignal = () => {
|
|
192
|
-
server.close(() => process.exit(0));
|
|
193
|
-
};
|
|
194
|
-
process.once("SIGINT", onSignal);
|
|
195
|
-
process.once("SIGTERM", onSignal);
|
|
196
|
-
return server;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
//#endregion
|
|
200
|
-
export { SandboxSidecar, SocketTransport, serveSandboxSidecar };
|
|
201
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#socket","#queue","#waiters","#onData","#fail","#error","#awaitDrain","#updateReadFlow","#buffer","#paused"],"sources":["../../src/sidecar/socket-transport.ts","../../src/sidecar/index.ts"],"sourcesContent":["import type { Socket } from 'node:net';\nimport type { RpcTransport } from 'capnweb';\n\nconst HEADER_BYTES = 4;\nconst MAX_FRAME_BYTES = 16 * 1024 * 1024;\nconst MAX_QUEUED_MESSAGES = 128;\nconst RESUME_QUEUED_MESSAGES = 64;\n\n/**\n * capnweb `RpcTransport` over a unix-domain socket, mirroring the host-side\n * transport in `@repo/sandbox-container`. The wire format is a 4-byte\n * big-endian length prefix followed by UTF-8 JSON, identical on both sides.\n *\n * Exported from `@cloudflare/sandbox/sidecar` so a sidecar process can run\n * its own capnweb session without copy-pasting the framing logic.\n */\nexport class SocketTransport implements RpcTransport {\n readonly #socket: Socket;\n #buffer: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n readonly #queue: string[] = [];\n readonly #waiters: Array<{\n resolve: (msg: string) => void;\n reject: (err: Error) => void;\n }> = [];\n #error: Error | null = null;\n #paused = false;\n\n constructor(socket: Socket) {\n this.#socket = socket;\n socket.on('data', (chunk: Buffer) => this.#onData(chunk));\n socket.on('error', (err) => this.#fail(err));\n socket.on('close', () => this.#fail(new Error('Extension socket closed')));\n }\n\n async send(message: string): Promise<void> {\n if (this.#error) throw this.#error;\n const body = Buffer.from(message, 'utf8');\n if (body.length > MAX_FRAME_BYTES) {\n throw new Error(\n `Extension capnweb message exceeds maximum size (${body.length} > ${MAX_FRAME_BYTES} bytes)`\n );\n }\n const header = Buffer.allocUnsafe(HEADER_BYTES);\n header.writeUInt32BE(body.length, 0);\n const flushed = this.#socket.write(Buffer.concat([header, body]));\n if (!flushed) await this.#awaitDrain();\n }\n\n #awaitDrain(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const onDrain = () => {\n cleanup();\n resolve();\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const onClose = () => {\n cleanup();\n reject(this.#error ?? new Error('Extension socket closed'));\n };\n const cleanup = () => {\n this.#socket.off('drain', onDrain);\n this.#socket.off('error', onError);\n this.#socket.off('close', onClose);\n };\n if (this.#error) {\n reject(this.#error);\n return;\n }\n this.#socket.once('drain', onDrain);\n this.#socket.once('error', onError);\n this.#socket.once('close', onClose);\n });\n }\n\n receive(): Promise<string> {\n if (this.#queue.length > 0) {\n const message = this.#queue.shift()!;\n this.#updateReadFlow();\n return Promise.resolve(message);\n }\n if (this.#error) return Promise.reject(this.#error);\n return new Promise<string>((resolve, reject) => {\n this.#waiters.push({ resolve, reject });\n });\n }\n\n abort(reason: unknown): void {\n this.#fail(reason instanceof Error ? reason : new Error(String(reason)));\n this.#socket.destroy();\n }\n\n #onData(chunk: Buffer): void {\n this.#buffer =\n this.#buffer.length === 0 ? chunk : Buffer.concat([this.#buffer, chunk]);\n while (this.#buffer.length >= HEADER_BYTES) {\n const length = this.#buffer.readUInt32BE(0);\n if (length > MAX_FRAME_BYTES) {\n this.#fail(\n new Error(\n `Extension capnweb message exceeds maximum size (${length} > ${MAX_FRAME_BYTES} bytes)`\n )\n );\n return;\n }\n if (this.#buffer.length < HEADER_BYTES + length) break;\n const message = this.#buffer\n .subarray(HEADER_BYTES, HEADER_BYTES + length)\n .toString('utf8');\n this.#buffer = this.#buffer.subarray(HEADER_BYTES + length);\n const waiter = this.#waiters.shift();\n if (waiter) {\n waiter.resolve(message);\n } else {\n this.#queue.push(message);\n this.#updateReadFlow();\n }\n }\n }\n\n #updateReadFlow(): void {\n if (!this.#paused && this.#queue.length >= MAX_QUEUED_MESSAGES) {\n this.#socket.pause();\n this.#paused = true;\n } else if (this.#paused && this.#queue.length <= RESUME_QUEUED_MESSAGES) {\n this.#socket.resume();\n this.#paused = false;\n }\n }\n\n #fail(err: Error): void {\n if (this.#error) return;\n this.#error = err;\n const waiters = this.#waiters.splice(0);\n for (const waiter of waiters) {\n waiter.reject(err);\n }\n }\n}\n","/**\n * Helpers for authoring a Cloudflare Sandbox SDK extension's **sidecar**\n * process \\u2014 the program the container spawns inside the sandbox and that\n * exchanges capnweb RPC with the host over a unix socket.\n *\n * This subpath is loaded only inside the sidecar (it depends on Node-style\n * built-ins like `node:net`); the SDK-side `@cloudflare/sandbox/extensions`\n * surface never imports it.\n *\n * Authoring contract:\n * - Extend {@link SandboxSidecar}. The base implements `__ping__` so the\n * host's health probe round-trips without per-sidecar boilerplate.\n * - Pass an instance to {@link serveSandboxSidecar}. The helper opens the\n * unix socket at `EXT_SOCKET` and serves a fresh capnweb session per\n * connection.\n */\n\nimport net from 'node:net';\nimport { RpcSession, RpcTarget } from 'capnweb';\nimport { SocketTransport } from './socket-transport.js';\n\nexport { SocketTransport } from './socket-transport.js';\n\n/**\n * Base class for a sidecar's main capnweb RpcTarget. Subclasses add their\n * own methods \\u2014 these become the typed remote main visible to the SDK.\n *\n * `__ping__` is reserved for host health probes; do not override it.\n */\nexport abstract class SandboxSidecar extends RpcTarget {\n /**\n * Health probe \\u2014 the container's `ExtensionHost.health()` calls this and\n * expects the literal string `'pong'`. The host bounds the call with its\n * own timeout so a misbehaving sidecar cannot hang the probe.\n */\n __ping__(): 'pong' {\n return 'pong';\n }\n}\n\n/**\n * Options for {@link serveSandboxSidecar}. Mostly defaults; the socket path\n * is always taken from `process.env.EXT_SOCKET` (set by the container's\n * `ExtensionHost`) so authors do not have to plumb it themselves.\n */\nexport interface ServeSidecarOptions {\n /**\n * Override the diagnostic log line printed to stdout after the sidecar opens\n * its socket. Readiness is determined by the host's successful capnweb\n * connection, not by this line. Defaults to `sandbox sidecar listening`.\n */\n readyMessage?: string;\n}\n\n/**\n * Open the sidecar's unix socket and serve capnweb sessions backed by\n * `target`. Each inbound connection gets its own `RpcSession`; the `target`\n * instance is shared across all sessions, so authors can keep per-sidecar\n * state on it without sync.\n *\n * Idiomatic usage:\n *\n * ```ts\n * import {\n * SandboxSidecar,\n * serveSandboxSidecar\n * } from '@cloudflare/sandbox/sidecar';\n *\n * class MyApi extends SandboxSidecar {\n * async isOdd(n: number): Promise<boolean> {\n * return n % 2 === 1;\n * }\n * }\n *\n * serveSandboxSidecar(new MyApi());\n * ```\n */\nexport function serveSandboxSidecar(\n target: SandboxSidecar,\n options: ServeSidecarOptions = {}\n): net.Server {\n const socketPath = process.env.EXT_SOCKET;\n if (!socketPath) {\n throw new Error(\n 'EXT_SOCKET is not set \\u2014 sidecars are spawned by the container ExtensionHost, which injects this env var'\n );\n }\n\n const server = net.createServer((socket) => {\n // One capnweb session per connection. The shared `target` is exposed as\n // each session's local main; capnweb hands a typed stub of it to the\n // host on connect via `session.getRemoteMain()`.\n new RpcSession(new SocketTransport(socket), target);\n });\n\n server.once('error', (error) => {\n process.stderr.write(\n `sandbox sidecar failed to listen on ${socketPath}: ${error.message}\\n`\n );\n process.exit(1);\n });\n\n server.listen(socketPath, () => {\n const message = options.readyMessage ?? 'sandbox sidecar listening';\n process.stdout.write(`${message}\\n`);\n });\n\n // Stay alive on terminal signals so the host's own SIGTERM \\u2192 SIGKILL\n // sequence is what tears us down (not a default Node handler).\n const onSignal = () => {\n server.close(() => process.exit(0));\n };\n process.once('SIGINT', onSignal);\n process.once('SIGTERM', onSignal);\n\n return server;\n}\n"],"mappings":"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"}
|
package/dist/xterm/index.d.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { ITerminalAddon, Terminal } from "@xterm/xterm";
|
|
2
|
-
|
|
3
|
-
//#region src/xterm/types.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Connection state for the SandboxAddon.
|
|
6
|
-
*/
|
|
7
|
-
type ConnectionState = 'disconnected' | 'connecting' | 'connected';
|
|
8
|
-
/**
|
|
9
|
-
* Connection target for the SandboxAddon.
|
|
10
|
-
*/
|
|
11
|
-
interface ConnectionTarget {
|
|
12
|
-
sandboxId: string;
|
|
13
|
-
terminalId?: string;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Options for creating a SandboxAddon.
|
|
17
|
-
*/
|
|
18
|
-
interface SandboxAddonOptions {
|
|
19
|
-
/**
|
|
20
|
-
* Build WebSocket URL for connection.
|
|
21
|
-
* Called on every connection attempt.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* getWebSocketUrl: ({ origin, terminalId }) =>
|
|
26
|
-
* `${origin}/terminal/${terminalId ?? ''}`
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
getWebSocketUrl: (params: {
|
|
30
|
-
sandboxId: string;
|
|
31
|
-
terminalId?: string;
|
|
32
|
-
cursor?: string;
|
|
33
|
-
/** WebSocket origin derived from `window.location` (e.g. `wss://example.com`). */
|
|
34
|
-
origin: string;
|
|
35
|
-
}) => string;
|
|
36
|
-
/**
|
|
37
|
-
* Whether to automatically reconnect on disconnection.
|
|
38
|
-
* Uses exponential backoff with jitter.
|
|
39
|
-
* @default true
|
|
40
|
-
*/
|
|
41
|
-
reconnect?: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Called when connection state changes.
|
|
44
|
-
* Use this to update your UI (spinners, overlays, etc).
|
|
45
|
-
*
|
|
46
|
-
* @param state - The new connection state
|
|
47
|
-
* @param error - Error details if state change was due to an error
|
|
48
|
-
*/
|
|
49
|
-
onStateChange?: (state: ConnectionState, error?: Error) => void;
|
|
50
|
-
}
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/xterm/addon.d.ts
|
|
53
|
-
declare class SandboxAddon implements ITerminalAddon {
|
|
54
|
-
private options;
|
|
55
|
-
private terminal;
|
|
56
|
-
private socket;
|
|
57
|
-
private disposables;
|
|
58
|
-
private reconnectAttempts;
|
|
59
|
-
private reconnectTimer;
|
|
60
|
-
private restoredOutput;
|
|
61
|
-
private intentionalDisconnect;
|
|
62
|
-
private textEncoder;
|
|
63
|
-
private pendingChunk;
|
|
64
|
-
private cursor;
|
|
65
|
-
private _state;
|
|
66
|
-
private _sandboxId;
|
|
67
|
-
private _terminalId;
|
|
68
|
-
get state(): ConnectionState;
|
|
69
|
-
get sandboxId(): string | undefined;
|
|
70
|
-
get terminalId(): string | undefined;
|
|
71
|
-
constructor(options: SandboxAddonOptions);
|
|
72
|
-
activate(terminal: Terminal): void;
|
|
73
|
-
dispose(): void;
|
|
74
|
-
connect(target: ConnectionTarget): void;
|
|
75
|
-
disconnect(): void;
|
|
76
|
-
private setState;
|
|
77
|
-
private doConnect;
|
|
78
|
-
private registerSocketListener;
|
|
79
|
-
private onSocketOpen;
|
|
80
|
-
private onSocketMessage;
|
|
81
|
-
private consumeBinary;
|
|
82
|
-
private handleControlMessage;
|
|
83
|
-
private onSocketClose;
|
|
84
|
-
private onSocketError;
|
|
85
|
-
private scheduleReconnect;
|
|
86
|
-
private cancelReconnect;
|
|
87
|
-
private closeSocket;
|
|
88
|
-
private sendData;
|
|
89
|
-
private sendResize;
|
|
90
|
-
}
|
|
91
|
-
//#endregion
|
|
92
|
-
export { type ConnectionState, type ConnectionTarget, SandboxAddon, type SandboxAddonOptions };
|
|
93
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/xterm/types.ts","../../src/xterm/addon.ts"],"sourcesContent":[],"mappings":";;;;;;AAGY,KAAA,eAAA,GAAe,cAAA,GAAA,YAAA,GAAA,WAAA;AAK3B;AAQA;;UARiB,gBAAA;;ECKJ,UAAA,CAAA,EAAA,MAAa;;;;;AAAW,UDGpB,mBAAA,CCHoB;EAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BDoCzB,yBAAyB;;;;cCpCtC,YAAA,YAAwB;EDVzB,QAAA,OAAA;EAKK,QAAA,QAAA;EAQA,QAAA,MAAA;;;;ECHJ,QAAA,cAAa;EAgBX,QAAA,qBAAA;EAUgB,QAAA,WAAA;EAEV,QAAA,YAAA;EAWH,QAAA,MAAA;EAvCmB,QAAA,MAAA;EAAc,QAAA,UAAA;;eAgBpC;;;uBAUgB;qBAEV;;kBAWH"}
|
package/dist/xterm/index.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
//#region src/xterm/addon.ts
|
|
2
|
-
const DEFAULT_RECONNECT_DELAY = 1e3;
|
|
3
|
-
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
4
|
-
const MAX_BACKOFF_EXPONENT = 5;
|
|
5
|
-
const JITTER_FACTOR = .1;
|
|
6
|
-
var SandboxAddon = class {
|
|
7
|
-
terminal = null;
|
|
8
|
-
socket = null;
|
|
9
|
-
disposables = [];
|
|
10
|
-
reconnectAttempts = 0;
|
|
11
|
-
reconnectTimer = null;
|
|
12
|
-
restoredOutput = false;
|
|
13
|
-
intentionalDisconnect = false;
|
|
14
|
-
textEncoder = new TextEncoder();
|
|
15
|
-
pendingChunk = null;
|
|
16
|
-
cursor;
|
|
17
|
-
_state = "disconnected";
|
|
18
|
-
_sandboxId;
|
|
19
|
-
_terminalId;
|
|
20
|
-
get state() {
|
|
21
|
-
return this._state;
|
|
22
|
-
}
|
|
23
|
-
get sandboxId() {
|
|
24
|
-
return this._sandboxId;
|
|
25
|
-
}
|
|
26
|
-
get terminalId() {
|
|
27
|
-
return this._terminalId;
|
|
28
|
-
}
|
|
29
|
-
constructor(options) {
|
|
30
|
-
this.options = options;
|
|
31
|
-
}
|
|
32
|
-
activate(terminal) {
|
|
33
|
-
this.terminal = terminal;
|
|
34
|
-
}
|
|
35
|
-
dispose() {
|
|
36
|
-
this.intentionalDisconnect = true;
|
|
37
|
-
this.cancelReconnect();
|
|
38
|
-
this.closeSocket();
|
|
39
|
-
this.terminal = null;
|
|
40
|
-
}
|
|
41
|
-
connect(target) {
|
|
42
|
-
if (!this.terminal) return;
|
|
43
|
-
if (target.sandboxId === this._sandboxId && target.terminalId === this._terminalId && this._state !== "disconnected") return;
|
|
44
|
-
this._sandboxId = target.sandboxId;
|
|
45
|
-
this._terminalId = target.terminalId;
|
|
46
|
-
this.cancelReconnect();
|
|
47
|
-
this.closeSocket();
|
|
48
|
-
this.reconnectAttempts = 0;
|
|
49
|
-
this.cursor = void 0;
|
|
50
|
-
this.restoredOutput = false;
|
|
51
|
-
this.pendingChunk = null;
|
|
52
|
-
this.intentionalDisconnect = false;
|
|
53
|
-
if (this._state !== "disconnected") this.terminal.clear();
|
|
54
|
-
this.doConnect();
|
|
55
|
-
}
|
|
56
|
-
disconnect() {
|
|
57
|
-
this.intentionalDisconnect = true;
|
|
58
|
-
this.cancelReconnect();
|
|
59
|
-
this.closeSocket();
|
|
60
|
-
this.setState("disconnected");
|
|
61
|
-
}
|
|
62
|
-
setState(state, error) {
|
|
63
|
-
if (this._state === state && !error) return;
|
|
64
|
-
this._state = state;
|
|
65
|
-
this.options.onStateChange?.(state, error);
|
|
66
|
-
}
|
|
67
|
-
doConnect() {
|
|
68
|
-
if (!this.terminal || !this._sandboxId) return;
|
|
69
|
-
this.setState("connecting");
|
|
70
|
-
const origin = `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}`;
|
|
71
|
-
const url = this.options.getWebSocketUrl({
|
|
72
|
-
sandboxId: this._sandboxId,
|
|
73
|
-
terminalId: this._terminalId,
|
|
74
|
-
cursor: this.cursor,
|
|
75
|
-
origin
|
|
76
|
-
});
|
|
77
|
-
const socket = new WebSocket(url);
|
|
78
|
-
socket.binaryType = "arraybuffer";
|
|
79
|
-
this.socket = socket;
|
|
80
|
-
this.registerSocketListener(socket, "open", this.onSocketOpen.bind(this));
|
|
81
|
-
this.registerSocketListener(socket, "message", (e) => this.onSocketMessage(e));
|
|
82
|
-
this.registerSocketListener(socket, "close", this.onSocketClose.bind(this));
|
|
83
|
-
this.registerSocketListener(socket, "error", this.onSocketError.bind(this));
|
|
84
|
-
}
|
|
85
|
-
registerSocketListener(socket, type, listener) {
|
|
86
|
-
socket.addEventListener(type, listener);
|
|
87
|
-
this.disposables.push({ dispose: () => socket.removeEventListener(type, listener) });
|
|
88
|
-
}
|
|
89
|
-
onSocketOpen() {
|
|
90
|
-
if (!this.terminal) return;
|
|
91
|
-
this.disposables.push(this.terminal.onData((data) => this.sendData(data)));
|
|
92
|
-
this.disposables.push(this.terminal.onResize(({ cols, rows }) => this.sendResize(cols, rows)));
|
|
93
|
-
}
|
|
94
|
-
onSocketMessage(event) {
|
|
95
|
-
if (!this.terminal) return;
|
|
96
|
-
const { data } = event;
|
|
97
|
-
if (data instanceof ArrayBuffer) {
|
|
98
|
-
this.consumeBinary(new Uint8Array(data));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (typeof data === "string") try {
|
|
102
|
-
this.handleControlMessage(JSON.parse(data));
|
|
103
|
-
} catch {
|
|
104
|
-
this.setState(this._state, /* @__PURE__ */ new Error("Invalid terminal control frame"));
|
|
105
|
-
this.closeSocket();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
consumeBinary(data) {
|
|
109
|
-
if (!this.pendingChunk) {
|
|
110
|
-
this.setState(this._state, /* @__PURE__ */ new Error("Unexpected terminal data frame"));
|
|
111
|
-
this.closeSocket();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (data.byteLength !== this.pendingChunk.byteLength) {
|
|
115
|
-
this.setState(this._state, /* @__PURE__ */ new Error("Terminal data frame length mismatch"));
|
|
116
|
-
this.closeSocket();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
this.restoredOutput = true;
|
|
120
|
-
this.terminal?.write(data);
|
|
121
|
-
this.cursor = this.pendingChunk.cursor;
|
|
122
|
-
this.pendingChunk = null;
|
|
123
|
-
}
|
|
124
|
-
handleControlMessage(msg) {
|
|
125
|
-
switch (msg.type) {
|
|
126
|
-
case "ready":
|
|
127
|
-
if (msg.cursor) this.cursor = msg.cursor;
|
|
128
|
-
if (!this.restoredOutput) this.terminal?.clear();
|
|
129
|
-
this.reconnectAttempts = 0;
|
|
130
|
-
this.setState("connected");
|
|
131
|
-
this.terminal?.focus();
|
|
132
|
-
if (this.terminal) this.sendResize(this.terminal.cols, this.terminal.rows);
|
|
133
|
-
break;
|
|
134
|
-
case "chunk":
|
|
135
|
-
if (this.pendingChunk) {
|
|
136
|
-
this.setState(this._state, /* @__PURE__ */ new Error("Terminal data frame missing"));
|
|
137
|
-
this.closeSocket();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
this.pendingChunk = {
|
|
141
|
-
cursor: msg.cursor,
|
|
142
|
-
byteLength: msg.byteLength
|
|
143
|
-
};
|
|
144
|
-
break;
|
|
145
|
-
case "truncated":
|
|
146
|
-
this.cursor = msg.cursor;
|
|
147
|
-
this.terminal?.clear();
|
|
148
|
-
this.restoredOutput = false;
|
|
149
|
-
break;
|
|
150
|
-
case "error":
|
|
151
|
-
this.options.onStateChange?.(this._state, new Error(msg.message));
|
|
152
|
-
break;
|
|
153
|
-
case "exit":
|
|
154
|
-
this.cursor = msg.cursor;
|
|
155
|
-
this.intentionalDisconnect = true;
|
|
156
|
-
this.options.onStateChange?.(this._state, /* @__PURE__ */ new Error(`Session exited with code ${msg.exit.code}${msg.exit.signal ? ` (${msg.exit.signal})` : ""}`));
|
|
157
|
-
this.closeSocket();
|
|
158
|
-
this.setState("disconnected");
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
onSocketClose() {
|
|
163
|
-
this.closeSocket();
|
|
164
|
-
if (this.intentionalDisconnect) {
|
|
165
|
-
this.setState("disconnected");
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (!(this.options.reconnect !== false)) {
|
|
169
|
-
this.setState("disconnected");
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
173
|
-
this.setState("disconnected", /* @__PURE__ */ new Error("Max reconnection attempts exceeded"));
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
this.scheduleReconnect();
|
|
177
|
-
}
|
|
178
|
-
onSocketError() {
|
|
179
|
-
this.options.onStateChange?.(this._state, /* @__PURE__ */ new Error("WebSocket error"));
|
|
180
|
-
}
|
|
181
|
-
scheduleReconnect() {
|
|
182
|
-
const delay = DEFAULT_RECONNECT_DELAY * 2 ** Math.min(this.reconnectAttempts, MAX_BACKOFF_EXPONENT);
|
|
183
|
-
const jitter = delay * JITTER_FACTOR * Math.random();
|
|
184
|
-
this.setState("disconnected");
|
|
185
|
-
this.reconnectTimer = setTimeout(() => {
|
|
186
|
-
this.reconnectTimer = null;
|
|
187
|
-
this.reconnectAttempts++;
|
|
188
|
-
this.doConnect();
|
|
189
|
-
}, delay + jitter);
|
|
190
|
-
}
|
|
191
|
-
cancelReconnect() {
|
|
192
|
-
if (this.reconnectTimer) {
|
|
193
|
-
clearTimeout(this.reconnectTimer);
|
|
194
|
-
this.reconnectTimer = null;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
closeSocket() {
|
|
198
|
-
if (this.socket) {
|
|
199
|
-
this.socket.close();
|
|
200
|
-
this.socket = null;
|
|
201
|
-
}
|
|
202
|
-
this.pendingChunk = null;
|
|
203
|
-
for (const d of this.disposables) d.dispose();
|
|
204
|
-
this.disposables = [];
|
|
205
|
-
}
|
|
206
|
-
sendData(data) {
|
|
207
|
-
if (this.socket?.readyState === WebSocket.OPEN) this.socket.send(this.textEncoder.encode(data));
|
|
208
|
-
}
|
|
209
|
-
sendResize(cols, rows) {
|
|
210
|
-
if (this.socket?.readyState === WebSocket.OPEN) this.socket.send(JSON.stringify({
|
|
211
|
-
type: "resize",
|
|
212
|
-
cols,
|
|
213
|
-
rows
|
|
214
|
-
}));
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
//#endregion
|
|
219
|
-
export { SandboxAddon };
|
|
220
|
-
//# sourceMappingURL=index.js.map
|
package/dist/xterm/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["options: SandboxAddonOptions"],"sources":["../../src/xterm/addon.ts"],"sourcesContent":["import type { PtyServerControlMessage } from '@repo/shared';\nimport type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm';\nimport type {\n ConnectionState,\n ConnectionTarget,\n SandboxAddonOptions\n} from './types';\n\nconst DEFAULT_RECONNECT_DELAY = 1000;\nconst MAX_RECONNECT_ATTEMPTS = 10;\nconst MAX_BACKOFF_EXPONENT = 5;\nconst JITTER_FACTOR = 0.1;\n\nexport class SandboxAddon implements ITerminalAddon {\n private terminal: Terminal | null = null;\n private socket: WebSocket | null = null;\n private disposables: IDisposable[] = [];\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private restoredOutput = false;\n private intentionalDisconnect = false;\n private textEncoder = new TextEncoder();\n private pendingChunk: { cursor: string; byteLength: number } | null = null;\n private cursor: string | undefined;\n\n private _state: ConnectionState = 'disconnected';\n private _sandboxId: string | undefined;\n private _terminalId: string | undefined;\n\n get state(): ConnectionState {\n return this._state;\n }\n get sandboxId(): string | undefined {\n return this._sandboxId;\n }\n get terminalId(): string | undefined {\n return this._terminalId;\n }\n\n constructor(private options: SandboxAddonOptions) {}\n\n activate(terminal: Terminal): void {\n this.terminal = terminal;\n }\n\n dispose(): void {\n this.intentionalDisconnect = true;\n this.cancelReconnect();\n this.closeSocket();\n this.terminal = null;\n }\n\n connect(target: ConnectionTarget): void {\n if (!this.terminal) return;\n\n const isSameTarget =\n target.sandboxId === this._sandboxId &&\n target.terminalId === this._terminalId;\n\n if (isSameTarget && this._state !== 'disconnected') {\n return;\n }\n\n this._sandboxId = target.sandboxId;\n this._terminalId = target.terminalId;\n\n this.cancelReconnect();\n this.closeSocket();\n this.reconnectAttempts = 0;\n this.cursor = undefined;\n this.restoredOutput = false;\n this.pendingChunk = null;\n this.intentionalDisconnect = false;\n\n if (this._state !== 'disconnected') {\n this.terminal.clear();\n }\n\n this.doConnect();\n }\n\n disconnect(): void {\n this.intentionalDisconnect = true;\n this.cancelReconnect();\n this.closeSocket();\n this.setState('disconnected');\n }\n\n private setState(state: ConnectionState, error?: Error): void {\n if (this._state === state && !error) return;\n this._state = state;\n this.options.onStateChange?.(state, error);\n }\n\n private doConnect(): void {\n if (!this.terminal || !this._sandboxId) return;\n\n this.setState('connecting');\n\n const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\n const origin = `${wsProtocol}//${location.host}`;\n\n const url = this.options.getWebSocketUrl({\n sandboxId: this._sandboxId,\n terminalId: this._terminalId,\n cursor: this.cursor,\n origin\n });\n\n const socket = new WebSocket(url);\n socket.binaryType = 'arraybuffer';\n this.socket = socket;\n\n this.registerSocketListener(socket, 'open', this.onSocketOpen.bind(this));\n this.registerSocketListener(socket, 'message', (e) =>\n this.onSocketMessage(e)\n );\n this.registerSocketListener(socket, 'close', this.onSocketClose.bind(this));\n this.registerSocketListener(socket, 'error', this.onSocketError.bind(this));\n }\n\n private registerSocketListener(\n socket: WebSocket,\n type: string,\n listener: (ev: Event) => void\n ): void {\n socket.addEventListener(type, listener);\n this.disposables.push({\n dispose: () => socket.removeEventListener(type, listener)\n });\n }\n\n private onSocketOpen(): void {\n if (!this.terminal) return;\n this.disposables.push(this.terminal.onData((data) => this.sendData(data)));\n this.disposables.push(\n this.terminal.onResize(({ cols, rows }) => this.sendResize(cols, rows))\n );\n }\n\n private onSocketMessage(event: Event): void {\n if (!this.terminal) return;\n const { data } = event as MessageEvent;\n\n if (data instanceof ArrayBuffer) {\n this.consumeBinary(new Uint8Array(data));\n return;\n }\n\n if (typeof data === 'string') {\n try {\n this.handleControlMessage(JSON.parse(data) as PtyServerControlMessage);\n } catch {\n this.setState(this._state, new Error('Invalid terminal control frame'));\n this.closeSocket();\n }\n }\n }\n\n private consumeBinary(data: Uint8Array): void {\n if (!this.pendingChunk) {\n this.setState(this._state, new Error('Unexpected terminal data frame'));\n this.closeSocket();\n return;\n }\n if (data.byteLength !== this.pendingChunk.byteLength) {\n this.setState(\n this._state,\n new Error('Terminal data frame length mismatch')\n );\n this.closeSocket();\n return;\n }\n this.restoredOutput = true;\n this.terminal?.write(data);\n this.cursor = this.pendingChunk.cursor;\n this.pendingChunk = null;\n }\n\n private handleControlMessage(msg: PtyServerControlMessage): void {\n switch (msg.type) {\n case 'ready':\n if (msg.cursor) this.cursor = msg.cursor;\n if (!this.restoredOutput) {\n this.terminal?.clear();\n }\n this.reconnectAttempts = 0;\n this.setState('connected');\n this.terminal?.focus();\n if (this.terminal) {\n this.sendResize(this.terminal.cols, this.terminal.rows);\n }\n break;\n\n case 'chunk':\n if (this.pendingChunk) {\n this.setState(this._state, new Error('Terminal data frame missing'));\n this.closeSocket();\n return;\n }\n this.pendingChunk = { cursor: msg.cursor, byteLength: msg.byteLength };\n break;\n\n case 'truncated':\n this.cursor = msg.cursor;\n this.terminal?.clear();\n this.restoredOutput = false;\n break;\n\n case 'error':\n this.options.onStateChange?.(this._state, new Error(msg.message));\n break;\n\n case 'exit':\n this.cursor = msg.cursor;\n this.intentionalDisconnect = true;\n this.options.onStateChange?.(\n this._state,\n new Error(\n `Session exited with code ${msg.exit.code}${msg.exit.signal ? ` (${msg.exit.signal})` : ''}`\n )\n );\n this.closeSocket();\n this.setState('disconnected');\n break;\n }\n }\n\n private onSocketClose(): void {\n this.closeSocket();\n\n if (this.intentionalDisconnect) {\n this.setState('disconnected');\n return;\n }\n\n const shouldReconnect = this.options.reconnect !== false;\n\n if (!shouldReconnect) {\n this.setState('disconnected');\n return;\n }\n\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n this.setState(\n 'disconnected',\n new Error('Max reconnection attempts exceeded')\n );\n return;\n }\n\n this.scheduleReconnect();\n }\n\n private onSocketError(): void {\n this.options.onStateChange?.(this._state, new Error('WebSocket error'));\n }\n\n private scheduleReconnect(): void {\n const exponent = Math.min(this.reconnectAttempts, MAX_BACKOFF_EXPONENT);\n const delay = DEFAULT_RECONNECT_DELAY * 2 ** exponent;\n const jitter = delay * JITTER_FACTOR * Math.random();\n\n this.setState('disconnected');\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.reconnectAttempts++;\n this.doConnect();\n }, delay + jitter);\n }\n\n private cancelReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n private closeSocket(): void {\n if (this.socket) {\n this.socket.close();\n this.socket = null;\n }\n this.pendingChunk = null;\n for (const d of this.disposables) {\n d.dispose();\n }\n this.disposables = [];\n }\n\n private sendData(data: string): void {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(this.textEncoder.encode(data));\n }\n }\n\n private sendResize(cols: number, rows: number): void {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(JSON.stringify({ type: 'resize', cols, rows }));\n }\n }\n}\n"],"mappings":"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"}
|