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.
Files changed (97) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +88 -0
  3. package/dist/account.js +850 -0
  4. package/dist/agent.js +374 -0
  5. package/dist/argv.js +159 -0
  6. package/dist/bin.js +332 -0
  7. package/dist/bundle.js +133 -0
  8. package/dist/capacity.js +69 -0
  9. package/dist/commands.d.ts +110 -0
  10. package/dist/commands.js +2675 -0
  11. package/dist/connectreq.js +125 -0
  12. package/dist/credfile.js +188 -0
  13. package/dist/credits.js +197 -0
  14. package/dist/devcontainer.d.ts +99 -0
  15. package/dist/devcontainer.js +744 -0
  16. package/dist/device.js +129 -0
  17. package/dist/dirsnap.js +378 -0
  18. package/dist/errfix.js +262 -0
  19. package/dist/generated.d.ts +35 -0
  20. package/dist/generated.js +307 -0
  21. package/dist/github.js +307 -0
  22. package/dist/gitlab.js +210 -0
  23. package/dist/guide.d.ts +19 -0
  24. package/dist/guide.js +366 -0
  25. package/dist/handlers.js +511 -0
  26. package/dist/index.d.ts +14 -0
  27. package/dist/index.js +7 -0
  28. package/dist/init.d.ts +40 -0
  29. package/dist/init.js +352 -0
  30. package/dist/install.d.ts +23 -0
  31. package/dist/install.js +127 -0
  32. package/dist/io.d.ts +37 -0
  33. package/dist/io.js +318 -0
  34. package/dist/live.js +469 -0
  35. package/dist/login.js +1063 -0
  36. package/dist/mcp.js +889 -0
  37. package/dist/money.js +5 -0
  38. package/dist/orgs.js +475 -0
  39. package/dist/pricing.d.ts +52 -0
  40. package/dist/pricing.js +181 -0
  41. package/dist/report.js +132 -0
  42. package/dist/routes.js +135 -0
  43. package/dist/runners-github.js +257 -0
  44. package/dist/runners-gitlab.js +125 -0
  45. package/dist/runners-spec.js +137 -0
  46. package/dist/runners.js +1458 -0
  47. package/dist/sdk/blake3.d.ts +10 -0
  48. package/dist/sdk/blake3.js +111 -0
  49. package/dist/sdk/bundle.d.ts +96 -0
  50. package/dist/sdk/bundle.js +227 -0
  51. package/dist/sdk/ca-guard.js +166 -0
  52. package/dist/sdk/caller.d.ts +73 -0
  53. package/dist/sdk/caller.js +47 -0
  54. package/dist/sdk/client.d.ts +3015 -0
  55. package/dist/sdk/client.js +2583 -0
  56. package/dist/sdk/command-errors.d.ts +41 -0
  57. package/dist/sdk/command-errors.js +36 -0
  58. package/dist/sdk/context.d.ts +119 -0
  59. package/dist/sdk/context.js +588 -0
  60. package/dist/sdk/core.d.ts +285 -0
  61. package/dist/sdk/core.js +266 -0
  62. package/dist/sdk/credstore.d.ts +90 -0
  63. package/dist/sdk/credstore.js +206 -0
  64. package/dist/sdk/dispatcher.js +86 -0
  65. package/dist/sdk/github.d.ts +158 -0
  66. package/dist/sdk/github.js +78 -0
  67. package/dist/sdk/gitlab.d.ts +104 -0
  68. package/dist/sdk/gitlab.js +20 -0
  69. package/dist/sdk/handles.d.ts +82 -0
  70. package/dist/sdk/handles.js +135 -0
  71. package/dist/sdk/hint.js +7 -0
  72. package/dist/sdk/http2.js +1921 -0
  73. package/dist/sdk/index.d.ts +34 -0
  74. package/dist/sdk/index.js +15 -0
  75. package/dist/sdk/keyed.gen.js +7 -0
  76. package/dist/sdk/models.gen.d.ts +6162 -0
  77. package/dist/sdk/models.gen.js +1 -0
  78. package/dist/sdk/proxy.js +249 -0
  79. package/dist/sdk/pty.d.ts +171 -0
  80. package/dist/sdk/pty.js +496 -0
  81. package/dist/sdk/sandbox.d.ts +537 -0
  82. package/dist/sdk/sandbox.js +725 -0
  83. package/dist/sdk/ssh.d.ts +25 -0
  84. package/dist/sdk/ssh.js +114 -0
  85. package/dist/sdk/trace.js +80 -0
  86. package/dist/sdk/transport-worker.js +466 -0
  87. package/dist/sdk/warm.js +40 -0
  88. package/dist/ssh.js +267 -0
  89. package/dist/surface.d.ts +95 -0
  90. package/dist/surface.js +1452 -0
  91. package/dist/templates.js +53 -0
  92. package/dist/term.js +50 -0
  93. package/dist/timing.js +71 -0
  94. package/dist/untar.js +324 -0
  95. package/dist/uploadguard.js +75 -0
  96. package/llms.txt +209 -0
  97. package/package.json +52 -0
