@opengeni/network 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +37 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +490 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
- package/src/index.ts +695 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Agent, fetch as undiciFetchImpl } from "undici/index.js";
|
|
3
|
+
import { lookup as nodeLookup } from "dns/promises";
|
|
4
|
+
import { isIP } from "net";
|
|
5
|
+
var OAUTH_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
6
|
+
function validateHttpUrl(rawUrl, options = {}) {
|
|
7
|
+
const label = options.label ?? "HTTP endpoint";
|
|
8
|
+
let url;
|
|
9
|
+
try {
|
|
10
|
+
url = new URL(rawUrl);
|
|
11
|
+
} catch {
|
|
12
|
+
throw new DestinationPolicyError("invalid_url", `${label} URL is invalid`);
|
|
13
|
+
}
|
|
14
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
15
|
+
throw new DestinationPolicyError(
|
|
16
|
+
"unsupported_protocol",
|
|
17
|
+
`${label} only supports http and https URLs`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (url.username || url.password) {
|
|
21
|
+
throw new DestinationPolicyError("invalid_url", `${label} URL may not contain credentials`);
|
|
22
|
+
}
|
|
23
|
+
if (url.hash) {
|
|
24
|
+
throw new DestinationPolicyError("invalid_url", `${label} URL may not contain a fragment`);
|
|
25
|
+
}
|
|
26
|
+
if (url.protocol === "http:" && !(options.allowLoopbackHttp && isLoopbackHostname(url.hostname))) {
|
|
27
|
+
throw new DestinationPolicyError("https_required", `${label} must use https`);
|
|
28
|
+
}
|
|
29
|
+
return url.toString();
|
|
30
|
+
}
|
|
31
|
+
var ResponseBodyLimitError = class extends Error {
|
|
32
|
+
constructor(label, actualBytes, maxBytes, reason) {
|
|
33
|
+
super(`${label} exceeded its ${maxBytes}-byte response limit`);
|
|
34
|
+
this.label = label;
|
|
35
|
+
this.actualBytes = actualBytes;
|
|
36
|
+
this.maxBytes = maxBytes;
|
|
37
|
+
this.reason = reason;
|
|
38
|
+
this.name = "ResponseBodyLimitError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
async function readResponseBodyBounded(response, maxBytes, label) {
|
|
42
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
43
|
+
throw new RangeError("response body limit must be a non-negative safe integer");
|
|
44
|
+
}
|
|
45
|
+
const declared = response.headers.get("content-length");
|
|
46
|
+
if (declared !== null) {
|
|
47
|
+
const normalized = declared.trim();
|
|
48
|
+
if (!/^\d+$/.test(normalized)) {
|
|
49
|
+
await cancelResponse(response);
|
|
50
|
+
throw new ResponseBodyLimitError(label, 0, maxBytes, "invalid_content_length");
|
|
51
|
+
}
|
|
52
|
+
const declaredBytes = Number(normalized);
|
|
53
|
+
if (!Number.isSafeInteger(declaredBytes) || declaredBytes > maxBytes) {
|
|
54
|
+
await cancelResponse(response);
|
|
55
|
+
throw new ResponseBodyLimitError(label, declaredBytes, maxBytes, "declared_length");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!response.body) {
|
|
59
|
+
return new Uint8Array();
|
|
60
|
+
}
|
|
61
|
+
const reader = response.body.getReader();
|
|
62
|
+
const chunks = [];
|
|
63
|
+
let receivedBytes = 0;
|
|
64
|
+
try {
|
|
65
|
+
for (; ; ) {
|
|
66
|
+
const result = await reader.read();
|
|
67
|
+
if (result.done) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
receivedBytes += result.value.byteLength;
|
|
71
|
+
if (receivedBytes > maxBytes) {
|
|
72
|
+
await reader.cancel().catch(() => void 0);
|
|
73
|
+
throw new ResponseBodyLimitError(label, receivedBytes, maxBytes, "stream_overflow");
|
|
74
|
+
}
|
|
75
|
+
chunks.push(result.value);
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
await reader.cancel().catch(() => void 0);
|
|
79
|
+
throw error;
|
|
80
|
+
} finally {
|
|
81
|
+
reader.releaseLock();
|
|
82
|
+
}
|
|
83
|
+
const body = new Uint8Array(receivedBytes);
|
|
84
|
+
let offset = 0;
|
|
85
|
+
for (const chunk of chunks) {
|
|
86
|
+
body.set(chunk, offset);
|
|
87
|
+
offset += chunk.byteLength;
|
|
88
|
+
}
|
|
89
|
+
return body;
|
|
90
|
+
}
|
|
91
|
+
async function readResponseTextBounded(response, maxBytes, label) {
|
|
92
|
+
return new TextDecoder().decode(await readResponseBodyBounded(response, maxBytes, label));
|
|
93
|
+
}
|
|
94
|
+
async function readResponseJsonBounded(response, maxBytes, label) {
|
|
95
|
+
return JSON.parse(await readResponseTextBounded(response, maxBytes, label));
|
|
96
|
+
}
|
|
97
|
+
var DestinationPolicyError = class extends Error {
|
|
98
|
+
constructor(reason, message) {
|
|
99
|
+
super(message);
|
|
100
|
+
this.reason = reason;
|
|
101
|
+
this.name = "DestinationPolicyError";
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var defaultDnsLookup = async (hostname) => {
|
|
105
|
+
const answers = await nodeLookup(hostname, { all: true });
|
|
106
|
+
return answers.map((entry) => normalizeDnsAnswer(entry));
|
|
107
|
+
};
|
|
108
|
+
var defaultFetch = (input, init) => undiciFetchImpl(input, init);
|
|
109
|
+
var undiciFetch = defaultFetch;
|
|
110
|
+
async function resolvePinnedDestination(rawUrl, settings, options = {}) {
|
|
111
|
+
const label = options.label ?? "Outbound request";
|
|
112
|
+
let url;
|
|
113
|
+
try {
|
|
114
|
+
url = new URL(rawUrl);
|
|
115
|
+
} catch {
|
|
116
|
+
throw new DestinationPolicyError("invalid_url", `${label} URL is invalid`);
|
|
117
|
+
}
|
|
118
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
119
|
+
throw new DestinationPolicyError(
|
|
120
|
+
"unsupported_protocol",
|
|
121
|
+
`${label} only supports http and https URLs`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const localTestEscape = isLocalTestEnvironment(settings.environment);
|
|
125
|
+
if (options.requireHttpsOutsideLocalTest && !localTestEscape && url.protocol !== "https:") {
|
|
126
|
+
throw new DestinationPolicyError(
|
|
127
|
+
"https_required",
|
|
128
|
+
`${label} must use https outside local/test`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const hostname = normalizeHostname(url.hostname);
|
|
132
|
+
if (!hostname) {
|
|
133
|
+
throw new DestinationPolicyError("invalid_url", `${label} URL has no hostname`);
|
|
134
|
+
}
|
|
135
|
+
const literalFamily = isIP(hostname);
|
|
136
|
+
let addresses;
|
|
137
|
+
try {
|
|
138
|
+
addresses = literalFamily ? [{ address: hostname, family: literalFamily === 6 ? 6 : 4 }] : await (options.dnsLookup ?? defaultDnsLookup)(hostname);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error instanceof DestinationPolicyError && error.reason === "invalid_dns_answer") {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
throw new DestinationPolicyError("dns_failed", `${label} hostname could not be resolved`);
|
|
144
|
+
}
|
|
145
|
+
const normalizedAddresses = dedupeAddresses(addresses);
|
|
146
|
+
if (normalizedAddresses.length === 0) {
|
|
147
|
+
throw new DestinationPolicyError("dns_empty", `${label} hostname has no addresses`);
|
|
148
|
+
}
|
|
149
|
+
if (normalizedAddresses.some((entry) => isInvalidAddress(entry.address))) {
|
|
150
|
+
throw new DestinationPolicyError(
|
|
151
|
+
"invalid_dns_answer",
|
|
152
|
+
`${label} hostname returned an invalid address`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const privateEscape = localTestEscape || settings.integrationsAllowPrivateNetworkTargets === true;
|
|
156
|
+
if (!privateEscape && (isLocalHostname(hostname) || normalizedAddresses.some((entry) => isNonPublicAddress(entry.address)))) {
|
|
157
|
+
throw new DestinationPolicyError(
|
|
158
|
+
"private_or_special_use",
|
|
159
|
+
`${label} may not target a private or special-use network address`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return { url, hostname, addresses: normalizedAddresses };
|
|
163
|
+
}
|
|
164
|
+
async function pinnedFetch(input, init, settings, options = {}) {
|
|
165
|
+
const rawUrl = input instanceof Request ? input.url : input;
|
|
166
|
+
const destination = await resolvePinnedDestination(rawUrl, settings, options);
|
|
167
|
+
const dispatcher = options.agentFactory?.(destination.addresses) ?? createPinnedAgent(destination.addresses);
|
|
168
|
+
const fetchImpl = options.fetchImpl ?? defaultFetch;
|
|
169
|
+
const fetchInit = {
|
|
170
|
+
...init,
|
|
171
|
+
redirect: "manual",
|
|
172
|
+
dispatcher: dispatcher.dispatcher ?? dispatcher
|
|
173
|
+
};
|
|
174
|
+
let response;
|
|
175
|
+
try {
|
|
176
|
+
response = await fetchImpl(input, fetchInit);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
await destroyDispatcher(dispatcher, error);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
if (!response.body) {
|
|
182
|
+
await closeDispatcher(dispatcher);
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
return responseWithDispatcherLifecycle(response, dispatcher);
|
|
186
|
+
}
|
|
187
|
+
function isLocalTestEnvironment(environment) {
|
|
188
|
+
return environment === "local" || environment === "test";
|
|
189
|
+
}
|
|
190
|
+
function isInvalidAddress(address) {
|
|
191
|
+
return isIP(stripAddressBrackets(address.trim())) === 0;
|
|
192
|
+
}
|
|
193
|
+
function isNonPublicAddress(address) {
|
|
194
|
+
const normalized = stripAddressBrackets(address.trim().toLowerCase());
|
|
195
|
+
const family = isIP(normalized);
|
|
196
|
+
if (family === 0) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (family === 4) {
|
|
200
|
+
return isNonPublicIpv4(normalized);
|
|
201
|
+
}
|
|
202
|
+
const mappedText = ipv4FromMappedText(normalized);
|
|
203
|
+
if (mappedText !== null) {
|
|
204
|
+
return isNonPublicIpv4(mappedText);
|
|
205
|
+
}
|
|
206
|
+
const value = parseIpv6(normalized);
|
|
207
|
+
if (value === null) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
const mapped = ipv4FromMappedIpv6(value);
|
|
211
|
+
if (mapped !== null) {
|
|
212
|
+
return isNonPublicIpv4(mapped);
|
|
213
|
+
}
|
|
214
|
+
if (!hasIpv6Prefix(value, IPV6_GLOBAL_UNICAST_PREFIX, 3)) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return IPV6_SPECIAL_PREFIXES.some(([prefix, bits]) => hasIpv6Prefix(value, prefix, bits));
|
|
218
|
+
}
|
|
219
|
+
var isPrivateAddress = isNonPublicAddress;
|
|
220
|
+
function createPinnedAgent(addresses) {
|
|
221
|
+
const agent = new Agent({
|
|
222
|
+
connect: {
|
|
223
|
+
lookup: (_hostname, options, callback) => {
|
|
224
|
+
const candidates = options.family === 4 || options.family === 6 ? addresses.filter((entry) => entry.family === options.family) : addresses;
|
|
225
|
+
if (candidates.length === 0) {
|
|
226
|
+
callback(new Error("pinned DNS answer has no address for requested family"));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (options.all) {
|
|
230
|
+
callback(
|
|
231
|
+
null,
|
|
232
|
+
candidates.map((entry) => ({ address: entry.address, family: entry.family }))
|
|
233
|
+
);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const first = candidates[0];
|
|
237
|
+
callback(null, first.address, first.family);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
dispatcher: agent,
|
|
243
|
+
close: async () => {
|
|
244
|
+
await agent.close();
|
|
245
|
+
},
|
|
246
|
+
destroy: async (error) => {
|
|
247
|
+
await agent.destroy(error instanceof Error ? error : null);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function responseWithDispatcherLifecycle(response, dispatcher) {
|
|
252
|
+
const reader = response.body.getReader();
|
|
253
|
+
let disposed = null;
|
|
254
|
+
let cancelled = false;
|
|
255
|
+
const finish = (destroy, error) => {
|
|
256
|
+
if (!disposed) {
|
|
257
|
+
disposed = destroy ? destroyDispatcher(dispatcher, error) : closeDispatcher(dispatcher);
|
|
258
|
+
}
|
|
259
|
+
return disposed;
|
|
260
|
+
};
|
|
261
|
+
const body = new ReadableStream({
|
|
262
|
+
async pull(controller) {
|
|
263
|
+
try {
|
|
264
|
+
const chunk = await reader.read();
|
|
265
|
+
if (chunk.done) {
|
|
266
|
+
await finish(cancelled);
|
|
267
|
+
controller.close();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
controller.enqueue(chunk.value);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
await finish(true, error);
|
|
273
|
+
controller.error(error);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
async cancel(reason) {
|
|
277
|
+
cancelled = true;
|
|
278
|
+
try {
|
|
279
|
+
await reader.cancel(reason);
|
|
280
|
+
} finally {
|
|
281
|
+
await finish(true, reason);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
const wrapped = new Response(body, {
|
|
286
|
+
status: response.status,
|
|
287
|
+
statusText: response.statusText,
|
|
288
|
+
headers: response.headers
|
|
289
|
+
});
|
|
290
|
+
Object.defineProperties(wrapped, {
|
|
291
|
+
redirected: { value: response.redirected },
|
|
292
|
+
type: { value: response.type },
|
|
293
|
+
url: { value: response.url }
|
|
294
|
+
});
|
|
295
|
+
return wrapped;
|
|
296
|
+
}
|
|
297
|
+
async function closeDispatcher(dispatcher) {
|
|
298
|
+
try {
|
|
299
|
+
await dispatcher.close();
|
|
300
|
+
} catch {
|
|
301
|
+
await destroyDispatcher(dispatcher);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function destroyDispatcher(dispatcher, error) {
|
|
305
|
+
try {
|
|
306
|
+
await dispatcher.destroy(error);
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function cancelResponse(response) {
|
|
311
|
+
await response.body?.cancel().catch(() => void 0);
|
|
312
|
+
}
|
|
313
|
+
function normalizeHostname(hostname) {
|
|
314
|
+
return stripAddressBrackets(hostname.trim().toLowerCase()).replace(/\.$/, "");
|
|
315
|
+
}
|
|
316
|
+
function stripAddressBrackets(address) {
|
|
317
|
+
return address.startsWith("[") && address.endsWith("]") ? address.slice(1, -1) : address;
|
|
318
|
+
}
|
|
319
|
+
function isLocalHostname(hostname) {
|
|
320
|
+
return hostname === "localhost" || hostname.endsWith(".localhost");
|
|
321
|
+
}
|
|
322
|
+
function isLoopbackHostname(hostname) {
|
|
323
|
+
const normalized = normalizeHostname(hostname);
|
|
324
|
+
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "::1" || isLoopbackIpv4(normalized);
|
|
325
|
+
}
|
|
326
|
+
function isLoopbackIpv4(hostname) {
|
|
327
|
+
if (isIP(hostname) !== 4) return false;
|
|
328
|
+
return hostname.split(".")[0] === "127";
|
|
329
|
+
}
|
|
330
|
+
function dedupeAddresses(addresses) {
|
|
331
|
+
const out = [];
|
|
332
|
+
const seen = /* @__PURE__ */ new Set();
|
|
333
|
+
if (!Array.isArray(addresses)) {
|
|
334
|
+
throw invalidDnsAnswer();
|
|
335
|
+
}
|
|
336
|
+
for (const entry of addresses) {
|
|
337
|
+
const normalized = normalizeDnsAnswer(entry);
|
|
338
|
+
const key = `${normalized.family}:${normalized.address}`;
|
|
339
|
+
if (!seen.has(key)) {
|
|
340
|
+
seen.add(key);
|
|
341
|
+
out.push(normalized);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
346
|
+
function normalizeDnsAnswer(entry) {
|
|
347
|
+
if (!entry || typeof entry !== "object") {
|
|
348
|
+
throw invalidDnsAnswer();
|
|
349
|
+
}
|
|
350
|
+
const candidate = entry;
|
|
351
|
+
if (typeof candidate.address !== "string") {
|
|
352
|
+
throw invalidDnsAnswer();
|
|
353
|
+
}
|
|
354
|
+
const family = candidate.family;
|
|
355
|
+
if (family !== 4 && family !== 6) {
|
|
356
|
+
throw invalidDnsAnswer();
|
|
357
|
+
}
|
|
358
|
+
const address = stripAddressBrackets(candidate.address.trim().toLowerCase());
|
|
359
|
+
const actualFamily = isIP(address);
|
|
360
|
+
if (actualFamily !== family) {
|
|
361
|
+
throw invalidDnsAnswer();
|
|
362
|
+
}
|
|
363
|
+
return { address, family };
|
|
364
|
+
}
|
|
365
|
+
function invalidDnsAnswer() {
|
|
366
|
+
return new DestinationPolicyError(
|
|
367
|
+
"invalid_dns_answer",
|
|
368
|
+
"hostname returned an invalid DNS answer"
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
function isNonPublicIpv4(address) {
|
|
372
|
+
const parts = address.split(".").map(Number);
|
|
373
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
const value = parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3];
|
|
377
|
+
const unsigned = value >>> 0;
|
|
378
|
+
return inIpv4Range(unsigned, 0, 16777215) || inIpv4Range(unsigned, 167772160, 184549375) || inIpv4Range(unsigned, 1681915904, 1686110207) || inIpv4Range(unsigned, 2130706432, 2147483647) || inIpv4Range(unsigned, 2851995648, 2852061183) || inIpv4Range(unsigned, 2886729728, 2887778303) || inIpv4Range(unsigned, 3221225472, 3221225727) || inIpv4Range(unsigned, 3221225984, 3221226239) || inIpv4Range(unsigned, 3223307264, 3223307519) || inIpv4Range(unsigned, 3224682752, 3224683007) || inIpv4Range(unsigned, 3227017984, 3227018239) || inIpv4Range(unsigned, 3232235520, 3232301055) || inIpv4Range(unsigned, 3232706560, 3232706815) || inIpv4Range(unsigned, 3323068416, 3323199487) || inIpv4Range(unsigned, 3325256704, 3325256959) || inIpv4Range(unsigned, 3405803776, 3405804031) || inIpv4Range(unsigned, 3758096384, 4294967295);
|
|
379
|
+
}
|
|
380
|
+
function inIpv4Range(value, start, end) {
|
|
381
|
+
return value >= start && value <= end;
|
|
382
|
+
}
|
|
383
|
+
var IPV6_GLOBAL_UNICAST_PREFIX = ipv6Constant("2000::");
|
|
384
|
+
var IPV6_SPECIAL_PREFIXES = [
|
|
385
|
+
[ipv6Constant("::"), 96],
|
|
386
|
+
[ipv6Constant("64:ff9b::"), 96],
|
|
387
|
+
[ipv6Constant("64:ff9b:1::"), 48],
|
|
388
|
+
[ipv6Constant("100::"), 64],
|
|
389
|
+
// IETF protocol assignments contain globally reachable carve-outs, but they
|
|
390
|
+
// are control-plane anycast/protocol addresses rather than integration
|
|
391
|
+
// endpoints. Credential-bearing MCP/OAuth egress fails closed on the full
|
|
392
|
+
// special-purpose block.
|
|
393
|
+
[ipv6Constant("2001::"), 23],
|
|
394
|
+
[ipv6Constant("2001:db8::"), 32],
|
|
395
|
+
[ipv6Constant("2002::"), 16],
|
|
396
|
+
[ipv6Constant("2620:4f:8000::"), 48],
|
|
397
|
+
[ipv6Constant("3ffe::"), 16],
|
|
398
|
+
[ipv6Constant("3fff::"), 20],
|
|
399
|
+
[ipv6Constant("5f00::"), 16],
|
|
400
|
+
[ipv6Constant("fc00::"), 7],
|
|
401
|
+
[ipv6Constant("fe80::"), 10],
|
|
402
|
+
[ipv6Constant("fec0::"), 10],
|
|
403
|
+
[ipv6Constant("ff00::"), 8]
|
|
404
|
+
];
|
|
405
|
+
function hasIpv6Prefix(value, prefix, bits) {
|
|
406
|
+
const shift = 128n - BigInt(bits);
|
|
407
|
+
return value >> shift === prefix >> shift;
|
|
408
|
+
}
|
|
409
|
+
function ipv4FromMappedIpv6(value) {
|
|
410
|
+
if (value >> 32n !== 0xffffn) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const embedded = Number(value & 0xffffffffn);
|
|
414
|
+
return `${embedded >>> 24}.${embedded >>> 16 & 255}.${embedded >>> 8 & 255}.${embedded & 255}`;
|
|
415
|
+
}
|
|
416
|
+
function ipv4FromMappedText(address) {
|
|
417
|
+
if (!address.startsWith("::ffff:")) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
const embedded = address.slice("::ffff:".length);
|
|
421
|
+
if (isIP(embedded) === 4) {
|
|
422
|
+
return embedded;
|
|
423
|
+
}
|
|
424
|
+
const parts = embedded.split(":");
|
|
425
|
+
if (parts.length !== 2 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
const high = Number.parseInt(parts[0], 16);
|
|
429
|
+
const low = Number.parseInt(parts[1], 16);
|
|
430
|
+
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
431
|
+
}
|
|
432
|
+
function parseIpv6(address) {
|
|
433
|
+
const percent = address.indexOf("%");
|
|
434
|
+
if (percent >= 0) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
let value = address;
|
|
438
|
+
if (value.includes(".")) {
|
|
439
|
+
const lastColon = value.lastIndexOf(":");
|
|
440
|
+
const dotted = value.slice(lastColon + 1);
|
|
441
|
+
const parts = dotted.split(".").map(Number);
|
|
442
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
const hex = (parts[0] << 8 | parts[1]).toString(16).padStart(4, "0") + (parts[2] << 8 | parts[3]).toString(16).padStart(4, "0");
|
|
446
|
+
value = `${value.slice(0, lastColon + 1)}${hex}`;
|
|
447
|
+
}
|
|
448
|
+
const halves = value.split("::");
|
|
449
|
+
if (halves.length > 2) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
const left = halves[0] ? halves[0].split(":") : [];
|
|
453
|
+
const right = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
|
|
454
|
+
if (left.concat(right).some((part) => !/^[0-9a-f]{1,4}$/i.test(part))) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const missing = halves.length === 2 ? 8 - left.length - right.length : 0;
|
|
458
|
+
if (missing < 0 || halves.length === 1 && missing !== 0) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
const groups = [...left, ...Array.from({ length: missing }, () => "0"), ...right];
|
|
462
|
+
if (groups.length !== 8) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
return groups.reduce((acc, group) => acc << 16n | BigInt(`0x${group}`), 0n);
|
|
466
|
+
}
|
|
467
|
+
function ipv6Constant(address) {
|
|
468
|
+
const value = parseIpv6(address);
|
|
469
|
+
if (value === null) {
|
|
470
|
+
throw new Error(`invalid IPv6 constant: ${address}`);
|
|
471
|
+
}
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
474
|
+
export {
|
|
475
|
+
DestinationPolicyError,
|
|
476
|
+
OAUTH_MAX_RESPONSE_BYTES,
|
|
477
|
+
ResponseBodyLimitError,
|
|
478
|
+
isInvalidAddress,
|
|
479
|
+
isLocalTestEnvironment,
|
|
480
|
+
isNonPublicAddress,
|
|
481
|
+
isPrivateAddress,
|
|
482
|
+
pinnedFetch,
|
|
483
|
+
readResponseBodyBounded,
|
|
484
|
+
readResponseJsonBounded,
|
|
485
|
+
readResponseTextBounded,
|
|
486
|
+
resolvePinnedDestination,
|
|
487
|
+
undiciFetch,
|
|
488
|
+
validateHttpUrl
|
|
489
|
+
};
|
|
490
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// The explicit entrypoint avoids Bun's native `undici` compatibility shim,\n// which exposes an Agent-shaped object without Dispatcher methods.\nimport { Agent, fetch as undiciFetchImpl } from \"undici/index.js\";\nimport { lookup as nodeLookup } from \"node:dns/promises\";\nimport { isIP } from \"node:net\";\n\nexport type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n\nexport type DnsAddress = {\n address: string;\n family: 4 | 6;\n};\n\nexport type DnsLookup = (hostname: string) => Promise<readonly DnsAddress[]>;\n\nexport type OutboundNetworkSettings = {\n environment: string;\n integrationsAllowPrivateNetworkTargets: boolean;\n};\n\nexport type PinnedDestination = {\n url: URL;\n hostname: string;\n addresses: readonly DnsAddress[];\n};\n\nexport type DispatcherLifecycle = {\n /** The raw dispatcher handed to fetch; omitted for test-only lifecycle fakes. */\n dispatcher?: unknown;\n close: () => Promise<void> | void;\n destroy: (error?: unknown) => Promise<void> | void;\n};\n\nexport type PinnedFetchOptions = {\n fetchImpl?: FetchLike;\n dnsLookup?: DnsLookup;\n agentFactory?: (addresses: readonly DnsAddress[]) => DispatcherLifecycle;\n label?: string;\n requireHttpsOutsideLocalTest?: boolean;\n};\n\nexport const OAUTH_MAX_RESPONSE_BYTES = 1024 * 1024;\n\nexport type HttpUrlValidationOptions = {\n allowLoopbackHttp?: boolean;\n label?: string;\n};\n\n/** Validate a protocol endpoint before it is persisted, returned, or opened. */\nexport function validateHttpUrl(rawUrl: string, options: HttpUrlValidationOptions = {}): string {\n const label = options.label ?? \"HTTP endpoint\";\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n throw new DestinationPolicyError(\"invalid_url\", `${label} URL is invalid`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new DestinationPolicyError(\n \"unsupported_protocol\",\n `${label} only supports http and https URLs`,\n );\n }\n if (url.username || url.password) {\n throw new DestinationPolicyError(\"invalid_url\", `${label} URL may not contain credentials`);\n }\n if (url.hash) {\n throw new DestinationPolicyError(\"invalid_url\", `${label} URL may not contain a fragment`);\n }\n if (\n url.protocol === \"http:\" &&\n !(options.allowLoopbackHttp && isLoopbackHostname(url.hostname))\n ) {\n throw new DestinationPolicyError(\"https_required\", `${label} must use https`);\n }\n return url.toString();\n}\n\n/**\n * Raised when a response cannot be safely consumed within its caller's byte\n * budget. The error intentionally contains no response bytes or provider\n * message: these readers are used on credential-bearing paths.\n */\nexport class ResponseBodyLimitError extends Error {\n constructor(\n readonly label: string,\n readonly actualBytes: number,\n readonly maxBytes: number,\n readonly reason: \"declared_length\" | \"stream_overflow\" | \"invalid_content_length\",\n ) {\n super(`${label} exceeded its ${maxBytes}-byte response limit`);\n this.name = \"ResponseBodyLimitError\";\n }\n}\n\n/**\n * Read a response through its stream with a hard byte ceiling.\n *\n * A declared Content-Length above the ceiling is rejected before reading. A\n * body exactly at the ceiling is accepted; the first byte beyond it cancels\n * the stream and rejects. Cancellation is important because pinnedFetch owns a\n * per-response dispatcher which must be closed on every exit path.\n */\nexport async function readResponseBodyBounded(\n response: Response,\n maxBytes: number,\n label: string,\n): Promise<Uint8Array> {\n if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {\n throw new RangeError(\"response body limit must be a non-negative safe integer\");\n }\n const declared = response.headers.get(\"content-length\");\n if (declared !== null) {\n const normalized = declared.trim();\n if (!/^\\d+$/.test(normalized)) {\n await cancelResponse(response);\n throw new ResponseBodyLimitError(label, 0, maxBytes, \"invalid_content_length\");\n }\n const declaredBytes = Number(normalized);\n if (!Number.isSafeInteger(declaredBytes) || declaredBytes > maxBytes) {\n await cancelResponse(response);\n throw new ResponseBodyLimitError(label, declaredBytes, maxBytes, \"declared_length\");\n }\n }\n if (!response.body) {\n return new Uint8Array();\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let receivedBytes = 0;\n try {\n for (;;) {\n const result = await reader.read();\n if (result.done) {\n break;\n }\n receivedBytes += result.value.byteLength;\n if (receivedBytes > maxBytes) {\n await reader.cancel().catch(() => undefined);\n throw new ResponseBodyLimitError(label, receivedBytes, maxBytes, \"stream_overflow\");\n }\n chunks.push(result.value);\n }\n } catch (error) {\n await reader.cancel().catch(() => undefined);\n throw error;\n } finally {\n reader.releaseLock();\n }\n\n const body = new Uint8Array(receivedBytes);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return body;\n}\n\nexport async function readResponseTextBounded(\n response: Response,\n maxBytes: number,\n label: string,\n): Promise<string> {\n return new TextDecoder().decode(await readResponseBodyBounded(response, maxBytes, label));\n}\n\nexport async function readResponseJsonBounded<T = unknown>(\n response: Response,\n maxBytes: number,\n label: string,\n): Promise<T> {\n return JSON.parse(await readResponseTextBounded(response, maxBytes, label)) as T;\n}\n\nexport type ResolvePinnedDestinationOptions = {\n dnsLookup?: DnsLookup;\n label?: string;\n requireHttpsOutsideLocalTest?: boolean;\n};\n\nexport type DestinationPolicyReason =\n | \"invalid_url\"\n | \"unsupported_protocol\"\n | \"https_required\"\n | \"dns_failed\"\n | \"dns_empty\"\n | \"invalid_dns_answer\"\n | \"private_or_special_use\";\n\nexport class DestinationPolicyError extends Error {\n constructor(\n readonly reason: DestinationPolicyReason,\n message: string,\n ) {\n super(message);\n this.name = \"DestinationPolicyError\";\n }\n}\n\nconst defaultDnsLookup: DnsLookup = async (hostname) => {\n const answers = await nodeLookup(hostname, { all: true });\n return answers.map((entry) => normalizeDnsAnswer(entry));\n};\n\nconst defaultFetch: FetchLike = (input, init) =>\n (undiciFetchImpl as unknown as FetchLike)(input, init);\n\n/** Undici fetch used by the pinned transport; Bun callers should prefer this over native fetch. */\nexport const undiciFetch: FetchLike = defaultFetch;\n\n/**\n * Resolve and policy-check one destination. The returned address set is the\n * complete DNS answer that the caller is allowed to use; the transport never\n * performs a second resolver call.\n */\nexport async function resolvePinnedDestination(\n rawUrl: string | URL,\n settings: OutboundNetworkSettings,\n options: ResolvePinnedDestinationOptions = {},\n): Promise<PinnedDestination> {\n const label = options.label ?? \"Outbound request\";\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n throw new DestinationPolicyError(\"invalid_url\", `${label} URL is invalid`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new DestinationPolicyError(\n \"unsupported_protocol\",\n `${label} only supports http and https URLs`,\n );\n }\n const localTestEscape = isLocalTestEnvironment(settings.environment);\n if (options.requireHttpsOutsideLocalTest && !localTestEscape && url.protocol !== \"https:\") {\n throw new DestinationPolicyError(\n \"https_required\",\n `${label} must use https outside local/test`,\n );\n }\n\n const hostname = normalizeHostname(url.hostname);\n if (!hostname) {\n throw new DestinationPolicyError(\"invalid_url\", `${label} URL has no hostname`);\n }\n\n const literalFamily = isIP(hostname);\n let addresses: readonly DnsAddress[];\n try {\n addresses = literalFamily\n ? [{ address: hostname, family: literalFamily === 6 ? 6 : 4 }]\n : await (options.dnsLookup ?? defaultDnsLookup)(hostname);\n } catch (error) {\n if (error instanceof DestinationPolicyError && error.reason === \"invalid_dns_answer\") {\n throw error;\n }\n throw new DestinationPolicyError(\"dns_failed\", `${label} hostname could not be resolved`);\n }\n\n const normalizedAddresses = dedupeAddresses(addresses);\n if (normalizedAddresses.length === 0) {\n throw new DestinationPolicyError(\"dns_empty\", `${label} hostname has no addresses`);\n }\n if (normalizedAddresses.some((entry) => isInvalidAddress(entry.address))) {\n throw new DestinationPolicyError(\n \"invalid_dns_answer\",\n `${label} hostname returned an invalid address`,\n );\n }\n\n const privateEscape = localTestEscape || settings.integrationsAllowPrivateNetworkTargets === true;\n if (\n !privateEscape &&\n (isLocalHostname(hostname) ||\n normalizedAddresses.some((entry) => isNonPublicAddress(entry.address)))\n ) {\n throw new DestinationPolicyError(\n \"private_or_special_use\",\n `${label} may not target a private or special-use network address`,\n );\n }\n return { url, hostname, addresses: normalizedAddresses };\n}\n\n/**\n * Fetch through a dispatcher whose lookup is pinned to the result of exactly\n * one policy resolution. The response body owns the agent until completion,\n * cancellation, or stream failure.\n */\nexport async function pinnedFetch(\n input: string | URL | Request,\n init: RequestInit | undefined,\n settings: OutboundNetworkSettings,\n options: PinnedFetchOptions = {},\n): Promise<Response> {\n const rawUrl = input instanceof Request ? input.url : input;\n const destination = await resolvePinnedDestination(rawUrl, settings, options);\n const dispatcher =\n options.agentFactory?.(destination.addresses) ?? createPinnedAgent(destination.addresses);\n const fetchImpl = options.fetchImpl ?? defaultFetch;\n const fetchInit = {\n ...init,\n redirect: \"manual\" as const,\n dispatcher: dispatcher.dispatcher ?? dispatcher,\n } as RequestInit & { dispatcher: DispatcherLifecycle };\n let response: Response;\n try {\n response = await fetchImpl(input, fetchInit);\n } catch (error) {\n await destroyDispatcher(dispatcher, error);\n throw error;\n }\n if (!response.body) {\n await closeDispatcher(dispatcher);\n return response;\n }\n return responseWithDispatcherLifecycle(response, dispatcher);\n}\n\nexport function isLocalTestEnvironment(environment: string): boolean {\n return environment === \"local\" || environment === \"test\";\n}\n\n/** Return true for malformed, non-IPv4, and non-IPv6 address strings. */\nexport function isInvalidAddress(address: string): boolean {\n return isIP(stripAddressBrackets(address.trim())) === 0;\n}\n\n/**\n * Classify private, reserved, documentation, benchmark, multicast, and other\n * special-use answers. IPv4-mapped IPv6 addresses are classified through their\n * embedded IPv4 value.\n */\nexport function isNonPublicAddress(address: string): boolean {\n const normalized = stripAddressBrackets(address.trim().toLowerCase());\n const family = isIP(normalized);\n if (family === 0) {\n return true;\n }\n if (family === 4) {\n return isNonPublicIpv4(normalized);\n }\n const mappedText = ipv4FromMappedText(normalized);\n if (mappedText !== null) {\n return isNonPublicIpv4(mappedText);\n }\n const value = parseIpv6(normalized);\n if (value === null) {\n return true;\n }\n const mapped = ipv4FromMappedIpv6(value);\n if (mapped !== null) {\n return isNonPublicIpv4(mapped);\n }\n // Fail closed on unallocated/non-global IPv6 space. Current globally routed\n // unicast addresses live in 2000::/3; local, transition, multicast, and\n // future-use ranges must not become credential-bearing egress merely because\n // they were absent from a denylist.\n if (!hasIpv6Prefix(value, IPV6_GLOBAL_UNICAST_PREFIX, 3)) {\n return true;\n }\n return IPV6_SPECIAL_PREFIXES.some(([prefix, bits]) => hasIpv6Prefix(value, prefix, bits));\n}\n\n// Backwards-compatible name used by the database token-broker API.\nexport const isPrivateAddress = isNonPublicAddress;\n\nfunction createPinnedAgent(addresses: readonly DnsAddress[]): DispatcherLifecycle {\n const agent = new Agent({\n connect: {\n lookup: ((\n _hostname: string,\n options: { all?: boolean; family?: number },\n callback: (\n error: Error | null,\n address?: string | Array<{ address: string; family: number }>,\n family?: number,\n ) => void,\n ) => {\n const candidates =\n options.family === 4 || options.family === 6\n ? addresses.filter((entry) => entry.family === options.family)\n : addresses;\n if (candidates.length === 0) {\n callback(new Error(\"pinned DNS answer has no address for requested family\"));\n return;\n }\n if (options.all) {\n callback(\n null,\n candidates.map((entry) => ({ address: entry.address, family: entry.family })),\n );\n return;\n }\n const first = candidates[0]!;\n callback(null, first.address, first.family);\n }) as never,\n },\n });\n return {\n dispatcher: agent,\n close: async () => {\n await agent.close();\n },\n destroy: async (error) => {\n await agent.destroy(error instanceof Error ? error : null);\n },\n };\n}\n\nfunction responseWithDispatcherLifecycle(\n response: Response,\n dispatcher: DispatcherLifecycle,\n): Response {\n const reader = response.body!.getReader();\n let disposed: Promise<void> | null = null;\n let cancelled = false;\n const finish = (destroy: boolean, error?: unknown): Promise<void> => {\n if (!disposed) {\n disposed = destroy ? destroyDispatcher(dispatcher, error) : closeDispatcher(dispatcher);\n }\n return disposed;\n };\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const chunk = await reader.read();\n if (chunk.done) {\n await finish(cancelled);\n controller.close();\n return;\n }\n controller.enqueue(chunk.value);\n } catch (error) {\n await finish(true, error);\n controller.error(error);\n }\n },\n async cancel(reason) {\n cancelled = true;\n try {\n await reader.cancel(reason);\n } finally {\n await finish(true, reason);\n }\n },\n });\n const wrapped = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n Object.defineProperties(wrapped, {\n redirected: { value: response.redirected },\n type: { value: response.type },\n url: { value: response.url },\n });\n return wrapped;\n}\n\nasync function closeDispatcher(dispatcher: DispatcherLifecycle): Promise<void> {\n try {\n await dispatcher.close();\n } catch {\n await destroyDispatcher(dispatcher);\n }\n}\n\nasync function destroyDispatcher(dispatcher: DispatcherLifecycle, error?: unknown): Promise<void> {\n try {\n await dispatcher.destroy(error);\n } catch {\n // Cleanup is best effort after the dispatcher has already failed.\n }\n}\n\nasync function cancelResponse(response: Response): Promise<void> {\n await response.body?.cancel().catch(() => undefined);\n}\n\nfunction normalizeHostname(hostname: string): string {\n return stripAddressBrackets(hostname.trim().toLowerCase()).replace(/\\.$/, \"\");\n}\n\nfunction stripAddressBrackets(address: string): string {\n return address.startsWith(\"[\") && address.endsWith(\"]\") ? address.slice(1, -1) : address;\n}\n\nfunction isLocalHostname(hostname: string): boolean {\n return hostname === \"localhost\" || hostname.endsWith(\".localhost\");\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n const normalized = normalizeHostname(hostname);\n return (\n normalized === \"localhost\" ||\n normalized.endsWith(\".localhost\") ||\n normalized === \"::1\" ||\n isLoopbackIpv4(normalized)\n );\n}\n\nfunction isLoopbackIpv4(hostname: string): boolean {\n if (isIP(hostname) !== 4) return false;\n return hostname.split(\".\")[0] === \"127\";\n}\n\nfunction dedupeAddresses(addresses: readonly DnsAddress[]): DnsAddress[] {\n const out: DnsAddress[] = [];\n const seen = new Set<string>();\n if (!Array.isArray(addresses)) {\n throw invalidDnsAnswer();\n }\n for (const entry of addresses) {\n const normalized = normalizeDnsAnswer(entry);\n const key = `${normalized.family}:${normalized.address}`;\n if (!seen.has(key)) {\n seen.add(key);\n out.push(normalized);\n }\n }\n return out;\n}\n\n/**\n * Validate resolver metadata before it can influence either policy checks or\n * the pinned dispatcher. Never infer a family from the address while retaining\n * a conflicting resolver claim: an invalid answer is an invalid answer.\n */\nfunction normalizeDnsAnswer(entry: unknown): DnsAddress {\n if (!entry || typeof entry !== \"object\") {\n throw invalidDnsAnswer();\n }\n const candidate = entry as { address?: unknown; family?: unknown };\n if (typeof candidate.address !== \"string\") {\n throw invalidDnsAnswer();\n }\n const family = candidate.family;\n if (family !== 4 && family !== 6) {\n throw invalidDnsAnswer();\n }\n const address = stripAddressBrackets(candidate.address.trim().toLowerCase());\n const actualFamily = isIP(address);\n if (actualFamily !== family) {\n throw invalidDnsAnswer();\n }\n return { address, family };\n}\n\nfunction invalidDnsAnswer(): DestinationPolicyError {\n return new DestinationPolicyError(\n \"invalid_dns_answer\",\n \"hostname returned an invalid DNS answer\",\n );\n}\n\nfunction isNonPublicIpv4(address: string): boolean {\n const parts = address.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return true;\n }\n const value = (parts[0]! << 24) | (parts[1]! << 16) | (parts[2]! << 8) | parts[3]!;\n const unsigned = value >>> 0;\n return (\n inIpv4Range(unsigned, 0x00000000, 0x00ffffff) ||\n inIpv4Range(unsigned, 0x0a000000, 0x0affffff) ||\n inIpv4Range(unsigned, 0x64400000, 0x647fffff) ||\n inIpv4Range(unsigned, 0x7f000000, 0x7fffffff) ||\n inIpv4Range(unsigned, 0xa9fe0000, 0xa9feffff) ||\n inIpv4Range(unsigned, 0xac100000, 0xac1fffff) ||\n inIpv4Range(unsigned, 0xc0000000, 0xc00000ff) ||\n inIpv4Range(unsigned, 0xc0000200, 0xc00002ff) ||\n inIpv4Range(unsigned, 0xc01fc400, 0xc01fc4ff) ||\n inIpv4Range(unsigned, 0xc034c100, 0xc034c1ff) ||\n inIpv4Range(unsigned, 0xc0586300, 0xc05863ff) ||\n inIpv4Range(unsigned, 0xc0a80000, 0xc0a8ffff) ||\n inIpv4Range(unsigned, 0xc0af3000, 0xc0af30ff) ||\n inIpv4Range(unsigned, 0xc6120000, 0xc613ffff) ||\n inIpv4Range(unsigned, 0xc6336400, 0xc63364ff) ||\n inIpv4Range(unsigned, 0xcb007100, 0xcb0071ff) ||\n inIpv4Range(unsigned, 0xe0000000, 0xffffffff)\n );\n}\n\nfunction inIpv4Range(value: number, start: number, end: number): boolean {\n return value >= start && value <= end;\n}\n\nconst IPV6_GLOBAL_UNICAST_PREFIX = ipv6Constant(\"2000::\");\n\nconst IPV6_SPECIAL_PREFIXES: readonly [bigint, number][] = [\n [ipv6Constant(\"::\"), 96],\n [ipv6Constant(\"64:ff9b::\"), 96],\n [ipv6Constant(\"64:ff9b:1::\"), 48],\n [ipv6Constant(\"100::\"), 64],\n // IETF protocol assignments contain globally reachable carve-outs, but they\n // are control-plane anycast/protocol addresses rather than integration\n // endpoints. Credential-bearing MCP/OAuth egress fails closed on the full\n // special-purpose block.\n [ipv6Constant(\"2001::\"), 23],\n [ipv6Constant(\"2001:db8::\"), 32],\n [ipv6Constant(\"2002::\"), 16],\n [ipv6Constant(\"2620:4f:8000::\"), 48],\n [ipv6Constant(\"3ffe::\"), 16],\n [ipv6Constant(\"3fff::\"), 20],\n [ipv6Constant(\"5f00::\"), 16],\n [ipv6Constant(\"fc00::\"), 7],\n [ipv6Constant(\"fe80::\"), 10],\n [ipv6Constant(\"fec0::\"), 10],\n [ipv6Constant(\"ff00::\"), 8],\n];\n\nfunction hasIpv6Prefix(value: bigint, prefix: bigint, bits: number): boolean {\n const shift = 128n - BigInt(bits);\n return value >> shift === prefix >> shift;\n}\n\nfunction ipv4FromMappedIpv6(value: bigint): string | null {\n if (value >> 32n !== 0xffffn) {\n return null;\n }\n const embedded = Number(value & 0xffffffffn);\n return `${embedded >>> 24}.${(embedded >>> 16) & 0xff}.${(embedded >>> 8) & 0xff}.${embedded & 0xff}`;\n}\n\nfunction ipv4FromMappedText(address: string): string | null {\n if (!address.startsWith(\"::ffff:\")) {\n return null;\n }\n const embedded = address.slice(\"::ffff:\".length);\n if (isIP(embedded) === 4) {\n return embedded;\n }\n const parts = embedded.split(\":\");\n if (parts.length !== 2 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) {\n return null;\n }\n const high = Number.parseInt(parts[0]!, 16);\n const low = Number.parseInt(parts[1]!, 16);\n return `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;\n}\n\nfunction parseIpv6(address: string): bigint | null {\n const percent = address.indexOf(\"%\");\n if (percent >= 0) {\n return null;\n }\n let value = address;\n if (value.includes(\".\")) {\n const lastColon = value.lastIndexOf(\":\");\n const dotted = value.slice(lastColon + 1);\n const parts = dotted.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return null;\n }\n const hex =\n ((parts[0]! << 8) | parts[1]!).toString(16).padStart(4, \"0\") +\n ((parts[2]! << 8) | parts[3]!).toString(16).padStart(4, \"0\");\n value = `${value.slice(0, lastColon + 1)}${hex}`;\n }\n const halves = value.split(\"::\");\n if (halves.length > 2) {\n return null;\n }\n const left = halves[0] ? halves[0].split(\":\") : [];\n const right = halves.length === 2 && halves[1] ? halves[1].split(\":\") : [];\n if (left.concat(right).some((part) => !/^[0-9a-f]{1,4}$/i.test(part))) {\n return null;\n }\n const missing = halves.length === 2 ? 8 - left.length - right.length : 0;\n if (missing < 0 || (halves.length === 1 && missing !== 0)) {\n return null;\n }\n const groups = [...left, ...Array.from({ length: missing }, () => \"0\"), ...right];\n if (groups.length !== 8) {\n return null;\n }\n return groups.reduce((acc, group) => (acc << 16n) | BigInt(`0x${group}`), 0n);\n}\n\nfunction ipv6Constant(address: string): bigint {\n const value = parseIpv6(address);\n if (value === null) {\n throw new Error(`invalid IPv6 constant: ${address}`);\n }\n return value;\n}\n"],"mappings":";AAEA,SAAS,OAAO,SAAS,uBAAuB;AAChD,SAAS,UAAU,kBAAkB;AACrC,SAAS,YAAY;AAqCd,IAAM,2BAA2B,OAAO;AAQxC,SAAS,gBAAgB,QAAgB,UAAoC,CAAC,GAAW;AAC9F,QAAM,QAAQ,QAAQ,SAAS;AAC/B,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,MAAM;AAAA,EACtB,QAAQ;AACN,UAAM,IAAI,uBAAuB,eAAe,GAAG,KAAK,iBAAiB;AAAA,EAC3E;AACA,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAAU;AACzD,UAAM,IAAI;AAAA,MACR;AAAA,MACA,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AACA,MAAI,IAAI,YAAY,IAAI,UAAU;AAChC,UAAM,IAAI,uBAAuB,eAAe,GAAG,KAAK,kCAAkC;AAAA,EAC5F;AACA,MAAI,IAAI,MAAM;AACZ,UAAM,IAAI,uBAAuB,eAAe,GAAG,KAAK,iCAAiC;AAAA,EAC3F;AACA,MACE,IAAI,aAAa,WACjB,EAAE,QAAQ,qBAAqB,mBAAmB,IAAI,QAAQ,IAC9D;AACA,UAAM,IAAI,uBAAuB,kBAAkB,GAAG,KAAK,iBAAiB;AAAA,EAC9E;AACA,SAAO,IAAI,SAAS;AACtB;AAOO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACW,OACA,aACA,UACA,QACT;AACA,UAAM,GAAG,KAAK,iBAAiB,QAAQ,sBAAsB;AALpD;AACA;AACA;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AACF;AAUA,eAAsB,wBACpB,UACA,UACA,OACqB;AACrB,MAAI,CAAC,OAAO,cAAc,QAAQ,KAAK,WAAW,GAAG;AACnD,UAAM,IAAI,WAAW,yDAAyD;AAAA,EAChF;AACA,QAAM,WAAW,SAAS,QAAQ,IAAI,gBAAgB;AACtD,MAAI,aAAa,MAAM;AACrB,UAAM,aAAa,SAAS,KAAK;AACjC,QAAI,CAAC,QAAQ,KAAK,UAAU,GAAG;AAC7B,YAAM,eAAe,QAAQ;AAC7B,YAAM,IAAI,uBAAuB,OAAO,GAAG,UAAU,wBAAwB;AAAA,IAC/E;AACA,UAAM,gBAAgB,OAAO,UAAU;AACvC,QAAI,CAAC,OAAO,cAAc,aAAa,KAAK,gBAAgB,UAAU;AACpE,YAAM,eAAe,QAAQ;AAC7B,YAAM,IAAI,uBAAuB,OAAO,eAAe,UAAU,iBAAiB;AAAA,IACpF;AAAA,EACF;AACA,MAAI,CAAC,SAAS,MAAM;AAClB,WAAO,IAAI,WAAW;AAAA,EACxB;AAEA,QAAM,SAAS,SAAS,KAAK,UAAU;AACvC,QAAM,SAAuB,CAAC;AAC9B,MAAI,gBAAgB;AACpB,MAAI;AACF,eAAS;AACP,YAAM,SAAS,MAAM,OAAO,KAAK;AACjC,UAAI,OAAO,MAAM;AACf;AAAA,MACF;AACA,uBAAiB,OAAO,MAAM;AAC9B,UAAI,gBAAgB,UAAU;AAC5B,cAAM,OAAO,OAAO,EAAE,MAAM,MAAM,MAAS;AAC3C,cAAM,IAAI,uBAAuB,OAAO,eAAe,UAAU,iBAAiB;AAAA,MACpF;AACA,aAAO,KAAK,OAAO,KAAK;AAAA,IAC1B;AAAA,EACF,SAAS,OAAO;AACd,UAAM,OAAO,OAAO,EAAE,MAAM,MAAM,MAAS;AAC3C,UAAM;AAAA,EACR,UAAE;AACA,WAAO,YAAY;AAAA,EACrB;AAEA,QAAM,OAAO,IAAI,WAAW,aAAa;AACzC,MAAI,SAAS;AACb,aAAW,SAAS,QAAQ;AAC1B,SAAK,IAAI,OAAO,MAAM;AACtB,cAAU,MAAM;AAAA,EAClB;AACA,SAAO;AACT;AAEA,eAAsB,wBACpB,UACA,UACA,OACiB;AACjB,SAAO,IAAI,YAAY,EAAE,OAAO,MAAM,wBAAwB,UAAU,UAAU,KAAK,CAAC;AAC1F;AAEA,eAAsB,wBACpB,UACA,UACA,OACY;AACZ,SAAO,KAAK,MAAM,MAAM,wBAAwB,UAAU,UAAU,KAAK,CAAC;AAC5E;AAiBO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACW,QACT,SACA;AACA,UAAM,OAAO;AAHJ;AAIT,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAM,mBAA8B,OAAO,aAAa;AACtD,QAAM,UAAU,MAAM,WAAW,UAAU,EAAE,KAAK,KAAK,CAAC;AACxD,SAAO,QAAQ,IAAI,CAAC,UAAU,mBAAmB,KAAK,CAAC;AACzD;AAEA,IAAM,eAA0B,CAAC,OAAO,SACrC,gBAAyC,OAAO,IAAI;AAGhD,IAAM,cAAyB;AAOtC,eAAsB,yBACpB,QACA,UACA,UAA2C,CAAC,GAChB;AAC5B,QAAM,QAAQ,QAAQ,SAAS;AAC/B,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,MAAM;AAAA,EACtB,QAAQ;AACN,UAAM,IAAI,uBAAuB,eAAe,GAAG,KAAK,iBAAiB;AAAA,EAC3E;AACA,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAAU;AACzD,UAAM,IAAI;AAAA,MACR;AAAA,MACA,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AACA,QAAM,kBAAkB,uBAAuB,SAAS,WAAW;AACnE,MAAI,QAAQ,gCAAgC,CAAC,mBAAmB,IAAI,aAAa,UAAU;AACzF,UAAM,IAAI;AAAA,MACR;AAAA,MACA,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,QAAM,WAAW,kBAAkB,IAAI,QAAQ;AAC/C,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,uBAAuB,eAAe,GAAG,KAAK,sBAAsB;AAAA,EAChF;AAEA,QAAM,gBAAgB,KAAK,QAAQ;AACnC,MAAI;AACJ,MAAI;AACF,gBAAY,gBACR,CAAC,EAAE,SAAS,UAAU,QAAQ,kBAAkB,IAAI,IAAI,EAAE,CAAC,IAC3D,OAAO,QAAQ,aAAa,kBAAkB,QAAQ;AAAA,EAC5D,SAAS,OAAO;AACd,QAAI,iBAAiB,0BAA0B,MAAM,WAAW,sBAAsB;AACpF,YAAM;AAAA,IACR;AACA,UAAM,IAAI,uBAAuB,cAAc,GAAG,KAAK,iCAAiC;AAAA,EAC1F;AAEA,QAAM,sBAAsB,gBAAgB,SAAS;AACrD,MAAI,oBAAoB,WAAW,GAAG;AACpC,UAAM,IAAI,uBAAuB,aAAa,GAAG,KAAK,4BAA4B;AAAA,EACpF;AACA,MAAI,oBAAoB,KAAK,CAAC,UAAU,iBAAiB,MAAM,OAAO,CAAC,GAAG;AACxE,UAAM,IAAI;AAAA,MACR;AAAA,MACA,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,QAAM,gBAAgB,mBAAmB,SAAS,2CAA2C;AAC7F,MACE,CAAC,kBACA,gBAAgB,QAAQ,KACvB,oBAAoB,KAAK,CAAC,UAAU,mBAAmB,MAAM,OAAO,CAAC,IACvE;AACA,UAAM,IAAI;AAAA,MACR;AAAA,MACA,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AACA,SAAO,EAAE,KAAK,UAAU,WAAW,oBAAoB;AACzD;AAOA,eAAsB,YACpB,OACA,MACA,UACA,UAA8B,CAAC,GACZ;AACnB,QAAM,SAAS,iBAAiB,UAAU,MAAM,MAAM;AACtD,QAAM,cAAc,MAAM,yBAAyB,QAAQ,UAAU,OAAO;AAC5E,QAAM,aACJ,QAAQ,eAAe,YAAY,SAAS,KAAK,kBAAkB,YAAY,SAAS;AAC1F,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,YAAY;AAAA,IAChB,GAAG;AAAA,IACH,UAAU;AAAA,IACV,YAAY,WAAW,cAAc;AAAA,EACvC;AACA,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,UAAU,OAAO,SAAS;AAAA,EAC7C,SAAS,OAAO;AACd,UAAM,kBAAkB,YAAY,KAAK;AACzC,UAAM;AAAA,EACR;AACA,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,gBAAgB,UAAU;AAChC,WAAO;AAAA,EACT;AACA,SAAO,gCAAgC,UAAU,UAAU;AAC7D;AAEO,SAAS,uBAAuB,aAA8B;AACnE,SAAO,gBAAgB,WAAW,gBAAgB;AACpD;AAGO,SAAS,iBAAiB,SAA0B;AACzD,SAAO,KAAK,qBAAqB,QAAQ,KAAK,CAAC,CAAC,MAAM;AACxD;AAOO,SAAS,mBAAmB,SAA0B;AAC3D,QAAM,aAAa,qBAAqB,QAAQ,KAAK,EAAE,YAAY,CAAC;AACpE,QAAM,SAAS,KAAK,UAAU;AAC9B,MAAI,WAAW,GAAG;AAChB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,GAAG;AAChB,WAAO,gBAAgB,UAAU;AAAA,EACnC;AACA,QAAM,aAAa,mBAAmB,UAAU;AAChD,MAAI,eAAe,MAAM;AACvB,WAAO,gBAAgB,UAAU;AAAA,EACnC;AACA,QAAM,QAAQ,UAAU,UAAU;AAClC,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AACA,QAAM,SAAS,mBAAmB,KAAK;AACvC,MAAI,WAAW,MAAM;AACnB,WAAO,gBAAgB,MAAM;AAAA,EAC/B;AAKA,MAAI,CAAC,cAAc,OAAO,4BAA4B,CAAC,GAAG;AACxD,WAAO;AAAA,EACT;AACA,SAAO,sBAAsB,KAAK,CAAC,CAAC,QAAQ,IAAI,MAAM,cAAc,OAAO,QAAQ,IAAI,CAAC;AAC1F;AAGO,IAAM,mBAAmB;AAEhC,SAAS,kBAAkB,WAAuD;AAChF,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB,SAAS;AAAA,MACP,QAAS,CACP,WACA,SACA,aAKG;AACH,cAAM,aACJ,QAAQ,WAAW,KAAK,QAAQ,WAAW,IACvC,UAAU,OAAO,CAAC,UAAU,MAAM,WAAW,QAAQ,MAAM,IAC3D;AACN,YAAI,WAAW,WAAW,GAAG;AAC3B,mBAAS,IAAI,MAAM,uDAAuD,CAAC;AAC3E;AAAA,QACF;AACA,YAAI,QAAQ,KAAK;AACf;AAAA,YACE;AAAA,YACA,WAAW,IAAI,CAAC,WAAW,EAAE,SAAS,MAAM,SAAS,QAAQ,MAAM,OAAO,EAAE;AAAA,UAC9E;AACA;AAAA,QACF;AACA,cAAM,QAAQ,WAAW,CAAC;AAC1B,iBAAS,MAAM,MAAM,SAAS,MAAM,MAAM;AAAA,MAC5C;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,OAAO,YAAY;AACjB,YAAM,MAAM,MAAM;AAAA,IACpB;AAAA,IACA,SAAS,OAAO,UAAU;AACxB,YAAM,MAAM,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI;AAAA,IAC3D;AAAA,EACF;AACF;AAEA,SAAS,gCACP,UACA,YACU;AACV,QAAM,SAAS,SAAS,KAAM,UAAU;AACxC,MAAI,WAAiC;AACrC,MAAI,YAAY;AAChB,QAAM,SAAS,CAAC,SAAkB,UAAmC;AACnE,QAAI,CAAC,UAAU;AACb,iBAAW,UAAU,kBAAkB,YAAY,KAAK,IAAI,gBAAgB,UAAU;AAAA,IACxF;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI,eAA2B;AAAA,IAC1C,MAAM,KAAK,YAAY;AACrB,UAAI;AACF,cAAM,QAAQ,MAAM,OAAO,KAAK;AAChC,YAAI,MAAM,MAAM;AACd,gBAAM,OAAO,SAAS;AACtB,qBAAW,MAAM;AACjB;AAAA,QACF;AACA,mBAAW,QAAQ,MAAM,KAAK;AAAA,MAChC,SAAS,OAAO;AACd,cAAM,OAAO,MAAM,KAAK;AACxB,mBAAW,MAAM,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,IACA,MAAM,OAAO,QAAQ;AACnB,kBAAY;AACZ,UAAI;AACF,cAAM,OAAO,OAAO,MAAM;AAAA,MAC5B,UAAE;AACA,cAAM,OAAO,MAAM,MAAM;AAAA,MAC3B;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM,UAAU,IAAI,SAAS,MAAM;AAAA,IACjC,QAAQ,SAAS;AAAA,IACjB,YAAY,SAAS;AAAA,IACrB,SAAS,SAAS;AAAA,EACpB,CAAC;AACD,SAAO,iBAAiB,SAAS;AAAA,IAC/B,YAAY,EAAE,OAAO,SAAS,WAAW;AAAA,IACzC,MAAM,EAAE,OAAO,SAAS,KAAK;AAAA,IAC7B,KAAK,EAAE,OAAO,SAAS,IAAI;AAAA,EAC7B,CAAC;AACD,SAAO;AACT;AAEA,eAAe,gBAAgB,YAAgD;AAC7E,MAAI;AACF,UAAM,WAAW,MAAM;AAAA,EACzB,QAAQ;AACN,UAAM,kBAAkB,UAAU;AAAA,EACpC;AACF;AAEA,eAAe,kBAAkB,YAAiC,OAAgC;AAChG,MAAI;AACF,UAAM,WAAW,QAAQ,KAAK;AAAA,EAChC,QAAQ;AAAA,EAER;AACF;AAEA,eAAe,eAAe,UAAmC;AAC/D,QAAM,SAAS,MAAM,OAAO,EAAE,MAAM,MAAM,MAAS;AACrD;AAEA,SAAS,kBAAkB,UAA0B;AACnD,SAAO,qBAAqB,SAAS,KAAK,EAAE,YAAY,CAAC,EAAE,QAAQ,OAAO,EAAE;AAC9E;AAEA,SAAS,qBAAqB,SAAyB;AACrD,SAAO,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI;AACnF;AAEA,SAAS,gBAAgB,UAA2B;AAClD,SAAO,aAAa,eAAe,SAAS,SAAS,YAAY;AACnE;AAEA,SAAS,mBAAmB,UAA2B;AACrD,QAAM,aAAa,kBAAkB,QAAQ;AAC7C,SACE,eAAe,eACf,WAAW,SAAS,YAAY,KAChC,eAAe,SACf,eAAe,UAAU;AAE7B;AAEA,SAAS,eAAe,UAA2B;AACjD,MAAI,KAAK,QAAQ,MAAM,EAAG,QAAO;AACjC,SAAO,SAAS,MAAM,GAAG,EAAE,CAAC,MAAM;AACpC;AAEA,SAAS,gBAAgB,WAAgD;AACvE,QAAM,MAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,CAAC,MAAM,QAAQ,SAAS,GAAG;AAC7B,UAAM,iBAAiB;AAAA,EACzB;AACA,aAAW,SAAS,WAAW;AAC7B,UAAM,aAAa,mBAAmB,KAAK;AAC3C,UAAM,MAAM,GAAG,WAAW,MAAM,IAAI,WAAW,OAAO;AACtD,QAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAClB,WAAK,IAAI,GAAG;AACZ,UAAI,KAAK,UAAU;AAAA,IACrB;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,mBAAmB,OAA4B;AACtD,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,UAAM,iBAAiB;AAAA,EACzB;AACA,QAAM,YAAY;AAClB,MAAI,OAAO,UAAU,YAAY,UAAU;AACzC,UAAM,iBAAiB;AAAA,EACzB;AACA,QAAM,SAAS,UAAU;AACzB,MAAI,WAAW,KAAK,WAAW,GAAG;AAChC,UAAM,iBAAiB;AAAA,EACzB;AACA,QAAM,UAAU,qBAAqB,UAAU,QAAQ,KAAK,EAAE,YAAY,CAAC;AAC3E,QAAM,eAAe,KAAK,OAAO;AACjC,MAAI,iBAAiB,QAAQ;AAC3B,UAAM,iBAAiB;AAAA,EACzB;AACA,SAAO,EAAE,SAAS,OAAO;AAC3B;AAEA,SAAS,mBAA2C;AAClD,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,SAA0B;AACjD,QAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAC3C,MACE,MAAM,WAAW,KACjB,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,GAAG,GACtE;AACA,WAAO;AAAA,EACT;AACA,QAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,CAAC,KAAM,KAAO,MAAM,CAAC,KAAM,IAAK,MAAM,CAAC;AAChF,QAAM,WAAW,UAAU;AAC3B,SACE,YAAY,UAAU,GAAY,QAAU,KAC5C,YAAY,UAAU,WAAY,SAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU,KAC5C,YAAY,UAAU,YAAY,UAAU;AAEhD;AAEA,SAAS,YAAY,OAAe,OAAe,KAAsB;AACvE,SAAO,SAAS,SAAS,SAAS;AACpC;AAEA,IAAM,6BAA6B,aAAa,QAAQ;AAExD,IAAM,wBAAqD;AAAA,EACzD,CAAC,aAAa,IAAI,GAAG,EAAE;AAAA,EACvB,CAAC,aAAa,WAAW,GAAG,EAAE;AAAA,EAC9B,CAAC,aAAa,aAAa,GAAG,EAAE;AAAA,EAChC,CAAC,aAAa,OAAO,GAAG,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,EAK1B,CAAC,aAAa,QAAQ,GAAG,EAAE;AAAA,EAC3B,CAAC,aAAa,YAAY,GAAG,EAAE;AAAA,EAC/B,CAAC,aAAa,QAAQ,GAAG,EAAE;AAAA,EAC3B,CAAC,aAAa,gBAAgB,GAAG,EAAE;AAAA,EACnC,CAAC,aAAa,QAAQ,GAAG,EAAE;AAAA,EAC3B,CAAC,aAAa,QAAQ,GAAG,EAAE;AAAA,EAC3B,CAAC,aAAa,QAAQ,GAAG,EAAE;AAAA,EAC3B,CAAC,aAAa,QAAQ,GAAG,CAAC;AAAA,EAC1B,CAAC,aAAa,QAAQ,GAAG,EAAE;AAAA,EAC3B,CAAC,aAAa,QAAQ,GAAG,EAAE;AAAA,EAC3B,CAAC,aAAa,QAAQ,GAAG,CAAC;AAC5B;AAEA,SAAS,cAAc,OAAe,QAAgB,MAAuB;AAC3E,QAAM,QAAQ,OAAO,OAAO,IAAI;AAChC,SAAO,SAAS,UAAU,UAAU;AACtC;AAEA,SAAS,mBAAmB,OAA8B;AACxD,MAAI,SAAS,QAAQ,SAAS;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,WAAW,OAAO,QAAQ,WAAW;AAC3C,SAAO,GAAG,aAAa,EAAE,IAAK,aAAa,KAAM,GAAI,IAAK,aAAa,IAAK,GAAI,IAAI,WAAW,GAAI;AACrG;AAEA,SAAS,mBAAmB,SAAgC;AAC1D,MAAI,CAAC,QAAQ,WAAW,SAAS,GAAG;AAClC,WAAO;AAAA,EACT;AACA,QAAM,WAAW,QAAQ,MAAM,UAAU,MAAM;AAC/C,MAAI,KAAK,QAAQ,MAAM,GAAG;AACxB,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,MAAI,MAAM,WAAW,KAAK,MAAM,KAAK,CAAC,SAAS,CAAC,kBAAkB,KAAK,IAAI,CAAC,GAAG;AAC7E,WAAO;AAAA,EACT;AACA,QAAM,OAAO,OAAO,SAAS,MAAM,CAAC,GAAI,EAAE;AAC1C,QAAM,MAAM,OAAO,SAAS,MAAM,CAAC,GAAI,EAAE;AACzC,SAAO,GAAI,QAAQ,IAAK,GAAI,IAAI,OAAO,GAAI,IAAK,OAAO,IAAK,GAAI,IAAI,MAAM,GAAI;AAChF;AAEA,SAAS,UAAU,SAAgC;AACjD,QAAM,UAAU,QAAQ,QAAQ,GAAG;AACnC,MAAI,WAAW,GAAG;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ;AACZ,MAAI,MAAM,SAAS,GAAG,GAAG;AACvB,UAAM,YAAY,MAAM,YAAY,GAAG;AACvC,UAAM,SAAS,MAAM,MAAM,YAAY,CAAC;AACxC,UAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,IAAI,MAAM;AAC1C,QACE,MAAM,WAAW,KACjB,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,GAAG,GACtE;AACA,aAAO;AAAA,IACT;AACA,UAAM,OACF,MAAM,CAAC,KAAM,IAAK,MAAM,CAAC,GAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,KACzD,MAAM,CAAC,KAAM,IAAK,MAAM,CAAC,GAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC7D,YAAQ,GAAG,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,GAAG;AAAA,EAChD;AACA,QAAM,SAAS,MAAM,MAAM,IAAI;AAC/B,MAAI,OAAO,SAAS,GAAG;AACrB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,OAAO,CAAC,IAAI,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;AACjD,QAAM,QAAQ,OAAO,WAAW,KAAK,OAAO,CAAC,IAAI,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;AACzE,MAAI,KAAK,OAAO,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,mBAAmB,KAAK,IAAI,CAAC,GAAG;AACrE,WAAO;AAAA,EACT;AACA,QAAM,UAAU,OAAO,WAAW,IAAI,IAAI,KAAK,SAAS,MAAM,SAAS;AACvE,MAAI,UAAU,KAAM,OAAO,WAAW,KAAK,YAAY,GAAI;AACzD,WAAO;AAAA,EACT;AACA,QAAM,SAAS,CAAC,GAAG,MAAM,GAAG,MAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK;AAChF,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,OAAO,OAAO,CAAC,KAAK,UAAW,OAAO,MAAO,OAAO,KAAK,KAAK,EAAE,GAAG,EAAE;AAC9E;AAEA,SAAS,aAAa,SAAyB;AAC7C,QAAM,QAAQ,UAAU,OAAO;AAC/B,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI,MAAM,0BAA0B,OAAO,EAAE;AAAA,EACrD;AACA,SAAO;AACT;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opengeni/network",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "DNS-pinned outbound HTTP transport for OpenGeni credential-bearing requests.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Cloudgeni-ai/opengeni.git",
|
|
9
|
+
"directory": "packages/network"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"provenance": true
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"typecheck": "tsgo --noEmit",
|
|
34
|
+
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"undici": "^6.21.3"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
}
|
|
42
|
+
}
|