@promigence/sdk 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 +54 -0
- package/dist/blake3.d.ts +10 -0
- package/dist/blake3.js +111 -0
- package/dist/bundle.d.ts +96 -0
- package/dist/bundle.js +227 -0
- package/dist/ca-guard.d.ts +1 -0
- package/dist/ca-guard.js +166 -0
- package/dist/caller.d.ts +73 -0
- package/dist/caller.js +47 -0
- package/dist/cjs/blake3.d.ts +10 -0
- package/dist/cjs/blake3.js +116 -0
- package/dist/cjs/bundle.d.ts +96 -0
- package/dist/cjs/bundle.js +242 -0
- package/dist/cjs/ca-guard.d.ts +1 -0
- package/dist/cjs/ca-guard.js +174 -0
- package/dist/cjs/caller.d.ts +73 -0
- package/dist/cjs/caller.js +57 -0
- package/dist/cjs/client.d.ts +3015 -0
- package/dist/cjs/client.js +2617 -0
- package/dist/cjs/command-errors.d.ts +41 -0
- package/dist/cjs/command-errors.js +43 -0
- package/dist/cjs/context.d.ts +119 -0
- package/dist/cjs/context.js +598 -0
- package/dist/cjs/core.d.ts +285 -0
- package/dist/cjs/core.js +290 -0
- package/dist/cjs/credstore.d.ts +90 -0
- package/dist/cjs/credstore.js +219 -0
- package/dist/cjs/dispatcher.d.ts +58 -0
- package/dist/cjs/dispatcher.js +91 -0
- package/dist/cjs/github.d.ts +158 -0
- package/dist/cjs/github.js +84 -0
- package/dist/cjs/gitlab.d.ts +104 -0
- package/dist/cjs/gitlab.js +24 -0
- package/dist/cjs/handles.d.ts +82 -0
- package/dist/cjs/handles.js +139 -0
- package/dist/cjs/hint.d.ts +1 -0
- package/dist/cjs/hint.js +10 -0
- package/dist/cjs/http2.d.ts +206 -0
- package/dist/cjs/http2.js +1932 -0
- package/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/keyed.gen.d.ts +1 -0
- package/dist/cjs/keyed.gen.js +10 -0
- package/dist/cjs/models.gen.d.ts +6162 -0
- package/dist/cjs/models.gen.js +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/proxy.d.ts +40 -0
- package/dist/cjs/proxy.js +257 -0
- package/dist/cjs/pty.d.ts +171 -0
- package/dist/cjs/pty.js +504 -0
- package/dist/cjs/sandbox.d.ts +537 -0
- package/dist/cjs/sandbox.js +736 -0
- package/dist/cjs/ssh.d.ts +25 -0
- package/dist/cjs/ssh.js +117 -0
- package/dist/cjs/trace.d.ts +33 -0
- package/dist/cjs/trace.js +88 -0
- package/dist/cjs/transport-worker.d.ts +1 -0
- package/dist/cjs/transport-worker.js +468 -0
- package/dist/cjs/warm.d.ts +1 -0
- package/dist/cjs/warm.js +43 -0
- package/dist/client.d.ts +3015 -0
- package/dist/client.js +2583 -0
- package/dist/command-errors.d.ts +41 -0
- package/dist/command-errors.js +36 -0
- package/dist/context.d.ts +119 -0
- package/dist/context.js +588 -0
- package/dist/core.d.ts +285 -0
- package/dist/core.js +266 -0
- package/dist/credstore.d.ts +90 -0
- package/dist/credstore.js +206 -0
- package/dist/dispatcher.d.ts +58 -0
- package/dist/dispatcher.js +86 -0
- package/dist/github.d.ts +158 -0
- package/dist/github.js +78 -0
- package/dist/gitlab.d.ts +104 -0
- package/dist/gitlab.js +20 -0
- package/dist/handles.d.ts +82 -0
- package/dist/handles.js +135 -0
- package/dist/hint.d.ts +1 -0
- package/dist/hint.js +7 -0
- package/dist/http2.d.ts +206 -0
- package/dist/http2.js +1921 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +15 -0
- package/dist/keyed.gen.d.ts +1 -0
- package/dist/keyed.gen.js +7 -0
- package/dist/models.gen.d.ts +6162 -0
- package/dist/models.gen.js +1 -0
- package/dist/proxy.d.ts +40 -0
- package/dist/proxy.js +249 -0
- package/dist/pty.d.ts +171 -0
- package/dist/pty.js +496 -0
- package/dist/sandbox.d.ts +537 -0
- package/dist/sandbox.js +725 -0
- package/dist/ssh.d.ts +25 -0
- package/dist/ssh.js +114 -0
- package/dist/trace.d.ts +33 -0
- package/dist/trace.js +80 -0
- package/dist/transport-worker.d.ts +1 -0
- package/dist/transport-worker.js +466 -0
- package/dist/warm.d.ts +1 -0
- package/dist/warm.js +40 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` / `NO_PROXY` for the SDK and the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Node's built-in `fetch` ignores the proxy variables (Node 22 has no `NODE_USE_ENV_PROXY`), so
|
|
5
|
+
* behind a corporate proxy the CLI could not reach the API while the Python SDK, which honours
|
|
6
|
+
* them, could. This is a small `fetch` over `node:http`/`node:https` that goes through the proxy
|
|
7
|
+
* the environment names: an `http://` URL is sent to the proxy in absolute form, an `https://` URL
|
|
8
|
+
* through a `CONNECT` tunnel with TLS to the API inside it (the proxy never sees the key). It is
|
|
9
|
+
* used only when a proxy variable applies to the API's host; otherwise nothing changes.
|
|
10
|
+
*
|
|
11
|
+
* No dependency: the proxy agents live in the `undici` package, which this SDK does not ship.
|
|
12
|
+
*/
|
|
13
|
+
import { type ClientRequest, type RequestOptions } from "node:http";
|
|
14
|
+
type FetchLike = typeof globalThis.fetch;
|
|
15
|
+
/**
|
|
16
|
+
* Every variable `proxyFor` and `http2Allowed` read (a test holds them to this list), each read once
|
|
17
|
+
* from the process environment into a plain object the two then read instead: each is a lookup in
|
|
18
|
+
* the environment, and the two checks read six of them each. NO_PROXY matters only once a proxy
|
|
19
|
+
* variable names a proxy, so it is read only then.
|
|
20
|
+
*/
|
|
21
|
+
export declare const TRANSPORT_ENV: readonly string[];
|
|
22
|
+
/** The variables of `TRANSPORT_ENV`, read now from `env`. */
|
|
23
|
+
export declare function transportEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
24
|
+
/** Does `NO_PROXY` exempt `host` (`*`, an exact name, a `.suffix` or `suffix`, with an optional port)? */
|
|
25
|
+
export declare function noProxy(host: string, port: string, env?: NodeJS.ProcessEnv): boolean;
|
|
26
|
+
/** The proxy URL the environment names for `url`, or `undefined` for a direct connection. */
|
|
27
|
+
export declare function proxyFor(url: string, env?: NodeJS.ProcessEnv): URL | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* A `fetch` that sends every request through `proxy`. Request bodies are buffered (the SDK
|
|
30
|
+
* sends JSON and file chunks of at most 16 MiB); response bodies stream.
|
|
31
|
+
*/
|
|
32
|
+
export declare function proxyFetch(proxy: URL): FetchLike;
|
|
33
|
+
/**
|
|
34
|
+
* `node:http(s).request(url, options)`, through the proxy the environment names for `url` when one
|
|
35
|
+
* applies ([`proxyFor`]): an https URL over a `CONNECT` tunnel with TLS to the server inside it
|
|
36
|
+
* (the proxy never sees what is sent), an http URL in absolute form. For what `fetch` cannot
|
|
37
|
+
* carry: the terminal's and the SSH tunnel's WebSocket upgrades. With no proxy, the plain request.
|
|
38
|
+
*/
|
|
39
|
+
export declare function requestVia(url: URL, options: RequestOptions, env?: NodeJS.ProcessEnv): ClientRequest;
|
|
40
|
+
export {};
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { request as httpsRequest } from "node:https";
|
|
3
|
+
import { connect as tlsConnect } from "node:tls";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import { guardCertChain, makeCertChainGuard } from "./ca-guard.js";
|
|
6
|
+
function withCertGuard(existing) {
|
|
7
|
+
return existing === undefined ? guardCertChain : makeCertChainGuard(existing);
|
|
8
|
+
}
|
|
9
|
+
function envOf(env, name) {
|
|
10
|
+
const v = env[name] ?? env[name.toLowerCase()];
|
|
11
|
+
return v !== undefined && v.trim() !== "" ? v.trim() : undefined;
|
|
12
|
+
}
|
|
13
|
+
export const TRANSPORT_ENV = [
|
|
14
|
+
"HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy", "PROMIGENCE_HTTP2", "NO_PROXY", "no_proxy",
|
|
15
|
+
];
|
|
16
|
+
export function transportEnv(env = process.env) {
|
|
17
|
+
const view = {};
|
|
18
|
+
let proxied = false;
|
|
19
|
+
for (let i = 0; i < 6; i++) {
|
|
20
|
+
const k = TRANSPORT_ENV[i];
|
|
21
|
+
const v = env[k];
|
|
22
|
+
if (v === undefined)
|
|
23
|
+
continue;
|
|
24
|
+
view[k] = v;
|
|
25
|
+
proxied = true;
|
|
26
|
+
}
|
|
27
|
+
const h2 = env["PROMIGENCE_HTTP2"];
|
|
28
|
+
if (h2 !== undefined)
|
|
29
|
+
view["PROMIGENCE_HTTP2"] = h2;
|
|
30
|
+
if (proxied) {
|
|
31
|
+
const a = env["NO_PROXY"];
|
|
32
|
+
const b = env["no_proxy"];
|
|
33
|
+
if (a !== undefined)
|
|
34
|
+
view["NO_PROXY"] = a;
|
|
35
|
+
if (b !== undefined)
|
|
36
|
+
view["no_proxy"] = b;
|
|
37
|
+
}
|
|
38
|
+
return view;
|
|
39
|
+
}
|
|
40
|
+
export function noProxy(host, port, env = process.env) {
|
|
41
|
+
const list = envOf(env, "NO_PROXY");
|
|
42
|
+
if (!list)
|
|
43
|
+
return false;
|
|
44
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
45
|
+
for (const raw of list.split(/[,\s]+/)) {
|
|
46
|
+
const entry = raw.trim().toLowerCase();
|
|
47
|
+
if (!entry)
|
|
48
|
+
continue;
|
|
49
|
+
if (entry === "*")
|
|
50
|
+
return true;
|
|
51
|
+
const [name, p] = entry.startsWith("[") ? [entry.replace(/\]:?.*$/, "").slice(1), entry.split("]:")[1]] : entry.split(":");
|
|
52
|
+
if (p && p !== port)
|
|
53
|
+
continue;
|
|
54
|
+
const bare = (name ?? "").replace(/^\*?\./, "");
|
|
55
|
+
if (h === bare || h.endsWith(`.${bare}`))
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
export function proxyFor(url, env = process.env) {
|
|
61
|
+
const https = envOf(env, "HTTPS_PROXY");
|
|
62
|
+
const http = envOf(env, "HTTP_PROXY");
|
|
63
|
+
const all = envOf(env, "ALL_PROXY");
|
|
64
|
+
if (https === undefined && http === undefined && all === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
let u;
|
|
67
|
+
try {
|
|
68
|
+
u = new URL(url);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const port = u.port || (u.protocol === "https:" ? "443" : "80");
|
|
74
|
+
if (noProxy(u.hostname, port, env))
|
|
75
|
+
return undefined;
|
|
76
|
+
const raw = (u.protocol === "https:" ? https : http) ?? all;
|
|
77
|
+
if (!raw)
|
|
78
|
+
return undefined;
|
|
79
|
+
try {
|
|
80
|
+
const p = new URL(raw.includes("://") ? raw : `http://${raw}`);
|
|
81
|
+
if (p.protocol !== "http:" && p.protocol !== "https:")
|
|
82
|
+
return undefined;
|
|
83
|
+
return p;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function proxyAuth(p) {
|
|
90
|
+
if (!p.username)
|
|
91
|
+
return {};
|
|
92
|
+
const cred = `${decodeURIComponent(p.username)}:${decodeURIComponent(p.password)}`;
|
|
93
|
+
return { "proxy-authorization": `Basic ${Buffer.from(cred).toString("base64")}` };
|
|
94
|
+
}
|
|
95
|
+
function headersOf(init) {
|
|
96
|
+
const out = {};
|
|
97
|
+
const h = init?.headers;
|
|
98
|
+
if (!h)
|
|
99
|
+
return out;
|
|
100
|
+
if (h instanceof Headers)
|
|
101
|
+
h.forEach((v, k) => { out[k] = v; });
|
|
102
|
+
else if (Array.isArray(h))
|
|
103
|
+
for (const [k, v] of h)
|
|
104
|
+
out[k.toLowerCase()] = v;
|
|
105
|
+
else
|
|
106
|
+
for (const [k, v] of Object.entries(h))
|
|
107
|
+
out[k.toLowerCase()] = v;
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
async function bodyOf(init) {
|
|
111
|
+
const b = init?.body;
|
|
112
|
+
if (b === undefined || b === null)
|
|
113
|
+
return undefined;
|
|
114
|
+
if (typeof b === "string")
|
|
115
|
+
return Buffer.from(b);
|
|
116
|
+
if (b instanceof Uint8Array)
|
|
117
|
+
return Buffer.from(b.buffer, b.byteOffset, b.byteLength);
|
|
118
|
+
if (b instanceof ArrayBuffer)
|
|
119
|
+
return Buffer.from(b);
|
|
120
|
+
return Buffer.from(await new Response(b).arrayBuffer());
|
|
121
|
+
}
|
|
122
|
+
function toResponse(res) {
|
|
123
|
+
const headers = new Headers();
|
|
124
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
125
|
+
if (v === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
if (Array.isArray(v))
|
|
128
|
+
for (const x of v)
|
|
129
|
+
headers.append(k, x);
|
|
130
|
+
else
|
|
131
|
+
headers.set(k, String(v));
|
|
132
|
+
}
|
|
133
|
+
const status = res.statusCode ?? 502;
|
|
134
|
+
const noBody = status === 204 || status === 304;
|
|
135
|
+
return new Response(noBody ? null : Readable.toWeb(res), {
|
|
136
|
+
status, statusText: res.statusMessage ?? "", headers,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function abortError(signal) {
|
|
140
|
+
const r = signal.reason;
|
|
141
|
+
return r instanceof Error ? r : Object.assign(new Error("This operation was aborted"), { name: "AbortError" });
|
|
142
|
+
}
|
|
143
|
+
export function proxyFetch(proxy) {
|
|
144
|
+
const fetchViaProxy = async (input, init) => {
|
|
145
|
+
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
|
|
146
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
147
|
+
const headers = headersOf(init);
|
|
148
|
+
const body = await bodyOf(init);
|
|
149
|
+
if (body)
|
|
150
|
+
headers["content-length"] = String(body.length);
|
|
151
|
+
const signal = init?.signal ?? undefined;
|
|
152
|
+
if (signal?.aborted)
|
|
153
|
+
throw abortError(signal);
|
|
154
|
+
const proxyRequest = proxy.protocol === "https:" ? httpsRequest : httpRequest;
|
|
155
|
+
const proxyPort = Number(proxy.port || (proxy.protocol === "https:" ? 443 : 80));
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
let settled = false;
|
|
158
|
+
const fail = (e) => {
|
|
159
|
+
if (settled)
|
|
160
|
+
return;
|
|
161
|
+
settled = true;
|
|
162
|
+
const isAbort = e?.name === "AbortError" || e?.name === "TimeoutError";
|
|
163
|
+
reject(isAbort || e instanceof TypeError ? e : new TypeError("fetch failed", { cause: e }));
|
|
164
|
+
};
|
|
165
|
+
const onAbort = () => fail(abortError(signal));
|
|
166
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
167
|
+
const done = (res) => {
|
|
168
|
+
settled = true;
|
|
169
|
+
signal?.removeEventListener("abort", onAbort);
|
|
170
|
+
resolve(toResponse(res));
|
|
171
|
+
};
|
|
172
|
+
if (url.protocol === "http:") {
|
|
173
|
+
const req = proxyRequest({
|
|
174
|
+
host: proxy.hostname, port: proxyPort, method, path: url.href,
|
|
175
|
+
headers: { ...headers, host: url.host, ...proxyAuth(proxy) }, ...(signal ? { signal } : {}),
|
|
176
|
+
}, done);
|
|
177
|
+
req.on("error", fail);
|
|
178
|
+
req.end(body);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const port = Number(url.port || 443);
|
|
182
|
+
const connect = proxyRequest({
|
|
183
|
+
host: proxy.hostname, port: proxyPort, method: "CONNECT", path: `${url.hostname}:${port}`,
|
|
184
|
+
headers: { host: `${url.hostname}:${port}`, ...proxyAuth(proxy) }, ...(signal ? { signal } : {}),
|
|
185
|
+
});
|
|
186
|
+
connect.on("error", fail);
|
|
187
|
+
connect.on("connect", (res, socket) => {
|
|
188
|
+
if (res.statusCode !== 200) {
|
|
189
|
+
socket.destroy();
|
|
190
|
+
fail(new TypeError(`fetch failed: the proxy ${proxy.host} refused CONNECT ${url.host} (HTTP ${res.statusCode})`));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const tls = tlsConnect({ socket, servername: url.hostname, ALPNProtocols: ["http/1.1"], checkServerIdentity: guardCertChain });
|
|
194
|
+
tls.on("error", fail);
|
|
195
|
+
const req = httpsRequest({
|
|
196
|
+
host: url.hostname, port, method, path: `${url.pathname}${url.search}`,
|
|
197
|
+
headers: { ...headers, host: url.host }, createConnection: () => tls, agent: false,
|
|
198
|
+
...(signal ? { signal } : {}),
|
|
199
|
+
}, done);
|
|
200
|
+
req.on("error", fail);
|
|
201
|
+
req.end(body);
|
|
202
|
+
});
|
|
203
|
+
connect.end();
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
return fetchViaProxy;
|
|
207
|
+
}
|
|
208
|
+
export function requestVia(url, options, env = process.env) {
|
|
209
|
+
const proxy = proxyFor(url.href, env);
|
|
210
|
+
const httpsOptions = (o) => {
|
|
211
|
+
if (url.protocol !== "https:")
|
|
212
|
+
return o;
|
|
213
|
+
const prev = o.checkServerIdentity;
|
|
214
|
+
return Object.assign({}, o, { checkServerIdentity: withCertGuard(prev) });
|
|
215
|
+
};
|
|
216
|
+
if (!proxy)
|
|
217
|
+
return (url.protocol === "https:" ? httpsRequest : httpRequest)(url, httpsOptions(options));
|
|
218
|
+
const proxyRequest = proxy.protocol === "https:" ? httpsRequest : httpRequest;
|
|
219
|
+
const proxyPort = Number(proxy.port || (proxy.protocol === "https:" ? 443 : 80));
|
|
220
|
+
if (url.protocol === "http:") {
|
|
221
|
+
return proxyRequest({
|
|
222
|
+
...options, host: proxy.hostname, port: proxyPort, path: url.href,
|
|
223
|
+
headers: { ...options.headers, host: url.host, ...proxyAuth(proxy) },
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const port = Number(url.port || 443);
|
|
227
|
+
const createConnection = (_opts, done) => {
|
|
228
|
+
const connect = proxyRequest({
|
|
229
|
+
host: proxy.hostname, port: proxyPort, method: "CONNECT", path: `${url.hostname}:${port}`,
|
|
230
|
+
headers: { host: `${url.hostname}:${port}`, ...proxyAuth(proxy) },
|
|
231
|
+
});
|
|
232
|
+
connect.on("error", (e) => done(e));
|
|
233
|
+
connect.on("connect", (res, socket) => {
|
|
234
|
+
if (res.statusCode !== 200) {
|
|
235
|
+
socket.destroy();
|
|
236
|
+
done(new Error(`the proxy ${proxy.host} refused CONNECT ${url.host} (HTTP ${res.statusCode})`));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
done(null, tlsConnect({ socket, servername: url.hostname, ALPNProtocols: ["http/1.1"], checkServerIdentity: guardCertChain }));
|
|
240
|
+
});
|
|
241
|
+
connect.end();
|
|
242
|
+
return undefined;
|
|
243
|
+
};
|
|
244
|
+
const { agent: _agent, ...direct } = options;
|
|
245
|
+
return httpsRequest(url, {
|
|
246
|
+
...direct,
|
|
247
|
+
createConnection: createConnection,
|
|
248
|
+
});
|
|
249
|
+
}
|
package/dist/pty.d.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive terminals: `GET /v1/sandboxes/:id/pty` over a WebSocket (RFC 6455).
|
|
3
|
+
*
|
|
4
|
+
* The protocol:
|
|
5
|
+
*
|
|
6
|
+
* client → server binary = keystrokes; text = `{"resize":{cols,rows}}` | `{"signal":N}` | `{"eof":true}`
|
|
7
|
+
* server → client binary = terminal output; text = `{"event":"open",…}` first and
|
|
8
|
+
* `{"event":"exit"|"error"|"closed",…}` last, then a Close frame
|
|
9
|
+
*
|
|
10
|
+
* Zero dependencies, so this is a small client over `node:http` rather than the global
|
|
11
|
+
* `WebSocket`: that one cannot send an `Authorization` header, and a key in the query string is
|
|
12
|
+
* a key in every access log between here and the sandbox.
|
|
13
|
+
*/
|
|
14
|
+
/** Largest terminal input frame sent; the server's cap is 1 MiB per client frame. */
|
|
15
|
+
export declare const PTY_INPUT_CHUNK: number;
|
|
16
|
+
/** How much of what was typed a session keeps, to send again to a terminal picked up again. */
|
|
17
|
+
export declare const PTY_INPUT_KEEP: number;
|
|
18
|
+
/** The header a terminal is picked up again with (its resume token). */
|
|
19
|
+
export declare const PTY_RESUME_HEADER = "promigence-resume";
|
|
20
|
+
export interface PtySize {
|
|
21
|
+
cols: number;
|
|
22
|
+
rows: number;
|
|
23
|
+
}
|
|
24
|
+
/** How a terminal ended. `exitCode` is `null` when the shell died of a signal. */
|
|
25
|
+
export interface PtyExit {
|
|
26
|
+
exitCode: number | null;
|
|
27
|
+
signal: number | null;
|
|
28
|
+
wallMs?: number;
|
|
29
|
+
/** Set when the session ended without an exit record (`error`, or the socket just closed). */
|
|
30
|
+
error?: {
|
|
31
|
+
code: string;
|
|
32
|
+
message: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** The header the terminal's command travels in, base64. Never a query parameter. */
|
|
36
|
+
export declare const PTY_COMMAND_HEADER = "promigence-pty-command";
|
|
37
|
+
/** The header the terminal's secret **names** travel in, base64 JSON. */
|
|
38
|
+
export declare const PTY_SECRETS_HEADER = "promigence-pty-secrets";
|
|
39
|
+
export interface PtyConnectOpts {
|
|
40
|
+
/** `http(s)://host[:port]/v1/sandboxes/<id>/pty?cols=&rows=` — dimensions only. */
|
|
41
|
+
url: string;
|
|
42
|
+
apiKey: string;
|
|
43
|
+
userAgent: string;
|
|
44
|
+
/**
|
|
45
|
+
* The command to run, sent as a header rather than in the URL.
|
|
46
|
+
*
|
|
47
|
+
* It used to be `?cmd=`, with `envs` composed into it as `export K=v;` — so a terminal opened
|
|
48
|
+
* with an API key in its environment wrote that key into the query string of every request,
|
|
49
|
+
* and therefore into every access log along the path. Names and values both stay out of the
|
|
50
|
+
* URL now.
|
|
51
|
+
*/
|
|
52
|
+
command?: string;
|
|
53
|
+
/** Stored secret names (or `{name, as}` bindings), resolved server-side into the terminal's
|
|
54
|
+
* environment. JSON, base64, in a header. */
|
|
55
|
+
secrets?: unknown[];
|
|
56
|
+
/** The `promigence-caller` label, when one is set (`setCaller`). */
|
|
57
|
+
caller?: string;
|
|
58
|
+
onData?: (data: Uint8Array) => void;
|
|
59
|
+
onExit?: (exit: PtyExit) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Reconnect in a new shell when the service is updated (a WebSocket `Close` 1012) and the
|
|
62
|
+
* terminal cannot be picked up again ([`resume`]). The session goes on over a new connection,
|
|
63
|
+
* with its callbacks, its size and what was typed meanwhile, in a NEW shell: what ran in the old
|
|
64
|
+
* one has ended. Tried for up to 30 s ([`PTY_RECONNECT_FOR_MS`]); past that the session ends with
|
|
65
|
+
* `error.code` `"service_restart"`.
|
|
66
|
+
*
|
|
67
|
+
* Default: on for a shell, off when `command` is set (reconnecting starts the command again,
|
|
68
|
+
* which may not be safe to run twice); `true` or `false` decides either way.
|
|
69
|
+
*/
|
|
70
|
+
reconnect?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Pick the same terminal up again when its connection drops or the service restarts (default
|
|
73
|
+
* on): the terminal keeps running for a short while, and the session picks up where it left off
|
|
74
|
+
* over a new connection, with what was typed meanwhile delivered once — the same shell, or the
|
|
75
|
+
* same command, still running. Where it cannot be picked up (it ended meanwhile, or too much time
|
|
76
|
+
* passed), a restart falls back to [`reconnect`]; a dropped connection ends the session, as
|
|
77
|
+
* before.
|
|
78
|
+
*/
|
|
79
|
+
resume?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Called after each reconnect: `resumed` is `true` when it is the same terminal picked up
|
|
82
|
+
* again, `false` when it is a new shell (what ran in the old one has ended).
|
|
83
|
+
*/
|
|
84
|
+
onReconnected?: (how: {
|
|
85
|
+
resumed: boolean;
|
|
86
|
+
}) => void;
|
|
87
|
+
/** Handshake bound (default 30 s). The session itself has none. */
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
/** Maps a refused upgrade (`status`, body text, its `Retry-After`) onto the SDK's error classes. */
|
|
90
|
+
refuse: (status: number, body: string, retryAfter?: string) => Error;
|
|
91
|
+
}
|
|
92
|
+
/** The accept value a server must echo for `key` (RFC 6455 §4.2.2). */
|
|
93
|
+
export declare function wsAccept(key: string): string;
|
|
94
|
+
/** One client → server frame: FIN, the opcode, a 4-byte mask (mandatory from a client). */
|
|
95
|
+
export declare function encodeFrame(opcode: number, payload: Uint8Array): Buffer;
|
|
96
|
+
/**
|
|
97
|
+
* Incremental frame decoder. Feed it bytes as they arrive; it returns whole frames. Continuation
|
|
98
|
+
* frames are reassembled, so a server that fragments is read correctly.
|
|
99
|
+
*/
|
|
100
|
+
export declare class FrameReader {
|
|
101
|
+
private buf;
|
|
102
|
+
private partial;
|
|
103
|
+
push(chunk: Uint8Array): Array<{
|
|
104
|
+
opcode: number;
|
|
105
|
+
payload: Buffer;
|
|
106
|
+
}>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A live terminal. Resolved by [`connectPty`] once the server's `open` event has arrived, so a
|
|
110
|
+
* keystroke written immediately is never written into a pty that does not exist yet.
|
|
111
|
+
*/
|
|
112
|
+
export declare class PtySession {
|
|
113
|
+
#private;
|
|
114
|
+
private socket;
|
|
115
|
+
/**
|
|
116
|
+
* The id of the terminal's process inside the sandbox (`req_id`); what `pty.kill(pid)`
|
|
117
|
+
* addresses. It names this session: it stays the same after a reconnect.
|
|
118
|
+
*/
|
|
119
|
+
readonly pid: number;
|
|
120
|
+
cols: number;
|
|
121
|
+
rows: number;
|
|
122
|
+
private closed;
|
|
123
|
+
private readonly done;
|
|
124
|
+
/** Frames written while the session reconnects, sent once it has (at most 1 MiB). */
|
|
125
|
+
private pending;
|
|
126
|
+
/** Keystrokes written to a connection, in all, and the last of them, from `inputBase` on: what a
|
|
127
|
+
* terminal picked up again did not take is sent again. */
|
|
128
|
+
private inputSent;
|
|
129
|
+
private inputBase;
|
|
130
|
+
private inputLog;
|
|
131
|
+
/** `true` once the terminal has ended (its exit arrived, or the socket closed). */
|
|
132
|
+
get ended(): boolean;
|
|
133
|
+
private flush;
|
|
134
|
+
/** Write a frame to the connection now, counting keystrokes. */
|
|
135
|
+
private writeNow;
|
|
136
|
+
private send;
|
|
137
|
+
/** Keystrokes / a paste. A string is sent as UTF-8. */
|
|
138
|
+
write(data: string | Uint8Array): void;
|
|
139
|
+
resize(size: PtySize): void;
|
|
140
|
+
/** Deliver a signal to the terminal's process group (`9` = SIGKILL). */
|
|
141
|
+
signal(sig: number): void;
|
|
142
|
+
/** Close the terminal's stdin (`^D` without the line discipline). */
|
|
143
|
+
eof(): void;
|
|
144
|
+
/** Resolves with how the terminal ended. Never rejects. */
|
|
145
|
+
wait(): Promise<PtyExit>;
|
|
146
|
+
/**
|
|
147
|
+
* Hang up. The shell is sent `SIGHUP` when the socket goes away, which is what a
|
|
148
|
+
* terminal emulator closing its window does.
|
|
149
|
+
*/
|
|
150
|
+
disconnect(): void;
|
|
151
|
+
}
|
|
152
|
+
/** The `Close` code a terminal is asked to reconnect with: 1012, "service restart" (RFC 6455's registry). */
|
|
153
|
+
export declare const PTY_RECONNECT_CODE = 1012;
|
|
154
|
+
/** How long a terminal tries to reconnect before it ends (`error.code` `"service_restart"`). */
|
|
155
|
+
export declare const PTY_RECONNECT_FOR_MS = 30000;
|
|
156
|
+
/** The first wait before a reconnect, doubled each try up to [`PTY_RECONNECT_MAX_WAIT_MS`]; each wait is jittered (50-100 %). */
|
|
157
|
+
export declare const PTY_RECONNECT_FIRST_WAIT_MS = 250;
|
|
158
|
+
export declare const PTY_RECONNECT_MAX_WAIT_MS = 4000;
|
|
159
|
+
/** How a session that could not reconnect within [`PTY_RECONNECT_FOR_MS`] ends. */
|
|
160
|
+
export declare const PTY_RESTART_EXIT: PtyExit;
|
|
161
|
+
/** How a session that is not to reconnect (`reconnect` off, or a command's) ends at a 1012. */
|
|
162
|
+
export declare const PTY_RESTART_CLOSED_EXIT: PtyExit;
|
|
163
|
+
/**
|
|
164
|
+
* Open a terminal. Rejects with `opts.refuse(...)` if the server does not switch protocols.
|
|
165
|
+
*
|
|
166
|
+
* When its connection drops, or the service asks it to reconnect (`Close` 1012), the session goes
|
|
167
|
+
* on over a new connection: the same terminal picked up again where the service still holds it
|
|
168
|
+
* ([`PtyConnectOpts.resume`]), else, at a restart, a new shell ([`PtyConnectOpts.reconnect`]).
|
|
169
|
+
* `wait()` resolves only when the session really ends.
|
|
170
|
+
*/
|
|
171
|
+
export declare function connectPty(opts: PtyConnectOpts): Promise<PtySession>;
|