@@ -0,0 +1,25 @@
1
+ export interface SshTunnelOpts {
2
+ /** `https://api…/v1/sandboxes/:id/ssh/connect` */
3
+ url: string;
4
+ /** A `prom_ssh_` access credential, or an API key. */
5
+ token: string;
6
+ userAgent: string;
7
+ caller?: string;
8
+ timeoutMs?: number;
9
+ /** Bytes from the sandbox's SSH server. */
10
+ onData: (data: Uint8Array) => void;
11
+ /** The tunnel closed. */
12
+ onClose: () => void;
13
+ /** Build the error to reject with when the server does not switch protocols. */
14
+ refuse: (status: number, text: string) => Error;
15
+ }
16
+ export interface SshTunnel {
17
+ /** Send bytes to the sandbox's SSH server. */
18
+ write(data: Uint8Array): void;
19
+ /** Close the tunnel. */
20
+ close(): void;
21
+ /** Resolves when the tunnel is finished. */
22
+ done: Promise<void>;
23
+ }
24
+ /** Open the tunnel. Resolves once the socket has switched protocols. */
25
+ export declare function connectSshTunnel(opts: SshTunnelOpts): Promise<SshTunnel>;
@@ -0,0 +1,114 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { CALLER_HEADER } from "./caller.js";
3
+ import { requestVia } from "./proxy.js";
4
+ import { FrameReader, encodeFrame, wsAccept } from "./pty.js";
5
+ export function connectSshTunnel(opts) {
6
+ const url = new URL(opts.url);
7
+ const key = randomBytes(16).toString("base64");
8
+ const req = requestVia(url, {
9
+ method: "GET",
10
+ agent: false,
11
+ headers: {
12
+ connection: "Upgrade",
13
+ upgrade: "websocket",
14
+ "sec-websocket-version": "13",
15
+ "sec-websocket-key": key,
16
+ authorization: `Bearer ${opts.token}`,
17
+ "user-agent": opts.userAgent,
18
+ ...(opts.caller ? { [CALLER_HEADER]: opts.caller } : {}),
19
+ },
20
+ });
21
+ return new Promise((resolve, reject) => {
22
+ let settled = false;
23
+ const fail = (e) => {
24
+ if (!settled) {
25
+ settled = true;
26
+ reject(e);
27
+ }
28
+ };
29
+ const timer = setTimeout(() => {
30
+ req.destroy();
31
+ fail(opts.refuse(504, JSON.stringify({
32
+ error: {
33
+ code: "timeout",
34
+ message: `the SSH tunnel to ${url.host} did not open within ${opts.timeoutMs ?? 30_000} ms`,
35
+ fix: "Retry; if it repeats, check the sandbox is ready.",
36
+ },
37
+ })));
38
+ }, opts.timeoutMs ?? 30_000);
39
+ req.on("error", (e) => {
40
+ clearTimeout(timer);
41
+ fail(opts.refuse(0, JSON.stringify({
42
+ error: {
43
+ code: "unreachable",
44
+ message: `cannot reach ${url.host}: ${e.code ?? e.message}`,
45
+ },
46
+ })));
47
+ });
48
+ req.on("response", (res) => {
49
+ clearTimeout(timer);
50
+ const parts = [];
51
+ res.on("data", (d) => parts.push(d));
52
+ res.on("end", () => fail(opts.refuse(res.statusCode ?? 0, Buffer.concat(parts).toString("utf8"))));
53
+ });
54
+ req.on("upgrade", (res, socket, head) => {
55
+ clearTimeout(timer);
56
+ if (res.headers["sec-websocket-accept"] !== wsAccept(key)) {
57
+ socket.destroy();
58
+ fail(opts.refuse(502, JSON.stringify({
59
+ error: {
60
+ code: "internal",
61
+ message: "the tunnel answered the WebSocket handshake with the wrong accept key",
62
+ },
63
+ })));
64
+ return;
65
+ }
66
+ socket.setNoDelay(true);
67
+ const reader = new FrameReader();
68
+ let resolveDone;
69
+ const done = new Promise((r) => {
70
+ resolveDone = r;
71
+ });
72
+ let finished = false;
73
+ const finish = () => {
74
+ if (finished)
75
+ return;
76
+ finished = true;
77
+ opts.onClose();
78
+ resolveDone();
79
+ };
80
+ const onFrames = (chunk) => {
81
+ for (const f of reader.push(chunk)) {
82
+ if (f.opcode === 0x2 || f.opcode === 0x1) {
83
+ opts.onData(new Uint8Array(f.payload));
84
+ }
85
+ else if (f.opcode === 0x9) {
86
+ socket.write(encodeFrame(0xa, f.payload));
87
+ }
88
+ else if (f.opcode === 0x8) {
89
+ if (!socket.destroyed)
90
+ socket.end(encodeFrame(0x8, f.payload.subarray(0, 2)));
91
+ }
92
+ }
93
+ };
94
+ socket.on("data", onFrames);
95
+ socket.on("close", finish);
96
+ socket.on("error", finish);
97
+ if (head.length)
98
+ onFrames(head);
99
+ settled = true;
100
+ resolve({
101
+ write(data) {
102
+ if (!socket.destroyed)
103
+ socket.write(encodeFrame(0x2, data));
104
+ },
105
+ close() {
106
+ if (!socket.destroyed)
107
+ socket.end(encodeFrame(0x8, Buffer.from([0x03, 0xe8])));
108
+ },
109
+ done,
110
+ });
111
+ });
112
+ req.end();
113
+ });
114
+ }
@@ -0,0 +1,80 @@
1
+ import { writeSync } from "node:fs";
2
+ import { VERSION } from "./core.js";
3
+ export const TRACE_ON = (() => {
4
+ try {
5
+ return typeof process === "object" && process.env?.["PROMIGENCE_SDK_TRACE"] === "1";
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ })();
11
+ const stamps = [];
12
+ let seq = 0;
13
+ let hooked = false;
14
+ let muted = 0;
15
+ export function untraced(fn) {
16
+ muted++;
17
+ try {
18
+ return fn();
19
+ }
20
+ finally {
21
+ muted--;
22
+ }
23
+ }
24
+ export function traceBegin(method, path) {
25
+ if (!TRACE_ON || muted > 0)
26
+ return undefined;
27
+ const s = { seq: seq++, method, path, t: { call: performance.now() } };
28
+ stamps.push(s);
29
+ if (!hooked) {
30
+ hooked = true;
31
+ process.once("exit", flush);
32
+ }
33
+ return s;
34
+ }
35
+ export function mark(s, phase) {
36
+ if (s !== undefined)
37
+ s.t[phase] = performance.now();
38
+ }
39
+ const events = [];
40
+ export function traceEvent(name) {
41
+ if (!TRACE_ON || muted > 0)
42
+ return;
43
+ events.push([name, performance.now()]);
44
+ if (!hooked) {
45
+ hooked = true;
46
+ process.once("exit", flush);
47
+ }
48
+ }
49
+ export function traceEventAt(name, t) {
50
+ if (!TRACE_ON || muted > 0)
51
+ return;
52
+ events.push([name, t]);
53
+ if (!hooked) {
54
+ hooked = true;
55
+ process.once("exit", flush);
56
+ }
57
+ }
58
+ function modulePath() {
59
+ const frame = (new Error().stack ?? "").split("\n")[1] ?? "";
60
+ const m = /\((.*):\d+:\d+\)\s*$/.exec(frame) ?? /at (.*):\d+:\d+\s*$/.exec(frame);
61
+ return m?.[1] ?? "";
62
+ }
63
+ function flush() {
64
+ const head = {
65
+ trace: "promigence-sdk",
66
+ version: VERSION,
67
+ node: process.versions.node,
68
+ timeOrigin: performance.timeOrigin,
69
+ module: modulePath(),
70
+ requests: stamps.length,
71
+ events,
72
+ };
73
+ let out = `PROMIGENCE_SDK_TRACE ${JSON.stringify(head)}\n`;
74
+ for (const s of stamps)
75
+ out += `PROMIGENCE_SDK_TRACE ${JSON.stringify(s)}\n`;
76
+ try {
77
+ writeSync(2, out);
78
+ }
79
+ catch { }
80
+ }
@@ -0,0 +1,466 @@
1
+ import * as dns from "node:dns";
2
+ import { readFileSync } from "node:fs";
3
+ import * as http2 from "node:http2";
4
+ import { isIP, isIPv4 } from "node:net";
5
+ import * as tls from "node:tls";
6
+ import * as wt from "node:worker_threads";
7
+ import { __caGuardInternals, guardCertChain } from "./ca-guard.js";
8
+ const { NGHTTP2_CANCEL, NGHTTP2_REFUSED_STREAM } = http2.constants;
9
+ const MAX_UNPROCESSED_RESENDS = 3;
10
+ const MAX_REPLAYS = 1;
11
+ const STREAM_WINDOW = 8 * 1024 * 1024;
12
+ const SESSION_WINDOW = 32 * 1024 * 1024;
13
+ const DEFAULT_STREAM_LIMIT = 100;
14
+ const MAX_STREAM_LIMIT = 1_000;
15
+ const hrNow = () => Number(process.hrtime.bigint()) / 1e6;
16
+ function start(init) {
17
+ const port = init.port;
18
+ const origin = new URL(init.origin);
19
+ const host = origin.hostname.replace(/^\[(.*)\]$/, "$1");
20
+ const tcpPort = origin.port ? Number(origin.port) : 443;
21
+ for (const h of init.distrustedForTest ?? [])
22
+ __caGuardInternals.addForTest(h);
23
+ const sessions = [];
24
+ const inflight = new Map();
25
+ let everReady = false;
26
+ let closed = false;
27
+ let draining;
28
+ let running = 0;
29
+ let idleTimer;
30
+ let pendingConnects = 0;
31
+ const post = (m, transfer) => {
32
+ try {
33
+ port.postMessage(m, transfer ?? []);
34
+ }
35
+ catch { }
36
+ };
37
+ const live = () => sessions.filter((e) => !e.retired && !e.session.closed && !e.session.destroyed);
38
+ const end = (reason) => {
39
+ if (closed)
40
+ return;
41
+ closed = true;
42
+ if (idleTimer !== undefined)
43
+ clearTimeout(idleTimer);
44
+ if (pumpTimer !== undefined)
45
+ clearTimeout(pumpTimer);
46
+ if (pumpSoon !== undefined)
47
+ clearImmediate(pumpSoon);
48
+ for (const w of waiting.splice(0))
49
+ w.resolve(null);
50
+ for (const e of sessions) {
51
+ try {
52
+ e.session.close();
53
+ }
54
+ catch { }
55
+ }
56
+ for (const c of inflight.values())
57
+ c.cancel();
58
+ post({ t: "closed", reason });
59
+ try {
60
+ port.close();
61
+ }
62
+ catch { }
63
+ };
64
+ const armIdle = (ms) => {
65
+ if (idleTimer !== undefined)
66
+ clearTimeout(idleTimer);
67
+ idleTimer = setTimeout(() => { if (running === 0)
68
+ end("idle"); }, ms);
69
+ };
70
+ const drain = (reason) => {
71
+ if (closed || draining !== undefined)
72
+ return;
73
+ draining = reason;
74
+ post({ t: "draining", reason });
75
+ if (running === 0)
76
+ end(reason);
77
+ };
78
+ const retire = (e) => {
79
+ if (e.retired)
80
+ return;
81
+ e.retired = true;
82
+ if (!everReady)
83
+ return;
84
+ const n = live().length;
85
+ if (n === 0 && pendingConnects === 0)
86
+ drain("sessions closed");
87
+ else
88
+ post({ t: "sessions", sessions: n });
89
+ };
90
+ let context;
91
+ try {
92
+ context = tls.createSecureContext();
93
+ }
94
+ catch {
95
+ context = undefined;
96
+ }
97
+ const connect = (address) => new Promise((resolve) => {
98
+ let done = false;
99
+ const finish = (e) => { if (!done) {
100
+ done = true;
101
+ clearTimeout(timer);
102
+ resolve(e);
103
+ } };
104
+ const timer = setTimeout(() => { try {
105
+ socket.destroy();
106
+ }
107
+ catch { } finish(null); }, init.connectTimeoutMs);
108
+ const socket = tls.connect({
109
+ host: address,
110
+ port: tcpPort,
111
+ ...(isIP(host) !== 0 ? {} : { servername: host }),
112
+ ALPNProtocols: ["h2", "http/1.1"],
113
+ checkServerIdentity: guardCertChain,
114
+ ...(context === undefined ? {} : { secureContext: context }),
115
+ });
116
+ socket.once("error", () => finish(null));
117
+ socket.once("secureConnect", () => {
118
+ if (socket.alpnProtocol !== "h2") {
119
+ socket.destroy();
120
+ finish(null);
121
+ return;
122
+ }
123
+ let session;
124
+ try {
125
+ session = http2.connect(origin.origin, { createConnection: () => socket, settings: { enablePush: false, initialWindowSize: STREAM_WINDOW } });
126
+ }
127
+ catch {
128
+ socket.destroy();
129
+ finish(null);
130
+ return;
131
+ }
132
+ const entry = { session, active: 0, retired: false, goawayLast: undefined, nextFree: 0, granted: 0, index: -1, addr: address, localPort: socket.localPort ?? 0 };
133
+ try {
134
+ session.setLocalWindowSize(SESSION_WINDOW);
135
+ }
136
+ catch { }
137
+ session.on("error", () => retire(entry));
138
+ session.on("close", () => retire(entry));
139
+ session.on("frameError", () => { });
140
+ session.on("goaway", (_c, last) => {
141
+ entry.goawayLast = Math.min(entry.goawayLast ?? last, last);
142
+ retire(entry);
143
+ });
144
+ session.once("remoteSettings", () => finish(entry));
145
+ });
146
+ });
147
+ const lookup = () => new Promise((resolve) => {
148
+ if (isIP(host) !== 0) {
149
+ resolve([host]);
150
+ return;
151
+ }
152
+ const name = host.toLowerCase();
153
+ let named = false;
154
+ const v4 = [];
155
+ try {
156
+ for (const line of readFileSync(init.hostsFile || "/etc/hosts", "utf8").split("\n")) {
157
+ const f = line.replace(/#.*/, "").trim().split(/\s+/);
158
+ if (f.length < 2 || !f.slice(1).some((n) => n.toLowerCase() === name))
159
+ continue;
160
+ named = true;
161
+ if (isIPv4(f[0]) && !v4.includes(f[0]))
162
+ v4.push(f[0]);
163
+ }
164
+ }
165
+ catch { }
166
+ if (named) {
167
+ resolve(v4);
168
+ return;
169
+ }
170
+ try {
171
+ const r = new dns.Resolver({ timeout: init.lookupTimeoutMs, tries: 1 });
172
+ if (Array.isArray(init.servers) && init.servers.length > 0)
173
+ r.setServers(init.servers);
174
+ r.resolve4(name, (err, addrs) => resolve(!err && Array.isArray(addrs) ? addrs : []));
175
+ }
176
+ catch {
177
+ resolve([]);
178
+ }
179
+ });
180
+ const limitOf = (e) => {
181
+ try {
182
+ const m = e.session.remoteSettings?.maxConcurrentStreams;
183
+ return typeof m === "number" && m > 0 ? Math.min(m, MAX_STREAM_LIMIT) : DEFAULT_STREAM_LIMIT;
184
+ }
185
+ catch {
186
+ return DEFAULT_STREAM_LIMIT;
187
+ }
188
+ };
189
+ const open = async () => {
190
+ const addrs = await lookup();
191
+ if (closed)
192
+ return;
193
+ if (addrs.length === 0) {
194
+ end("lookup failed");
195
+ return;
196
+ }
197
+ const n = Math.max(1, init.sessions);
198
+ pendingConnects = n;
199
+ const tries = Array.from({ length: n }, (_, i) => connect(addrs[i % addrs.length]).then((e) => {
200
+ pendingConnects--;
201
+ if (closed) {
202
+ if (e)
203
+ try {
204
+ e.session.close();
205
+ }
206
+ catch { }
207
+ return;
208
+ }
209
+ if (e !== null) {
210
+ e.index = sessions.length;
211
+ sessions.push(e);
212
+ if (!everReady) {
213
+ everReady = true;
214
+ armIdle(init.firstUseMs);
215
+ }
216
+ post({ t: "ready", sessions: live().length, limit: limitOf(e), index: e.index, addr: e.addr, localPort: e.localPort, hr: Number(process.hrtime.bigint()) / 1e6 });
217
+ }
218
+ else if (pendingConnects === 0 && live().length === 0) {
219
+ if (everReady)
220
+ drain("sessions closed");
221
+ else
222
+ end("connect failed");
223
+ }
224
+ }));
225
+ await Promise.all(tries);
226
+ };
227
+ const gapMs = Math.max(0, init.gapUs) / 1000;
228
+ const waiting = [];
229
+ let rr = 0;
230
+ let rrAny = 0;
231
+ let pumpTimer;
232
+ let pumpSoon;
233
+ const freeNow = (l, now) => {
234
+ for (let i = 0; i < l.length; i++) {
235
+ const k = (rr + i) % l.length;
236
+ const e = l[k];
237
+ if (e.nextFree <= now) {
238
+ rr = k + 1;
239
+ return e;
240
+ }
241
+ }
242
+ return undefined;
243
+ };
244
+ const pump = () => {
245
+ pumpTimer = undefined;
246
+ pumpSoon = undefined;
247
+ while (waiting.length > 0) {
248
+ const l = live();
249
+ if (l.length === 0) {
250
+ for (const w of waiting.splice(0))
251
+ w.resolve(null);
252
+ return;
253
+ }
254
+ const now = hrNow();
255
+ const e = freeNow(l, now);
256
+ if (e === undefined)
257
+ break;
258
+ e.nextFree = now + gapMs;
259
+ e.granted = now;
260
+ waiting.shift().resolve(e);
261
+ }
262
+ if (waiting.length === 0)
263
+ return;
264
+ let next = Infinity;
265
+ for (const e of live())
266
+ next = Math.min(next, e.nextFree);
267
+ const wait = next - hrNow();
268
+ if (wait > 2)
269
+ pumpTimer = setTimeout(pump, Math.floor(wait) - 1);
270
+ else
271
+ pumpSoon = setImmediate(pump);
272
+ };
273
+ const nextSession = (id, paced) => {
274
+ const l = live();
275
+ if (l.length === 0)
276
+ return null;
277
+ if (gapMs <= 0) {
278
+ let best = l[0];
279
+ for (const e of l)
280
+ if (e.active < best.active)
281
+ best = e;
282
+ return best;
283
+ }
284
+ if (!paced) {
285
+ const e = l[rrAny % l.length];
286
+ rrAny = (rrAny % l.length) + 1;
287
+ return e;
288
+ }
289
+ if (waiting.length === 0) {
290
+ const now = hrNow();
291
+ const e = freeNow(l, now);
292
+ if (e !== undefined) {
293
+ e.nextFree = now + gapMs;
294
+ e.granted = now;
295
+ return e;
296
+ }
297
+ }
298
+ return new Promise((resolve) => {
299
+ waiting.push({ id, resolve });
300
+ if (pumpTimer === undefined && pumpSoon === undefined)
301
+ pump();
302
+ });
303
+ };
304
+ const unwait = (id) => {
305
+ const i = waiting.findIndex((w) => w.id === id);
306
+ if (i >= 0)
307
+ waiting.splice(i, 1)[0].resolve(null);
308
+ };
309
+ const attempt = async (req, deadline) => {
310
+ const paced = req.paced !== false;
311
+ const given = nextSession(req.id, paced);
312
+ const entry = given instanceof Promise ? await given : given;
313
+ if (entry === null || cancelled.has(req.id) || entry.retired || entry.session.closed || entry.session.destroyed) {
314
+ return { kind: "failed", processed: "no", code: "ERR_HTTP2_NO_SESSION", message: "no session", unsent: true };
315
+ }
316
+ return send(req, deadline, entry, gapMs > 0 && paced ? entry.granted : 0);
317
+ };
318
+ const send = (req, deadline, entry, granted) => new Promise((resolve) => {
319
+ const remaining = deadline - Date.now();
320
+ if (remaining <= 0) {
321
+ resolve({ kind: "timeout" });
322
+ return;
323
+ }
324
+ const headers = { ...req.headers };
325
+ headers[http2.sensitiveHeaders] = req.sensitive;
326
+ const body = req.body === undefined ? undefined : Buffer.from(req.body, "utf8");
327
+ let stream;
328
+ try {
329
+ stream = entry.session.request(headers, { endStream: body === undefined });
330
+ }
331
+ catch (e) {
332
+ retire(entry);
333
+ resolve({ kind: "failed", processed: "no", code: e.code ?? "ERR", message: String(e.message ?? e), unsent: true });
334
+ return;
335
+ }
336
+ entry.active++;
337
+ post({ t: "ack", id: req.id });
338
+ const sent = init.trace !== true ? 0 : granted > 0 ? granted : hrNow();
339
+ let head = 0;
340
+ let status = 0;
341
+ let resHeaders = {};
342
+ const chunks = [];
343
+ let settled = false;
344
+ const finish = (o) => { if (!settled) {
345
+ settled = true;
346
+ clearTimeout(timer);
347
+ inflight.delete(req.id);
348
+ resolve(o);
349
+ } };
350
+ const timer = setTimeout(() => { try {
351
+ stream.close(NGHTTP2_CANCEL);
352
+ }
353
+ catch { } finish({ kind: "timeout" }); }, remaining);
354
+ inflight.set(req.id, { cancel: () => { try {
355
+ stream.close(NGHTTP2_CANCEL);
356
+ }
357
+ catch { } finish({ kind: "failed", processed: "maybe", code: "ABORT", message: "cancelled" }); } });
358
+ stream.on("response", (h) => { resHeaders = h; status = Number(h[":status"]); if (init.trace === true)
359
+ head = hrNow(); });
360
+ stream.on("data", (c) => { chunks.push(c); });
361
+ stream.on("error", () => { });
362
+ stream.on("end", () => { if (status !== 0)
363
+ finish({ kind: "res", status, headers: resHeaders, body: Buffer.concat(chunks), sent, head, end: init.trace === true ? hrNow() : 0, conn: entry.index }); });
364
+ stream.on("close", () => {
365
+ entry.active = Math.max(0, entry.active - 1);
366
+ if (settled)
367
+ return;
368
+ const rst = stream.rstCode;
369
+ let processed;
370
+ if (status !== 0)
371
+ processed = "yes";
372
+ else if (rst === NGHTTP2_REFUSED_STREAM || stream.id === undefined)
373
+ processed = "no";
374
+ else if (entry.goawayLast !== undefined && stream.id > entry.goawayLast)
375
+ processed = "no";
376
+ else
377
+ processed = "maybe";
378
+ finish({ kind: "failed", processed, code: "ERR_HTTP2_STREAM_ERROR", message: `HTTP/2 stream closed (code ${rst ?? "?"})` });
379
+ });
380
+ if (body !== undefined)
381
+ stream.end(body);
382
+ });
383
+ const active = new Set();
384
+ const cancelled = new Set();
385
+ const run = async (req) => {
386
+ if (idleTimer !== undefined) {
387
+ clearTimeout(idleTimer);
388
+ idleTimer = undefined;
389
+ }
390
+ running++;
391
+ active.add(req.id);
392
+ const got = init.trace === true ? hrNow() : 0;
393
+ const deadline = Date.now() + req.timeoutMs;
394
+ let resends = 0;
395
+ let replays = 0;
396
+ const back = () => { post({ t: "err", id: req.id, kind: "back", resends, replays }); };
397
+ for (;;) {
398
+ if (cancelled.has(req.id))
399
+ break;
400
+ const out = await attempt(req, deadline);
401
+ if (cancelled.has(req.id))
402
+ break;
403
+ if (out.kind === "res") {
404
+ const ab = out.body.buffer.slice(out.body.byteOffset, out.body.byteOffset + out.body.byteLength);
405
+ post({ t: "res", id: req.id, status: out.status, headers: out.headers, body: ab, replayed: replays > 0, ...(init.trace === true ? { w: [got, out.sent, out.head, out.end], conn: out.conn } : {}) }, [ab]);
406
+ break;
407
+ }
408
+ if (out.kind === "timeout") {
409
+ post({ t: "err", id: req.id, kind: "timeout" });
410
+ break;
411
+ }
412
+ if (out.unsent === true) {
413
+ back();
414
+ break;
415
+ }
416
+ if (out.processed === "no") {
417
+ if (++resends <= MAX_UNPROCESSED_RESENDS) {
418
+ if (live().length > 0)
419
+ continue;
420
+ back();
421
+ break;
422
+ }
423
+ post({ t: "err", id: req.id, kind: "failed", processed: "no", code: out.code, message: out.message });
424
+ break;
425
+ }
426
+ if (out.processed === "maybe" && req.replayable && replays < MAX_REPLAYS) {
427
+ replays++;
428
+ if (live().length > 0)
429
+ continue;
430
+ back();
431
+ break;
432
+ }
433
+ post({ t: "err", id: req.id, kind: "failed", processed: out.processed, code: out.code, message: out.message });
434
+ break;
435
+ }
436
+ active.delete(req.id);
437
+ cancelled.delete(req.id);
438
+ running--;
439
+ if (running > 0 || closed)
440
+ return;
441
+ if (draining !== undefined)
442
+ end(draining);
443
+ else
444
+ armIdle(init.idleMs);
445
+ };
446
+ port.on("message", (m) => {
447
+ if (closed)
448
+ return;
449
+ if (m?.t === "req")
450
+ void run(m);
451
+ else if (m?.t === "drain")
452
+ drain("asked");
453
+ else if (m?.t === "cancel" && typeof m.id === "number") {
454
+ if (active.has(m.id))
455
+ cancelled.add(m.id);
456
+ unwait(m.id);
457
+ inflight.get(m.id)?.cancel();
458
+ }
459
+ else if (m?.t === "crash-for-test")
460
+ setImmediate(() => { throw new Error("transport thread crash (test)"); });
461
+ });
462
+ void open();
463
+ }
464
+ if (!wt.isMainThread && wt.workerData && typeof wt.workerData === "object" && wt.workerData.promigenceTransport === true) {
465
+ start(wt.workerData);
466
+ }