promigence 0.1.0-beta.12
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/NOTICE +14 -0
- package/README.md +88 -0
- package/dist/account.js +850 -0
- package/dist/agent.js +374 -0
- package/dist/argv.js +159 -0
- package/dist/bin.js +332 -0
- package/dist/bundle.js +133 -0
- package/dist/capacity.js +69 -0
- package/dist/commands.d.ts +110 -0
- package/dist/commands.js +2675 -0
- package/dist/connectreq.js +125 -0
- package/dist/credfile.js +188 -0
- package/dist/credits.js +197 -0
- package/dist/devcontainer.d.ts +99 -0
- package/dist/devcontainer.js +744 -0
- package/dist/device.js +129 -0
- package/dist/dirsnap.js +378 -0
- package/dist/errfix.js +262 -0
- package/dist/generated.d.ts +35 -0
- package/dist/generated.js +307 -0
- package/dist/github.js +307 -0
- package/dist/gitlab.js +210 -0
- package/dist/guide.d.ts +19 -0
- package/dist/guide.js +366 -0
- package/dist/handlers.js +511 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +7 -0
- package/dist/init.d.ts +40 -0
- package/dist/init.js +352 -0
- package/dist/install.d.ts +23 -0
- package/dist/install.js +127 -0
- package/dist/io.d.ts +37 -0
- package/dist/io.js +318 -0
- package/dist/live.js +469 -0
- package/dist/login.js +1063 -0
- package/dist/mcp.js +889 -0
- package/dist/money.js +5 -0
- package/dist/orgs.js +475 -0
- package/dist/pricing.d.ts +52 -0
- package/dist/pricing.js +181 -0
- package/dist/report.js +132 -0
- package/dist/routes.js +135 -0
- package/dist/runners-github.js +257 -0
- package/dist/runners-gitlab.js +125 -0
- package/dist/runners-spec.js +137 -0
- package/dist/runners.js +1458 -0
- package/dist/sdk/blake3.d.ts +10 -0
- package/dist/sdk/blake3.js +111 -0
- package/dist/sdk/bundle.d.ts +96 -0
- package/dist/sdk/bundle.js +227 -0
- package/dist/sdk/ca-guard.js +166 -0
- package/dist/sdk/caller.d.ts +73 -0
- package/dist/sdk/caller.js +47 -0
- package/dist/sdk/client.d.ts +3015 -0
- package/dist/sdk/client.js +2583 -0
- package/dist/sdk/command-errors.d.ts +41 -0
- package/dist/sdk/command-errors.js +36 -0
- package/dist/sdk/context.d.ts +119 -0
- package/dist/sdk/context.js +588 -0
- package/dist/sdk/core.d.ts +285 -0
- package/dist/sdk/core.js +266 -0
- package/dist/sdk/credstore.d.ts +90 -0
- package/dist/sdk/credstore.js +206 -0
- package/dist/sdk/dispatcher.js +86 -0
- package/dist/sdk/github.d.ts +158 -0
- package/dist/sdk/github.js +78 -0
- package/dist/sdk/gitlab.d.ts +104 -0
- package/dist/sdk/gitlab.js +20 -0
- package/dist/sdk/handles.d.ts +82 -0
- package/dist/sdk/handles.js +135 -0
- package/dist/sdk/hint.js +7 -0
- package/dist/sdk/http2.js +1921 -0
- package/dist/sdk/index.d.ts +34 -0
- package/dist/sdk/index.js +15 -0
- package/dist/sdk/keyed.gen.js +7 -0
- package/dist/sdk/models.gen.d.ts +6162 -0
- package/dist/sdk/models.gen.js +1 -0
- package/dist/sdk/proxy.js +249 -0
- package/dist/sdk/pty.d.ts +171 -0
- package/dist/sdk/pty.js +496 -0
- package/dist/sdk/sandbox.d.ts +537 -0
- package/dist/sdk/sandbox.js +725 -0
- package/dist/sdk/ssh.d.ts +25 -0
- package/dist/sdk/ssh.js +114 -0
- package/dist/sdk/trace.js +80 -0
- package/dist/sdk/transport-worker.js +466 -0
- package/dist/sdk/warm.js +40 -0
- package/dist/ssh.js +267 -0
- package/dist/surface.d.ts +95 -0
- package/dist/surface.js +1452 -0
- package/dist/templates.js +53 -0
- package/dist/term.js +50 -0
- package/dist/timing.js +71 -0
- package/dist/untar.js +324 -0
- package/dist/uploadguard.js +75 -0
- package/llms.txt +209 -0
- package/package.json +52 -0
package/dist/sdk/pty.js
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { CALLER_HEADER } from "./caller.js";
|
|
3
|
+
import { requestVia } from "./proxy.js";
|
|
4
|
+
export const PTY_INPUT_CHUNK = 64 * 1024;
|
|
5
|
+
export const PTY_INPUT_KEEP = 256 * 1024;
|
|
6
|
+
export const PTY_RESUME_HEADER = "promigence-resume";
|
|
7
|
+
const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
8
|
+
export const PTY_COMMAND_HEADER = "promigence-pty-command";
|
|
9
|
+
export const PTY_SECRETS_HEADER = "promigence-pty-secrets";
|
|
10
|
+
export function wsAccept(key) {
|
|
11
|
+
return createHash("sha1").update(key + WS_GUID).digest("base64");
|
|
12
|
+
}
|
|
13
|
+
export function encodeFrame(opcode, payload) {
|
|
14
|
+
const len = payload.length;
|
|
15
|
+
const head = [0x80 | opcode];
|
|
16
|
+
if (len < 126)
|
|
17
|
+
head.push(0x80 | len);
|
|
18
|
+
else if (len < 65536)
|
|
19
|
+
head.push(0x80 | 126, len >> 8, len & 0xff);
|
|
20
|
+
else {
|
|
21
|
+
head.push(0x80 | 127);
|
|
22
|
+
const big = Buffer.alloc(8);
|
|
23
|
+
big.writeBigUInt64BE(BigInt(len));
|
|
24
|
+
head.push(...big);
|
|
25
|
+
}
|
|
26
|
+
const mask = randomBytes(4);
|
|
27
|
+
const out = Buffer.alloc(head.length + 4 + len);
|
|
28
|
+
Buffer.from(head).copy(out, 0);
|
|
29
|
+
mask.copy(out, head.length);
|
|
30
|
+
const at = head.length + 4;
|
|
31
|
+
for (let i = 0; i < len; i++)
|
|
32
|
+
out[at + i] = payload[i] ^ mask[i % 4];
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
export class FrameReader {
|
|
36
|
+
buf = Buffer.alloc(0);
|
|
37
|
+
partial;
|
|
38
|
+
push(chunk) {
|
|
39
|
+
this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : Buffer.from(chunk);
|
|
40
|
+
const out = [];
|
|
41
|
+
for (;;) {
|
|
42
|
+
if (this.buf.length < 2)
|
|
43
|
+
break;
|
|
44
|
+
const b0 = this.buf[0];
|
|
45
|
+
const b1 = this.buf[1];
|
|
46
|
+
let len = b1 & 0x7f;
|
|
47
|
+
let at = 2;
|
|
48
|
+
if (len === 126) {
|
|
49
|
+
if (this.buf.length < 4)
|
|
50
|
+
break;
|
|
51
|
+
len = this.buf.readUInt16BE(2);
|
|
52
|
+
at = 4;
|
|
53
|
+
}
|
|
54
|
+
else if (len === 127) {
|
|
55
|
+
if (this.buf.length < 10)
|
|
56
|
+
break;
|
|
57
|
+
len = Number(this.buf.readBigUInt64BE(2));
|
|
58
|
+
at = 10;
|
|
59
|
+
}
|
|
60
|
+
const masked = (b1 & 0x80) !== 0;
|
|
61
|
+
if (this.buf.length < at + (masked ? 4 : 0) + len)
|
|
62
|
+
break;
|
|
63
|
+
let payload = this.buf.subarray(at + (masked ? 4 : 0), at + (masked ? 4 : 0) + len);
|
|
64
|
+
if (masked) {
|
|
65
|
+
const key = this.buf.subarray(at, at + 4);
|
|
66
|
+
payload = Buffer.from(payload.map((b, i) => b ^ key[i % 4]));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
payload = Buffer.from(payload);
|
|
70
|
+
}
|
|
71
|
+
this.buf = this.buf.subarray(at + (masked ? 4 : 0) + len);
|
|
72
|
+
const fin = (b0 & 0x80) !== 0;
|
|
73
|
+
const opcode = b0 & 0x0f;
|
|
74
|
+
if (opcode >= 0x8) {
|
|
75
|
+
out.push({ opcode, payload });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (opcode !== 0)
|
|
79
|
+
this.partial = { opcode, parts: [payload] };
|
|
80
|
+
else
|
|
81
|
+
this.partial?.parts.push(payload);
|
|
82
|
+
if (fin && this.partial) {
|
|
83
|
+
out.push({ opcode: this.partial.opcode, payload: Buffer.concat(this.partial.parts) });
|
|
84
|
+
this.partial = undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export class PtySession {
|
|
91
|
+
socket;
|
|
92
|
+
pid;
|
|
93
|
+
cols;
|
|
94
|
+
rows;
|
|
95
|
+
closed = false;
|
|
96
|
+
done;
|
|
97
|
+
pending;
|
|
98
|
+
resumeToken;
|
|
99
|
+
inputSent = 0;
|
|
100
|
+
inputBase = 0;
|
|
101
|
+
inputLog = Buffer.alloc(0);
|
|
102
|
+
constructor(socket, open, done) {
|
|
103
|
+
this.socket = socket;
|
|
104
|
+
this.pid = open.pid;
|
|
105
|
+
this.cols = open.cols;
|
|
106
|
+
this.rows = open.rows;
|
|
107
|
+
this.resumeToken = open.resume;
|
|
108
|
+
this.done = done;
|
|
109
|
+
void done.then(() => { this.#ended = true; });
|
|
110
|
+
}
|
|
111
|
+
#ended = false;
|
|
112
|
+
get ended() {
|
|
113
|
+
return this.#ended;
|
|
114
|
+
}
|
|
115
|
+
holdInput() {
|
|
116
|
+
this.pending ??= [];
|
|
117
|
+
}
|
|
118
|
+
rebind(socket, open) {
|
|
119
|
+
this.socket = socket;
|
|
120
|
+
this.resumeToken = open.resume;
|
|
121
|
+
this.inputSent = 0;
|
|
122
|
+
this.inputBase = 0;
|
|
123
|
+
this.inputLog = Buffer.alloc(0);
|
|
124
|
+
this.flush(open);
|
|
125
|
+
}
|
|
126
|
+
resumed(socket, open) {
|
|
127
|
+
this.socket = socket;
|
|
128
|
+
this.resumeToken = open.resume ?? this.resumeToken;
|
|
129
|
+
const took = open.input ?? this.inputSent;
|
|
130
|
+
if (took < this.inputSent) {
|
|
131
|
+
const from = Math.max(took, this.inputBase) - this.inputBase;
|
|
132
|
+
const again = this.inputLog.subarray(from);
|
|
133
|
+
this.inputSent = Math.max(took, this.inputBase);
|
|
134
|
+
for (let at = 0; at < again.length; at += PTY_INPUT_CHUNK) {
|
|
135
|
+
this.writeNow(0x2, again.subarray(at, at + PTY_INPUT_CHUNK));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this.flush(open);
|
|
139
|
+
}
|
|
140
|
+
flush(open) {
|
|
141
|
+
const queued = this.pending ?? [];
|
|
142
|
+
this.pending = undefined;
|
|
143
|
+
if (open.cols !== this.cols || open.rows !== this.rows) {
|
|
144
|
+
this.resize({ cols: this.cols, rows: this.rows });
|
|
145
|
+
}
|
|
146
|
+
for (const f of queued)
|
|
147
|
+
this.writeNow(f.opcode, f.payload);
|
|
148
|
+
}
|
|
149
|
+
writeNow(opcode, payload) {
|
|
150
|
+
if (this.closed || this.socket.destroyed)
|
|
151
|
+
return;
|
|
152
|
+
this.socket.write(encodeFrame(opcode, payload));
|
|
153
|
+
if (opcode === 0x2) {
|
|
154
|
+
this.inputSent += payload.length;
|
|
155
|
+
this.inputLog = Buffer.concat([this.inputLog, payload]);
|
|
156
|
+
if (this.inputLog.length > PTY_INPUT_KEEP) {
|
|
157
|
+
const drop = this.inputLog.length - PTY_INPUT_KEEP;
|
|
158
|
+
this.inputLog = this.inputLog.subarray(drop);
|
|
159
|
+
this.inputBase += drop;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
get hungUp() {
|
|
164
|
+
return this.closed;
|
|
165
|
+
}
|
|
166
|
+
send(opcode, payload) {
|
|
167
|
+
if (this.closed)
|
|
168
|
+
return;
|
|
169
|
+
if (this.pending) {
|
|
170
|
+
if (this.pending.reduce((n, f) => n + f.payload.length, 0) + payload.length <= 1 << 20) {
|
|
171
|
+
this.pending.push({ opcode, payload: Buffer.from(payload) });
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.writeNow(opcode, payload);
|
|
176
|
+
}
|
|
177
|
+
write(data) {
|
|
178
|
+
const bytes = typeof data === "string" ? Buffer.from(data, "utf8") : data;
|
|
179
|
+
for (let at = 0; at < bytes.length || at === 0; at += PTY_INPUT_CHUNK) {
|
|
180
|
+
this.send(0x2, bytes.subarray(at, at + PTY_INPUT_CHUNK));
|
|
181
|
+
if (bytes.length === 0)
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
resize(size) {
|
|
186
|
+
this.cols = size.cols;
|
|
187
|
+
this.rows = size.rows;
|
|
188
|
+
this.send(0x1, Buffer.from(JSON.stringify({ resize: { cols: size.cols, rows: size.rows } })));
|
|
189
|
+
}
|
|
190
|
+
signal(sig) {
|
|
191
|
+
this.send(0x1, Buffer.from(JSON.stringify({ signal: sig })));
|
|
192
|
+
}
|
|
193
|
+
eof() {
|
|
194
|
+
this.send(0x1, Buffer.from(JSON.stringify({ eof: true })));
|
|
195
|
+
}
|
|
196
|
+
wait() {
|
|
197
|
+
return this.done;
|
|
198
|
+
}
|
|
199
|
+
disconnect() {
|
|
200
|
+
if (this.closed)
|
|
201
|
+
return;
|
|
202
|
+
this.pending = undefined;
|
|
203
|
+
this.send(0x8, Buffer.from([0x03, 0xe8]));
|
|
204
|
+
this.closed = true;
|
|
205
|
+
this.socket.end();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
export const PTY_RECONNECT_CODE = 1012;
|
|
209
|
+
export const PTY_RECONNECT_FOR_MS = 30_000;
|
|
210
|
+
export const PTY_RECONNECT_FIRST_WAIT_MS = 250;
|
|
211
|
+
export const PTY_RECONNECT_MAX_WAIT_MS = 4_000;
|
|
212
|
+
export const PTY_RESTART_EXIT = {
|
|
213
|
+
exitCode: null,
|
|
214
|
+
signal: null,
|
|
215
|
+
error: {
|
|
216
|
+
code: "service_restart",
|
|
217
|
+
message: "the service restarted and the terminal could not reconnect within 30 s; open it again",
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
export const PTY_RESTART_CLOSED_EXIT = {
|
|
221
|
+
exitCode: null,
|
|
222
|
+
signal: null,
|
|
223
|
+
error: { code: "service_restart", message: "the service restarted and closed the terminal; open it again" },
|
|
224
|
+
};
|
|
225
|
+
const CLOSED_EXIT = {
|
|
226
|
+
exitCode: null,
|
|
227
|
+
signal: null,
|
|
228
|
+
error: { code: "closed", message: "the terminal connection closed without an exit record" },
|
|
229
|
+
};
|
|
230
|
+
class LegRefused {
|
|
231
|
+
status;
|
|
232
|
+
error;
|
|
233
|
+
restart;
|
|
234
|
+
constructor(status, error, restart = false) {
|
|
235
|
+
this.status = status;
|
|
236
|
+
this.error = error;
|
|
237
|
+
this.restart = restart;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function openLeg(opts, url, resume) {
|
|
241
|
+
const key = randomBytes(16).toString("base64");
|
|
242
|
+
const req = requestVia(url, {
|
|
243
|
+
method: "GET",
|
|
244
|
+
agent: false,
|
|
245
|
+
headers: {
|
|
246
|
+
connection: "Upgrade",
|
|
247
|
+
upgrade: "websocket",
|
|
248
|
+
"sec-websocket-version": "13",
|
|
249
|
+
"sec-websocket-key": key,
|
|
250
|
+
authorization: `Bearer ${opts.apiKey}`,
|
|
251
|
+
"user-agent": opts.userAgent,
|
|
252
|
+
...(opts.caller ? { [CALLER_HEADER]: opts.caller } : {}),
|
|
253
|
+
...(resume ? { [PTY_RESUME_HEADER]: resume } : {}),
|
|
254
|
+
...(opts.command && !resume
|
|
255
|
+
? { [PTY_COMMAND_HEADER]: Buffer.from(opts.command, "utf8").toString("base64") }
|
|
256
|
+
: {}),
|
|
257
|
+
...(opts.secrets?.length && !resume
|
|
258
|
+
? {
|
|
259
|
+
[PTY_SECRETS_HEADER]: Buffer.from(JSON.stringify(opts.secrets), "utf8").toString("base64"),
|
|
260
|
+
}
|
|
261
|
+
: {}),
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
let resolveEnded;
|
|
265
|
+
const ended = new Promise((r) => { resolveEnded = r; });
|
|
266
|
+
const opened = new Promise((resolve, reject) => {
|
|
267
|
+
let settled = false;
|
|
268
|
+
const fail = (status, body, retryAfter, restart = false) => {
|
|
269
|
+
if (settled)
|
|
270
|
+
return;
|
|
271
|
+
settled = true;
|
|
272
|
+
reject(new LegRefused(status, opts.refuse(status, body, retryAfter), restart));
|
|
273
|
+
};
|
|
274
|
+
const timer = setTimeout(() => {
|
|
275
|
+
req.destroy();
|
|
276
|
+
fail(504, JSON.stringify({ error: { code: "timeout", message: `terminal handshake to ${url.host} took over ${opts.timeoutMs ?? 30_000} ms`, fix: "Retry; if it repeats, check the sandbox is ready (`promigence run report <run_id>`)." } }));
|
|
277
|
+
}, opts.timeoutMs ?? 30_000);
|
|
278
|
+
req.on("error", (e) => { clearTimeout(timer); fail(0, JSON.stringify({ error: { code: "unreachable", message: `cannot reach ${url.host}: ${e.code ?? e.message}` } })); });
|
|
279
|
+
req.on("response", (res) => {
|
|
280
|
+
clearTimeout(timer);
|
|
281
|
+
const parts = [];
|
|
282
|
+
res.on("data", (d) => parts.push(d));
|
|
283
|
+
const retryAfter = res.headers["retry-after"];
|
|
284
|
+
res.on("end", () => fail(res.statusCode ?? 0, Buffer.concat(parts).toString("utf8"), typeof retryAfter === "string" ? retryAfter : undefined));
|
|
285
|
+
});
|
|
286
|
+
req.on("upgrade", (res, socket, head) => {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
if (res.headers["sec-websocket-accept"] !== wsAccept(key)) {
|
|
289
|
+
socket.destroy();
|
|
290
|
+
fail(502, JSON.stringify({ error: { code: "internal", message: "the terminal server answered the WebSocket handshake with the wrong Sec-WebSocket-Accept" } }));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
socket.setNoDelay(true);
|
|
294
|
+
const reader = new FrameReader();
|
|
295
|
+
let exit;
|
|
296
|
+
let closeCode;
|
|
297
|
+
let wasOpen = false;
|
|
298
|
+
const onFrames = (chunk) => {
|
|
299
|
+
for (const f of reader.push(chunk)) {
|
|
300
|
+
if (f.opcode === 0x2) {
|
|
301
|
+
opts.onData?.(new Uint8Array(f.payload));
|
|
302
|
+
}
|
|
303
|
+
else if (f.opcode === 0x1) {
|
|
304
|
+
let v = {};
|
|
305
|
+
try {
|
|
306
|
+
v = JSON.parse(f.payload.toString("utf8"));
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (v["event"] === "open" && !wasOpen) {
|
|
312
|
+
wasOpen = true;
|
|
313
|
+
settled = true;
|
|
314
|
+
resolve({
|
|
315
|
+
socket,
|
|
316
|
+
pid: typeof v["req_id"] === "number" ? v["req_id"] : 0,
|
|
317
|
+
cols: Number(v["cols"] ?? 80),
|
|
318
|
+
rows: Number(v["rows"] ?? 24),
|
|
319
|
+
...(typeof v["resume"] === "string" ? { resume: v["resume"] } : {}),
|
|
320
|
+
...(v["reattached"] === true ? { reattached: true } : {}),
|
|
321
|
+
...(typeof v["input"] === "number" ? { input: v["input"] } : {}),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
else if (v["event"] === "exit") {
|
|
325
|
+
exit = {
|
|
326
|
+
exitCode: typeof v["exit_code"] === "number" ? v["exit_code"] : null,
|
|
327
|
+
signal: typeof v["signal"] === "number" ? v["signal"] : null,
|
|
328
|
+
...(typeof v["wall_ms"] === "number" ? { wallMs: v["wall_ms"] } : {}),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
else if (v["event"] === "error" || v["event"] === "closed") {
|
|
332
|
+
exit = {
|
|
333
|
+
exitCode: null, signal: null,
|
|
334
|
+
error: { code: String(v["code"] ?? v["event"]), message: String(v["msg"] ?? v["message"] ?? "the terminal ended") },
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else if (f.opcode === 0x9) {
|
|
339
|
+
socket.write(encodeFrame(0xa, f.payload));
|
|
340
|
+
}
|
|
341
|
+
else if (f.opcode === 0x8) {
|
|
342
|
+
if (closeCode === undefined)
|
|
343
|
+
closeCode = f.payload.length >= 2 ? f.payload.readUInt16BE(0) : 1005;
|
|
344
|
+
if (!socket.destroyed)
|
|
345
|
+
socket.end(encodeFrame(0x8, f.payload.subarray(0, 2)));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
socket.on("data", onFrames);
|
|
350
|
+
socket.on("close", () => {
|
|
351
|
+
if (!wasOpen) {
|
|
352
|
+
const restart = closeCode === PTY_RECONNECT_CODE && exit === undefined;
|
|
353
|
+
const e = restart ? PTY_RESTART_CLOSED_EXIT : (exit ?? CLOSED_EXIT);
|
|
354
|
+
fail(restart ? 503 : 502, JSON.stringify({ error: { code: e.error?.code ?? "internal", message: e.error?.message ?? "the terminal ended before it opened" } }), undefined, restart);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
resolveEnded({ code: closeCode, exit });
|
|
358
|
+
});
|
|
359
|
+
socket.on("error", () => { });
|
|
360
|
+
if (head.length)
|
|
361
|
+
onFrames(head);
|
|
362
|
+
});
|
|
363
|
+
req.end();
|
|
364
|
+
});
|
|
365
|
+
return { opened, ended };
|
|
366
|
+
}
|
|
367
|
+
function sizedUrl(url, size) {
|
|
368
|
+
const u = new URL(url);
|
|
369
|
+
u.searchParams.set("cols", String(Math.floor(size.cols)));
|
|
370
|
+
u.searchParams.set("rows", String(Math.floor(size.rows)));
|
|
371
|
+
return u;
|
|
372
|
+
}
|
|
373
|
+
function resumeUrl(url, size, received) {
|
|
374
|
+
const u = sizedUrl(url, size);
|
|
375
|
+
u.searchParams.set("received", String(received));
|
|
376
|
+
return u;
|
|
377
|
+
}
|
|
378
|
+
function worthRetrying(status) {
|
|
379
|
+
return status === 0 || status === 429 || status >= 500;
|
|
380
|
+
}
|
|
381
|
+
export function connectPty(opts) {
|
|
382
|
+
const reconnect = opts.reconnect ?? opts.command === undefined;
|
|
383
|
+
const resumeOn = opts.resume ?? true;
|
|
384
|
+
let received = 0;
|
|
385
|
+
const legOpts = {
|
|
386
|
+
...opts,
|
|
387
|
+
onData: (d) => {
|
|
388
|
+
received += d.length;
|
|
389
|
+
opts.onData?.(d);
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
let resolveDone;
|
|
393
|
+
const done = new Promise((r) => { resolveDone = r; });
|
|
394
|
+
let finished = false;
|
|
395
|
+
const finish = (e) => {
|
|
396
|
+
if (finished)
|
|
397
|
+
return;
|
|
398
|
+
finished = true;
|
|
399
|
+
opts.onExit?.(e);
|
|
400
|
+
resolveDone(e);
|
|
401
|
+
};
|
|
402
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
403
|
+
const follow = (session, leg) => {
|
|
404
|
+
void leg.ended.then(async ({ code, exit }) => {
|
|
405
|
+
if (exit !== undefined || session.hungUp) {
|
|
406
|
+
finish(exit ?? CLOSED_EXIT);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const restart = code === PTY_RECONNECT_CODE;
|
|
410
|
+
const dropped = code === undefined || code === 1006;
|
|
411
|
+
let resumable = resumeOn && session.resumeToken !== undefined && (restart || dropped);
|
|
412
|
+
if (!restart && !resumable) {
|
|
413
|
+
finish(CLOSED_EXIT);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!resumable && !reconnect) {
|
|
417
|
+
finish(PTY_RESTART_CLOSED_EXIT);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
session.holdInput();
|
|
421
|
+
const t0 = Date.now();
|
|
422
|
+
for (let attempt = 0;; attempt++) {
|
|
423
|
+
const wait = Math.min(PTY_RECONNECT_FIRST_WAIT_MS * 2 ** attempt, PTY_RECONNECT_MAX_WAIT_MS) * (0.5 + Math.random() / 2);
|
|
424
|
+
if (Date.now() - t0 + wait > (opts.reconnectForMs ?? PTY_RECONNECT_FOR_MS))
|
|
425
|
+
break;
|
|
426
|
+
await sleep(wait);
|
|
427
|
+
if (session.hungUp) {
|
|
428
|
+
finish(CLOSED_EXIT);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const next = resumable
|
|
432
|
+
? openLeg(legOpts, resumeUrl(opts.url, session, received), session.resumeToken)
|
|
433
|
+
: openLeg(legOpts, sizedUrl(opts.url, session));
|
|
434
|
+
try {
|
|
435
|
+
const o = await next.opened;
|
|
436
|
+
if (session.hungUp) {
|
|
437
|
+
o.socket.destroy();
|
|
438
|
+
finish(CLOSED_EXIT);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (resumable) {
|
|
442
|
+
session.resumed(o.socket, o);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
received = 0;
|
|
446
|
+
session.rebind(o.socket, o);
|
|
447
|
+
}
|
|
448
|
+
follow(session, next);
|
|
449
|
+
opts.onReconnected?.({ resumed: resumable });
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
const refused = e instanceof LegRefused ? e : undefined;
|
|
454
|
+
if (resumable && refused?.status === 404) {
|
|
455
|
+
resumable = false;
|
|
456
|
+
if (!restart) {
|
|
457
|
+
finish(CLOSED_EXIT);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (!reconnect) {
|
|
461
|
+
finish(PTY_RESTART_CLOSED_EXIT);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (refused && !worthRetrying(refused.status)) {
|
|
467
|
+
const err = refused.error;
|
|
468
|
+
finish({ exitCode: null, signal: null, error: { code: typeof err.code === "string" ? err.code : "internal", message: err.message } });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
finish(restart ? PTY_RESTART_EXIT : CLOSED_EXIT);
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
return (async () => {
|
|
477
|
+
const t0 = Date.now();
|
|
478
|
+
for (let attempt = 0;; attempt++) {
|
|
479
|
+
const first = openLeg(legOpts, new URL(opts.url));
|
|
480
|
+
try {
|
|
481
|
+
const o = await first.opened;
|
|
482
|
+
const session = new PtySession(o.socket, o, done);
|
|
483
|
+
follow(session, first);
|
|
484
|
+
return session;
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
const wait = Math.min(PTY_RECONNECT_FIRST_WAIT_MS * 2 ** attempt, PTY_RECONNECT_MAX_WAIT_MS) * (0.5 + Math.random() / 2);
|
|
488
|
+
if (e instanceof LegRefused && e.restart && reconnect && Date.now() - t0 + wait <= (opts.reconnectForMs ?? PTY_RECONNECT_FOR_MS)) {
|
|
489
|
+
await sleep(wait);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
throw e instanceof LegRefused ? e.error : e;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
})();
|
|
496
|
+
}
|