@openclaw/proxyline 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ import type { ProxylineHandle, ProxylineOptions } from "./types.js";
2
+ export declare function installProxyline(options: ProxylineOptions): ProxylineHandle;
3
+ export declare const installGlobalProxy: typeof installProxyline;
4
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAIV,eAAe,EACf,gBAAgB,EAIjB,MAAM,YAAY,CAAC;AA4sBpB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAyI3E;AAED,eAAO,MAAM,kBAAkB,yBAAmB,CAAC"}
@@ -0,0 +1,651 @@
1
+ import http from "node:http";
2
+ import https from "node:https";
3
+ import { Agent as UndiciAgent, Dispatcher, FormData as UndiciFormData, Headers as UndiciHeaders, Request as UndiciRequest, Response as UndiciResponse, errors as undiciErrors, fetch as undiciFetch, getGlobalDispatcher, ProxyAgent as UndiciProxyAgent, setGlobalDispatcher, } from "undici";
4
+ import { createAmbientProxyResolver, EMPTY_PROXY_ENV, resolveAmbientProxyForUrl, readProxyEnv, } from "./env.js";
5
+ import { bindNodeHttpMethod, createDirectNodeAgent, createNodeProxyAgent, } from "./node-http.js";
6
+ import { formatUrl, ProxylineError, redactProxyUrl, resolveProxyTlsCa, } from "./shared.js";
7
+ import { PROXYLINE_DISPATCHER_BRAND } from "./dispatcher-brand.js";
8
+ let activeRuntime;
9
+ let activeHandle;
10
+ // Node's global fetch types come from bundled undici-types, while the runtime
11
+ // implementation intentionally delegates to this package's undici dependency.
12
+ const proxylineHeaders = UndiciHeaders;
13
+ const proxylineRequest = UndiciRequest;
14
+ const proxylineResponse = UndiciResponse;
15
+ const proxylineFormData = UndiciFormData;
16
+ function getRequestDispatcher(request) {
17
+ for (const symbol of Object.getOwnPropertySymbols(request)) {
18
+ if (symbol.description !== "dispatcher") {
19
+ continue;
20
+ }
21
+ return Reflect.get(request, symbol);
22
+ }
23
+ return undefined;
24
+ }
25
+ function isFetchRequestLike(value) {
26
+ if (typeof value !== "object" || value === null) {
27
+ return false;
28
+ }
29
+ const record = value;
30
+ return (typeof record.url === "string" &&
31
+ typeof record.method === "string" &&
32
+ typeof record.arrayBuffer === "function" &&
33
+ record.headers !== undefined);
34
+ }
35
+ async function createProxylineRequestFromRequestLike(request, options) {
36
+ const init = {
37
+ headers: request.headers,
38
+ method: request.method,
39
+ };
40
+ if (request.cache !== undefined) {
41
+ init.cache = request.cache;
42
+ }
43
+ if (request.credentials !== undefined) {
44
+ init.credentials = request.credentials;
45
+ }
46
+ if (request.integrity !== undefined) {
47
+ init.integrity = request.integrity;
48
+ }
49
+ if (request.keepalive !== undefined) {
50
+ init.keepalive = request.keepalive;
51
+ }
52
+ if (request.mode !== undefined) {
53
+ init.mode = request.mode;
54
+ }
55
+ if (request.redirect !== undefined) {
56
+ init.redirect = request.redirect;
57
+ }
58
+ if (request.referrer !== undefined) {
59
+ init.referrer = request.referrer;
60
+ }
61
+ if (request.referrerPolicy !== undefined) {
62
+ init.referrerPolicy = request.referrerPolicy;
63
+ }
64
+ if (options.preserveDispatcher) {
65
+ const dispatcher = getRequestDispatcher(request);
66
+ if (dispatcher !== undefined) {
67
+ Reflect.set(init, "dispatcher", dispatcher);
68
+ }
69
+ }
70
+ if (request.signal !== undefined) {
71
+ init.signal = request.signal;
72
+ }
73
+ if (options.includeBody &&
74
+ request.body !== null &&
75
+ request.method !== "GET" &&
76
+ request.method !== "HEAD") {
77
+ init.body = request.body;
78
+ init.duplex = "half";
79
+ }
80
+ const requestUnknown = Reflect.construct(proxylineRequest, [request.url, init]);
81
+ if (!(requestUnknown instanceof proxylineRequest)) {
82
+ throw new TypeError("Proxyline failed to normalize a fetch Request.");
83
+ }
84
+ return requestUnknown;
85
+ }
86
+ function requestInitOverridesBody(init) {
87
+ if (typeof init !== "object" || init === null) {
88
+ return false;
89
+ }
90
+ return "body" in init;
91
+ }
92
+ async function normalizeFetchInput(input, init, options) {
93
+ if ((input instanceof proxylineRequest && options.preserveDispatcher) || !isFetchRequestLike(input)) {
94
+ return input;
95
+ }
96
+ return await createProxylineRequestFromRequestLike(input, {
97
+ includeBody: !requestInitOverridesBody(init),
98
+ preserveDispatcher: options.preserveDispatcher,
99
+ });
100
+ }
101
+ function stripFetchDispatcher(init) {
102
+ if (typeof init !== "object" || init === null) {
103
+ return init;
104
+ }
105
+ const sanitized = Object.create(init);
106
+ Reflect.defineProperty(sanitized, "dispatcher", {
107
+ configurable: true,
108
+ enumerable: true,
109
+ value: undefined,
110
+ writable: true,
111
+ });
112
+ return sanitized;
113
+ }
114
+ const proxylineFetch = async (input, init) => {
115
+ const managedMode = activeRuntime?.mode === "managed";
116
+ const normalizedInput = await normalizeFetchInput(input, init, {
117
+ preserveDispatcher: !managedMode,
118
+ });
119
+ const normalizedInit = managedMode ? stripFetchDispatcher(init) : init;
120
+ const response = await Reflect.apply(undiciFetch, undefined, normalizedInit === undefined ? [normalizedInput] : [normalizedInput, normalizedInit]);
121
+ if (!(response instanceof proxylineResponse)) {
122
+ throw new TypeError("Proxyline fetch returned a non-Response value.");
123
+ }
124
+ return response;
125
+ };
126
+ function normalizeProxyUrl(value) {
127
+ if (value === undefined) {
128
+ return undefined;
129
+ }
130
+ const url = value instanceof URL ? new URL(value.href) : new URL(value);
131
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
132
+ throw new ProxylineError("UNSUPPORTED_PROXY_PROTOCOL", `Proxyline only supports http:// and https:// proxy endpoints in this slice: ${url.protocol}`);
133
+ }
134
+ return url;
135
+ }
136
+ function emit(onEvent, event) {
137
+ onEvent?.(event);
138
+ }
139
+ function isProxyableUrlProtocol(protocol) {
140
+ return protocol === "http:" ||
141
+ protocol === "https:" ||
142
+ protocol === "ws:" ||
143
+ protocol === "wss:";
144
+ }
145
+ function shouldBypassManagedProxy(bypassPolicy, bypasses, url, surface) {
146
+ if (bypasses.has(url, surface)) {
147
+ return true;
148
+ }
149
+ if (bypassPolicy === undefined) {
150
+ return false;
151
+ }
152
+ return bypassPolicy({ surface, url: formatUrl(url) });
153
+ }
154
+ function bypassKey(url, surface) {
155
+ return `${surface ?? "*"}\n${formatUrl(url)}`;
156
+ }
157
+ function createDynamicBypassRegistry() {
158
+ const counts = new Map();
159
+ return {
160
+ add: (registration) => {
161
+ const key = bypassKey(registration.url, registration.surface);
162
+ counts.set(key, (counts.get(key) ?? 0) + 1);
163
+ let stopped = false;
164
+ return () => {
165
+ if (stopped) {
166
+ return;
167
+ }
168
+ stopped = true;
169
+ const next = (counts.get(key) ?? 1) - 1;
170
+ if (next <= 0) {
171
+ counts.delete(key);
172
+ }
173
+ else {
174
+ counts.set(key, next);
175
+ }
176
+ };
177
+ },
178
+ has: (url, surface) => (counts.get(bypassKey(url, surface)) ?? 0) > 0 ||
179
+ (counts.get(bypassKey(url, undefined)) ?? 0) > 0,
180
+ };
181
+ }
182
+ function proxyEnvSnapshotKey(env) {
183
+ return JSON.stringify(env ?? EMPTY_PROXY_ENV);
184
+ }
185
+ function createManagedProxyResolver(proxyUrl, bypassPolicy, bypasses) {
186
+ const redactedProxyUrl = redactProxyUrl(proxyUrl);
187
+ return {
188
+ active: true,
189
+ describeProxy: () => redactedProxyUrl,
190
+ explain: (url, surface) => {
191
+ const formattedUrl = formatUrl(url);
192
+ if (!isProxyableUrlProtocol(new URL(url).protocol)) {
193
+ return {
194
+ kind: "direct",
195
+ reason: "managed-proxy-unsupported-url-scheme",
196
+ surface,
197
+ url: formattedUrl,
198
+ };
199
+ }
200
+ if (shouldBypassManagedProxy(bypassPolicy, bypasses, url, surface)) {
201
+ return {
202
+ kind: "direct",
203
+ reason: "managed-proxy-bypass-policy",
204
+ surface,
205
+ url: formattedUrl,
206
+ };
207
+ }
208
+ return {
209
+ kind: "proxied",
210
+ reason: "managed-proxy-active",
211
+ surface,
212
+ url: formattedUrl,
213
+ proxyUrl: redactedProxyUrl,
214
+ };
215
+ },
216
+ getProxyForUrl: (url, surface = "unknown") => {
217
+ const protocol = new URL(url).protocol;
218
+ return isProxyableUrlProtocol(protocol) &&
219
+ !shouldBypassManagedProxy(bypassPolicy, bypasses, url, surface)
220
+ ? proxyUrl.href
221
+ : "";
222
+ },
223
+ };
224
+ }
225
+ function finiteNonNegativeInteger(value) {
226
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
227
+ ? Math.floor(value)
228
+ : undefined;
229
+ }
230
+ function finitePositiveInteger(value) {
231
+ return typeof value === "number" && Number.isFinite(value) && value > 0
232
+ ? Math.floor(value)
233
+ : undefined;
234
+ }
235
+ function resolveUndiciBaseOptions(options) {
236
+ const bodyTimeout = finiteNonNegativeInteger(options?.bodyTimeout);
237
+ const headersTimeout = finiteNonNegativeInteger(options?.headersTimeout);
238
+ return {
239
+ ...(options?.allowH2 !== undefined ? { allowH2: options.allowH2 } : {}),
240
+ ...(bodyTimeout !== undefined ? { bodyTimeout } : {}),
241
+ ...(headersTimeout !== undefined ? { headersTimeout } : {}),
242
+ ...(options?.connect !== undefined
243
+ ? {
244
+ connect: {
245
+ ...(options.connect.autoSelectFamily !== undefined
246
+ ? { autoSelectFamily: options.connect.autoSelectFamily }
247
+ : {}),
248
+ ...(finitePositiveInteger(options.connect.autoSelectFamilyAttemptTimeout) !== undefined
249
+ ? {
250
+ autoSelectFamilyAttemptTimeout: finitePositiveInteger(options.connect.autoSelectFamilyAttemptTimeout),
251
+ }
252
+ : {}),
253
+ },
254
+ }
255
+ : {}),
256
+ };
257
+ }
258
+ function createUndiciAgent(options) {
259
+ return new UndiciAgent(resolveUndiciBaseOptions(options));
260
+ }
261
+ function createUndiciProxyAgent(proxyUrl, options) {
262
+ return new UndiciProxyAgent({
263
+ ...resolveUndiciBaseOptions(options.undici),
264
+ uri: proxyUrl,
265
+ ...(options.proxyCa !== undefined ? { proxyTls: { ca: options.proxyCa } } : {}),
266
+ });
267
+ }
268
+ function createUndiciProxyDispatcher(options, dispatcherOptions) {
269
+ if (options.mode === "ambient") {
270
+ if (!options.active) {
271
+ return createUndiciAgent(dispatcherOptions.undici);
272
+ }
273
+ return new AmbientUndiciDispatcher(options.env, dispatcherOptions);
274
+ }
275
+ return new ManagedUndiciDispatcher(options.resolver, dispatcherOptions);
276
+ }
277
+ class ManagedUndiciDispatcher extends Dispatcher {
278
+ [PROXYLINE_DISPATCHER_BRAND] = true;
279
+ #directDispatcher;
280
+ #dispatcherOptions;
281
+ #proxyDispatchers = new Map();
282
+ #resolver;
283
+ #closedError;
284
+ constructor(resolver, dispatcherOptions) {
285
+ super();
286
+ this.#resolver = resolver;
287
+ this.#dispatcherOptions = dispatcherOptions;
288
+ this.#directDispatcher = createUndiciAgent(dispatcherOptions.undici);
289
+ }
290
+ dispatch(options, handler) {
291
+ if (this.#closedError !== undefined) {
292
+ if (handler.onError === undefined) {
293
+ throw this.#closedError;
294
+ }
295
+ handler.onError(this.#closedError);
296
+ return false;
297
+ }
298
+ const url = resolveUndiciDispatchUrl(options);
299
+ const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
300
+ const dispatcher = proxyUrl === "" ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
301
+ return dispatcher.dispatch(options, handler);
302
+ }
303
+ close(callback) {
304
+ const closing = this.#closeAll();
305
+ if (callback === undefined) {
306
+ return closing;
307
+ }
308
+ closing.then(callback, callback);
309
+ }
310
+ destroy(errorOrCallback, callback) {
311
+ const error = typeof errorOrCallback === "function" ? null : errorOrCallback ?? null;
312
+ const destroyCallback = typeof errorOrCallback === "function" ? errorOrCallback : callback;
313
+ const destroying = this.#destroyAll(error);
314
+ if (destroyCallback === undefined) {
315
+ return destroying;
316
+ }
317
+ destroying.then(destroyCallback, destroyCallback);
318
+ }
319
+ #proxyDispatcher(proxyUrl) {
320
+ const existing = this.#proxyDispatchers.get(proxyUrl);
321
+ if (existing !== undefined) {
322
+ return existing;
323
+ }
324
+ const dispatcher = createUndiciProxyAgent(proxyUrl, this.#dispatcherOptions);
325
+ this.#proxyDispatchers.set(proxyUrl, dispatcher);
326
+ return dispatcher;
327
+ }
328
+ async #closeAll() {
329
+ this.#closedError ??= new undiciErrors.ClientClosedError();
330
+ const proxyDispatchers = [...this.#proxyDispatchers.values()];
331
+ this.#proxyDispatchers.clear();
332
+ await Promise.all([
333
+ this.#directDispatcher.close(),
334
+ ...proxyDispatchers.map((dispatcher) => dispatcher.close()),
335
+ ]);
336
+ }
337
+ async #destroyAll(error) {
338
+ this.#closedError ??= error ?? new undiciErrors.ClientDestroyedError();
339
+ const proxyDispatchers = [...this.#proxyDispatchers.values()];
340
+ this.#proxyDispatchers.clear();
341
+ await Promise.all([
342
+ this.#directDispatcher.destroy(error),
343
+ ...proxyDispatchers.map((dispatcher) => dispatcher.destroy(error)),
344
+ ]);
345
+ }
346
+ }
347
+ class AmbientUndiciDispatcher extends Dispatcher {
348
+ [PROXYLINE_DISPATCHER_BRAND] = true;
349
+ #directDispatcher;
350
+ #dispatcherOptions;
351
+ #env;
352
+ #proxyDispatchers = new Map();
353
+ #closedError;
354
+ constructor(env, dispatcherOptions) {
355
+ super();
356
+ this.#env = env;
357
+ this.#dispatcherOptions = dispatcherOptions;
358
+ this.#directDispatcher = createUndiciAgent(dispatcherOptions.undici);
359
+ }
360
+ dispatch(options, handler) {
361
+ if (this.#closedError !== undefined) {
362
+ if (handler.onError === undefined) {
363
+ throw this.#closedError;
364
+ }
365
+ handler.onError(this.#closedError);
366
+ return false;
367
+ }
368
+ const url = resolveUndiciDispatchUrl(options);
369
+ const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
370
+ const dispatcher = proxyUrl === undefined ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
371
+ return dispatcher.dispatch(options, handler);
372
+ }
373
+ close(callback) {
374
+ const closing = this.#closeAll();
375
+ if (callback === undefined) {
376
+ return closing;
377
+ }
378
+ closing.then(callback, callback);
379
+ }
380
+ destroy(errorOrCallback, callback) {
381
+ const error = typeof errorOrCallback === "function" ? null : errorOrCallback ?? null;
382
+ const destroyCallback = typeof errorOrCallback === "function" ? errorOrCallback : callback;
383
+ const destroying = this.#destroyAll(error);
384
+ if (destroyCallback === undefined) {
385
+ return destroying;
386
+ }
387
+ destroying.then(destroyCallback, destroyCallback);
388
+ }
389
+ #proxyDispatcher(proxyUrl) {
390
+ const existing = this.#proxyDispatchers.get(proxyUrl);
391
+ if (existing !== undefined) {
392
+ return existing;
393
+ }
394
+ const dispatcher = createUndiciProxyAgent(proxyUrl, this.#dispatcherOptions);
395
+ this.#proxyDispatchers.set(proxyUrl, dispatcher);
396
+ return dispatcher;
397
+ }
398
+ async #closeAll() {
399
+ this.#closedError ??= new undiciErrors.ClientClosedError();
400
+ const proxyDispatchers = [...this.#proxyDispatchers.values()];
401
+ this.#proxyDispatchers.clear();
402
+ await Promise.all([
403
+ this.#directDispatcher.close(),
404
+ ...proxyDispatchers.map((dispatcher) => dispatcher.close()),
405
+ ]);
406
+ }
407
+ async #destroyAll(error) {
408
+ this.#closedError ??= error ?? new undiciErrors.ClientDestroyedError();
409
+ const proxyDispatchers = [...this.#proxyDispatchers.values()];
410
+ this.#proxyDispatchers.clear();
411
+ await Promise.all([
412
+ this.#directDispatcher.destroy(error),
413
+ ...proxyDispatchers.map((dispatcher) => dispatcher.destroy(error)),
414
+ ]);
415
+ }
416
+ }
417
+ function resolveUndiciDispatchUrl(options) {
418
+ if (options.origin !== undefined) {
419
+ const origin = options.origin.toString().replace(/\/$/, "");
420
+ const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
421
+ return new URL(`${origin}${path}`).href;
422
+ }
423
+ try {
424
+ return new URL(options.path).href;
425
+ }
426
+ catch {
427
+ return undefined;
428
+ }
429
+ }
430
+ function restoreNodeHttpSnapshot(snapshot) {
431
+ http.request = snapshot.httpRequest;
432
+ http.get = snapshot.httpGet;
433
+ http.globalAgent = snapshot.httpGlobalAgent;
434
+ https.request = snapshot.httpsRequest;
435
+ https.get = snapshot.httpsGet;
436
+ https.globalAgent = snapshot.httpsGlobalAgent;
437
+ }
438
+ function installRuntime(resolver, dispatcherOptions, proxyCa, options) {
439
+ if (activeRuntime !== undefined) {
440
+ throw new ProxylineError("RUNTIME_ALREADY_ACTIVE", "Proxyline already has an active runtime.");
441
+ }
442
+ const snapshot = {
443
+ httpRequest: http.request,
444
+ httpGet: http.get,
445
+ httpGlobalAgent: http.globalAgent,
446
+ httpsRequest: https.request,
447
+ httpsGet: https.get,
448
+ httpsGlobalAgent: https.globalAgent,
449
+ };
450
+ const nodeHttpAgent = createNodeProxyAgent(resolver, proxyCa, "http");
451
+ const nodeHttpsAgent = createNodeProxyAgent(resolver, proxyCa, "https");
452
+ const originalDispatcher = getGlobalDispatcher();
453
+ const originalFetch = globalThis.fetch;
454
+ const originalFormData = globalThis.FormData;
455
+ const originalHeaders = globalThis.Headers;
456
+ const originalRequest = globalThis.Request;
457
+ const originalResponse = globalThis.Response;
458
+ const installedDispatcher = createUndiciProxyDispatcher(dispatcherOptions, {
459
+ proxyCa,
460
+ undici: options.undici,
461
+ });
462
+ const runtime = {
463
+ ambientEnv: options.ambientEnv,
464
+ bypassPolicy: options.bypassPolicy,
465
+ installedDispatcher,
466
+ mode: dispatcherOptions.mode,
467
+ nodeHttpAgent,
468
+ nodeHttpsAgent,
469
+ originalDispatcher,
470
+ originalFetch,
471
+ originalFormData,
472
+ originalHeaders,
473
+ originalRequest,
474
+ originalResponse,
475
+ proxyCa,
476
+ proxyUrl: options.proxyUrl?.href,
477
+ snapshot,
478
+ undiciOptions: options.undici,
479
+ };
480
+ activeRuntime = runtime;
481
+ try {
482
+ http.globalAgent = nodeHttpAgent;
483
+ https.globalAgent = nodeHttpsAgent;
484
+ http.request = bindNodeHttpMethod(snapshot.httpRequest, () => createNodeProxyAgent(resolver, proxyCa, "http"));
485
+ http.get = bindNodeHttpMethod(snapshot.httpGet, () => createNodeProxyAgent(resolver, proxyCa, "http"));
486
+ https.request = bindNodeHttpMethod(snapshot.httpsRequest, () => createNodeProxyAgent(resolver, proxyCa, "https"));
487
+ https.get = bindNodeHttpMethod(snapshot.httpsGet, () => createNodeProxyAgent(resolver, proxyCa, "https"));
488
+ setGlobalDispatcher(installedDispatcher);
489
+ globalThis.fetch = proxylineFetch;
490
+ globalThis.FormData = proxylineFormData;
491
+ globalThis.Headers = proxylineHeaders;
492
+ globalThis.Request = proxylineRequest;
493
+ globalThis.Response = proxylineResponse;
494
+ }
495
+ catch (error) {
496
+ restoreNodeHttpSnapshot(snapshot);
497
+ setGlobalDispatcher(originalDispatcher);
498
+ globalThis.fetch = originalFetch;
499
+ globalThis.FormData = originalFormData;
500
+ globalThis.Headers = originalHeaders;
501
+ globalThis.Request = originalRequest;
502
+ globalThis.Response = originalResponse;
503
+ activeRuntime = undefined;
504
+ void installedDispatcher.destroy();
505
+ nodeHttpAgent.destroy();
506
+ nodeHttpsAgent.destroy();
507
+ throw error;
508
+ }
509
+ return runtime;
510
+ }
511
+ function stopRuntime(runtime) {
512
+ if (activeRuntime !== runtime) {
513
+ return;
514
+ }
515
+ restoreNodeHttpSnapshot(runtime.snapshot);
516
+ setGlobalDispatcher(runtime.originalDispatcher);
517
+ globalThis.fetch = runtime.originalFetch;
518
+ globalThis.FormData = runtime.originalFormData;
519
+ globalThis.Headers = runtime.originalHeaders;
520
+ globalThis.Request = runtime.originalRequest;
521
+ globalThis.Response = runtime.originalResponse;
522
+ void runtime.installedDispatcher.destroy();
523
+ runtime.nodeHttpAgent.destroy();
524
+ runtime.nodeHttpsAgent.destroy();
525
+ activeRuntime = undefined;
526
+ activeHandle = undefined;
527
+ }
528
+ export function installProxyline(options) {
529
+ const proxyUrl = options.mode === "managed" ? normalizeProxyUrl(options.proxyUrl) : undefined;
530
+ const ambientEnv = proxyUrl === undefined ? readProxyEnv() : undefined;
531
+ if (options.mode === "managed" && proxyUrl === undefined) {
532
+ throw new ProxylineError("MANAGED_PROXY_URL_REQUIRED", "Proxyline managed mode requires an explicit proxyUrl.");
533
+ }
534
+ const activePolicy = options.ifActive ?? "error";
535
+ if (activeRuntime !== undefined) {
536
+ if (activePolicy === "replace") {
537
+ activeHandle?.stop();
538
+ }
539
+ else if (activePolicy === "reuse-compatible" &&
540
+ activeHandle !== undefined &&
541
+ activeRuntime.mode === options.mode &&
542
+ activeRuntime.proxyUrl === proxyUrl?.href &&
543
+ proxyEnvSnapshotKey(activeRuntime.ambientEnv) === proxyEnvSnapshotKey(ambientEnv) &&
544
+ activeRuntime.proxyCa === resolveProxyTlsCa(options.proxyTls) &&
545
+ activeRuntime.bypassPolicy === options.bypassPolicy &&
546
+ JSON.stringify(activeRuntime.undiciOptions ?? {}) === JSON.stringify(options.undici ?? {})) {
547
+ return activeHandle;
548
+ }
549
+ else {
550
+ throw new ProxylineError("RUNTIME_ALREADY_ACTIVE", "Proxyline already has an active runtime.");
551
+ }
552
+ }
553
+ let stopped = false;
554
+ const proxyCa = resolveProxyTlsCa(options.proxyTls);
555
+ const dynamicBypasses = createDynamicBypassRegistry();
556
+ const resolver = proxyUrl !== undefined
557
+ ? createManagedProxyResolver(proxyUrl, options.bypassPolicy, dynamicBypasses)
558
+ : createAmbientProxyResolver(ambientEnv ?? EMPTY_PROXY_ENV);
559
+ const redactedProxyUrl = resolver.describeProxy();
560
+ const hasActiveProxy = resolver.active;
561
+ const runtime = hasActiveProxy
562
+ ? installRuntime(resolver, proxyUrl !== undefined
563
+ ? { mode: "managed", resolver }
564
+ : { mode: "ambient", env: ambientEnv ?? EMPTY_PROXY_ENV, active: hasActiveProxy }, proxyCa, {
565
+ ambientEnv,
566
+ bypassPolicy: options.bypassPolicy,
567
+ proxyUrl,
568
+ undici: options.undici,
569
+ })
570
+ : undefined;
571
+ emit(options.onEvent, {
572
+ type: "runtime.installed",
573
+ mode: options.mode,
574
+ active: hasActiveProxy,
575
+ ...(redactedProxyUrl ? { proxyUrl: redactedProxyUrl } : {}),
576
+ });
577
+ const handle = {
578
+ mode: options.mode,
579
+ active: hasActiveProxy,
580
+ ...(redactedProxyUrl ? { proxyUrl: redactedProxyUrl } : {}),
581
+ createNodeAgent: () => {
582
+ if (!hasActiveProxy || stopped) {
583
+ return createDirectNodeAgent();
584
+ }
585
+ return createNodeProxyAgent(resolver, proxyCa);
586
+ },
587
+ createUndiciDispatcher: () => stopped
588
+ ? createUndiciAgent(options.undici)
589
+ : createUndiciProxyDispatcher(proxyUrl !== undefined
590
+ ? { mode: "managed", resolver }
591
+ : { mode: "ambient", env: ambientEnv ?? EMPTY_PROXY_ENV, active: hasActiveProxy }, { proxyCa, undici: options.undici }),
592
+ createWebSocketAgent: () => {
593
+ if (!hasActiveProxy || stopped) {
594
+ return createDirectNodeAgent();
595
+ }
596
+ return createNodeProxyAgent(resolver, proxyCa);
597
+ },
598
+ explain: (url, explainOptions) => {
599
+ const decision = stopped
600
+ ? {
601
+ kind: "direct",
602
+ reason: "runtime-stopped",
603
+ surface: explainOptions?.surface ?? "unknown",
604
+ url: formatUrl(url),
605
+ }
606
+ : resolver.explain(url, explainOptions?.surface ?? "unknown");
607
+ emit(options.onEvent, { type: "decision", decision });
608
+ return decision;
609
+ },
610
+ registerBypass: (registration) => {
611
+ if (stopped || proxyUrl === undefined) {
612
+ return () => { };
613
+ }
614
+ return dynamicBypasses.add(registration);
615
+ },
616
+ stop: () => {
617
+ if (stopped) {
618
+ return;
619
+ }
620
+ stopped = true;
621
+ if (runtime !== undefined) {
622
+ stopRuntime(runtime);
623
+ }
624
+ emit(options.onEvent, { type: "runtime.stopped", mode: options.mode });
625
+ },
626
+ withBypass: (registration, run) => {
627
+ const unregister = handle.registerBypass(registration);
628
+ try {
629
+ const result = run();
630
+ if (isPromiseLike(result)) {
631
+ void Promise.resolve(result).then(unregister, unregister);
632
+ return result;
633
+ }
634
+ unregister();
635
+ return result;
636
+ }
637
+ catch (error) {
638
+ unregister();
639
+ throw error;
640
+ }
641
+ },
642
+ };
643
+ activeHandle = hasActiveProxy ? handle : activeHandle;
644
+ return handle;
645
+ }
646
+ export const installGlobalProxy = installProxyline;
647
+ function isPromiseLike(value) {
648
+ return typeof value === "object" &&
649
+ value !== null &&
650
+ typeof value.then === "function";
651
+ }
package/dist/shared.d.ts CHANGED
@@ -7,5 +7,6 @@ export declare class ProxylineError extends Error {
7
7
  constructor(code: string, message: string);
8
8
  }
9
9
  export declare function resolveProxyTlsCa(options: ProxylineTlsOptions | undefined): string | undefined;
10
+ export declare function formatUrl(value: string | URL): string;
10
11
  export declare function redactProxyUrl(value: string | URL): string;
11
12
  //# sourceMappingURL=shared.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAKjD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAO1D"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAKjD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAO1D"}
package/dist/shared.js CHANGED
@@ -19,6 +19,9 @@ export function resolveProxyTlsCa(options) {
19
19
  }
20
20
  return undefined;
21
21
  }
22
+ export function formatUrl(value) {
23
+ return value instanceof URL ? value.href : new URL(value).href;
24
+ }
22
25
  export function redactProxyUrl(value) {
23
26
  const url = value instanceof URL ? new URL(value.href) : new URL(value);
24
27
  url.username = "";