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,1921 @@
1
+ import * as http2 from "node:http2";
2
+ import { isIP } from "node:net";
3
+ import * as tls from "node:tls";
4
+ import { MessageChannel, Worker, receiveMessageOnPort } from "node:worker_threads";
5
+ import * as zlib from "node:zlib";
6
+ import { __caGuardInternals, guardCertChain } from "./ca-guard.js";
7
+ import { DEFAULT_API_URL } from "./core.js";
8
+ import { customDispatcher } from "./dispatcher.js";
9
+ import { mark, TRACE_ON, traceEvent, traceEventAt } from "./trace.js";
10
+ const MAX_SESSIONS = 4;
11
+ const FIRST_SESSIONS = 2;
12
+ const BURST_MIN = 8;
13
+ const HOLD_MS = 10;
14
+ const LOAD_FIRST_USE_MS = 120_000;
15
+ const CONNECT_TIMEOUT_MS = 10_000;
16
+ const IDLE_CLOSE_MS = 10_000;
17
+ const PING_TIMEOUT_MS = 2_000;
18
+ const RETRY_AFTER_MS = 30_000;
19
+ const MAX_UNPROCESSED_RESENDS = 3;
20
+ const MAX_REPLAYS = 1;
21
+ const MAX_CONNECT_FAILURES = 3;
22
+ const MAX_BAD_SESSIONS = 3;
23
+ const MAX_STREAM_FAILURES = 5;
24
+ const DEFAULT_STREAM_LIMIT = 100;
25
+ const MAX_STREAM_LIMIT = 1_000;
26
+ const STREAM_WINDOW = 8 * 1024 * 1024;
27
+ const SESSION_WINDOW = 32 * 1024 * 1024;
28
+ const { NGHTTP2_CANCEL, NGHTTP2_REFUSED_STREAM, NGHTTP2_HTTP_1_1_REQUIRED } = http2.constants;
29
+ const FETCH_AT_LOAD = globalThis.fetch;
30
+ const PROXY_VARS = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"];
31
+ const OFF = new Set(["0", "false", "off", "no"]);
32
+ const NOT_IN_H2 = new Set(["connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "host", "te"]);
33
+ export function http2Allowed(opt, env = process.env) {
34
+ try {
35
+ const envFlag = env["PROMIGENCE_HTTP2"]?.trim().toLowerCase();
36
+ const wanted = opt ?? (envFlag === undefined || envFlag === "" ? true : !OFF.has(envFlag));
37
+ if (!wanted)
38
+ return false;
39
+ if (!isNode())
40
+ return false;
41
+ if (typeof http2.connect !== "function")
42
+ return false;
43
+ if (globalThis.fetch !== FETCH_AT_LOAD)
44
+ return false;
45
+ if (customDispatcher())
46
+ return false;
47
+ if (PROXY_VARS.some((k) => (env[k] ?? "").trim() !== ""))
48
+ return false;
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ function isNode() {
56
+ const v = typeof process === "object" ? process.versions : undefined;
57
+ return Boolean(v?.["node"]) && !v?.["bun"] && !v?.["deno"];
58
+ }
59
+ let tlsWarmed = false;
60
+ export function warmTls() {
61
+ if (tlsWarmed)
62
+ return;
63
+ tlsWarmed = true;
64
+ try {
65
+ if (isNode())
66
+ tls.createSecureContext();
67
+ }
68
+ catch {
69
+ }
70
+ }
71
+ const utf8 = new TextDecoder("utf-8");
72
+ export class H2Response {
73
+ status;
74
+ raw;
75
+ bytes;
76
+ ok;
77
+ built;
78
+ constructor(status, raw, bytes) {
79
+ this.status = status;
80
+ this.raw = raw;
81
+ this.bytes = bytes;
82
+ this.ok = status >= 200 && status < 300;
83
+ }
84
+ replayed = false;
85
+ header(name) {
86
+ const v = this.raw[name.toLowerCase()];
87
+ if (v === undefined)
88
+ return null;
89
+ return Array.isArray(v) ? v.join(", ") : String(v);
90
+ }
91
+ textNow() {
92
+ const b = this.bytes;
93
+ if (b === null || b.length === 0)
94
+ return "";
95
+ return utf8.decode(b);
96
+ }
97
+ get headers() {
98
+ if (this.built === undefined) {
99
+ const h = new Headers();
100
+ for (const [k, v] of Object.entries(this.raw)) {
101
+ if (k.startsWith(":") || v === undefined)
102
+ continue;
103
+ if (Array.isArray(v))
104
+ for (const x of v)
105
+ h.append(k, x);
106
+ else
107
+ h.append(k, String(v));
108
+ }
109
+ this.built = h;
110
+ }
111
+ return this.built;
112
+ }
113
+ text() {
114
+ return Promise.resolve(this.textNow());
115
+ }
116
+ }
117
+ class OriginPool {
118
+ origin;
119
+ sessions = [];
120
+ connecting;
121
+ backgrounds = [];
122
+ opening;
123
+ holding = false;
124
+ parked;
125
+ warmed = 0;
126
+ off = false;
127
+ offReason = "";
128
+ suspendedUntil = 0;
129
+ connectFailures = 0;
130
+ badSessions = 0;
131
+ streamFailures = 0;
132
+ opened = 0;
133
+ knownLimit;
134
+ constructor(origin) {
135
+ this.origin = origin;
136
+ }
137
+ usable() {
138
+ return !this.off && Date.now() >= this.suspendedUntil;
139
+ }
140
+ turnOff(reason) {
141
+ if (!this.off) {
142
+ this.off = true;
143
+ this.offReason = reason;
144
+ }
145
+ }
146
+ tryAcquire() {
147
+ if (this.holding)
148
+ return undefined;
149
+ const best = this.leastLoaded();
150
+ if (best === undefined)
151
+ return undefined;
152
+ if (best.gotSettings && best.active >= streamLimit(best) && this.sessions.length < MAX_SESSIONS)
153
+ return undefined;
154
+ this.begin(best);
155
+ return best;
156
+ }
157
+ acquire(deadline, signal) {
158
+ if (signal?.aborted)
159
+ return Promise.reject(signal.reason);
160
+ return new Promise((resolve, reject) => {
161
+ let done = false;
162
+ let rounds = 0;
163
+ let counted;
164
+ const settle = () => {
165
+ done = true;
166
+ clearTimeout(timer);
167
+ if (signal !== undefined)
168
+ signal.removeEventListener("abort", onAbort);
169
+ if (counted !== undefined && !counted.released)
170
+ counted.waiters--;
171
+ counted = undefined;
172
+ };
173
+ const onAbort = () => { if (!done) {
174
+ settle();
175
+ reject(signal?.reason);
176
+ } };
177
+ const timer = setTimeout(() => { if (!done) {
178
+ settle();
179
+ reject(timeoutReason());
180
+ } }, Math.max(0, deadline - performance.now()));
181
+ timer.unref?.();
182
+ if (signal !== undefined)
183
+ signal.addEventListener("abort", onAbort, { once: true });
184
+ const attempt = (opened) => {
185
+ if (done)
186
+ return;
187
+ counted = undefined;
188
+ try {
189
+ if (!this.usable()) {
190
+ settle();
191
+ resolve(null);
192
+ return;
193
+ }
194
+ const ready = this.tryAcquire();
195
+ if (ready !== undefined) {
196
+ settle();
197
+ resolve(ready);
198
+ return;
199
+ }
200
+ if (opened === null || ++rounds > MAX_SESSIONS + 1) {
201
+ const any = this.leastLoaded();
202
+ if (any !== undefined)
203
+ this.begin(any);
204
+ settle();
205
+ resolve(any ?? null);
206
+ return;
207
+ }
208
+ if (!this.connecting) {
209
+ this.unpark();
210
+ const opening = this.leastLoaded() === undefined ? this.openFirst(false) : this.open();
211
+ this.connecting = opening.finally(() => { this.connecting = undefined; });
212
+ }
213
+ this.hold();
214
+ const o = this.opening;
215
+ if (o !== undefined && !o.released) {
216
+ o.waiters++;
217
+ counted = o;
218
+ }
219
+ this.connecting.then(attempt, () => attempt(null));
220
+ }
221
+ catch {
222
+ settle();
223
+ resolve(null);
224
+ }
225
+ };
226
+ attempt(undefined);
227
+ });
228
+ }
229
+ leastLoaded() {
230
+ let best;
231
+ for (const e of this.sessions) {
232
+ if (e.retired || e.session.closed || e.session.destroyed)
233
+ continue;
234
+ if (!best || e.active < best.active)
235
+ best = e;
236
+ }
237
+ return best;
238
+ }
239
+ warm(firstUseMs) {
240
+ if (!this.usable() || this.connecting !== undefined || this.leastLoaded() !== undefined || this.parked !== undefined)
241
+ return;
242
+ const route = backgroundRoute(this.origin);
243
+ if (route.kind === "none") {
244
+ traceEvent("h2:warm-none");
245
+ return;
246
+ }
247
+ if (route.kind === "wait") {
248
+ const parked = { cancelled: false };
249
+ this.parked = parked;
250
+ traceEvent("h2:warm-park");
251
+ onEarlyAnswer(route.early, () => {
252
+ if (parked.cancelled || this.parked !== parked)
253
+ return;
254
+ this.parked = undefined;
255
+ const next = backgroundRoute(this.origin);
256
+ if (next.kind === "answer")
257
+ this.startWarm(firstUseMs, next.answer);
258
+ else
259
+ traceEvent("h2:warm-none");
260
+ });
261
+ return;
262
+ }
263
+ this.startWarm(firstUseMs, route.kind === "answer" ? route.answer : undefined);
264
+ }
265
+ startWarm(firstUseMs, pre) {
266
+ if (!this.usable() || this.connecting !== undefined || this.leastLoaded() !== undefined)
267
+ return;
268
+ if (pre !== undefined && !busyElsewhere()) {
269
+ traceEvent("h2:warm-idle");
270
+ return;
271
+ }
272
+ this.warmed++;
273
+ traceEvent("h2:warm");
274
+ const opening = this.openFirst(true, firstUseMs, pre).finally(() => { this.connecting = undefined; });
275
+ this.connecting = opening;
276
+ opening.catch(() => { });
277
+ }
278
+ isConnecting() { return this.connecting !== undefined; }
279
+ isParked() { return this.parked !== undefined; }
280
+ unpark() {
281
+ if (this.parked === undefined)
282
+ return;
283
+ this.parked.cancelled = true;
284
+ this.parked = undefined;
285
+ traceEvent("h2:warm-unpark");
286
+ }
287
+ hold() {
288
+ for (const bg of this.backgrounds) {
289
+ if (bg.held)
290
+ continue;
291
+ bg.held = true;
292
+ safely(() => { bg.socket?.ref(); });
293
+ safely(() => { bg.timer?.ref?.(); });
294
+ }
295
+ }
296
+ track() {
297
+ const bg = { held: false, done: false, socket: undefined, timer: undefined };
298
+ this.backgrounds.push(bg);
299
+ return bg;
300
+ }
301
+ untrack(bg) {
302
+ const k = this.backgrounds.indexOf(bg);
303
+ if (k >= 0)
304
+ this.backgrounds.splice(k, 1);
305
+ }
306
+ openFirst(background, firstUseMs, pre) {
307
+ const n = sessionsWanted();
308
+ const idle = firstUseMs === undefined ? {} : { firstUseMs };
309
+ const ownOpts = pre === undefined ? idle : { ...idle, preResolved: pre };
310
+ if (n <= 1) {
311
+ if (!background)
312
+ return this.open(undefined, ownOpts);
313
+ const bg = this.track();
314
+ return this.open(bg, ownOpts).finally(() => { this.untrack(bg); });
315
+ }
316
+ let resolve;
317
+ const handed = new Promise((r) => { resolve = r; });
318
+ const own = this.track();
319
+ const o = { waiters: 0, pending: 1, first: null, own, released: false, hold: undefined, resolve };
320
+ this.opening = o;
321
+ let started = false;
322
+ const observe = (answer) => {
323
+ if (started || o.released || !this.usable())
324
+ return;
325
+ started = true;
326
+ for (const order of extraOrders(answer, n)) {
327
+ o.pending++;
328
+ traceEvent("h2:extra");
329
+ const bg = { held: false, done: false, socket: undefined, timer: undefined };
330
+ this.open(bg, { ...idle, answer: order }).then((e) => { this.settled(o, e, false); }, () => { this.settled(o, null, false); });
331
+ }
332
+ };
333
+ this.open(own, pre === undefined ? { ...idle, observe } : ownOpts).finally(() => { this.untrack(own); })
334
+ .then((e) => { this.settled(o, e, true); }, () => { this.settled(o, null, true); });
335
+ if (pre !== undefined)
336
+ observe(pre);
337
+ return handed;
338
+ }
339
+ settled(o, entry, own) {
340
+ o.pending--;
341
+ if (o.released)
342
+ return;
343
+ if (entry !== null && o.first === null)
344
+ o.first = entry;
345
+ if (o.first === null) {
346
+ if (own || o.pending === 0)
347
+ this.release(o);
348
+ return;
349
+ }
350
+ if (o.pending === 0) {
351
+ this.release(o);
352
+ return;
353
+ }
354
+ if (o.hold !== undefined)
355
+ return;
356
+ if (o.waiters >= tuning.burstMin && tuning.holdMs > 0) {
357
+ this.holding = true;
358
+ traceEvent("h2:hold");
359
+ o.hold = setTimeout(() => { this.release(o); }, tuning.holdMs);
360
+ return;
361
+ }
362
+ this.release(o);
363
+ }
364
+ release(o) {
365
+ if (o.released)
366
+ return;
367
+ o.released = true;
368
+ if (o.hold !== undefined) {
369
+ clearTimeout(o.hold);
370
+ o.hold = undefined;
371
+ }
372
+ if (this.opening === o) {
373
+ this.opening = undefined;
374
+ this.holding = false;
375
+ }
376
+ this.untrack(o.own);
377
+ if (!o.own.done && o.own.held) {
378
+ o.own.held = false;
379
+ safely(() => { o.own.socket?.unref(); });
380
+ safely(() => { o.own.timer?.unref?.(); });
381
+ }
382
+ traceEvent("h2:release");
383
+ o.resolve(o.first);
384
+ }
385
+ async open(bg, o = {}) {
386
+ const growing = this.leastLoaded() !== undefined;
387
+ const extra = o.answer !== undefined;
388
+ const pre = o.preResolved;
389
+ let asked = { v4: false, early: false };
390
+ const lookup = extra ? fixedLookup(o.answer) : pre !== undefined ? fixedLookup(pre) : apiLookup(asked, o.observe);
391
+ let res = await tlsConnect(this.origin, bg, lookup, extra || pre !== undefined);
392
+ if ((pre !== undefined || asked.early) && !(res.ok && res.socket.alpnProtocol === "h2") && !givenUp(res)) {
393
+ if (res.ok)
394
+ res.socket.destroy();
395
+ if (pre !== undefined && (bg === undefined || !bg.held))
396
+ return null;
397
+ traceEvent("dns:fallback");
398
+ if (bg !== undefined)
399
+ bg.done = false;
400
+ asked = { v4: false, early: false };
401
+ res = await tlsConnect(this.origin, bg, apiLookup(asked, undefined, undefined, false), false);
402
+ }
403
+ if (!res.ok && res.phase === "tcp" && asked.v4 && res.error?.givenUp !== true) {
404
+ traceEvent("dns:both");
405
+ if (bg !== undefined)
406
+ bg.done = false;
407
+ res = await tlsConnect(this.origin, bg, apiLookup({ v4: false }, undefined, false), extra);
408
+ }
409
+ if (!res.ok) {
410
+ if (growing || extra)
411
+ return null;
412
+ if (o.observe !== undefined && this.leastLoaded() !== undefined)
413
+ return null;
414
+ if (bg !== undefined && !bg.held && res.phase === "tcp")
415
+ return null;
416
+ if (res.phase === "tls") {
417
+ this.turnOff(`TLS handshake failed: ${errCode(res.error)}`);
418
+ }
419
+ else if (++this.connectFailures >= MAX_CONNECT_FAILURES) {
420
+ this.turnOff(`connect failed ${this.connectFailures} times: ${errCode(res.error)}`);
421
+ }
422
+ else {
423
+ this.suspendedUntil = Date.now() + RETRY_AFTER_MS;
424
+ }
425
+ return null;
426
+ }
427
+ const socket = res.socket;
428
+ if (socket.alpnProtocol !== "h2") {
429
+ socket.destroy();
430
+ if (!extra)
431
+ this.turnOff(`server chose ${socket.alpnProtocol || "no protocol"} over h2`);
432
+ return null;
433
+ }
434
+ this.connectFailures = 0;
435
+ let session;
436
+ try {
437
+ session = http2.connect(this.origin.origin, {
438
+ createConnection: () => socket,
439
+ settings: { enablePush: false, initialWindowSize: STREAM_WINDOW },
440
+ ...(this.knownLimit === undefined ? {} : { peerMaxConcurrentStreams: this.knownLimit }),
441
+ });
442
+ }
443
+ catch (err) {
444
+ socket.destroy();
445
+ if (!extra)
446
+ this.turnOff(`http2.connect threw: ${errCode(err)}`);
447
+ return null;
448
+ }
449
+ const entry = {
450
+ session, active: 0, retired: false, goawayLast: undefined, idleTimer: undefined,
451
+ pinging: false, lostStreams: 0, gotSettings: false,
452
+ };
453
+ traceEvent("h2:session");
454
+ session.on("remoteSettings", (settings) => {
455
+ traceEvent("h2:settings");
456
+ entry.gotSettings = true;
457
+ const m = settings.maxConcurrentStreams;
458
+ if (typeof m === "number" && m > 0)
459
+ this.knownLimit = Math.min(m, MAX_STREAM_LIMIT);
460
+ });
461
+ session.on("error", () => { this.retire(entry); });
462
+ session.on("goaway", (_code, lastStreamId) => {
463
+ entry.goawayLast = Math.min(entry.goawayLast ?? lastStreamId, lastStreamId);
464
+ this.retire(entry);
465
+ });
466
+ session.on("close", () => { this.retire(entry); });
467
+ safely(() => session.setLocalWindowSize(SESSION_WINDOW));
468
+ session.on("frameError", () => { });
469
+ this.sessions.push(entry);
470
+ this.opened++;
471
+ this.markIdle(entry, o.firstUseMs);
472
+ return entry;
473
+ }
474
+ retire(entry) {
475
+ if (entry.idleTimer !== undefined) {
476
+ clearTimeout(entry.idleTimer);
477
+ entry.idleTimer = undefined;
478
+ }
479
+ if (entry.retired)
480
+ return;
481
+ entry.retired = true;
482
+ const i = this.sessions.indexOf(entry);
483
+ if (i >= 0)
484
+ this.sessions.splice(i, 1);
485
+ if (entry.active === 0)
486
+ safely(() => entry.session.unref());
487
+ }
488
+ begin(entry) {
489
+ if (entry.idleTimer !== undefined) {
490
+ clearTimeout(entry.idleTimer);
491
+ entry.idleTimer = undefined;
492
+ }
493
+ if (entry.active++ === 0)
494
+ safely(() => entry.session.ref());
495
+ }
496
+ end(entry) {
497
+ entry.active = Math.max(0, entry.active - 1);
498
+ if (entry.active === 0)
499
+ this.markIdle(entry);
500
+ }
501
+ markIdle(entry, ms = IDLE_CLOSE_MS) {
502
+ safely(() => entry.session.unref());
503
+ if (entry.retired)
504
+ return;
505
+ entry.idleTimer = setTimeout(() => {
506
+ entry.idleTimer = undefined;
507
+ if (entry.active !== 0)
508
+ return;
509
+ this.retire(entry);
510
+ safely(() => entry.session.close());
511
+ }, ms);
512
+ entry.idleTimer.unref?.();
513
+ }
514
+ succeeded() {
515
+ this.streamFailures = 0;
516
+ this.badSessions = 0;
517
+ }
518
+ streamFailed(entry, sessionDying) {
519
+ if (sessionDying) {
520
+ if (entry.lostStreams++ === 0 && ++this.badSessions >= MAX_BAD_SESSIONS) {
521
+ this.turnOff(`${this.badSessions} sessions in a row died with requests in flight`);
522
+ }
523
+ return;
524
+ }
525
+ if (++this.streamFailures >= MAX_STREAM_FAILURES)
526
+ this.turnOff(`${this.streamFailures} requests in a row failed on a live session`);
527
+ }
528
+ checkAlive(entry) {
529
+ if (entry.pinging || entry.retired)
530
+ return;
531
+ entry.pinging = true;
532
+ const drop = () => {
533
+ if (++this.badSessions >= MAX_BAD_SESSIONS)
534
+ this.turnOff(`${this.badSessions} sessions in a row stopped answering`);
535
+ this.retire(entry);
536
+ safely(() => entry.session.destroy());
537
+ };
538
+ const timer = setTimeout(drop, PING_TIMEOUT_MS);
539
+ timer.unref?.();
540
+ try {
541
+ const sent = entry.session.ping((err) => {
542
+ entry.pinging = false;
543
+ clearTimeout(timer);
544
+ if (err)
545
+ drop();
546
+ });
547
+ if (!sent) {
548
+ clearTimeout(timer);
549
+ entry.pinging = false;
550
+ }
551
+ }
552
+ catch {
553
+ clearTimeout(timer);
554
+ entry.pinging = false;
555
+ drop();
556
+ }
557
+ }
558
+ }
559
+ export const tuning = {
560
+ sessions: undefined,
561
+ holdMs: HOLD_MS,
562
+ burstMin: BURST_MIN,
563
+ loadFirstUseMs: LOAD_FIRST_USE_MS,
564
+ earlyPlatform: undefined,
565
+ earlyServers: undefined,
566
+ earlyHostsFile: undefined,
567
+ };
568
+ let sessionsFromEnv;
569
+ function sessionsWanted() {
570
+ if (tuning.sessions !== undefined)
571
+ return parseSessions(String(tuning.sessions));
572
+ if (sessionsFromEnv === undefined) {
573
+ let raw;
574
+ try {
575
+ raw = process.env["PROMIGENCE_HTTP2_SESSIONS"];
576
+ }
577
+ catch {
578
+ raw = undefined;
579
+ }
580
+ sessionsFromEnv = parseSessions(raw);
581
+ }
582
+ return sessionsFromEnv;
583
+ }
584
+ export function parseSessions(raw) {
585
+ const t = raw?.trim().toLowerCase() ?? "";
586
+ if (t === "")
587
+ return FIRST_SESSIONS;
588
+ if (OFF.has(t))
589
+ return 1;
590
+ const n = Number(t);
591
+ if (!Number.isInteger(n) || n < 0)
592
+ return FIRST_SESSIONS;
593
+ return Math.max(1, Math.min(MAX_SESSIONS, n));
594
+ }
595
+ export function extraOrders(answer, sessions) {
596
+ if (sessions <= 1 || answer.length < 2)
597
+ return [];
598
+ const family = answer[0].family;
599
+ const seen = new Set();
600
+ const mine = [];
601
+ const other = [];
602
+ for (const a of answer) {
603
+ if (typeof a?.address !== "string" || seen.has(a.address))
604
+ continue;
605
+ seen.add(a.address);
606
+ (a.family === family ? mine : other).push(a);
607
+ }
608
+ const rot = (list, by) => {
609
+ if (list.length === 0)
610
+ return [];
611
+ const k = by % list.length;
612
+ return [...list.slice(k), ...list.slice(0, k)];
613
+ };
614
+ const out = [];
615
+ for (let i = 1; i < Math.min(sessions, mine.length); i++)
616
+ out.push([...rot(mine, i), ...rot(other, i)]);
617
+ return out;
618
+ }
619
+ function streamLimit(entry) {
620
+ try {
621
+ const m = entry.session.remoteSettings?.maxConcurrentStreams;
622
+ return typeof m === "number" && m > 0 ? Math.min(m, MAX_STREAM_LIMIT) : DEFAULT_STREAM_LIMIT;
623
+ }
624
+ catch {
625
+ return DEFAULT_STREAM_LIMIT;
626
+ }
627
+ }
628
+ function safely(fn) {
629
+ try {
630
+ fn();
631
+ }
632
+ catch { }
633
+ }
634
+ function errCode(err) {
635
+ const e = err;
636
+ return e?.code ?? e?.message ?? String(err);
637
+ }
638
+ const TRANSIENT = new Set(["ECONNRESET", "ETIMEDOUT", "EPIPE", "ECONNABORTED", "EHOSTUNREACH", "ENETUNREACH", "EAI_AGAIN"]);
639
+ const IDLE_CHECK_MS = 5;
640
+ const onTheWire = new Set();
641
+ let idleCheck;
642
+ function activeResources() {
643
+ try {
644
+ const info = process.getActiveResourcesInfo;
645
+ return typeof info === "function" ? info.call(process) : undefined;
646
+ }
647
+ catch {
648
+ return undefined;
649
+ }
650
+ }
651
+ function watchWire(w) {
652
+ onTheWire.add(w);
653
+ if (idleCheck !== undefined)
654
+ return;
655
+ idleCheck = setInterval(checkIdle, IDLE_CHECK_MS);
656
+ safely(() => { idleCheck?.unref?.(); });
657
+ }
658
+ function unwatchWire(w) {
659
+ if (!onTheWire.delete(w))
660
+ return;
661
+ if (onTheWire.size === 0 && idleCheck !== undefined) {
662
+ clearInterval(idleCheck);
663
+ idleCheck = undefined;
664
+ }
665
+ }
666
+ function checkIdle() {
667
+ const info = activeResources();
668
+ if (info === undefined)
669
+ return;
670
+ let connects = 0, lookups = 0, timers = 0;
671
+ for (const t of info) {
672
+ if (t === "ConnectWrap")
673
+ connects++;
674
+ else if (t === "GetAddrInfoReqWrap")
675
+ lookups++;
676
+ else if (t === "Timeout")
677
+ timers++;
678
+ else if (t !== "PipeWrap" && t !== "TTYWrap")
679
+ return;
680
+ }
681
+ let c = 0, l = 0, t = 0;
682
+ for (const w of onTheWire) {
683
+ if (w.bg.held)
684
+ return;
685
+ if (w.tcp)
686
+ c++;
687
+ if (w.lookup)
688
+ l++;
689
+ if (w.attemptTimer)
690
+ t++;
691
+ }
692
+ if (connects > c || lookups > l || timers > t)
693
+ return;
694
+ for (const w of [...onTheWire])
695
+ safely(w.giveUp);
696
+ }
697
+ function tlsConnect(origin, bg, lookup, fixed = false) {
698
+ return new Promise((resolve) => {
699
+ const host = origin.hostname.replace(/^\[(.*)\]$/, "$1");
700
+ const port = origin.port ? Number(origin.port) : 443;
701
+ let tcpUp = false;
702
+ let done = false;
703
+ let socket;
704
+ const release = () => {
705
+ if (bg !== undefined && !bg.held && !done)
706
+ safely(() => { socket.unref(); });
707
+ };
708
+ let wire;
709
+ const finish = (r) => {
710
+ if (done)
711
+ return;
712
+ done = true;
713
+ if (bg !== undefined)
714
+ bg.done = true;
715
+ if (wire !== undefined)
716
+ unwatchWire(wire);
717
+ clearTimeout(timer);
718
+ socket.removeListener("error", onError);
719
+ if (bg !== undefined)
720
+ socket.removeListener("connectionAttempt", release);
721
+ if (!r.ok)
722
+ socket.destroy();
723
+ resolve(r);
724
+ };
725
+ const onError = (error) => {
726
+ const phase = tcpUp && !TRANSIENT.has(errCode(error)) ? "tls" : "tcp";
727
+ finish({ ok: false, phase, error });
728
+ };
729
+ const timer = setTimeout(() => {
730
+ finish({ ok: false, phase: "tcp", error: Object.assign(new Error("connect timed out"), { code: "ETIMEDOUT" }) });
731
+ }, CONNECT_TIMEOUT_MS);
732
+ try {
733
+ traceEvent("tls:connect");
734
+ socket = tls.connect({
735
+ host,
736
+ port,
737
+ ...(isIP(host) ? {} : { servername: host }),
738
+ ALPNProtocols: ["h2", "http/1.1"],
739
+ checkServerIdentity: guardCertChain,
740
+ ...(lookup === undefined || isIP(host) ? {} : { lookup }),
741
+ });
742
+ traceEvent("tls:connect-returned");
743
+ }
744
+ catch (error) {
745
+ clearTimeout(timer);
746
+ resolve({ ok: false, phase: "tls", error });
747
+ return;
748
+ }
749
+ if (bg !== undefined) {
750
+ bg.socket = socket;
751
+ bg.timer = timer;
752
+ if (!bg.held)
753
+ safely(() => { timer.unref?.(); });
754
+ release();
755
+ socket.on("connectionAttempt", release);
756
+ const w = {
757
+ bg, lookup: !isIP(host) && !fixed, tcp: false, attemptTimer: false, addresses: new Set(), attempts: 0,
758
+ giveUp: () => finish({ ok: false, phase: "tcp", error: Object.assign(new Error("connect given up: nothing else keeps the process alive"), { code: "ECONNABORTED", givenUp: true }) }),
759
+ };
760
+ const attemptOver = () => { w.tcp = false; w.attemptTimer = false; };
761
+ socket.on("lookup", (_err, ip) => { w.lookup = false; if (typeof ip === "string")
762
+ w.addresses.add(ip); });
763
+ socket.on("connectionAttempt", () => { w.tcp = true; w.attempts++; w.attemptTimer = w.addresses.size > w.attempts; });
764
+ socket.on("connectionAttemptFailed", attemptOver);
765
+ socket.on("connectionAttemptTimeout", attemptOver);
766
+ socket.once("connect", () => { attemptOver(); unwatchWire(w); });
767
+ wire = w;
768
+ watchWire(w);
769
+ }
770
+ socket.once("connect", () => { tcpUp = true; release(); traceEvent("tcp:connected"); });
771
+ socket.once("secureConnect", () => { traceEvent("tls:secure"); finish({ ok: true, socket }); });
772
+ socket.on("error", onError);
773
+ });
774
+ }
775
+ function nodeDns() {
776
+ try {
777
+ const get = process.getBuiltinModule;
778
+ return typeof get === "function" ? get("node:dns") : undefined;
779
+ }
780
+ catch {
781
+ return undefined;
782
+ }
783
+ }
784
+ let familySetting;
785
+ function v4First() {
786
+ if (familySetting === undefined) {
787
+ let raw;
788
+ try {
789
+ raw = process.env["PROMIGENCE_DNS_FAMILY"];
790
+ }
791
+ catch {
792
+ raw = undefined;
793
+ }
794
+ familySetting = raw?.trim() === "0" ? 0 : 4;
795
+ }
796
+ return familySetting === 4;
797
+ }
798
+ export function lookupApiName(dnsLookup, hostname, options, callback, asked, ipv4First = true) {
799
+ const o = options;
800
+ const either = o.family === undefined || o.family === 0 || o.family === "IPv4or6";
801
+ if (!ipv4First || !either) {
802
+ dnsLookup(hostname, options, callback);
803
+ return;
804
+ }
805
+ dnsLookup(hostname, { ...options, family: 4 }, (err, address, family) => {
806
+ const found = err === null && (Array.isArray(address) ? address.length > 0 : typeof address === "string" && address !== "");
807
+ if (!found) {
808
+ dnsLookup(hostname, options, callback);
809
+ return;
810
+ }
811
+ asked.v4 = true;
812
+ callback(null, address, family);
813
+ });
814
+ }
815
+ function apiLookup(asked, observe, ipv4First = v4First(), earlyOk = true) {
816
+ if (!ipv4First && observe === undefined)
817
+ return undefined;
818
+ const dns = nodeDns();
819
+ if (dns === undefined)
820
+ return undefined;
821
+ return ((hostname, options, callback) => {
822
+ const lookup = dns.lookup;
823
+ const early = earlyOk && observe !== undefined && ipv4First ? earlyForRequest(hostname, options, lookup) : undefined;
824
+ if (early !== undefined) {
825
+ asked.early = true;
826
+ traceEvent("dns:early");
827
+ const all = options.all === true;
828
+ process.nextTick(() => {
829
+ if (all)
830
+ callback(null, early);
831
+ else
832
+ callback(null, early[0].address, 4);
833
+ safely(() => observe(early));
834
+ });
835
+ return;
836
+ }
837
+ lookupApiName(lookup, hostname, options, (err, address, family) => {
838
+ callback(err, address, family);
839
+ if (observe !== undefined && err === null && Array.isArray(address))
840
+ safely(() => observe(address));
841
+ }, asked, ipv4First);
842
+ });
843
+ }
844
+ const earlies = new Map();
845
+ const MAX_EARLIES = 4;
846
+ const EARLY_MAX_AGE_MS = 60_000;
847
+ const EARLY_TIMEOUT_MS = 2_000;
848
+ const LOOKUP_AT_LOAD = (() => {
849
+ try {
850
+ return nodeDns()?.lookup;
851
+ }
852
+ catch {
853
+ return undefined;
854
+ }
855
+ })();
856
+ function lookupReplaced() {
857
+ try {
858
+ const d = nodeDns();
859
+ return d !== undefined && d.lookup !== LOOKUP_AT_LOAD;
860
+ }
861
+ catch {
862
+ return true;
863
+ }
864
+ }
865
+ const EARLY_WORKER = `
866
+ const { workerData } = require("node:worker_threads");
867
+ const { isIPv4 } = require("node:net");
868
+ const { port, host, timeoutMs, servers, hostsFile } = workerData;
869
+ const done = (m) => { try { port.postMessage(m); } catch {} try { port.close(); } catch {} };
870
+ try {
871
+ let named = false;
872
+ const v4 = [];
873
+ try {
874
+ for (const line of require("node:fs").readFileSync(hostsFile || "/etc/hosts", "utf8").split("\\n")) {
875
+ const f = line.replace(/#.*/, "").trim().split(/\\s+/);
876
+ if (f.length < 2 || !f.slice(1).some((n) => n.toLowerCase() === host)) continue;
877
+ named = true;
878
+ if (isIPv4(f[0]) && !v4.includes(f[0])) v4.push(f[0]);
879
+ }
880
+ } catch {}
881
+ if (named) done(v4.length > 0 ? { host, addrs: v4, ttl: 60, at: Date.now() } : { host, none: "hosts" });
882
+ else {
883
+ const r = new (require("node:dns").Resolver)({ timeout: timeoutMs, tries: 1 });
884
+ if (Array.isArray(servers) && servers.length > 0) r.setServers(servers);
885
+ r.resolve4(host, { ttl: true }, (err, recs) => {
886
+ if (err || !Array.isArray(recs) || recs.length === 0) return done({ host, none: (err && err.code) || "empty" });
887
+ done({ host, addrs: recs.map((x) => x.address), ttl: Math.min(...recs.map((x) => x.ttl)), at: Date.now() });
888
+ });
889
+ }
890
+ } catch { done({ host, none: "error" }); }
891
+ `;
892
+ const EARLY_SEAM = Symbol.for("promigence.sdk.early-seam");
893
+ function earlySeam() {
894
+ const v = globalThis[EARLY_SEAM];
895
+ return v !== null && typeof v === "object" ? v : {};
896
+ }
897
+ function earlyPlatform() {
898
+ const seam = earlySeam().platform;
899
+ return (tuning.earlyPlatform ?? (typeof seam === "string" ? seam : undefined) ?? process.platform) === "linux";
900
+ }
901
+ function busyElsewhere() {
902
+ const info = activeResources();
903
+ if (info === undefined)
904
+ return true;
905
+ return info.some((t) => t !== "PipeWrap" && t !== "TTYWrap" && t !== "MessagePort" && t !== "Worker");
906
+ }
907
+ function earlyLookupFor(host) {
908
+ const known = earlies.get(host);
909
+ if (known !== undefined)
910
+ return known;
911
+ if (!earlyPlatform() || earlies.size >= MAX_EARLIES)
912
+ return undefined;
913
+ try {
914
+ const { port1, port2 } = new MessageChannel();
915
+ port1.unref();
916
+ const worker = new Worker(EARLY_WORKER, {
917
+ eval: true,
918
+ workerData: {
919
+ port: port2, host, timeoutMs: EARLY_TIMEOUT_MS,
920
+ servers: tuning.earlyServers ?? (Array.isArray(earlySeam().servers) ? earlySeam().servers : undefined),
921
+ hostsFile: tuning.earlyHostsFile ?? (typeof earlySeam().hostsFile === "string" ? earlySeam().hostsFile : undefined),
922
+ },
923
+ transferList: [port2],
924
+ env: {},
925
+ argv: [],
926
+ execArgv: [],
927
+ });
928
+ worker.unref();
929
+ const e = { host, port: port1, state: "pending", waiters: [], listening: false };
930
+ worker.on("error", () => { settleEarly(e, undefined); });
931
+ worker.on("exit", () => { pollEarly(e); if (e.state === "pending")
932
+ settleEarly(e, undefined); });
933
+ earlies.set(host, e);
934
+ traceEvent("dns:early-start");
935
+ return e;
936
+ }
937
+ catch {
938
+ return undefined;
939
+ }
940
+ }
941
+ function pollEarly(e) {
942
+ if (e.state !== "pending")
943
+ return;
944
+ try {
945
+ const got = receiveMessageOnPort(e.port);
946
+ if (got !== undefined)
947
+ settleEarly(e, got.message);
948
+ }
949
+ catch {
950
+ settleEarly(e, undefined);
951
+ }
952
+ }
953
+ function settleEarly(e, message) {
954
+ if (e.state !== "pending")
955
+ return;
956
+ const m = message;
957
+ const addrs = Array.isArray(m?.addrs) ? m.addrs.filter((a) => typeof a === "string" && isIP(a) === 4) : [];
958
+ if (m !== undefined && m.host === e.host && addrs.length > 0 && typeof m.at === "number") {
959
+ e.state = "answered";
960
+ e.answer = addrs.map((address) => ({ address, family: 4 }));
961
+ const ttlMs = typeof m.ttl === "number" && m.ttl >= 0 ? m.ttl * 1000 : 0;
962
+ e.until = m.at + Math.min(ttlMs, EARLY_MAX_AGE_MS);
963
+ }
964
+ else {
965
+ e.state = "none";
966
+ }
967
+ safely(() => { e.port.close(); });
968
+ const waiters = e.waiters.splice(0);
969
+ for (const w of waiters)
970
+ safely(w);
971
+ }
972
+ function onEarlyAnswer(e, cb) {
973
+ pollEarly(e);
974
+ if (e.state !== "pending") {
975
+ process.nextTick(cb);
976
+ return;
977
+ }
978
+ e.waiters.push(cb);
979
+ if (e.listening)
980
+ return;
981
+ e.listening = true;
982
+ try {
983
+ e.port.on("message", (m) => { settleEarly(e, m); });
984
+ e.port.unref();
985
+ }
986
+ catch {
987
+ settleEarly(e, undefined);
988
+ }
989
+ }
990
+ function earlyFresh(e) {
991
+ pollEarly(e);
992
+ if (e.state !== "answered" || e.answer === undefined || e.until === undefined || Date.now() > e.until)
993
+ return undefined;
994
+ return e.answer.map((a) => ({ ...a }));
995
+ }
996
+ function backgroundRoute(origin) {
997
+ try {
998
+ const host = origin.hostname.replace(/^\[(.*)\]$/, "$1").toLowerCase();
999
+ if (isIP(host) !== 0 || !earlyPlatform() || lookupReplaced() || !v4First())
1000
+ return { kind: "lookup" };
1001
+ const e = earlyLookupFor(host);
1002
+ if (e === undefined)
1003
+ return { kind: "none" };
1004
+ const answer = earlyFresh(e);
1005
+ if (answer !== undefined)
1006
+ return { kind: "answer", answer };
1007
+ return e.state === "pending" ? { kind: "wait", early: e } : { kind: "none" };
1008
+ }
1009
+ catch {
1010
+ return { kind: "none" };
1011
+ }
1012
+ }
1013
+ function earlyForRequest(hostname, options, lookup) {
1014
+ try {
1015
+ if (lookup !== LOOKUP_AT_LOAD)
1016
+ return undefined;
1017
+ const o = options;
1018
+ if (!(o.family === undefined || o.family === 0 || o.family === 4 || o.family === "IPv4or6"))
1019
+ return undefined;
1020
+ const e = earlies.get(hostname.toLowerCase());
1021
+ return e === undefined ? undefined : earlyFresh(e);
1022
+ }
1023
+ catch {
1024
+ return undefined;
1025
+ }
1026
+ }
1027
+ function givenUp(res) {
1028
+ return !res.ok && res.error?.givenUp === true;
1029
+ }
1030
+ function fixedLookup(answer) {
1031
+ return ((_hostname, options, callback) => {
1032
+ const all = options.all === true;
1033
+ process.nextTick(() => {
1034
+ if (all)
1035
+ callback(null, answer);
1036
+ else
1037
+ callback(null, answer[0].address, answer[0].family);
1038
+ });
1039
+ });
1040
+ }
1041
+ function timeoutReason() {
1042
+ try {
1043
+ return new DOMException("The operation was aborted due to timeout", "TimeoutError");
1044
+ }
1045
+ catch {
1046
+ return Object.assign(new Error("The operation was aborted due to timeout"), { name: "TimeoutError" });
1047
+ }
1048
+ }
1049
+ function exchange(pool, entry, target, init, deadline) {
1050
+ return new Promise((resolve) => {
1051
+ const remaining = deadline - performance.now();
1052
+ if (remaining <= 0) {
1053
+ pool.end(entry);
1054
+ resolve({ kind: "aborted", reason: timeoutReason() });
1055
+ return;
1056
+ }
1057
+ const headers = {
1058
+ ":method": init.method,
1059
+ ":path": target.path,
1060
+ ":scheme": "https",
1061
+ ":authority": target.host,
1062
+ };
1063
+ for (const k in init.headers) {
1064
+ const name = k.toLowerCase();
1065
+ if (!NOT_IN_H2.has(name))
1066
+ headers[name] = init.headers[k];
1067
+ }
1068
+ headers[http2.sensitiveHeaders] = ["authorization"];
1069
+ const body = init.body === undefined ? undefined : Buffer.from(init.body, "utf8");
1070
+ if (body !== undefined)
1071
+ headers["content-length"] = body.length;
1072
+ let stream;
1073
+ try {
1074
+ stream = entry.session.request(headers, { endStream: body === undefined });
1075
+ }
1076
+ catch (error) {
1077
+ pool.end(entry);
1078
+ const code = errCode(error);
1079
+ if (entry.session.closed || entry.session.destroyed || code === "ERR_HTTP2_GOAWAY_SESSION"
1080
+ || code === "ERR_HTTP2_INVALID_SESSION" || code === "ERR_HTTP2_OUT_OF_STREAMS") {
1081
+ pool.retire(entry);
1082
+ if (code === "ERR_HTTP2_OUT_OF_STREAMS")
1083
+ safely(() => entry.session.close());
1084
+ resolve({ kind: "failed", error, processed: "no" });
1085
+ }
1086
+ else {
1087
+ resolve({ kind: "use-fetch" });
1088
+ }
1089
+ return;
1090
+ }
1091
+ const tr = init.trace;
1092
+ if (tr !== undefined) {
1093
+ mark(tr, "req");
1094
+ tr.via = "h2";
1095
+ }
1096
+ let status = 0;
1097
+ let resHeaders;
1098
+ let first;
1099
+ let chunks;
1100
+ let settled = false;
1101
+ let streamError;
1102
+ const signal = init.signal;
1103
+ const finish = (o) => {
1104
+ if (settled)
1105
+ return;
1106
+ settled = true;
1107
+ clearTimeout(timer);
1108
+ if (signal !== undefined)
1109
+ signal.removeEventListener("abort", onAbort);
1110
+ resolve(o);
1111
+ };
1112
+ const onTimeout = () => {
1113
+ if (settled)
1114
+ return;
1115
+ safely(() => stream.close(NGHTTP2_CANCEL));
1116
+ pool.streamFailed(entry, false);
1117
+ pool.checkAlive(entry);
1118
+ finish({ kind: "aborted", reason: timeoutReason() });
1119
+ };
1120
+ const onAbort = () => {
1121
+ safely(() => stream.close(NGHTTP2_CANCEL));
1122
+ finish({ kind: "aborted", reason: signal?.reason });
1123
+ };
1124
+ const timer = setTimeout(onTimeout, remaining);
1125
+ timer.unref?.();
1126
+ if (signal !== undefined) {
1127
+ if (signal.aborted)
1128
+ onAbort();
1129
+ else
1130
+ signal.addEventListener("abort", onAbort, { once: true });
1131
+ }
1132
+ stream.on("response", (h) => {
1133
+ resHeaders = h;
1134
+ status = Number(h[":status"]);
1135
+ if (tr !== undefined) {
1136
+ mark(tr, "hdr");
1137
+ tr.status = status;
1138
+ const rx = h["x-promigence-rx-ns"], tx = h["x-promigence-tx-ns"];
1139
+ if (typeof rx === "string")
1140
+ tr.rx = rx;
1141
+ if (typeof tx === "string")
1142
+ tr.tx = tx;
1143
+ }
1144
+ });
1145
+ stream.on("data", (c) => {
1146
+ if (first === undefined)
1147
+ first = c;
1148
+ else
1149
+ (chunks ??= [first]).push(c);
1150
+ });
1151
+ stream.on("error", (err) => { streamError = err; });
1152
+ stream.on("end", () => {
1153
+ if (tr !== undefined)
1154
+ mark(tr, "end");
1155
+ if (settled || status === 0)
1156
+ return;
1157
+ if (status === 421) {
1158
+ pool.turnOff("421 Misdirected Request");
1159
+ finish({ kind: "use-fetch" });
1160
+ return;
1161
+ }
1162
+ try {
1163
+ const raw = chunks !== undefined ? Buffer.concat(chunks) : (first ?? EMPTY);
1164
+ const response = toResponse(status, resHeaders ?? {}, raw, init.method);
1165
+ if (tr !== undefined)
1166
+ mark(tr, "out");
1167
+ pool.succeeded();
1168
+ finish({ kind: "response", response });
1169
+ }
1170
+ catch (error) {
1171
+ finish({ kind: "failed", error, processed: "yes" });
1172
+ }
1173
+ });
1174
+ stream.on("close", () => {
1175
+ pool.end(entry);
1176
+ if (settled)
1177
+ return;
1178
+ if (signal?.aborted) {
1179
+ finish({ kind: "aborted", reason: signal.reason });
1180
+ return;
1181
+ }
1182
+ const rst = stream.rstCode;
1183
+ const sessionDying = entry.session.destroyed || entry.session.closed || entry.retired;
1184
+ const error = streamError ?? Object.assign(new Error(`HTTP/2 stream closed (code ${rst ?? "?"})`), { code: "ERR_HTTP2_STREAM_ERROR" });
1185
+ if (rst === NGHTTP2_HTTP_1_1_REQUIRED && status === 0) {
1186
+ pool.turnOff("server asked for HTTP/1.1");
1187
+ finish({ kind: "use-fetch" });
1188
+ return;
1189
+ }
1190
+ let processed;
1191
+ if (status !== 0)
1192
+ processed = "yes";
1193
+ else if (rst === NGHTTP2_REFUSED_STREAM)
1194
+ processed = "no";
1195
+ else if (stream.id === undefined)
1196
+ processed = "no";
1197
+ else if (entry.goawayLast !== undefined && stream.id > entry.goawayLast)
1198
+ processed = "no";
1199
+ else
1200
+ processed = "maybe";
1201
+ if (processed !== "no")
1202
+ pool.streamFailed(entry, sessionDying);
1203
+ finish({ kind: "failed", error, processed });
1204
+ });
1205
+ if (body !== undefined) {
1206
+ stream.end(body);
1207
+ }
1208
+ if (tr !== undefined)
1209
+ mark(tr, "sub");
1210
+ });
1211
+ }
1212
+ const EMPTY = Buffer.alloc(0);
1213
+ function toResponse(status, raw, body, method) {
1214
+ const nullBody = method === "HEAD" || status === 204 || status === 205 || status === 304;
1215
+ const enc = raw["content-encoding"];
1216
+ const bytes = nullBody ? null : decodeBody(body, typeof enc === "string" ? enc : null);
1217
+ return new H2Response(status, raw, bytes);
1218
+ }
1219
+ function decodeBody(body, encoding) {
1220
+ if (!encoding || body.length === 0)
1221
+ return body;
1222
+ const codings = encoding.toLowerCase().split(",").map((s) => s.trim()).filter((s) => s && s !== "identity");
1223
+ let out = body;
1224
+ for (const c of codings.reverse()) {
1225
+ if (c === "gzip" || c === "x-gzip")
1226
+ out = zlib.gunzipSync(out);
1227
+ else if (c === "br")
1228
+ out = zlib.brotliDecompressSync(out);
1229
+ else if (c === "deflate") {
1230
+ try {
1231
+ out = zlib.inflateSync(out);
1232
+ }
1233
+ catch {
1234
+ out = zlib.inflateRawSync(out);
1235
+ }
1236
+ }
1237
+ else
1238
+ return body;
1239
+ }
1240
+ return out;
1241
+ }
1242
+ const pools = new Map();
1243
+ function poolFor(origin) {
1244
+ let p = pools.get(origin);
1245
+ if (!p) {
1246
+ p = new OriginPool(new URL(origin));
1247
+ pools.set(origin, p);
1248
+ }
1249
+ return p;
1250
+ }
1251
+ function plainPath(url, at) {
1252
+ if (url.charCodeAt(at) !== 0x2f)
1253
+ return false;
1254
+ let query = false;
1255
+ for (let i = at + 1; i < url.length; i++) {
1256
+ const c = url.charCodeAt(i);
1257
+ if ((c >= 0x61 && c <= 0x7a) || (c >= 0x41 && c <= 0x5a) || (c >= 0x30 && c <= 0x39))
1258
+ continue;
1259
+ if (c === 0x2f || c === 0x2d || c === 0x5f || c === 0x7e || c === 0x3d || c === 0x26)
1260
+ continue;
1261
+ if (c === 0x3f && !query && i + 1 < url.length) {
1262
+ query = true;
1263
+ continue;
1264
+ }
1265
+ return false;
1266
+ }
1267
+ return true;
1268
+ }
1269
+ function targetOf(url, known, remember) {
1270
+ if (known !== undefined && url.startsWith(known.text) && plainPath(url, known.text.length)) {
1271
+ return { origin: known.origin, host: known.host, path: url.slice(known.text.length) };
1272
+ }
1273
+ const u = new URL(url);
1274
+ if (u.protocol !== "https:")
1275
+ return undefined;
1276
+ const path = u.pathname + u.search;
1277
+ const target = { origin: u.origin, host: u.host, path };
1278
+ if (remember !== undefined && url.startsWith("https://")) {
1279
+ let end = 8;
1280
+ while (end < url.length) {
1281
+ const c = url.charCodeAt(end);
1282
+ if (c === 0x2f || c === 0x3f || c === 0x23 || c === 0x5c)
1283
+ break;
1284
+ end++;
1285
+ }
1286
+ if (end > 8 && url.charCodeAt(end) === 0x2f)
1287
+ remember({ text: url.slice(0, end), origin: target.origin, host: target.host });
1288
+ }
1289
+ return target;
1290
+ }
1291
+ function fetchInit(init, deadline) {
1292
+ const timeout = AbortSignal.timeout(Math.max(0, Math.ceil(deadline - performance.now())));
1293
+ return {
1294
+ method: init.method,
1295
+ headers: init.headers,
1296
+ signal: init.signal === undefined ? timeout : AbortSignal.any([timeout, init.signal]),
1297
+ ...(init.body === undefined ? {} : { body: init.body }),
1298
+ ...(init.dispatcher === undefined ? {} : { dispatcher: init.dispatcher }),
1299
+ };
1300
+ }
1301
+ function fetchFailed(cause) {
1302
+ return new TypeError("fetch failed", { cause });
1303
+ }
1304
+ class HandBack {
1305
+ resends;
1306
+ replays;
1307
+ constructor(resends, replays) {
1308
+ this.resends = resends;
1309
+ this.replays = replays;
1310
+ }
1311
+ }
1312
+ const UNSENT = new HandBack(0, 0);
1313
+ const VIA_FETCH = Symbol("via-fetch");
1314
+ const THREAD_GRACE_MS = 1_000;
1315
+ const THREAD_PHASES = ["w.got", "w.sent", "w.head", "w.end"];
1316
+ class TransportThread {
1317
+ origin;
1318
+ worker;
1319
+ port;
1320
+ state = "starting";
1321
+ sessions = 0;
1322
+ limit = DEFAULT_STREAM_LIMIT;
1323
+ everReady = false;
1324
+ closedReason = "";
1325
+ onFail;
1326
+ inflight = new Map();
1327
+ nextId = 1;
1328
+ listening = false;
1329
+ answered = 0;
1330
+ pace = THREAD_DEFAULTS.pace;
1331
+ hrOffset = Number(process.hrtime.bigint()) / 1e6 - performance.now();
1332
+ constructor(origin, worker, port) {
1333
+ this.origin = origin;
1334
+ this.worker = worker;
1335
+ this.port = port;
1336
+ }
1337
+ poll() {
1338
+ for (;;) {
1339
+ let got;
1340
+ try {
1341
+ got = receiveMessageOnPort(this.port);
1342
+ }
1343
+ catch {
1344
+ got = undefined;
1345
+ }
1346
+ if (got === undefined)
1347
+ break;
1348
+ this.onMessage(got.message);
1349
+ }
1350
+ }
1351
+ ready() {
1352
+ if (this.state !== "closed")
1353
+ this.poll();
1354
+ return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit;
1355
+ }
1356
+ onMessage(m) {
1357
+ const msg = m;
1358
+ switch (msg?.t) {
1359
+ case "ready":
1360
+ if (this.state === "starting" || this.state === "ready") {
1361
+ const first = !this.everReady;
1362
+ this.state = "ready";
1363
+ this.everReady = true;
1364
+ this.sessions = msg.sessions ?? 1;
1365
+ if (typeof msg.limit === "number" && msg.limit > 0)
1366
+ this.limit = first ? msg.limit : Math.min(this.limit, msg.limit);
1367
+ const hr = msg.hr;
1368
+ if (typeof hr === "number") {
1369
+ const t = hr - this.hrOffset;
1370
+ traceEventAt(`thread:session${this.sessions}`, t);
1371
+ if (first)
1372
+ traceEventAt(`thread:limit${this.limit}`, t);
1373
+ const c = msg;
1374
+ if (typeof c.index === "number" && typeof c.addr === "string" && typeof c.localPort === "number")
1375
+ traceEventAt(`thread:conn${c.index} ${c.addr} ${c.localPort}`, t);
1376
+ }
1377
+ }
1378
+ return;
1379
+ case "sessions":
1380
+ if (typeof msg.sessions === "number")
1381
+ this.sessions = msg.sessions;
1382
+ return;
1383
+ case "draining":
1384
+ if (this.state !== "closed" && this.state !== "draining") {
1385
+ this.state = "draining";
1386
+ if (this.closedReason === "")
1387
+ this.closedReason = msg.reason ?? "draining";
1388
+ traceEvent("thread:draining");
1389
+ }
1390
+ return;
1391
+ case "closed":
1392
+ this.close(msg.reason ?? "closed");
1393
+ return;
1394
+ case "ack": {
1395
+ const p = this.inflight.get(msg.id ?? -1);
1396
+ if (p !== undefined)
1397
+ p.acked = true;
1398
+ return;
1399
+ }
1400
+ case "res":
1401
+ case "err":
1402
+ this.settle(m);
1403
+ return;
1404
+ }
1405
+ }
1406
+ settle(m) {
1407
+ const id = m["id"];
1408
+ const p = this.inflight.get(id);
1409
+ if (p === undefined)
1410
+ return;
1411
+ this.inflight.delete(id);
1412
+ this.release(p);
1413
+ if (m["t"] === "res") {
1414
+ this.answered++;
1415
+ const body = Buffer.from(m["body"]);
1416
+ let res;
1417
+ try {
1418
+ res = toResponse(m["status"], (m["headers"] ?? {}), body, p.method);
1419
+ }
1420
+ catch (error) {
1421
+ p.reject(fetchFailed(error));
1422
+ return;
1423
+ }
1424
+ if (m["replayed"] === true)
1425
+ res.replayed = true;
1426
+ const tr = p.trace;
1427
+ if (tr !== undefined) {
1428
+ tr.status = res.status;
1429
+ const h = (m["headers"] ?? {});
1430
+ const rx = h["x-promigence-rx-ns"], tx = h["x-promigence-tx-ns"];
1431
+ if (typeof rx === "string")
1432
+ tr.rx = rx;
1433
+ if (typeof tx === "string")
1434
+ tr.tx = tx;
1435
+ if (typeof m["conn"] === "number")
1436
+ tr.conn = m["conn"];
1437
+ const w = m["w"];
1438
+ if (Array.isArray(w)) {
1439
+ for (let i = 0; i < THREAD_PHASES.length; i++)
1440
+ if (typeof w[i] === "number" && w[i] > 0)
1441
+ tr.t[THREAD_PHASES[i]] = w[i] - this.hrOffset;
1442
+ }
1443
+ }
1444
+ p.resolve(res);
1445
+ return;
1446
+ }
1447
+ if (m["kind"] === "timeout") {
1448
+ this.stopUsing("a request timed out");
1449
+ p.reject(timeoutReason());
1450
+ return;
1451
+ }
1452
+ if (m["kind"] === "back") {
1453
+ this.stopUsing("no session");
1454
+ p.resolve(new HandBack(Number(m["resends"]) || 0, Number(m["replays"]) || 0));
1455
+ return;
1456
+ }
1457
+ if (m["processed"] === "no") {
1458
+ this.stopUsing("requests refused");
1459
+ p.resolve(VIA_FETCH);
1460
+ return;
1461
+ }
1462
+ this.stopUsing("a request failed");
1463
+ p.reject(fetchFailed(Object.assign(new Error(String(m["message"] ?? "HTTP/2 stream error")), { code: m["code"] })));
1464
+ }
1465
+ stopUsing(reason) {
1466
+ if (this.state !== "starting" && this.state !== "ready")
1467
+ return;
1468
+ this.state = "draining";
1469
+ this.closedReason = reason;
1470
+ traceEvent("thread:draining");
1471
+ try {
1472
+ this.port.postMessage({ t: "drain" });
1473
+ }
1474
+ catch { }
1475
+ }
1476
+ close(reason) {
1477
+ if (this.state === "closed")
1478
+ return;
1479
+ this.state = "closed";
1480
+ if (this.closedReason === "")
1481
+ this.closedReason = reason;
1482
+ traceEvent("thread:closed");
1483
+ for (const [id, p] of [...this.inflight]) {
1484
+ this.inflight.delete(id);
1485
+ this.release(p);
1486
+ if (!p.acked)
1487
+ p.resolve(UNSENT);
1488
+ else if (p.replayable)
1489
+ p.resolve(new HandBack(0, 1));
1490
+ else
1491
+ p.reject(fetchFailed(Object.assign(new Error(`the transport thread ended: ${reason}`), { code: "ERR_HTTP2_SESSION_ERROR" })));
1492
+ }
1493
+ safely(() => { this.port.close(); });
1494
+ this.unref();
1495
+ if (!this.everReady) {
1496
+ const f = this.onFail;
1497
+ this.onFail = undefined;
1498
+ if (f !== undefined)
1499
+ safely(f);
1500
+ }
1501
+ }
1502
+ ref() {
1503
+ if (!this.listening) {
1504
+ this.listening = true;
1505
+ this.poll();
1506
+ this.port.on("message", (m) => { this.onMessage(m); });
1507
+ }
1508
+ safely(() => { this.port.ref(); });
1509
+ safely(() => { this.worker.ref(); });
1510
+ }
1511
+ unref() {
1512
+ safely(() => { this.port.unref(); });
1513
+ safely(() => { this.worker.unref(); });
1514
+ }
1515
+ release(p) {
1516
+ if (p.signal !== undefined && p.onAbort !== undefined)
1517
+ p.signal.removeEventListener("abort", p.onAbort);
1518
+ if (p.backstop !== undefined)
1519
+ clearTimeout(p.backstop);
1520
+ if (this.inflight.size === 0)
1521
+ this.unref();
1522
+ }
1523
+ send(target, init, replayable, deadline) {
1524
+ const remaining = Math.ceil(deadline - performance.now());
1525
+ if (remaining <= 0)
1526
+ return Promise.reject(timeoutReason());
1527
+ if (init.signal?.aborted)
1528
+ return Promise.reject(init.signal.reason);
1529
+ const headers = {
1530
+ ":method": init.method,
1531
+ ":path": target.path,
1532
+ ":scheme": "https",
1533
+ ":authority": target.host,
1534
+ };
1535
+ for (const k in init.headers) {
1536
+ const name = k.toLowerCase();
1537
+ if (!NOT_IN_H2.has(name))
1538
+ headers[name] = init.headers[k];
1539
+ }
1540
+ if (init.body !== undefined)
1541
+ headers["content-length"] = Buffer.byteLength(init.body, "utf8");
1542
+ const id = this.nextId++;
1543
+ const tr = init.trace;
1544
+ return new Promise((resolve, reject) => {
1545
+ const p = { resolve, reject, method: init.method, replayable, acked: false, trace: tr };
1546
+ const signal = init.signal;
1547
+ if (signal !== undefined) {
1548
+ p.signal = signal;
1549
+ p.onAbort = () => {
1550
+ if (!this.inflight.has(id))
1551
+ return;
1552
+ this.inflight.delete(id);
1553
+ this.release(p);
1554
+ try {
1555
+ this.port.postMessage({ t: "cancel", id });
1556
+ }
1557
+ catch { }
1558
+ reject(signal.reason);
1559
+ };
1560
+ signal.addEventListener("abort", p.onAbort, { once: true });
1561
+ }
1562
+ p.backstop = setTimeout(() => {
1563
+ this.poll();
1564
+ if (!this.inflight.has(id))
1565
+ return;
1566
+ this.inflight.delete(id);
1567
+ this.release(p);
1568
+ try {
1569
+ this.port.postMessage({ t: "cancel", id });
1570
+ }
1571
+ catch { }
1572
+ reject(timeoutReason());
1573
+ this.close("no answer from the transport thread");
1574
+ }, remaining + THREAD_GRACE_MS);
1575
+ safely(() => { p.backstop?.unref?.(); });
1576
+ this.inflight.set(id, p);
1577
+ this.ref();
1578
+ try {
1579
+ const paced = this.pace === "all" || (this.pace === "creates" && init.method === "POST" && CREATE_PATH.test(target.path));
1580
+ this.port.postMessage({ t: "req", id, headers, sensitive: ["authorization"], body: init.body, timeoutMs: remaining, replayable, paced });
1581
+ }
1582
+ catch {
1583
+ this.inflight.delete(id);
1584
+ this.release(p);
1585
+ resolve(UNSENT);
1586
+ return;
1587
+ }
1588
+ if (tr !== undefined) {
1589
+ mark(tr, "sub");
1590
+ tr.via = "thread";
1591
+ }
1592
+ });
1593
+ }
1594
+ }
1595
+ let transportThread;
1596
+ function threadFor(origin) {
1597
+ const t = transportThread;
1598
+ if (t === undefined || t.origin !== origin)
1599
+ return undefined;
1600
+ return t.ready() && !lookupReplaced() ? t : undefined;
1601
+ }
1602
+ function ownFile() {
1603
+ const frame = (new Error().stack ?? "").split("\n")[1] ?? "";
1604
+ const m = /\((.*):\d+:\d+\)\s*$/.exec(frame) ?? /at (.*):\d+:\d+\s*$/.exec(frame);
1605
+ return m?.[1] ?? "";
1606
+ }
1607
+ const THREAD_DEFAULTS = { sessions: 8, gapUs: 2000, pace: "all" };
1608
+ const THREAD_MAX_SESSIONS = 32;
1609
+ const THREAD_MAX_GAP_US = 10_000;
1610
+ const CREATE_PATH = /\/(?:runs|sandboxes)(?:\?|$)/;
1611
+ function threadKnobs(env = process.env) {
1612
+ const read = (name, dflt, off, min, max) => {
1613
+ const raw = (env[name] ?? "").trim().toLowerCase();
1614
+ if (raw === "")
1615
+ return dflt;
1616
+ if (OFF.has(raw))
1617
+ return off;
1618
+ const n = Number(raw);
1619
+ return Number.isInteger(n) && n >= min ? Math.min(max, n) : dflt;
1620
+ };
1621
+ const p = (env["PROMIGENCE_HTTP2_PACE"] ?? "").trim().toLowerCase();
1622
+ return {
1623
+ sessions: read("PROMIGENCE_HTTP2_SESSIONS", THREAD_DEFAULTS.sessions, 1, 1, THREAD_MAX_SESSIONS),
1624
+ gapUs: read("PROMIGENCE_HTTP2_STREAM_GAP_US", THREAD_DEFAULTS.gapUs, 0, 0, THREAD_MAX_GAP_US),
1625
+ pace: p === "all" || p === "creates" ? p : OFF.has(p) ? "off" : THREAD_DEFAULTS.pace,
1626
+ };
1627
+ }
1628
+ function startTransportThread(origin, onFail) {
1629
+ if (transportThread !== undefined)
1630
+ return;
1631
+ try {
1632
+ const here = ownFile();
1633
+ if (!/http2\.js$/.test(here))
1634
+ return;
1635
+ const seam = earlySeam();
1636
+ const knobs = threadKnobs();
1637
+ const file = typeof seam.transportFile === "string" ? seam.transportFile : here.replace(/http2\.js$/, "transport-worker.js");
1638
+ const { port1, port2 } = new MessageChannel();
1639
+ const worker = new Worker(file.startsWith("file:") ? new URL(file) : file, {
1640
+ workerData: {
1641
+ promigenceTransport: true, port: port2, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
1642
+ firstUseMs: tuning.loadFirstUseMs, idleMs: IDLE_CLOSE_MS, connectTimeoutMs: CONNECT_TIMEOUT_MS,
1643
+ lookupTimeoutMs: EARLY_TIMEOUT_MS, trace: TRACE_ON,
1644
+ servers: tuning.earlyServers ?? (Array.isArray(seam.servers) ? seam.servers : undefined),
1645
+ hostsFile: tuning.earlyHostsFile ?? (typeof seam.hostsFile === "string" ? seam.hostsFile : undefined),
1646
+ distrustedForTest: __caGuardInternals.testHashes(),
1647
+ },
1648
+ transferList: [port2],
1649
+ argv: [],
1650
+ execArgv: [],
1651
+ });
1652
+ worker.unref();
1653
+ port1.unref();
1654
+ const t = new TransportThread(origin.origin, worker, port1);
1655
+ t.pace = knobs.pace;
1656
+ t.onFail = onFail;
1657
+ worker.on("error", () => { t.poll(); t.close("error"); });
1658
+ worker.on("exit", () => { t.poll(); t.close("exit"); });
1659
+ transportThread = t;
1660
+ traceEvent("thread:start");
1661
+ }
1662
+ catch {
1663
+ transportThread = undefined;
1664
+ }
1665
+ }
1666
+ const prefixes = [];
1667
+ const MAX_PREFIXES = 4;
1668
+ function knownPrefix(url) {
1669
+ for (let i = 0; i < prefixes.length; i++) {
1670
+ const p = prefixes[i];
1671
+ if (url.startsWith(p.text) && url.charCodeAt(p.text.length) === 0x2f)
1672
+ return p;
1673
+ }
1674
+ return undefined;
1675
+ }
1676
+ function remember(p) {
1677
+ for (let i = 0; i < prefixes.length; i++)
1678
+ if (prefixes[i].text === p.text)
1679
+ return;
1680
+ prefixes.unshift(p);
1681
+ if (prefixes.length > MAX_PREFIXES)
1682
+ prefixes.pop();
1683
+ }
1684
+ export class Http2Transport {
1685
+ fallback;
1686
+ constructor(fallback) {
1687
+ this.fallback = fallback;
1688
+ }
1689
+ preconnect(url) {
1690
+ try {
1691
+ const u = new URL(url);
1692
+ if (u.protocol !== "https:" || customDispatcher())
1693
+ return;
1694
+ poolFor(u.origin).warm();
1695
+ }
1696
+ catch {
1697
+ }
1698
+ }
1699
+ viaFetch(url, init, deadline) {
1700
+ if (init.trace !== undefined)
1701
+ init.trace.via = "fetch";
1702
+ return this.fallback(url, fetchInit(init, deadline));
1703
+ }
1704
+ async fetch(url, init, replayable) {
1705
+ const tr = init.trace;
1706
+ if (tr !== undefined)
1707
+ mark(tr, "h2");
1708
+ const deadline = performance.now() + init.timeoutMs;
1709
+ let target;
1710
+ try {
1711
+ target = targetOf(url, knownPrefix(url), remember);
1712
+ }
1713
+ catch {
1714
+ return this.viaFetch(url, init, deadline);
1715
+ }
1716
+ if (target === undefined || customDispatcher())
1717
+ return this.viaFetch(url, init, deadline);
1718
+ let resends = 0;
1719
+ let replays = 0;
1720
+ const thread = threadFor(target.origin);
1721
+ if (thread !== undefined) {
1722
+ const out = await thread.send(target, init, replayable, deadline);
1723
+ if (out === VIA_FETCH)
1724
+ return this.viaFetch(url, init, deadline);
1725
+ if (!(out instanceof HandBack))
1726
+ return out;
1727
+ resends = out.resends;
1728
+ replays = out.replays;
1729
+ if (tr !== undefined)
1730
+ delete tr.via;
1731
+ }
1732
+ const pool = poolFor(target.origin);
1733
+ if (!pool.usable())
1734
+ return this.viaFetch(url, init, deadline);
1735
+ for (;;) {
1736
+ const entry = pool.tryAcquire() ?? await pool.acquire(deadline, init.signal);
1737
+ if (tr !== undefined)
1738
+ mark(tr, "acq");
1739
+ if (entry === null)
1740
+ return this.viaFetch(url, init, deadline);
1741
+ let out;
1742
+ try {
1743
+ out = await exchange(pool, entry, target, init, deadline);
1744
+ }
1745
+ catch {
1746
+ pool.turnOff("internal error");
1747
+ out = { kind: "failed", error: new Error("HTTP/2 transport error"), processed: "maybe" };
1748
+ }
1749
+ switch (out.kind) {
1750
+ case "response":
1751
+ if (replays > 0)
1752
+ out.response.replayed = true;
1753
+ return out.response;
1754
+ case "aborted":
1755
+ throw out.reason;
1756
+ case "use-fetch":
1757
+ return this.viaFetch(url, init, deadline);
1758
+ case "failed":
1759
+ if (out.processed === "no") {
1760
+ if (++resends <= MAX_UNPROCESSED_RESENDS)
1761
+ continue;
1762
+ return this.viaFetch(url, init, deadline);
1763
+ }
1764
+ if (out.processed === "maybe" && replayable && replays++ < MAX_REPLAYS)
1765
+ continue;
1766
+ throw fetchFailed(out.error);
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+ export function warmTransport() {
1772
+ const res = toResponse(200, { "content-type": "application/json" }, Buffer.from("{}"), "POST");
1773
+ res.header("server-timing");
1774
+ res.textNow();
1775
+ plainPath("https://a/b?c=d", 9);
1776
+ targetOf("https://a/b?c=d", undefined);
1777
+ const p = new Http2Transport(globalThis.fetch).fetch(undefined, undefined, false);
1778
+ p.catch(() => { });
1779
+ const none = "";
1780
+ exchange({ end() { } }, undefined, { origin: none, host: none, path: none }, undefined, 0).catch(() => { });
1781
+ }
1782
+ export const __http2Internals = {
1783
+ pools() {
1784
+ return [...pools.values()].map((p) => ({
1785
+ origin: p.origin.origin,
1786
+ sessions: p.sessions.length,
1787
+ opened: p.opened,
1788
+ warmed: p.warmed,
1789
+ connecting: p.isConnecting(),
1790
+ parked: p.isParked(),
1791
+ off: p.off,
1792
+ suspended: !p.off && !p.usable(),
1793
+ offReason: p.offReason,
1794
+ active: p.sessions.reduce((a, e) => a + e.active, 0),
1795
+ split: p.sessions.map((e) => e.active),
1796
+ }));
1797
+ },
1798
+ targetOf(url, known) {
1799
+ let prefix;
1800
+ const target = targetOf(url, known, (p) => { prefix = p; });
1801
+ return { target, prefix };
1802
+ },
1803
+ prefixes() {
1804
+ return prefixes.map((p) => p.text);
1805
+ },
1806
+ reset() {
1807
+ for (const p of pools.values()) {
1808
+ for (const e of [...p.sessions]) {
1809
+ p.retire(e);
1810
+ safely(() => e.session.destroy());
1811
+ }
1812
+ }
1813
+ pools.clear();
1814
+ },
1815
+ pool(origin) {
1816
+ return new OriginPool(new URL(origin));
1817
+ },
1818
+ thread() {
1819
+ const t = transportThread;
1820
+ if (t === undefined)
1821
+ return undefined;
1822
+ t.poll();
1823
+ return { state: t.state, sessions: t.sessions, limit: t.limit, answered: t.answered, everReady: t.everReady, closedReason: t.closedReason };
1824
+ },
1825
+ threadKnobs(env) {
1826
+ return threadKnobs(env);
1827
+ },
1828
+ crashThreadForTest() {
1829
+ try {
1830
+ transportThread?.port.postMessage({ t: "crash-for-test" });
1831
+ }
1832
+ catch { }
1833
+ },
1834
+ early: {
1835
+ start(host) {
1836
+ return earlyLookupFor(host.toLowerCase()) !== undefined;
1837
+ },
1838
+ state(host) {
1839
+ const e = earlies.get(host.toLowerCase());
1840
+ if (e === undefined)
1841
+ return undefined;
1842
+ const fresh = earlyFresh(e);
1843
+ return { state: e.state, ...(e.answer === undefined ? {} : { answer: e.answer }), fresh: fresh !== undefined };
1844
+ },
1845
+ hosts() {
1846
+ return [...earlies.keys()];
1847
+ },
1848
+ reset() {
1849
+ for (const e of earlies.values())
1850
+ safely(() => { e.port.close(); });
1851
+ earlies.clear();
1852
+ },
1853
+ },
1854
+ };
1855
+ const AT_LOAD = Symbol.for("promigence.sdk.connect-at-load");
1856
+ function isMainThread() {
1857
+ try {
1858
+ const get = process.getBuiltinModule;
1859
+ const wt = typeof get === "function" ? get("node:worker_threads") : undefined;
1860
+ return wt?.isMainThread === true;
1861
+ }
1862
+ catch {
1863
+ return false;
1864
+ }
1865
+ }
1866
+ function connectAtLoad() {
1867
+ try {
1868
+ if (!isNode() || Number(process.versions.node.split(".")[0]) < 22)
1869
+ return;
1870
+ const off = () => OFF.has((process.env["PROMIGENCE_PRECONNECT"] ?? "").trim().toLowerCase());
1871
+ if (off())
1872
+ return;
1873
+ const g = globalThis;
1874
+ if (g[AT_LOAD] !== undefined)
1875
+ return;
1876
+ g[AT_LOAD] = true;
1877
+ try {
1878
+ if ((process.env["PROMIGENCE_API_KEY"] ?? "").trim() !== "" && isMainThread() && http2Allowed(undefined, process.env)) {
1879
+ const raw = process.env["PROMIGENCE_API_URL"];
1880
+ const url = new URL(raw ? raw : DEFAULT_API_URL);
1881
+ const host = url.hostname.replace(/^\[(.*)\]$/, "$1").toLowerCase();
1882
+ const threadOff = OFF.has((process.env["PROMIGENCE_TRANSPORT_THREAD"] ?? "").trim().toLowerCase());
1883
+ if (url.protocol === "https:" && !customDispatcher() && earlyPlatform() && v4First() && !threadOff && earlySeam().transportThread !== false) {
1884
+ startTransportThread(url, () => { if (loadTurnRan)
1885
+ warmAtLoad(); });
1886
+ }
1887
+ else if (url.protocol === "https:" && !customDispatcher() && isIP(host) === 0 && earlyPlatform() && v4First())
1888
+ earlyLookupFor(host);
1889
+ }
1890
+ }
1891
+ catch {
1892
+ }
1893
+ let loadTurnRan = false;
1894
+ const warmAtLoad = () => {
1895
+ try {
1896
+ if (off() || (process.env["PROMIGENCE_API_KEY"] ?? "").trim() === "")
1897
+ return;
1898
+ if (!isMainThread() || !http2Allowed(undefined, process.env))
1899
+ return;
1900
+ const raw = process.env["PROMIGENCE_API_URL"];
1901
+ const url = new URL(raw ? raw : DEFAULT_API_URL);
1902
+ if (url.protocol !== "https:" || customDispatcher())
1903
+ return;
1904
+ if (transportThread !== undefined && transportThread.origin === url.origin) {
1905
+ transportThread.poll();
1906
+ if (transportThread.state !== "closed" || transportThread.everReady)
1907
+ return;
1908
+ }
1909
+ warmTls();
1910
+ poolFor(url.origin).warm(tuning.loadFirstUseMs);
1911
+ }
1912
+ catch {
1913
+ }
1914
+ };
1915
+ const immediate = setImmediate(() => { loadTurnRan = true; warmAtLoad(); });
1916
+ immediate.unref?.();
1917
+ }
1918
+ catch {
1919
+ }
1920
+ }
1921
+ connectAtLoad();