@ccheever/exact-ibex-runtime 0.1.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.
Files changed (161) hide show
  1. package/package.json +63 -0
  2. package/src/abort/AbortController.ts +23 -0
  3. package/src/abort/AbortSignal.ts +152 -0
  4. package/src/abort/index.ts +2 -0
  5. package/src/accessibility.ts +12 -0
  6. package/src/arraybuffer-detach.ts +109 -0
  7. package/src/base64/base64.ts +168 -0
  8. package/src/base64/index.ts +1 -0
  9. package/src/blob/Blob.ts +259 -0
  10. package/src/blob/File.ts +59 -0
  11. package/src/blob/FormData.ts +323 -0
  12. package/src/blob/index.ts +3 -0
  13. package/src/bootstrap.ts +1946 -0
  14. package/src/broadcast/BroadcastChannel.ts +280 -0
  15. package/src/broadcast/index.ts +5 -0
  16. package/src/cache/Cache.ts +349 -0
  17. package/src/cache/CacheStorage.ts +89 -0
  18. package/src/cache/index.ts +27 -0
  19. package/src/camera/index.ts +6202 -0
  20. package/src/camera/processor.worker.ts +194 -0
  21. package/src/camera/scene.ts +195 -0
  22. package/src/clipboard/Clipboard.ts +129 -0
  23. package/src/clipboard/ClipboardItem.ts +97 -0
  24. package/src/clipboard/index.ts +6 -0
  25. package/src/clone/index.ts +1 -0
  26. package/src/clone/structuredClone.ts +389 -0
  27. package/src/clone/transferableSymbols.ts +2 -0
  28. package/src/compression/CompressionStream.ts +146 -0
  29. package/src/compression/DecompressionStream.ts +342 -0
  30. package/src/compression/index.ts +4 -0
  31. package/src/console/Console.ts +341 -0
  32. package/src/console/index.ts +2 -0
  33. package/src/core/accessibility-state.ts +263 -0
  34. package/src/core/accessibility.ts +184 -0
  35. package/src/core/agent-state.ts +37 -0
  36. package/src/core/diagnostics-logs.ts +144 -0
  37. package/src/core/host-call-bridge.ts +16 -0
  38. package/src/core/i18n-helpers.ts +189 -0
  39. package/src/core/locale-state.ts +253 -0
  40. package/src/core/locale.ts +95 -0
  41. package/src/crypto/Crypto.ts +2743 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/diagnostics/logs.ts +7 -0
  44. package/src/encoding/TextDecoder.ts +1181 -0
  45. package/src/encoding/TextDecoderStream.ts +58 -0
  46. package/src/encoding/TextEncoder.ts +180 -0
  47. package/src/encoding/TextEncoderStream.ts +39 -0
  48. package/src/encoding/index.ts +8 -0
  49. package/src/events/CloseEvent.ts +91 -0
  50. package/src/events/DOMException.ts +409 -0
  51. package/src/events/ErrorEvent.ts +39 -0
  52. package/src/events/Event.ts +151 -0
  53. package/src/events/EventTarget.ts +280 -0
  54. package/src/events/FocusEvent.ts +27 -0
  55. package/src/events/KeyboardEvent.ts +46 -0
  56. package/src/events/MessageEvent.ts +61 -0
  57. package/src/events/ProgressEvent.ts +33 -0
  58. package/src/events/PromiseRejectionEvent.ts +31 -0
  59. package/src/events/index.ts +52 -0
  60. package/src/eventsource/EventSource.ts +371 -0
  61. package/src/eventsource/index.ts +2 -0
  62. package/src/fetch/Headers.ts +642 -0
  63. package/src/fetch/Request.ts +760 -0
  64. package/src/fetch/Response.ts +543 -0
  65. package/src/fetch/body.ts +1256 -0
  66. package/src/fetch/cookie-jar.ts +566 -0
  67. package/src/fetch/demo.ts +207 -0
  68. package/src/fetch/errors.ts +101 -0
  69. package/src/fetch/fetch.ts +2610 -0
  70. package/src/fetch/index.ts +101 -0
  71. package/src/fetch/native-bridge.ts +65 -0
  72. package/src/fetch/types.ts +258 -0
  73. package/src/filereader/FileReader.ts +236 -0
  74. package/src/filereader/index.ts +1 -0
  75. package/src/fs/Dirent.ts +39 -0
  76. package/src/fs/ExactFile.ts +450 -0
  77. package/src/fs/Stats.ts +80 -0
  78. package/src/fs/index.ts +944 -0
  79. package/src/fs/promises.ts +386 -0
  80. package/src/fs/shared.ts +328 -0
  81. package/src/http-server/index.js +697 -0
  82. package/src/http-server/index.ts +27 -0
  83. package/src/identity.generated.ts +14 -0
  84. package/src/index.ts +283 -0
  85. package/src/indexeddb/IDBCursor.ts +188 -0
  86. package/src/indexeddb/IDBDatabase.ts +343 -0
  87. package/src/indexeddb/IDBFactory.ts +269 -0
  88. package/src/indexeddb/IDBIndex.ts +194 -0
  89. package/src/indexeddb/IDBKeyRange.ts +109 -0
  90. package/src/indexeddb/IDBObjectStore.ts +468 -0
  91. package/src/indexeddb/IDBRequest.ts +163 -0
  92. package/src/indexeddb/IDBTransaction.ts +207 -0
  93. package/src/indexeddb/index.ts +34 -0
  94. package/src/indexeddb/utils.ts +52 -0
  95. package/src/inspect/index.ts +1 -0
  96. package/src/inspect/inspect.ts +465 -0
  97. package/src/internal/detect.ts +104 -0
  98. package/src/locale.ts +10 -0
  99. package/src/location/index.ts +1059 -0
  100. package/src/locks/LockManager.ts +460 -0
  101. package/src/locks/index.ts +12 -0
  102. package/src/media/VideoFrame.ts +58 -0
  103. package/src/messaging/MessageChannel.ts +31 -0
  104. package/src/messaging/MessagePort.ts +180 -0
  105. package/src/messaging/index.ts +2 -0
  106. package/src/messaging.ts +247 -0
  107. package/src/native/NativeModules.ts +354 -0
  108. package/src/native/index.ts +1 -0
  109. package/src/navigator/Navigator.ts +351 -0
  110. package/src/navigator/index.ts +1 -0
  111. package/src/node/Buffer.ts +1786 -0
  112. package/src/node/index.ts +4 -0
  113. package/src/node/path.ts +495 -0
  114. package/src/node/process.ts +2528 -0
  115. package/src/performance/Performance.ts +532 -0
  116. package/src/performance/index.ts +21 -0
  117. package/src/polyfills/array.ts +236 -0
  118. package/src/polyfills/arraybuffer.ts +172 -0
  119. package/src/polyfills/groupby.ts +85 -0
  120. package/src/polyfills/index.ts +85 -0
  121. package/src/polyfills/intl.ts +1956 -0
  122. package/src/polyfills/iterator.ts +479 -0
  123. package/src/polyfills/promise.ts +37 -0
  124. package/src/polyfills/set.ts +245 -0
  125. package/src/polyfills/string.ts +85 -0
  126. package/src/polyfills/typedarray.ts +110 -0
  127. package/src/promise-rejection-tracking.ts +464 -0
  128. package/src/react-native/index.ts +388 -0
  129. package/src/runtime-entry.ts +55 -0
  130. package/src/scheduling/AnimationFrame.ts +105 -0
  131. package/src/scheduling/IdleCallback.ts +167 -0
  132. package/src/scheduling/index.ts +13 -0
  133. package/src/security/Capabilities.ts +1146 -0
  134. package/src/security/Permissions.ts +392 -0
  135. package/src/security/capability-bits.generated.ts +63 -0
  136. package/src/security/index.ts +16 -0
  137. package/src/sqlite/Database.ts +456 -0
  138. package/src/sqlite/Statement.ts +206 -0
  139. package/src/sqlite/constants.ts +79 -0
  140. package/src/sqlite/errors.ts +25 -0
  141. package/src/sqlite/index.ts +34 -0
  142. package/src/sqlite/module.js +438 -0
  143. package/src/storage/Storage.ts +291 -0
  144. package/src/storage/StorageManager.ts +91 -0
  145. package/src/storage/index.ts +3 -0
  146. package/src/stream-compat.ts +47 -0
  147. package/src/streams/ReadableStream.ts +4131 -0
  148. package/src/streams/TransformStream.ts +375 -0
  149. package/src/streams/WritableStream.ts +866 -0
  150. package/src/streams/index.ts +41 -0
  151. package/src/timers/Timers.ts +296 -0
  152. package/src/timers/index.ts +11 -0
  153. package/src/url/URL.ts +656 -0
  154. package/src/url/URLPattern.ts +850 -0
  155. package/src/url/URLSearchParams.ts +244 -0
  156. package/src/url/index.ts +9 -0
  157. package/src/websocket/WebSocket.ts +770 -0
  158. package/src/websocket/WebSocketError.ts +52 -0
  159. package/src/websocket/WebSocketStream.ts +628 -0
  160. package/src/websocket/index.ts +7 -0
  161. package/src/window/index.ts +872 -0
@@ -0,0 +1,2610 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Fetch API Implementation
4
+ *
5
+ * Implements the WHATWG Fetch Standard fetch() function.
6
+ * @see https://fetch.spec.whatwg.org/#fetch-method
7
+ */
8
+
9
+ import { Headers } from './Headers.js';
10
+ import { Request, type RequestInput } from './Request.js';
11
+ import { Response } from './Response.js';
12
+ import { FetchError, AbortError } from './errors.js';
13
+ import { DOMException } from '../events/DOMException.js';
14
+ import type {
15
+ RequestInit,
16
+ NativeRequestInit,
17
+ NativeFetchModule,
18
+ NativeResponse,
19
+ NativeStreamingResponse,
20
+ } from './types.js';
21
+ import { requireCapability, Capabilities } from '../security/Capabilities';
22
+ import { cookieJar, getRuntimeOrigin } from './cookie-jar.js';
23
+ import { getTextEncoder, normalizeBlobContentType, readableStreamToUint8Array } from './body.js';
24
+
25
+ /**
26
+ * Global reference to the native fetch module.
27
+ * This is injected by the native layer at runtime.
28
+ */
29
+ let nativeFetchModule: NativeFetchModule | null = null;
30
+ const DEFAULT_FETCH_TIMEOUT_MS = 0;
31
+
32
+ /**
33
+ * Set the native fetch module.
34
+ * Called by the native layer during initialization.
35
+ */
36
+ export function setNativeFetchModule(module: NativeFetchModule): void {
37
+ nativeFetchModule = module;
38
+ }
39
+
40
+ /**
41
+ * Check if the native fetch module has been initialized.
42
+ * Use this to decide whether to set up a mock/fallback.
43
+ */
44
+ export function isNativeFetchModuleInitialized(): boolean {
45
+ return nativeFetchModule !== null;
46
+ }
47
+
48
+ /**
49
+ * Get the native fetch module.
50
+ * Throws if not initialized.
51
+ */
52
+ function getNativeFetchModule(): NativeFetchModule {
53
+ if (!nativeFetchModule) {
54
+ throw new FetchError('Native fetch module not initialized');
55
+ }
56
+ return nativeFetchModule;
57
+ }
58
+
59
+ /**
60
+ * Add an abort signal listener with cleanup function.
61
+ */
62
+ function addAbortSignalListener(
63
+ signal: AbortSignal | null | undefined,
64
+ listener: () => void
65
+ ): () => void {
66
+ if (!signal) {
67
+ return () => {};
68
+ }
69
+
70
+ signal.addEventListener('abort', listener);
71
+ return () => {
72
+ signal.removeEventListener('abort', listener);
73
+ };
74
+ }
75
+
76
+ function createDataUrlFetchError(): TypeError {
77
+ return new TypeError(
78
+ isBunCompatEnv() ? 'failed to fetch the data URL' : 'Failed to fetch: invalid data URL'
79
+ );
80
+ }
81
+
82
+ function isAsciiHexDigit(code: number): boolean {
83
+ return (
84
+ (code >= 0x30 && code <= 0x39) ||
85
+ (code >= 0x41 && code <= 0x46) ||
86
+ (code >= 0x61 && code <= 0x66)
87
+ );
88
+ }
89
+
90
+ function hexDigitToValue(code: number): number {
91
+ if (code >= 0x30 && code <= 0x39) {
92
+ return code - 0x30;
93
+ }
94
+ if (code >= 0x41 && code <= 0x46) {
95
+ return code - 0x41 + 10;
96
+ }
97
+ return code - 0x61 + 10;
98
+ }
99
+
100
+ function decodePercentEscapesLooseToLatin1(input: string): string {
101
+ let decoded = '';
102
+ for (let i = 0; i < input.length; i++) {
103
+ const code = input.charCodeAt(i);
104
+ if (
105
+ code === 0x25 &&
106
+ i + 2 < input.length &&
107
+ isAsciiHexDigit(input.charCodeAt(i + 1)) &&
108
+ isAsciiHexDigit(input.charCodeAt(i + 2))
109
+ ) {
110
+ decoded += String.fromCharCode(
111
+ (hexDigitToValue(input.charCodeAt(i + 1)) << 4) | hexDigitToValue(input.charCodeAt(i + 2))
112
+ );
113
+ i += 2;
114
+ continue;
115
+ }
116
+ decoded += input[i];
117
+ }
118
+ return decoded;
119
+ }
120
+
121
+ function decodeDataUrlPayloadLoose(input: string): Uint8Array {
122
+ const bytes: number[] = [];
123
+ const encoder = getTextEncoder();
124
+
125
+ for (let i = 0; i < input.length; ) {
126
+ const code = input.charCodeAt(i);
127
+ if (
128
+ code === 0x25 &&
129
+ i + 2 < input.length &&
130
+ isAsciiHexDigit(input.charCodeAt(i + 1)) &&
131
+ isAsciiHexDigit(input.charCodeAt(i + 2))
132
+ ) {
133
+ bytes.push(
134
+ (hexDigitToValue(input.charCodeAt(i + 1)) << 4) | hexDigitToValue(input.charCodeAt(i + 2))
135
+ );
136
+ i += 3;
137
+ continue;
138
+ }
139
+
140
+ const codePoint = input.codePointAt(i)!;
141
+ const encoded = encoder.encode(String.fromCodePoint(codePoint));
142
+ for (let j = 0; j < encoded.length; j++) {
143
+ bytes.push(encoded[j]);
144
+ }
145
+ i += codePoint > 0xffff ? 2 : 1;
146
+ }
147
+
148
+ return new Uint8Array(bytes);
149
+ }
150
+
151
+ // Internal coordination promises are observed indirectly through await/Promise.race.
152
+ // Attach a no-op rejection handler so the runtime's unhandled rejection tracker does not
153
+ // report the internal promise when the error is rethrown through the public fetch promise.
154
+ function suppressInternalPromiseRejection<T>(promise: Promise<T>): Promise<T> {
155
+ promise.catch(() => {});
156
+ return promise;
157
+ }
158
+
159
+ function resolveTimeoutMs(init?: RequestInit): number {
160
+ const timeout = init?.timeout;
161
+ if (timeout === undefined) {
162
+ return DEFAULT_FETCH_TIMEOUT_MS;
163
+ }
164
+ if (!Number.isFinite(timeout) || timeout < 0) {
165
+ throw new TypeError('Failed to fetch: timeout must be a non-negative finite number');
166
+ }
167
+ return timeout;
168
+ }
169
+
170
+ function createEffectiveSignal(
171
+ sourceSignal: AbortSignal | null | undefined,
172
+ timeoutMs: number
173
+ ): { signal: AbortSignal | null | undefined; cleanup: () => void } {
174
+ if (timeoutMs === 0) {
175
+ return { signal: sourceSignal, cleanup: () => {} };
176
+ }
177
+
178
+ const controller = new AbortController();
179
+ let timeoutId: ReturnType<typeof setTimeout> | undefined = setTimeout(() => {
180
+ controller.abort(new DOMException('The operation timed out.', 'TimeoutError'));
181
+ }, timeoutMs);
182
+
183
+ const forwardAbort = () => {
184
+ controller.abort(sourceSignal?.reason);
185
+ };
186
+
187
+ if (sourceSignal?.aborted) {
188
+ forwardAbort();
189
+ } else if (sourceSignal) {
190
+ sourceSignal.addEventListener('abort', forwardAbort);
191
+ }
192
+
193
+ return {
194
+ signal: controller.signal,
195
+ cleanup: () => {
196
+ if (timeoutId !== undefined) {
197
+ clearTimeout(timeoutId);
198
+ timeoutId = undefined;
199
+ }
200
+ if (sourceSignal) {
201
+ sourceSignal.removeEventListener('abort', forwardAbort);
202
+ }
203
+ },
204
+ };
205
+ }
206
+
207
+ type ReferrerSource = 'request-object' | 'input';
208
+ type CancelableNativeResponsePromise = Promise<NativeResponse> & {
209
+ __exactCancel?: () => void;
210
+ };
211
+
212
+ type SocketBuffer = Uint8Array & {
213
+ toString?(encoding?: string, start?: number, end?: number): string;
214
+ length: number;
215
+ subarray(start?: number, end?: number): SocketBuffer;
216
+ };
217
+
218
+ interface BufferConstructorLike {
219
+ from(input: string | ArrayBuffer | ArrayBufferView, encoding?: string): SocketBuffer;
220
+ alloc(size: number): SocketBuffer;
221
+ concat(list: ReadonlyArray<Uint8Array>): SocketBuffer;
222
+ }
223
+
224
+ type RequestTlsOptions = NonNullable<RequestInit['tls']>;
225
+
226
+ const CERTIFICATE_PEM_PATTERN = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
227
+
228
+ function normalizeNativeBody(body: unknown): ArrayBuffer | null {
229
+ if (body == null) {
230
+ return null;
231
+ }
232
+ if (body instanceof ArrayBuffer) {
233
+ return body;
234
+ }
235
+ if (ArrayBuffer.isView(body)) {
236
+ const view = body as ArrayBufferView;
237
+ return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
238
+ }
239
+ throw new TypeError('Failed to fetch: invalid native response body');
240
+ }
241
+
242
+ function validateNativeHeaders(headers: unknown): [string, string][] {
243
+ if (!Array.isArray(headers)) {
244
+ throw new TypeError('Failed to fetch: invalid native response headers');
245
+ }
246
+
247
+ return headers.map((header) => {
248
+ if (!Array.isArray(header) || header.length !== 2) {
249
+ throw new TypeError('Failed to fetch: invalid native response headers');
250
+ }
251
+ const [name, value] = header;
252
+ if (typeof name !== 'string' || typeof value !== 'string') {
253
+ throw new TypeError('Failed to fetch: invalid native response headers');
254
+ }
255
+ return [name, normalizeBunCompatHeaderValue(value)];
256
+ });
257
+ }
258
+
259
+ function validateNativeResponseBase(nativeResponse: unknown): Omit<NativeResponse, 'body'> {
260
+ if (typeof nativeResponse !== 'object' || nativeResponse === null) {
261
+ throw new TypeError('Failed to fetch: invalid native response');
262
+ }
263
+
264
+ const { status, statusText, headers, url, redirected } = nativeResponse as Record<string, unknown>;
265
+ if (!Number.isInteger(status) || (status as number) < 0 || (status as number) > 599) {
266
+ throw new TypeError('Failed to fetch: invalid native response status');
267
+ }
268
+ if (typeof statusText !== 'string') {
269
+ throw new TypeError('Failed to fetch: invalid native response status text');
270
+ }
271
+ if (typeof url !== 'string') {
272
+ throw new TypeError('Failed to fetch: invalid native response url');
273
+ }
274
+ if (typeof redirected !== 'boolean') {
275
+ throw new TypeError('Failed to fetch: invalid native response redirect flag');
276
+ }
277
+
278
+ return {
279
+ status,
280
+ statusText,
281
+ headers: validateNativeHeaders(headers),
282
+ url,
283
+ redirected,
284
+ };
285
+ }
286
+
287
+ function validateNativeResponse(nativeResponse: unknown): NativeResponse {
288
+ const base = validateNativeResponseBase(nativeResponse);
289
+ const body = normalizeNativeBody((nativeResponse as Record<string, unknown>).body);
290
+ return { ...base, body };
291
+ }
292
+
293
+ function validateNativeStreamingResponse(nativeResponse: unknown): NativeStreamingResponse {
294
+ return validateNativeResponseBase(nativeResponse);
295
+ }
296
+
297
+ function hasHeader(headers: [string, string][], name: string): boolean {
298
+ const target = name.toLowerCase();
299
+ for (const [headerName] of headers) {
300
+ if (headerName.toLowerCase() === target) {
301
+ return true;
302
+ }
303
+ }
304
+ return false;
305
+ }
306
+
307
+ function setHeader(headers: [string, string][], name: string, value: string): void {
308
+ const target = name.toLowerCase();
309
+ for (let i = 0; i < headers.length; i++) {
310
+ if (headers[i][0].toLowerCase() === target) {
311
+ headers[i] = [headers[i][0], value];
312
+ return;
313
+ }
314
+ }
315
+ headers.push([name, value]);
316
+ }
317
+
318
+ function copyErrorMetadata(target: Error, source: unknown): void {
319
+ if (typeof source !== 'object' || source === null) {
320
+ return;
321
+ }
322
+
323
+ const sourceRecord = source as Record<string, unknown>;
324
+ const targetRecord = target as Record<string, unknown>;
325
+ const fields = ['code', 'errno', 'syscall', 'address', 'port'];
326
+ for (let i = 0; i < fields.length; i++) {
327
+ const key = fields[i];
328
+ if (sourceRecord[key] !== undefined) {
329
+ targetRecord[key] = sourceRecord[key];
330
+ }
331
+ }
332
+ }
333
+
334
+ function createSocketTransportError(message: string, code: string): Error {
335
+ const error = new Error(message);
336
+ copyErrorMetadata(error, { code });
337
+ return error;
338
+ }
339
+
340
+ function getSocketBufferConstructor(
341
+ runtimeRequire: (specifier: string) => unknown
342
+ ): BufferConstructorLike {
343
+ const globalBuffer = (globalThis as {
344
+ Buffer?: BufferConstructorLike;
345
+ }).Buffer;
346
+ if (globalBuffer && typeof globalBuffer.from === 'function' && typeof globalBuffer.concat === 'function') {
347
+ return globalBuffer;
348
+ }
349
+
350
+ const bufferModule = runtimeRequire('node:buffer') as { Buffer?: BufferConstructorLike };
351
+ if (bufferModule && bufferModule.Buffer && typeof bufferModule.Buffer.from === 'function') {
352
+ return bufferModule.Buffer;
353
+ }
354
+
355
+ throw new FetchError('Buffer constructor unavailable for socket transport');
356
+ }
357
+
358
+ function getRuntimeProcessEnv(
359
+ runtimeRequire: (specifier: string) => unknown
360
+ ): Record<string, string | undefined> | null {
361
+ try {
362
+ const processModule = runtimeRequire('node:process') as {
363
+ env?: Record<string, string | undefined>;
364
+ };
365
+ return processModule?.env ?? null;
366
+ } catch {
367
+ return null;
368
+ }
369
+ }
370
+
371
+ function mergeCertificateSources(existing: unknown, extra: string): unknown {
372
+ if (existing === undefined || existing === null || existing === '') {
373
+ return extra;
374
+ }
375
+ if (Array.isArray(existing)) {
376
+ return existing.concat(extra);
377
+ }
378
+ return [existing, extra];
379
+ }
380
+
381
+ function loadNodeExtraCaBundle(
382
+ runtimeRequire: (specifier: string) => unknown,
383
+ env: Record<string, string | undefined> | null
384
+ ): string | null {
385
+ const extraCaPath = typeof env?.NODE_EXTRA_CA_CERTS === 'string'
386
+ ? env.NODE_EXTRA_CA_CERTS.trim()
387
+ : '';
388
+ if (!extraCaPath) {
389
+ return null;
390
+ }
391
+
392
+ let contents = '';
393
+ try {
394
+ const fs = runtimeRequire('node:fs') as {
395
+ readFileSync(path: string, encoding: string): string;
396
+ };
397
+ if (!fs || typeof fs.readFileSync !== 'function') {
398
+ return null;
399
+ }
400
+ contents = fs.readFileSync(extraCaPath, 'utf8');
401
+ } catch {
402
+ return null;
403
+ }
404
+
405
+ if (!contents.trim()) {
406
+ return null;
407
+ }
408
+
409
+ const matches = contents.match(CERTIFICATE_PEM_PATTERN);
410
+ if (!matches || matches.length === 0) {
411
+ return null;
412
+ }
413
+
414
+ const remainder = contents.replace(CERTIFICATE_PEM_PATTERN, '').trim();
415
+ if (remainder) {
416
+ if (typeof console !== 'undefined' && typeof console.error === 'function') {
417
+ console.error('ignoring extra certs from NODE_EXTRA_CA_CERTS');
418
+ }
419
+ return null;
420
+ }
421
+
422
+ return matches.join('\n');
423
+ }
424
+
425
+ function applyHttpsRequestTlsOptions(
426
+ requestOptions: Record<string, any>,
427
+ init: RequestInit | undefined,
428
+ runtimeRequire: (specifier: string) => unknown
429
+ ): void {
430
+ const tlsOptions = init?.tls;
431
+ if (tlsOptions && typeof tlsOptions === 'object') {
432
+ const tlsRecord = tlsOptions as RequestTlsOptions & Record<string, unknown>;
433
+ const keys = Object.keys(tlsRecord);
434
+ for (let i = 0; i < keys.length; i++) {
435
+ const key = keys[i];
436
+ requestOptions[key] = tlsRecord[key];
437
+ }
438
+ }
439
+
440
+ const env = getRuntimeProcessEnv(runtimeRequire);
441
+ if (
442
+ requestOptions.rejectUnauthorized === undefined &&
443
+ env?.NODE_TLS_REJECT_UNAUTHORIZED === '0'
444
+ ) {
445
+ requestOptions.rejectUnauthorized = false;
446
+ }
447
+
448
+ const extraCaBundle = loadNodeExtraCaBundle(runtimeRequire, env);
449
+ if (extraCaBundle) {
450
+ requestOptions.ca = mergeCertificateSources(requestOptions.ca, extraCaBundle);
451
+ }
452
+ }
453
+
454
+ function indexOfByteSequence(
455
+ buffer: Uint8Array,
456
+ sequence: readonly number[],
457
+ start = 0
458
+ ): number {
459
+ if (sequence.length === 0) {
460
+ return start;
461
+ }
462
+
463
+ outer:
464
+ for (let i = start; i <= buffer.length - sequence.length; i++) {
465
+ for (let j = 0; j < sequence.length; j++) {
466
+ if (buffer[i + j] !== sequence[j]) {
467
+ continue outer;
468
+ }
469
+ }
470
+ return i;
471
+ }
472
+
473
+ return -1;
474
+ }
475
+
476
+ function decodeSocketLatin1(buffer: SocketBuffer, start = 0, end = buffer.length): string {
477
+ if (typeof buffer.toString === 'function') {
478
+ return buffer.toString('latin1', start, end);
479
+ }
480
+
481
+ let decoded = '';
482
+ for (let i = start; i < end; i++) {
483
+ decoded += String.fromCharCode(buffer[i]);
484
+ }
485
+ return decoded;
486
+ }
487
+
488
+ function maybeDecompressSocketResponseBody(
489
+ responseBody: Uint8Array | null,
490
+ headers: [string, string][],
491
+ decompress: boolean | undefined,
492
+ runtimeRequire: (specifier: string) => unknown
493
+ ): Uint8Array | null {
494
+ if (!responseBody || decompress === false) {
495
+ return responseBody;
496
+ }
497
+
498
+ const encodingHeader = headers.find(([name]) => name.toLowerCase() === 'content-encoding');
499
+ const contentEncoding = encodingHeader
500
+ ? encodingHeader[1].split(',', 1)[0].trim().toLowerCase()
501
+ : '';
502
+ if (
503
+ contentEncoding !== 'gzip' &&
504
+ contentEncoding !== 'x-gzip' &&
505
+ contentEncoding !== 'deflate' &&
506
+ contentEncoding !== 'br' &&
507
+ contentEncoding !== 'zstd'
508
+ ) {
509
+ return responseBody;
510
+ }
511
+
512
+ const zlib = runtimeRequire('node:zlib') as {
513
+ gunzipSync(buffer: Uint8Array): Uint8Array;
514
+ inflateSync(buffer: Uint8Array): Uint8Array;
515
+ inflateRawSync?: (buffer: Uint8Array) => Uint8Array;
516
+ brotliDecompressSync?: (buffer: Uint8Array) => Uint8Array;
517
+ zstdDecompressSync?(buffer: Uint8Array): Uint8Array;
518
+ };
519
+ if (!zlib) {
520
+ return responseBody;
521
+ }
522
+
523
+ if (contentEncoding === 'gzip' || contentEncoding === 'x-gzip') {
524
+ return new Uint8Array(zlib.gunzipSync(responseBody));
525
+ }
526
+ if (contentEncoding === 'deflate') {
527
+ try {
528
+ return new Uint8Array(zlib.inflateSync(responseBody));
529
+ } catch (error) {
530
+ if (typeof zlib.inflateRawSync === 'function') {
531
+ try {
532
+ return new Uint8Array(zlib.inflateRawSync(responseBody));
533
+ } catch {
534
+ throw error;
535
+ }
536
+ }
537
+ throw error;
538
+ }
539
+ }
540
+ if (contentEncoding === 'br' && typeof zlib.brotliDecompressSync === 'function') {
541
+ return new Uint8Array(zlib.brotliDecompressSync(responseBody));
542
+ }
543
+ if (contentEncoding === 'zstd' && typeof zlib.zstdDecompressSync === 'function') {
544
+ return new Uint8Array(zlib.zstdDecompressSync(responseBody));
545
+ }
546
+
547
+ return responseBody;
548
+ }
549
+
550
+ function getSocketResponseContentEncoding(
551
+ headers: [string, string][],
552
+ decompress: boolean | undefined
553
+ ): string {
554
+ if (decompress === false) {
555
+ return '';
556
+ }
557
+
558
+ const encodingHeader = headers.find(([name]) => name.toLowerCase() === 'content-encoding');
559
+ return encodingHeader
560
+ ? encodingHeader[1].split(',', 1)[0].trim().toLowerCase()
561
+ : '';
562
+ }
563
+
564
+ function isLikelyZlibWrappedDeflatePrefix(prefix: Uint8Array): boolean {
565
+ if (prefix.byteLength < 2) {
566
+ return true;
567
+ }
568
+
569
+ const cmf = prefix[0];
570
+ const flg = prefix[1];
571
+ return (cmf & 0x0f) === 8 && (cmf >> 4) <= 7 && (((cmf << 8) + flg) % 31) === 0;
572
+ }
573
+
574
+ function normalizeSocketBodyStreamError(error: unknown, code?: string): Error {
575
+ const normalized = error instanceof Error ? error : new Error(String(error));
576
+ const normalizedCode = (normalized as Error & { code?: unknown }).code;
577
+ if (typeof normalizedCode === 'string' && normalizedCode.startsWith('HPE_')) {
578
+ const mapped = createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse');
579
+ copyErrorMetadata(mapped, normalized);
580
+ (mapped as Error & { code?: string }).code = 'InvalidHTTPResponse';
581
+ return mapped;
582
+ }
583
+ if (!code) {
584
+ return normalized;
585
+ }
586
+
587
+ const mapped = new Error(normalized.message);
588
+ mapped.name = 'Error';
589
+ copyErrorMetadata(mapped, normalized);
590
+ (mapped as Error & { code?: string }).code = code;
591
+ return mapped;
592
+ }
593
+
594
+ const SOCKET_DECOMPRESSED_CHUNK_SIZE = 16 * 1024;
595
+
596
+ function createSocketResponseBodyStream(
597
+ sourceStream: any,
598
+ headers: [string, string][],
599
+ decompress: boolean | undefined,
600
+ runtimeRequire: (specifier: string) => unknown,
601
+ signal: AbortSignal | null | undefined
602
+ ): ReadableStream<Uint8Array> {
603
+ const BufferCtor = getSocketBufferConstructor(runtimeRequire);
604
+ const contentEncoding = getSocketResponseContentEncoding(headers, decompress);
605
+ let settled = false;
606
+ let controller: ReadableStreamDefaultController<Uint8Array> | null = null;
607
+ let queuedChunks: Uint8Array[] = [];
608
+ let pendingError: Error | null = null;
609
+ let pendingClose = false;
610
+
611
+ function flushQueuedState(): void {
612
+ if (!controller || settled) {
613
+ return;
614
+ }
615
+
616
+ while (queuedChunks.length > 0) {
617
+ controller.enqueue(queuedChunks.shift()!);
618
+ }
619
+
620
+ if (pendingError) {
621
+ const error = pendingError;
622
+ pendingError = null;
623
+ const activeController = controller;
624
+ controller = null;
625
+ activeController.error(error);
626
+ return;
627
+ }
628
+
629
+ if (pendingClose) {
630
+ pendingClose = false;
631
+ const activeController = controller;
632
+ controller = null;
633
+ activeController.close();
634
+ }
635
+ }
636
+
637
+ function toSocketBodyChunk(chunk: Uint8Array | ArrayBufferView | string): Uint8Array {
638
+ return typeof chunk === 'string'
639
+ ? BufferCtor.from(chunk, 'utf8')
640
+ : BufferCtor.from(chunk as ArrayBufferView);
641
+ }
642
+
643
+ function queueChunkCopy(chunk: Uint8Array): void {
644
+ const copy = new Uint8Array(chunk.byteLength);
645
+ copy.set(chunk);
646
+
647
+ if (controller) {
648
+ controller.enqueue(copy);
649
+ return;
650
+ }
651
+
652
+ queuedChunks.push(copy);
653
+ }
654
+
655
+ function enqueueChunk(chunk: Uint8Array | ArrayBufferView | string, maxChunkSize?: number): void {
656
+ if (settled) {
657
+ return;
658
+ }
659
+
660
+ const bytes = toSocketBodyChunk(chunk);
661
+ if (typeof maxChunkSize !== 'number' || maxChunkSize <= 0 || bytes.byteLength <= maxChunkSize) {
662
+ queueChunkCopy(bytes);
663
+ return;
664
+ }
665
+
666
+ for (let offset = 0; offset < bytes.byteLength; offset += maxChunkSize) {
667
+ queueChunkCopy(bytes.subarray(offset, Math.min(offset + maxChunkSize, bytes.byteLength)));
668
+ }
669
+ }
670
+
671
+ const bodyStream = new ReadableStream<Uint8Array>({
672
+ start(streamController) {
673
+ controller = streamController;
674
+ flushQueuedState();
675
+ },
676
+ cancel(reason) {
677
+ cleanupAbortListener();
678
+ settled = true;
679
+ queuedChunks = [];
680
+ if (typeof sourceStream?.destroy === 'function') {
681
+ try {
682
+ sourceStream.destroy(reason instanceof Error ? reason : undefined);
683
+ } catch {}
684
+ }
685
+ },
686
+ });
687
+
688
+ const abortListener = () => {
689
+ const reason = signal?.reason instanceof Error
690
+ ? signal.reason
691
+ : new DOMException('The operation was aborted.', 'AbortError');
692
+ failOutput(reason);
693
+ };
694
+
695
+ if (signal) {
696
+ if (signal.aborted) {
697
+ abortListener();
698
+ } else {
699
+ signal.addEventListener('abort', abortListener);
700
+ }
701
+ }
702
+
703
+ function cleanupAbortListener(): void {
704
+ if (signal) {
705
+ signal.removeEventListener('abort', abortListener);
706
+ }
707
+ }
708
+
709
+ function finishOutput(): void {
710
+ if (settled) {
711
+ return;
712
+ }
713
+ settled = true;
714
+ cleanupAbortListener();
715
+ if (typeof sourceStream?.destroy === 'function') {
716
+ try {
717
+ sourceStream.destroy();
718
+ } catch {}
719
+ }
720
+ if (controller) {
721
+ const activeController = controller;
722
+ controller = null;
723
+ activeController.close();
724
+ return;
725
+ }
726
+ pendingClose = true;
727
+ }
728
+
729
+ function failOutput(error: unknown, code?: string): void {
730
+ if (settled) {
731
+ return;
732
+ }
733
+ settled = true;
734
+ cleanupAbortListener();
735
+ if (typeof sourceStream?.destroy === 'function') {
736
+ try {
737
+ sourceStream.destroy(error instanceof Error ? error : undefined);
738
+ } catch {}
739
+ }
740
+ const normalizedError = normalizeSocketBodyStreamError(error, code);
741
+ queuedChunks = [];
742
+ if (controller) {
743
+ const activeController = controller;
744
+ controller = null;
745
+ activeController.error(normalizedError);
746
+ return;
747
+ }
748
+ pendingError = normalizedError;
749
+ }
750
+
751
+ function forwardReadable(readable: any, errorCode?: string, maxChunkSize?: number): void {
752
+ readable.on('data', (chunk: Uint8Array | ArrayBufferView | string) => {
753
+ try {
754
+ enqueueChunk(chunk, maxChunkSize);
755
+ } catch (error) {
756
+ failOutput(error, errorCode);
757
+ }
758
+ });
759
+ readable.on('end', finishOutput);
760
+ readable.on('error', (error: unknown) => {
761
+ failOutput(error, errorCode);
762
+ });
763
+ if (typeof readable.resume === 'function') {
764
+ readable.resume();
765
+ }
766
+ }
767
+
768
+ sourceStream.on('aborted', () => {
769
+ failOutput(createSocketTransportError('aborted', 'ECONNRESET'));
770
+ });
771
+ sourceStream.on('close', () => {
772
+ if (!settled && typeof sourceStream.complete === 'boolean' && sourceStream.complete === false) {
773
+ failOutput(createSocketTransportError('aborted', 'ECONNRESET'));
774
+ }
775
+ });
776
+ sourceStream.on('error', (error: unknown) => {
777
+ failOutput(error);
778
+ });
779
+
780
+ if (!contentEncoding) {
781
+ forwardReadable(sourceStream);
782
+ return bodyStream;
783
+ }
784
+
785
+ const zlib = runtimeRequire('node:zlib') as {
786
+ createGunzip?: () => any;
787
+ createInflate?: () => any;
788
+ createInflateRaw?: () => any;
789
+ createBrotliDecompress?: () => any;
790
+ createZstdDecompress?: () => any;
791
+ };
792
+ if (!zlib) {
793
+ forwardReadable(sourceStream);
794
+ return bodyStream;
795
+ }
796
+
797
+ if (contentEncoding === 'gzip' || contentEncoding === 'x-gzip') {
798
+ if (typeof zlib.createGunzip !== 'function') {
799
+ forwardReadable(sourceStream);
800
+ return bodyStream;
801
+ }
802
+ const decoder = zlib.createGunzip();
803
+ sourceStream.pipe(decoder);
804
+ forwardReadable(decoder, 'ZlibError', SOCKET_DECOMPRESSED_CHUNK_SIZE);
805
+ return bodyStream;
806
+ }
807
+
808
+ if (contentEncoding === 'br') {
809
+ if (typeof zlib.createBrotliDecompress !== 'function') {
810
+ forwardReadable(sourceStream);
811
+ return bodyStream;
812
+ }
813
+ const decoder = zlib.createBrotliDecompress();
814
+ sourceStream.pipe(decoder);
815
+ forwardReadable(decoder, 'BrotliDecompressionError', SOCKET_DECOMPRESSED_CHUNK_SIZE);
816
+ return bodyStream;
817
+ }
818
+
819
+ if (contentEncoding === 'zstd') {
820
+ if (typeof zlib.createZstdDecompress !== 'function') {
821
+ forwardReadable(sourceStream);
822
+ return bodyStream;
823
+ }
824
+ const decoder = zlib.createZstdDecompress();
825
+ sourceStream.pipe(decoder);
826
+ forwardReadable(decoder, 'ZstdDecompressionError', SOCKET_DECOMPRESSED_CHUNK_SIZE);
827
+ return bodyStream;
828
+ }
829
+
830
+ if (
831
+ contentEncoding === 'deflate' &&
832
+ typeof zlib.createInflate === 'function' &&
833
+ typeof zlib.createInflateRaw === 'function'
834
+ ) {
835
+ let decoder: any = null;
836
+ const prefixChunks: Uint8Array[] = [];
837
+ let prefixLength = 0;
838
+
839
+ function startDeflateDecoder(): void {
840
+ if (decoder) {
841
+ return;
842
+ }
843
+
844
+ const prefix = prefixLength > 0 ? concatUint8Chunks(prefixChunks, prefixLength) : new Uint8Array(0);
845
+ decoder = isLikelyZlibWrappedDeflatePrefix(prefix)
846
+ ? zlib.createInflate!()
847
+ : zlib.createInflateRaw!();
848
+ forwardReadable(decoder, 'ZlibError', SOCKET_DECOMPRESSED_CHUNK_SIZE);
849
+ if (prefix.byteLength > 0) {
850
+ decoder.write(BufferCtor.from(prefix));
851
+ }
852
+ }
853
+
854
+ sourceStream.on('data', (chunk: Uint8Array | ArrayBufferView | string) => {
855
+ const bytes = toSocketBodyChunk(chunk);
856
+ if (!decoder) {
857
+ const copy = new Uint8Array(bytes.byteLength);
858
+ copy.set(bytes);
859
+ prefixChunks.push(copy);
860
+ prefixLength += copy.byteLength;
861
+ if (prefixLength >= 2) {
862
+ startDeflateDecoder();
863
+ }
864
+ return;
865
+ }
866
+ decoder.write(bytes);
867
+ });
868
+ sourceStream.on('end', () => {
869
+ startDeflateDecoder();
870
+ decoder.end();
871
+ });
872
+ return bodyStream;
873
+ }
874
+
875
+ forwardReadable(sourceStream);
876
+ return bodyStream;
877
+ }
878
+
879
+ interface ParsedSocketResponseHead {
880
+ status: number;
881
+ statusText: string;
882
+ headers: [string, string][];
883
+ bodyOffset: number;
884
+ chunked: boolean;
885
+ contentLength: number | null;
886
+ noBody: boolean;
887
+ }
888
+
889
+ function parseSocketResponseHead(
890
+ buffer: SocketBuffer,
891
+ method: string
892
+ ): ParsedSocketResponseHead | null {
893
+ const headerEnd = indexOfByteSequence(buffer, [13, 10, 13, 10], 0);
894
+ if (headerEnd === -1) {
895
+ return null;
896
+ }
897
+
898
+ const headerText = decodeSocketLatin1(buffer, 0, headerEnd);
899
+ const lines = headerText.split('\r\n');
900
+ const statusLine = lines.shift() ?? '';
901
+ const statusMatch = /^HTTP\/\d+\.\d+\s+(\d{3})(?:\s+(.*))?$/.exec(statusLine);
902
+ if (!statusMatch) {
903
+ throw createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse');
904
+ }
905
+
906
+ const status = Number.parseInt(statusMatch[1], 10);
907
+ const statusText = statusMatch[2] ?? '';
908
+ const headers: [string, string][] = [];
909
+ let transferEncoding = '';
910
+ let contentLength: number | null = null;
911
+
912
+ for (let i = 0; i < lines.length; i++) {
913
+ const line = lines[i];
914
+ if (!line) {
915
+ continue;
916
+ }
917
+
918
+ const colonIndex = line.indexOf(':');
919
+ if (colonIndex === -1) {
920
+ throw createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse');
921
+ }
922
+
923
+ const name = line.slice(0, colonIndex);
924
+ const value = line.slice(colonIndex + 1).trim();
925
+ headers.push([name, value]);
926
+
927
+ const normalizedName = name.toLowerCase();
928
+ if (normalizedName === 'transfer-encoding') {
929
+ transferEncoding = transferEncoding ? `${transferEncoding},${value}` : value;
930
+ } else if (normalizedName === 'content-length' && contentLength === null) {
931
+ const parsedLength = Number.parseInt(value, 10);
932
+ if (Number.isFinite(parsedLength) && parsedLength >= 0) {
933
+ contentLength = parsedLength;
934
+ }
935
+ }
936
+ }
937
+
938
+ const chunked = transferEncoding
939
+ .split(',')
940
+ .some((token) => token.trim().toLowerCase() === 'chunked');
941
+ if (chunked) {
942
+ contentLength = null;
943
+ }
944
+
945
+ const noBody =
946
+ method === 'HEAD' ||
947
+ (status >= 100 && status < 200) ||
948
+ status === 204 ||
949
+ status === 304;
950
+
951
+ return {
952
+ status,
953
+ statusText,
954
+ headers,
955
+ bodyOffset: headerEnd + 4,
956
+ chunked,
957
+ contentLength,
958
+ noBody,
959
+ };
960
+ }
961
+
962
+ async function executeRawHttpSocketFetchHop(
963
+ url: string,
964
+ nativeInit: NativeRequestInit,
965
+ effectiveHeaders: [string, string][],
966
+ body: Uint8Array | null,
967
+ runtimeRequire: (specifier: string) => unknown,
968
+ signal: AbortSignal | null | undefined,
969
+ socketPath?: string
970
+ ): Promise<NativeResponse> {
971
+ const parsedUrl = new URL(url);
972
+ const net = runtimeRequire('node:net') as {
973
+ createConnection(
974
+ options: { host?: string; port?: number; path?: string }
975
+ ): {
976
+ on(event: string, listener: (...args: any[]) => void): unknown;
977
+ once(event: string, listener: (...args: any[]) => void): unknown;
978
+ removeAllListeners?(event?: string): unknown;
979
+ write(chunk: Uint8Array | string, encoding?: string): unknown;
980
+ end(chunk?: Uint8Array | string, encoding?: string): unknown;
981
+ destroy(error?: Error): unknown;
982
+ setNoDelay?(noDelay?: boolean): unknown;
983
+ unref?(): unknown;
984
+ };
985
+ };
986
+ if (!net || typeof net.createConnection !== 'function') {
987
+ throw new FetchError('node:net not available for socket transport compatibility path');
988
+ }
989
+
990
+ const BufferCtor = getSocketBufferConstructor(runtimeRequire);
991
+ return await suppressInternalPromiseRejection(new Promise<NativeResponse>((resolve, reject) => {
992
+ const port = parsedUrl.port ? Number(parsedUrl.port) : 80;
993
+ const requestPath = `${parsedUrl.pathname || '/'}${parsedUrl.search || ''}`;
994
+ const socket = socketPath
995
+ ? net.createConnection({ path: socketPath })
996
+ : net.createConnection({ host: parsedUrl.hostname, port });
997
+
998
+ let settled = false;
999
+ let responseBuffer = BufferCtor.alloc(0);
1000
+ let parsedHead: ParsedSocketResponseHead | null = null;
1001
+ let bodyOffset = 0;
1002
+ let chunkedState: 'size' | 'data' | 'data-crlf' | 'trailers' = 'size';
1003
+ let currentChunkSize = 0;
1004
+ const bodyChunks: Uint8Array[] = [];
1005
+ let bodyLength = 0;
1006
+ const abortListener = () => {
1007
+ settleReject(signal?.reason instanceof Error ? signal.reason : new Error('The operation was aborted.'));
1008
+ };
1009
+
1010
+ const settleResolve = (nativeResponse: NativeResponse): void => {
1011
+ if (settled) {
1012
+ return;
1013
+ }
1014
+ settled = true;
1015
+ if (signal) {
1016
+ signal.removeEventListener('abort', abortListener);
1017
+ }
1018
+ if (typeof socket.removeAllListeners === 'function') {
1019
+ socket.removeAllListeners('data');
1020
+ socket.removeAllListeners('end');
1021
+ socket.removeAllListeners('error');
1022
+ }
1023
+ if (typeof socket.unref === 'function') {
1024
+ socket.unref();
1025
+ }
1026
+ resolve(nativeResponse);
1027
+ };
1028
+
1029
+ const settleReject = (error: unknown): void => {
1030
+ if (settled) {
1031
+ return;
1032
+ }
1033
+ settled = true;
1034
+ if (signal) {
1035
+ signal.removeEventListener('abort', abortListener);
1036
+ }
1037
+ socket.destroy(error instanceof Error ? error : undefined);
1038
+ reject(error);
1039
+ };
1040
+
1041
+ const pushBodyChunk = (start: number, end: number): void => {
1042
+ const length = end - start;
1043
+ if (length <= 0) {
1044
+ return;
1045
+ }
1046
+
1047
+ const chunk = new Uint8Array(length);
1048
+ chunk.set(responseBuffer.subarray(start, end));
1049
+ bodyChunks.push(chunk);
1050
+ bodyLength += chunk.byteLength;
1051
+ };
1052
+
1053
+ const finalizeResponse = (): void => {
1054
+ if (!parsedHead) {
1055
+ settleReject(createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse'));
1056
+ return;
1057
+ }
1058
+
1059
+ let responseBody = bodyLength > 0 ? concatUint8Chunks(bodyChunks, bodyLength) : null;
1060
+ try {
1061
+ responseBody = maybeDecompressSocketResponseBody(
1062
+ responseBody,
1063
+ parsedHead.headers,
1064
+ nativeInit.decompress,
1065
+ runtimeRequire
1066
+ );
1067
+ } catch (error) {
1068
+ settleReject(error);
1069
+ return;
1070
+ }
1071
+
1072
+ settleResolve({
1073
+ status: parsedHead.status,
1074
+ statusText: parsedHead.statusText,
1075
+ headers: parsedHead.headers,
1076
+ url,
1077
+ redirected: false,
1078
+ body: responseBody
1079
+ ? responseBody.buffer.slice(
1080
+ responseBody.byteOffset,
1081
+ responseBody.byteOffset + responseBody.byteLength
1082
+ )
1083
+ : null,
1084
+ });
1085
+ };
1086
+
1087
+ const consumeAvailable = (eof: boolean): void => {
1088
+ if (settled) {
1089
+ return;
1090
+ }
1091
+
1092
+ try {
1093
+ if (!parsedHead) {
1094
+ parsedHead = parseSocketResponseHead(responseBuffer, nativeInit.method);
1095
+ if (!parsedHead) {
1096
+ if (eof) {
1097
+ throw createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse');
1098
+ }
1099
+ return;
1100
+ }
1101
+
1102
+ bodyOffset = parsedHead.bodyOffset;
1103
+ if (parsedHead.noBody) {
1104
+ finalizeResponse();
1105
+ return;
1106
+ }
1107
+ if (parsedHead.contentLength === 0) {
1108
+ finalizeResponse();
1109
+ return;
1110
+ }
1111
+ }
1112
+
1113
+ if (parsedHead.chunked) {
1114
+ while (true) {
1115
+ if (chunkedState === 'size') {
1116
+ const lineEnd = indexOfByteSequence(responseBuffer, [13, 10], bodyOffset);
1117
+ if (lineEnd === -1) {
1118
+ if (eof) {
1119
+ throw createSocketTransportError('aborted', 'ECONNRESET');
1120
+ }
1121
+ return;
1122
+ }
1123
+
1124
+ const chunkLine = decodeSocketLatin1(responseBuffer, bodyOffset, lineEnd);
1125
+ const sizeToken = chunkLine.split(';', 1)[0].trim();
1126
+ if (!/^[0-9A-Fa-f]+$/.test(sizeToken)) {
1127
+ throw createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse');
1128
+ }
1129
+
1130
+ currentChunkSize = Number.parseInt(sizeToken, 16);
1131
+ bodyOffset = lineEnd + 2;
1132
+ if (currentChunkSize === 0) {
1133
+ chunkedState = 'trailers';
1134
+ } else {
1135
+ chunkedState = 'data';
1136
+ }
1137
+ continue;
1138
+ }
1139
+
1140
+ if (chunkedState === 'data') {
1141
+ if (responseBuffer.length - bodyOffset < currentChunkSize) {
1142
+ if (eof) {
1143
+ throw createSocketTransportError('aborted', 'ECONNRESET');
1144
+ }
1145
+ return;
1146
+ }
1147
+
1148
+ pushBodyChunk(bodyOffset, bodyOffset + currentChunkSize);
1149
+ bodyOffset += currentChunkSize;
1150
+ chunkedState = 'data-crlf';
1151
+ continue;
1152
+ }
1153
+
1154
+ if (chunkedState === 'data-crlf') {
1155
+ if (responseBuffer.length - bodyOffset < 2) {
1156
+ if (eof) {
1157
+ throw createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse');
1158
+ }
1159
+ return;
1160
+ }
1161
+
1162
+ if (responseBuffer[bodyOffset] !== 13 || responseBuffer[bodyOffset + 1] !== 10) {
1163
+ throw createSocketTransportError('InvalidHTTPResponse', 'InvalidHTTPResponse');
1164
+ }
1165
+
1166
+ bodyOffset += 2;
1167
+ currentChunkSize = 0;
1168
+ chunkedState = 'size';
1169
+ continue;
1170
+ }
1171
+
1172
+ if (responseBuffer.length - bodyOffset >= 2 &&
1173
+ responseBuffer[bodyOffset] === 13 &&
1174
+ responseBuffer[bodyOffset + 1] === 10) {
1175
+ bodyOffset += 2;
1176
+ finalizeResponse();
1177
+ return;
1178
+ }
1179
+
1180
+ const trailerEnd = indexOfByteSequence(responseBuffer, [13, 10, 13, 10], bodyOffset);
1181
+ if (trailerEnd !== -1) {
1182
+ bodyOffset = trailerEnd + 4;
1183
+ finalizeResponse();
1184
+ return;
1185
+ }
1186
+
1187
+ if (eof) {
1188
+ finalizeResponse();
1189
+ return;
1190
+ }
1191
+
1192
+ return;
1193
+ }
1194
+ }
1195
+
1196
+ if (parsedHead.contentLength !== null) {
1197
+ if (responseBuffer.length - bodyOffset < parsedHead.contentLength) {
1198
+ if (eof) {
1199
+ throw createSocketTransportError('aborted', 'ECONNRESET');
1200
+ }
1201
+ return;
1202
+ }
1203
+
1204
+ pushBodyChunk(bodyOffset, bodyOffset + parsedHead.contentLength);
1205
+ bodyOffset += parsedHead.contentLength;
1206
+ finalizeResponse();
1207
+ return;
1208
+ }
1209
+
1210
+ if (eof) {
1211
+ pushBodyChunk(bodyOffset, responseBuffer.length);
1212
+ bodyOffset = responseBuffer.length;
1213
+ finalizeResponse();
1214
+ }
1215
+ } catch (error) {
1216
+ settleReject(error);
1217
+ }
1218
+ };
1219
+
1220
+ socket.on('data', (chunk: Uint8Array | ArrayBufferView | string) => {
1221
+ if (settled) {
1222
+ return;
1223
+ }
1224
+
1225
+ const bytes =
1226
+ typeof chunk === 'string'
1227
+ ? BufferCtor.from(chunk, 'utf8')
1228
+ : BufferCtor.from(chunk as ArrayBufferView);
1229
+ responseBuffer = responseBuffer.length === 0
1230
+ ? bytes
1231
+ : BufferCtor.concat([responseBuffer, bytes]);
1232
+ consumeAvailable(false);
1233
+ });
1234
+
1235
+ socket.on('end', () => {
1236
+ consumeAvailable(true);
1237
+ });
1238
+
1239
+ socket.on('error', (error: unknown) => {
1240
+ settleReject(error);
1241
+ });
1242
+
1243
+ socket.on('connect', () => {
1244
+ if (typeof socket.setNoDelay === 'function') {
1245
+ socket.setNoDelay(true);
1246
+ }
1247
+
1248
+ const requestLines: string[] = [
1249
+ `${nativeInit.method} ${requestPath} HTTP/1.1`,
1250
+ ];
1251
+ for (let i = 0; i < effectiveHeaders.length; i++) {
1252
+ const [name, value] = effectiveHeaders[i];
1253
+ requestLines.push(`${name}: ${value}`);
1254
+ }
1255
+ requestLines.push('', '');
1256
+
1257
+ const requestHead = BufferCtor.from(requestLines.join('\r\n'), 'utf8');
1258
+ if (body && body.byteLength > 0) {
1259
+ socket.write(requestHead);
1260
+ socket.write(body);
1261
+ } else {
1262
+ socket.write(requestHead);
1263
+ }
1264
+ });
1265
+
1266
+ if (signal) {
1267
+ signal.addEventListener('abort', abortListener);
1268
+ }
1269
+
1270
+ if (signal?.aborted) {
1271
+ abortListener();
1272
+ return;
1273
+ }
1274
+ }));
1275
+ }
1276
+
1277
+ function isSecureUrlProtocol(protocol: string): boolean {
1278
+ return protocol === 'https:' || protocol === 'wss:';
1279
+ }
1280
+
1281
+ function isLoopbackHostname(hostname: string): boolean {
1282
+ const normalized = hostname.toLowerCase();
1283
+ return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
1284
+ }
1285
+
1286
+ function shouldUseBunCompatKeepaliveSocketTransport(url: string, request: Request): boolean {
1287
+ if (!isBunCompatEnv() || !request.hasExplicitKeepalive() || request.keepalive) {
1288
+ return false;
1289
+ }
1290
+
1291
+ try {
1292
+ const parsedUrl = new URL(url);
1293
+ return parsedUrl.protocol === 'http:' && isLoopbackHostname(parsedUrl.hostname);
1294
+ } catch {
1295
+ return false;
1296
+ }
1297
+ }
1298
+
1299
+ function shouldUseBunCompatLoopbackSocketTransport(url: string): boolean {
1300
+ if (!isBunCompatEnv()) {
1301
+ return false;
1302
+ }
1303
+
1304
+ try {
1305
+ const parsedUrl = new URL(url);
1306
+ return (
1307
+ (parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:') &&
1308
+ isLoopbackHostname(parsedUrl.hostname)
1309
+ );
1310
+ } catch {
1311
+ return false;
1312
+ }
1313
+ }
1314
+
1315
+ function shouldUseTlsSocketTransport(init?: RequestInit): boolean {
1316
+ if (init?.tls) {
1317
+ return true;
1318
+ }
1319
+
1320
+ const env = (globalThis as {
1321
+ process?: { env?: Record<string, string | undefined> };
1322
+ }).process?.env;
1323
+ if (!env) {
1324
+ return false;
1325
+ }
1326
+
1327
+ return (
1328
+ (typeof env.NODE_EXTRA_CA_CERTS === 'string' && env.NODE_EXTRA_CA_CERTS.trim().length > 0) ||
1329
+ env.NODE_TLS_REJECT_UNAUTHORIZED === '0'
1330
+ );
1331
+ }
1332
+
1333
+ function locationForReferrer(source: ReferrerSource): string | null {
1334
+ const location = (globalThis as any).location;
1335
+ if (!location || typeof location !== 'object') {
1336
+ return null;
1337
+ }
1338
+ const directHref = typeof location.href === 'string' ? location.href : null;
1339
+ if (source === 'request-object') {
1340
+ if (directHref) {
1341
+ return directHref;
1342
+ }
1343
+ }
1344
+
1345
+ if (source === 'input') {
1346
+ if (typeof location.toString === 'function') {
1347
+ const stringValue = location.toString();
1348
+ if (stringValue) {
1349
+ return stringValue;
1350
+ }
1351
+ }
1352
+ }
1353
+
1354
+ if (directHref) {
1355
+ return directHref;
1356
+ }
1357
+ return null;
1358
+ }
1359
+
1360
+ function isRequestInputObject(input: unknown): input is Request {
1361
+ return (
1362
+ typeof input === 'object' &&
1363
+ input !== null &&
1364
+ 'url' in input &&
1365
+ 'method' in input &&
1366
+ 'referrer' in input
1367
+ );
1368
+ }
1369
+
1370
+ function buildReferrerHeader(request: Request, source: ReferrerSource): string | null {
1371
+ const rawReferrer = request.referrer;
1372
+ if (rawReferrer === '' || rawReferrer === 'no-referrer') {
1373
+ return null;
1374
+ }
1375
+
1376
+ const policy = request.referrerPolicy || 'no-referrer-when-downgrade';
1377
+ const targetUrl = request.url;
1378
+ if (!targetUrl) {
1379
+ return null;
1380
+ }
1381
+
1382
+ let referrerUrl: URL;
1383
+ try {
1384
+ const resolvedReferrer = rawReferrer === 'about:client'
1385
+ ? locationForReferrer(source)
1386
+ : rawReferrer;
1387
+ if (!resolvedReferrer) {
1388
+ return null;
1389
+ }
1390
+ referrerUrl = new URL(resolvedReferrer);
1391
+ } catch {
1392
+ return null;
1393
+ }
1394
+
1395
+ let requestUrl: URL;
1396
+ try {
1397
+ requestUrl = new URL(targetUrl);
1398
+ } catch {
1399
+ return null;
1400
+ }
1401
+
1402
+ // Per Fetch spec, strip credentials and fragment from the referrer URL.
1403
+ referrerUrl.username = '';
1404
+ referrerUrl.password = '';
1405
+ referrerUrl.hash = '';
1406
+ const isDownGrade = isSecureUrlProtocol(referrerUrl.protocol) && !isSecureUrlProtocol(requestUrl.protocol);
1407
+ const sameOrigin = requestUrl.origin === referrerUrl.origin;
1408
+
1409
+ if (policy === 'no-referrer') {
1410
+ return null;
1411
+ }
1412
+ if (policy === 'no-referrer-when-downgrade') {
1413
+ return isDownGrade ? null : referrerUrl.href;
1414
+ }
1415
+ if (policy === 'same-origin') {
1416
+ return sameOrigin ? referrerUrl.href : null;
1417
+ }
1418
+ if (policy === 'origin') {
1419
+ return referrerUrl.origin;
1420
+ }
1421
+ if (policy === 'origin-when-cross-origin') {
1422
+ return sameOrigin ? referrerUrl.href : referrerUrl.origin;
1423
+ }
1424
+ if (policy === 'strict-origin') {
1425
+ return isDownGrade ? null : referrerUrl.origin;
1426
+ }
1427
+ if (policy === 'strict-origin-when-cross-origin') {
1428
+ if (sameOrigin) {
1429
+ return referrerUrl.href;
1430
+ }
1431
+ if (isDownGrade) {
1432
+ return null;
1433
+ }
1434
+ return referrerUrl.origin;
1435
+ }
1436
+ if (policy === 'unsafe-url') {
1437
+ return referrerUrl.href;
1438
+ }
1439
+
1440
+ // Default policy per Request defaults.
1441
+ return sameOrigin ? referrerUrl.href : referrerUrl.origin;
1442
+ }
1443
+
1444
+ /**
1445
+ * Check if cookies should be processed for this request.
1446
+ */
1447
+ function shouldProcessCookies(request: Request): boolean {
1448
+ if (request.credentials === 'omit') return false;
1449
+ if (request.credentials === 'same-origin' && request.mode === 'cors') return false;
1450
+ return true;
1451
+ }
1452
+
1453
+ /**
1454
+ * Extract Set-Cookie headers from a native response and store them in the cookie jar.
1455
+ */
1456
+ function storeCookiesFromHeaders(headers: [string, string][], url: string): void {
1457
+ try {
1458
+ const responseUrl = new URL(url);
1459
+ for (const [name, value] of headers) {
1460
+ if (name.toLowerCase() === 'set-cookie') {
1461
+ cookieJar.setCookie(value, responseUrl);
1462
+ }
1463
+ }
1464
+ } catch {
1465
+ // URL parse failure — skip cookie storage
1466
+ }
1467
+ }
1468
+
1469
+ /**
1470
+ * HTTP redirect status codes per Fetch spec.
1471
+ */
1472
+ const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
1473
+
1474
+ /**
1475
+ * Maximum number of redirects to follow per Fetch spec.
1476
+ */
1477
+ const MAX_REDIRECTS = 20;
1478
+
1479
+ /**
1480
+ * Headers that must be stripped on cross-origin redirects per Fetch spec.
1481
+ */
1482
+ const SENSITIVE_HEADERS = ['authorization', 'cookie', 'proxy-authorization'];
1483
+
1484
+ function isBunCompatEnv(): boolean {
1485
+ if ((globalThis as { __exactRuntimeContext?: string }).__exactRuntimeContext === 'shell') {
1486
+ return false;
1487
+ }
1488
+ const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;
1489
+ return env?.EXACT_COMPAT_TEST === '1' && env?.EXACT_TEST_SECTION === 'bun';
1490
+ }
1491
+
1492
+ function normalizeRedirectLocation(location: string, currentUrl?: string): string {
1493
+ let normalized = location;
1494
+
1495
+ if (isBunCompatEnv() && /[^\u0000-\u007f]/.test(normalized)) {
1496
+ const BufferCtor = (globalThis as { Buffer?: { from(input: string, encoding?: string): { toString(encoding?: string): string } } }).Buffer;
1497
+ if (BufferCtor && typeof BufferCtor.from === 'function') {
1498
+ try {
1499
+ let decoded = normalized;
1500
+ for (let i = 0; i < 3; i++) {
1501
+ let isLatin1Only = true;
1502
+ for (let j = 0; j < decoded.length; j++) {
1503
+ if (decoded.charCodeAt(j) > 0xff) {
1504
+ isLatin1Only = false;
1505
+ break;
1506
+ }
1507
+ }
1508
+ if (!isLatin1Only) {
1509
+ break;
1510
+ }
1511
+
1512
+ const next = BufferCtor.from(decoded, 'latin1').toString('utf8');
1513
+ if (next.includes('\uFFFD') || next === decoded) {
1514
+ break;
1515
+ }
1516
+ decoded = next;
1517
+ }
1518
+ if (!decoded.includes('\uFFFD')) {
1519
+ normalized = decoded;
1520
+ }
1521
+ } catch {
1522
+ // Preserve the original header value if decoding fails.
1523
+ }
1524
+ }
1525
+ }
1526
+
1527
+ if (normalized.startsWith('://')) {
1528
+ let protocol = 'http:';
1529
+ if (currentUrl) {
1530
+ const schemeMatch = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.exec(currentUrl);
1531
+ if (schemeMatch) {
1532
+ protocol = schemeMatch[0];
1533
+ }
1534
+ }
1535
+ return protocol + normalized.slice(1);
1536
+ }
1537
+
1538
+ return normalized;
1539
+ }
1540
+
1541
+ function normalizeBunCompatNetworkError(message: string): string | null {
1542
+ if (!isBunCompatEnv()) {
1543
+ return null;
1544
+ }
1545
+
1546
+ if (/specified hostname could not be found/i.test(message)) {
1547
+ return 'Unable to connect. Is the computer able to access the url?';
1548
+ }
1549
+
1550
+ return null;
1551
+ }
1552
+
1553
+ function normalizeBunCompatHeaderValue(value: string): string {
1554
+ if (!isBunCompatEnv() || !/[ÃÂ]/.test(value)) {
1555
+ return value;
1556
+ }
1557
+
1558
+ const BufferCtor = (globalThis as {
1559
+ Buffer?: {
1560
+ from(input: string, encoding?: string): { toString(encoding?: string): string };
1561
+ };
1562
+ }).Buffer;
1563
+ if (!BufferCtor || typeof BufferCtor.from !== 'function') {
1564
+ return value;
1565
+ }
1566
+
1567
+ try {
1568
+ const decoded = BufferCtor.from(value, 'latin1').toString('utf8');
1569
+ if (!decoded.includes('\uFFFD') && decoded !== value) {
1570
+ return decoded;
1571
+ }
1572
+ } catch {
1573
+ // Preserve the original header value if decoding fails.
1574
+ }
1575
+
1576
+ return value;
1577
+ }
1578
+
1579
+ /**
1580
+ * Compute the new HTTP method after a redirect per Fetch spec.
1581
+ * - 301/302 with POST → GET (body cleared)
1582
+ * - 303 → GET (body cleared) unless HEAD
1583
+ * - 307/308 → preserve method and body
1584
+ */
1585
+ function computeRedirectMethod(status: number, method: string): string {
1586
+ if (status === 301 || status === 302) {
1587
+ if (method === 'POST') return 'GET';
1588
+ }
1589
+ if (status === 303) {
1590
+ if (method !== 'HEAD') return 'GET';
1591
+ }
1592
+ // 307, 308: preserve method
1593
+ return method;
1594
+ }
1595
+
1596
+ /**
1597
+ * Create an opaque redirect response for redirect: 'manual'.
1598
+ * Per spec: status 0, no headers exposed, type 'opaqueredirect'.
1599
+ */
1600
+ function createOpaqueRedirectResponse(url: string): Response {
1601
+ const response = new Response(null);
1602
+ (response as any)._status = 0;
1603
+ (response as any)._statusText = '';
1604
+ (response as any)._ok = false;
1605
+ (response as any)._type = 'opaqueredirect';
1606
+ (response as any)._url = url;
1607
+ (response as any)._redirected = false;
1608
+ (response as any)._headers = new Headers();
1609
+ (response as any)._headers._guard = 'immutable';
1610
+ return response;
1611
+ }
1612
+
1613
+ function concatUint8Chunks(chunks: Uint8Array[], totalLength: number): Uint8Array {
1614
+ const merged = new Uint8Array(totalLength);
1615
+ let offset = 0;
1616
+ for (const chunk of chunks) {
1617
+ merged.set(chunk, offset);
1618
+ offset += chunk.byteLength;
1619
+ }
1620
+ return merged;
1621
+ }
1622
+
1623
+ function buildNativeHeaders(
1624
+ request: Request,
1625
+ source: ReferrerSource,
1626
+ decompress: boolean
1627
+ ): [string, string][] {
1628
+ const requestHeaders = request.headers.toTupleArray();
1629
+
1630
+ if (isBunCompatEnv() && !hasHeader(requestHeaders, 'connection')) {
1631
+ if (request.keepalive) {
1632
+ setHeader(requestHeaders, 'connection', 'keep-alive');
1633
+ } else if (!request.hasExplicitKeepalive()) {
1634
+ setHeader(requestHeaders, 'connection', 'keep-alive');
1635
+ }
1636
+ }
1637
+
1638
+ if (!hasHeader(requestHeaders, 'accept')) {
1639
+ requestHeaders.push(['accept', '*/*']);
1640
+ }
1641
+ if (!hasHeader(requestHeaders, 'accept-language')) {
1642
+ requestHeaders.push(['accept-language', 'en-US,en;q=0.9']);
1643
+ }
1644
+ if (
1645
+ isBunCompatEnv() &&
1646
+ !hasHeader(requestHeaders, 'user-agent') &&
1647
+ typeof navigator === 'object' &&
1648
+ navigator !== null &&
1649
+ typeof navigator.userAgent === 'string' &&
1650
+ navigator.userAgent.length > 0
1651
+ ) {
1652
+ requestHeaders.push(['user-agent', navigator.userAgent]);
1653
+ }
1654
+ const referrer = buildReferrerHeader(request, source);
1655
+ if (referrer !== null && !hasHeader(requestHeaders, 'referer')) {
1656
+ requestHeaders.push(['referer', referrer]);
1657
+ }
1658
+ if (!decompress && !hasHeader(requestHeaders, 'accept-encoding')) {
1659
+ requestHeaders.push(['accept-encoding', 'identity']);
1660
+ }
1661
+
1662
+ return requestHeaders;
1663
+ }
1664
+
1665
+ /**
1666
+ * The global fetch() method starts the process of fetching a resource from the network,
1667
+ * returning a promise which is fulfilled once the response is available.
1668
+ */
1669
+ /**
1670
+ * Fetch a data: URL and return a synthetic Response.
1671
+ */
1672
+ function fetchDataUrl(url: string, method?: string): Response {
1673
+ // Parse: data:[<mediatype>][;base64],<data>
1674
+ const commaIndex = url.indexOf(',');
1675
+ if (commaIndex === -1) {
1676
+ throw createDataUrlFetchError();
1677
+ }
1678
+
1679
+ const meta = url.slice(5, commaIndex); // after "data:" before ","
1680
+ const dataStr = url.slice(commaIndex + 1);
1681
+
1682
+ let mimeType = isBunCompatEnv() ? 'text/plain;charset=utf-8' : 'text/plain;charset=US-ASCII';
1683
+ let isBase64 = false;
1684
+
1685
+ if (meta) {
1686
+ if (meta.endsWith(';base64')) {
1687
+ isBase64 = true;
1688
+ mimeType = meta.slice(0, -7) || mimeType;
1689
+ } else {
1690
+ mimeType = meta;
1691
+ }
1692
+ }
1693
+ mimeType = isBunCompatEnv() ? normalizeBlobContentType(mimeType) : mimeType;
1694
+
1695
+ let bodyBytes: Uint8Array;
1696
+ try {
1697
+ if (isBase64) {
1698
+ const decodedBase64 = isBunCompatEnv()
1699
+ ? decodePercentEscapesLooseToLatin1(dataStr)
1700
+ : decodeURIComponent(dataStr);
1701
+ const binaryString = atob(decodedBase64);
1702
+ bodyBytes = new Uint8Array(binaryString.length);
1703
+ for (let i = 0; i < binaryString.length; i++) {
1704
+ bodyBytes[i] = binaryString.charCodeAt(i);
1705
+ }
1706
+ } else if (isBunCompatEnv()) {
1707
+ bodyBytes = decodeDataUrlPayloadLoose(dataStr);
1708
+ } else {
1709
+ const decoded = decodeURIComponent(dataStr);
1710
+ bodyBytes = getTextEncoder().encode(decoded);
1711
+ }
1712
+ } catch {
1713
+ throw createDataUrlFetchError();
1714
+ }
1715
+
1716
+ // Per spec: HEAD requests to data URLs return empty body
1717
+ const responseBody = method?.toUpperCase() === 'HEAD' ? new Uint8Array(0) : bodyBytes;
1718
+ const response = new Response(responseBody, {
1719
+ status: 200,
1720
+ statusText: 'OK',
1721
+ headers: { 'Content-Type': mimeType },
1722
+ });
1723
+ // Per spec: data URL responses have type "basic"
1724
+ (response as any)._type = 'basic';
1725
+ return response;
1726
+ }
1727
+
1728
+ export async function fetch(
1729
+ input: RequestInput,
1730
+ init?: RequestInit
1731
+ ): Promise<Response> {
1732
+ // Check capability before proceeding
1733
+ // This throws NotAllowedError if the capability is not granted
1734
+ requireCapability(Capabilities.NETWORK_FETCH);
1735
+
1736
+ // Handle data: URLs before creating Request (to avoid URL validation issues)
1737
+ const inputUrl = typeof input === 'string' ? input : (input instanceof URL ? input.href : input.url);
1738
+ if (typeof inputUrl === 'string' && inputUrl.startsWith('data:')) {
1739
+ try {
1740
+ const method = typeof input === 'string' ? init?.method : (input instanceof Request ? input.method : init?.method);
1741
+ return fetchDataUrl(inputUrl, method);
1742
+ } catch (error) {
1743
+ if (error instanceof TypeError) {
1744
+ throw error;
1745
+ }
1746
+ throw new TypeError('Failed to fetch');
1747
+ }
1748
+ }
1749
+
1750
+ // Create Request object
1751
+ // Per spec: if input is a Request, init overrides should still be applied
1752
+ let request: Request;
1753
+ try {
1754
+ request = new Request(input, init);
1755
+ } catch (error) {
1756
+ if (
1757
+ isBunCompatEnv() &&
1758
+ error instanceof TypeError &&
1759
+ init?.method === undefined &&
1760
+ init?.body !== undefined &&
1761
+ error.message.includes('cannot have body')
1762
+ ) {
1763
+ throw new TypeError('fetch() request with GET/HEAD/OPTIONS method cannot have body.');
1764
+ }
1765
+ throw error;
1766
+ }
1767
+ const isRequestInput = isRequestInputObject(input);
1768
+ const decompress = init?.decompress !== false;
1769
+ const timeoutMs = resolveTimeoutMs(init);
1770
+
1771
+ try {
1772
+ // Dispatching a fetch consumes the request body.
1773
+ request.markBodyAsUsedForFetch();
1774
+ } catch (error) {
1775
+ if (error instanceof TypeError) {
1776
+ throw error;
1777
+ }
1778
+ throw new FetchError('Request body was already used');
1779
+ }
1780
+
1781
+ // Get native module
1782
+ const nativeModule = getNativeFetchModule();
1783
+
1784
+ // Check if already aborted (signal may be null if AbortController not available)
1785
+ const signalContext = createEffectiveSignal(request.signal, timeoutMs);
1786
+ const signal = signalContext.signal;
1787
+ if (signal?.aborted) {
1788
+ throw signal.reason ?? new DOMException('The operation was aborted.', 'AbortError');
1789
+ }
1790
+
1791
+ // Set up abort handling
1792
+ let abortCleanup: () => void = () => {};
1793
+ let cancelled = false;
1794
+
1795
+ const abortPromise = suppressInternalPromiseRejection(new Promise<never>((_, reject) => {
1796
+ abortCleanup = addAbortSignalListener(signal, () => {
1797
+ cancelled = true;
1798
+ reject(signal?.reason ?? new DOMException('The operation was aborted.', 'AbortError'));
1799
+ });
1800
+ }));
1801
+ let cancelRequest: (() => void) | undefined;
1802
+
1803
+ try {
1804
+ const isStreamingUpload = request.isBodyStream();
1805
+ const requestBodySource = isStreamingUpload ? request.getBodyStream() : null;
1806
+ const requestSource = isRequestInput ? 'request-object' : 'input';
1807
+ const uploadChunks: Uint8Array[] = [];
1808
+ let uploadChunkLength = 0;
1809
+ const useSocketDecompressCompat =
1810
+ !decompress &&
1811
+ (nativeModule as NativeFetchModule & { __exactNativeBridge?: boolean }).__exactNativeBridge === true;
1812
+ const unixSocketPath =
1813
+ typeof (init as RequestInit | undefined)?.unix === 'string' &&
1814
+ (init as RequestInit).unix.length > 0
1815
+ ? (init as RequestInit).unix
1816
+ : undefined;
1817
+ const useTlsSocketTransport = shouldUseTlsSocketTransport(init);
1818
+ const preferSocketTransport =
1819
+ useSocketDecompressCompat ||
1820
+ !!unixSocketPath ||
1821
+ useTlsSocketTransport ||
1822
+ shouldUseBunCompatLoopbackSocketTransport(request.url) ||
1823
+ shouldUseBunCompatKeepaliveSocketTransport(request.url, request);
1824
+ let uploadReader =
1825
+ requestBodySource && nativeModule.fetchStreamingUpload && !preferSocketTransport
1826
+ ? requestBodySource.getReader()
1827
+ : null;
1828
+ let bufferedUploadBody: Uint8Array | null = null;
1829
+ let streamUploadPending = isStreamingUpload && !!nativeModule.fetchStreamingUpload && !!uploadReader;
1830
+ let currentBody: Uint8Array | null;
1831
+
1832
+ if (!isStreamingUpload) {
1833
+ currentBody = await request.getBodyAsUint8Array();
1834
+ } else if (!streamUploadPending) {
1835
+ currentBody = await readableStreamToUint8Array(requestBodySource!, signal);
1836
+ } else {
1837
+ currentBody = null;
1838
+ }
1839
+
1840
+ let redirectCount = 0;
1841
+ let redirected = false;
1842
+ let currentUrl = request.url;
1843
+ let currentMethod = request.method;
1844
+ let currentUnixSocketPath = unixSocketPath;
1845
+ const redirectMode = request.redirect;
1846
+ let stripSensitiveHeaders = false;
1847
+
1848
+ const getReplayableUploadBody = (): Uint8Array => {
1849
+ if (bufferedUploadBody) {
1850
+ return bufferedUploadBody;
1851
+ }
1852
+ bufferedUploadBody = concatUint8Chunks(uploadChunks, uploadChunkLength);
1853
+ return bufferedUploadBody;
1854
+ };
1855
+
1856
+ const finalizeResponse = (
1857
+ nativeResponse: NativeResponse | NativeStreamingResponse,
1858
+ bodyStream?: ReadableStream<Uint8Array>
1859
+ ): Response => {
1860
+ const response = bodyStream
1861
+ ? Response.fromNativeStreaming(nativeResponse as NativeStreamingResponse, bodyStream, currentUrl)
1862
+ : Response.fromNative(nativeResponse as NativeResponse, currentUrl);
1863
+
1864
+ (response as any)._redirected = redirected || (response as any)._redirected === true;
1865
+ (response as any)._url = redirected ? currentUrl : (nativeResponse.url || currentUrl);
1866
+
1867
+ try {
1868
+ const responseOrigin = new URL(nativeResponse.url || currentUrl).origin;
1869
+ if (responseOrigin !== getRuntimeOrigin().origin) {
1870
+ (response as any)._type = 'cors';
1871
+ }
1872
+ } catch {
1873
+ // URL parse failure — keep the default response type.
1874
+ }
1875
+
1876
+ if (signal) {
1877
+ response._signal = signal;
1878
+ }
1879
+
1880
+ return response;
1881
+ };
1882
+
1883
+ const executeSocketFetchHopStreaming = async (
1884
+ url: string,
1885
+ nativeInit: NativeRequestInit,
1886
+ body: Uint8Array | null,
1887
+ socketPath?: string
1888
+ ): Promise<{
1889
+ nativeResponse: NativeStreamingResponse;
1890
+ bodyStream?: ReadableStream<Uint8Array>;
1891
+ }> => {
1892
+ const parsedUrl = new URL(url);
1893
+ const runtimeRequire = (globalThis as { require?: (specifier: string) => any }).require;
1894
+ if (typeof runtimeRequire !== 'function') {
1895
+ throw new FetchError('Module loader not available for socket transport compatibility path');
1896
+ }
1897
+ const transport: any = parsedUrl.protocol === 'https:'
1898
+ ? runtimeRequire('node:https')
1899
+ : runtimeRequire('node:http');
1900
+ const disableSocketDefaultHeaders = shouldUseBunCompatKeepaliveSocketTransport(url, request);
1901
+ const effectiveHeaders = nativeInit.headers.slice();
1902
+ if (!hasHeader(effectiveHeaders, 'host')) {
1903
+ effectiveHeaders.push(['host', parsedUrl.host || parsedUrl.hostname]);
1904
+ }
1905
+ if (
1906
+ body !== null &&
1907
+ !hasHeader(effectiveHeaders, 'content-length') &&
1908
+ !hasHeader(effectiveHeaders, 'transfer-encoding')
1909
+ ) {
1910
+ effectiveHeaders.push(['content-length', String(body.byteLength)]);
1911
+ }
1912
+
1913
+ const requestHeaders: Record<string, string | string[]> = {};
1914
+ for (const [name, value] of effectiveHeaders) {
1915
+ const existing = requestHeaders[name];
1916
+ if (existing === undefined) {
1917
+ requestHeaders[name] = value;
1918
+ } else if (Array.isArray(existing)) {
1919
+ existing.push(value);
1920
+ } else {
1921
+ requestHeaders[name] = [existing, value];
1922
+ }
1923
+ }
1924
+
1925
+ let activeCancelRequest: (() => void) | undefined;
1926
+ const requestPromise = suppressInternalPromiseRejection(new Promise<{
1927
+ nativeResponse: NativeStreamingResponse;
1928
+ bodyStream?: ReadableStream<Uint8Array>;
1929
+ }>((resolve, reject) => {
1930
+ const requestOptions: Record<string, any> = {
1931
+ protocol: parsedUrl.protocol,
1932
+ method: nativeInit.method,
1933
+ headers: requestHeaders,
1934
+ path: parsedUrl.pathname + (parsedUrl.search || ''),
1935
+ };
1936
+ if (disableSocketDefaultHeaders) {
1937
+ requestOptions.setDefaultHeaders = false;
1938
+ requestOptions.setHost = false;
1939
+ }
1940
+ if (socketPath) {
1941
+ requestOptions.socketPath = socketPath;
1942
+ if (parsedUrl.hostname) {
1943
+ requestOptions.hostname = parsedUrl.hostname;
1944
+ }
1945
+ if (parsedUrl.port) {
1946
+ requestOptions.port = Number(parsedUrl.port);
1947
+ }
1948
+ } else {
1949
+ requestOptions.hostname = parsedUrl.hostname;
1950
+ requestOptions.port = parsedUrl.port ? Number(parsedUrl.port) : undefined;
1951
+ }
1952
+ if (parsedUrl.protocol === 'https:') {
1953
+ applyHttpsRequestTlsOptions(requestOptions, init, runtimeRequire);
1954
+ if (requestOptions.agent === undefined && transport?.globalAgent) {
1955
+ requestOptions.agent = transport.globalAgent;
1956
+ }
1957
+ }
1958
+ const req = transport.request(
1959
+ requestOptions,
1960
+ (res: any) => {
1961
+ const headers: [string, string][] = [];
1962
+ const rawHeaders = Array.isArray(res.rawHeaders) ? res.rawHeaders : [];
1963
+ for (let i = 0; i + 1 < rawHeaders.length; i += 2) {
1964
+ headers.push([String(rawHeaders[i]), String(rawHeaders[i + 1])]);
1965
+ }
1966
+
1967
+ const status = res.statusCode || 0;
1968
+ const noBody =
1969
+ nativeInit.method === 'HEAD' ||
1970
+ (status >= 100 && status < 200) ||
1971
+ status === 204 ||
1972
+ status === 304;
1973
+ if (noBody) {
1974
+ if (typeof res.resume === 'function') {
1975
+ res.resume();
1976
+ }
1977
+ resolve({
1978
+ nativeResponse: {
1979
+ status,
1980
+ statusText: res.statusMessage || '',
1981
+ headers,
1982
+ url,
1983
+ redirected: false,
1984
+ },
1985
+ });
1986
+ return;
1987
+ }
1988
+
1989
+ resolve({
1990
+ nativeResponse: {
1991
+ status,
1992
+ statusText: res.statusMessage || '',
1993
+ headers,
1994
+ url,
1995
+ redirected: false,
1996
+ },
1997
+ bodyStream: createSocketResponseBodyStream(
1998
+ res,
1999
+ headers,
2000
+ nativeInit.decompress,
2001
+ runtimeRequire,
2002
+ signal
2003
+ ),
2004
+ });
2005
+ res.on('error', reject);
2006
+ }
2007
+ );
2008
+
2009
+ activeCancelRequest = () => {
2010
+ req.destroy(signal?.reason instanceof Error ? signal.reason : new Error('The operation was aborted.'));
2011
+ };
2012
+ req.on('error', reject);
2013
+ if (body?.byteLength) {
2014
+ req.write(body);
2015
+ }
2016
+ req.end();
2017
+ }));
2018
+
2019
+ if (activeCancelRequest) {
2020
+ cancelRequest = activeCancelRequest;
2021
+ }
2022
+ try {
2023
+ return await Promise.race([requestPromise, abortPromise]);
2024
+ } finally {
2025
+ if (cancelRequest === activeCancelRequest) {
2026
+ cancelRequest = undefined;
2027
+ }
2028
+ }
2029
+ };
2030
+
2031
+ const executeHop = async (
2032
+ url: string,
2033
+ nativeInit: NativeRequestInit,
2034
+ body: Uint8Array | null,
2035
+ useStreamingUpload: boolean
2036
+ ): Promise<{
2037
+ nativeResponse: NativeResponse | NativeStreamingResponse;
2038
+ bodyStream?: ReadableStream<Uint8Array>;
2039
+ }> => {
2040
+ const preferSocketTransportForHop =
2041
+ useSocketDecompressCompat ||
2042
+ !!currentUnixSocketPath ||
2043
+ useTlsSocketTransport ||
2044
+ shouldUseBunCompatLoopbackSocketTransport(url) ||
2045
+ shouldUseBunCompatKeepaliveSocketTransport(url, request);
2046
+ if (preferSocketTransportForHop && !useStreamingUpload) {
2047
+ const socketResponse = await executeSocketFetchHopStreaming(
2048
+ url,
2049
+ nativeInit,
2050
+ body,
2051
+ currentUnixSocketPath
2052
+ );
2053
+ return {
2054
+ nativeResponse: validateNativeStreamingResponse(socketResponse.nativeResponse),
2055
+ bodyStream: socketResponse.bodyStream,
2056
+ };
2057
+ }
2058
+
2059
+ if (useStreamingUpload && nativeModule.fetchStreamingUpload && uploadReader) {
2060
+ let resolveHeaders!: (value: NativeStreamingResponse) => void;
2061
+ let rejectHeaders!: (error: Error) => void;
2062
+ let headersSettled = false;
2063
+ let streamClosed = false;
2064
+
2065
+ const headersPromise = suppressInternalPromiseRejection(new Promise<NativeStreamingResponse>((resolve, reject) => {
2066
+ resolveHeaders = resolve;
2067
+ rejectHeaders = reject;
2068
+ }));
2069
+
2070
+ let activeCancelRequest: (() => void) | undefined;
2071
+ const responseBodyStream = new ReadableStream<Uint8Array>({
2072
+ type: 'bytes',
2073
+ start(controller: any) {
2074
+ activeCancelRequest = nativeModule.fetchStreamingUpload!(
2075
+ url,
2076
+ nativeInit,
2077
+ async () => {
2078
+ const result = await uploadReader!.read();
2079
+ if (result.done) {
2080
+ return null;
2081
+ }
2082
+ const chunk = result.value;
2083
+ if (chunk?.byteLength) {
2084
+ const bufferedChunk = new Uint8Array(chunk);
2085
+ uploadChunks.push(bufferedChunk);
2086
+ uploadChunkLength += bufferedChunk.byteLength;
2087
+ }
2088
+ return chunk;
2089
+ },
2090
+ (nativeResponse) => {
2091
+ if (headersSettled) {
2092
+ return;
2093
+ }
2094
+ headersSettled = true;
2095
+ resolveHeaders(nativeResponse);
2096
+ },
2097
+ (chunk) => {
2098
+ if (streamClosed) {
2099
+ return;
2100
+ }
2101
+ if (chunk?.byteLength) {
2102
+ controller.enqueue(new Uint8Array(chunk));
2103
+ }
2104
+ },
2105
+ () => {
2106
+ if (streamClosed) {
2107
+ return;
2108
+ }
2109
+ streamClosed = true;
2110
+ controller.close();
2111
+ },
2112
+ (error) => {
2113
+ if (streamClosed) {
2114
+ return;
2115
+ }
2116
+ if (!headersSettled) {
2117
+ streamClosed = true;
2118
+ headersSettled = true;
2119
+ rejectHeaders(error);
2120
+ return;
2121
+ }
2122
+ streamClosed = true;
2123
+ controller.error(error);
2124
+ }
2125
+ );
2126
+
2127
+ if (!activeCancelRequest) {
2128
+ activeCancelRequest = () => {};
2129
+ }
2130
+ cancelRequest = activeCancelRequest;
2131
+ },
2132
+ cancel() {
2133
+ streamClosed = true;
2134
+ activeCancelRequest?.();
2135
+ },
2136
+ } as any);
2137
+
2138
+ const nativeResponse = validateNativeStreamingResponse(
2139
+ await Promise.race([headersPromise, abortPromise])
2140
+ );
2141
+ return {
2142
+ nativeResponse,
2143
+ bodyStream: responseBodyStream,
2144
+ };
2145
+ }
2146
+
2147
+ if (nativeModule.fetchStreaming) {
2148
+ let resolveHeaders!: (value: NativeStreamingResponse) => void;
2149
+ let rejectHeaders!: (error: Error) => void;
2150
+ let headersSettled = false;
2151
+ let streamClosed = false;
2152
+ let activeCancelRequest: (() => void) | undefined;
2153
+
2154
+ const headersPromise = suppressInternalPromiseRejection(new Promise<NativeStreamingResponse>((resolve, reject) => {
2155
+ resolveHeaders = resolve;
2156
+ rejectHeaders = reject;
2157
+ }));
2158
+
2159
+ const bodyStream = new ReadableStream<Uint8Array>({
2160
+ type: 'bytes',
2161
+ start(controller: any) {
2162
+ activeCancelRequest = nativeModule.fetchStreaming!(
2163
+ url,
2164
+ nativeInit,
2165
+ body,
2166
+ (nativeResponse) => {
2167
+ if (headersSettled) {
2168
+ return;
2169
+ }
2170
+ headersSettled = true;
2171
+ resolveHeaders(nativeResponse);
2172
+ },
2173
+ (chunk) => {
2174
+ if (streamClosed) {
2175
+ return;
2176
+ }
2177
+ if (chunk?.byteLength) {
2178
+ controller.enqueue(new Uint8Array(chunk));
2179
+ }
2180
+ },
2181
+ () => {
2182
+ if (streamClosed) {
2183
+ return;
2184
+ }
2185
+ streamClosed = true;
2186
+ controller.close();
2187
+ },
2188
+ (error) => {
2189
+ if (streamClosed) {
2190
+ return;
2191
+ }
2192
+ if (!headersSettled) {
2193
+ streamClosed = true;
2194
+ headersSettled = true;
2195
+ rejectHeaders(error);
2196
+ return;
2197
+ }
2198
+ streamClosed = true;
2199
+ controller.error(error);
2200
+ }
2201
+ );
2202
+
2203
+ if (!activeCancelRequest) {
2204
+ activeCancelRequest = () => {};
2205
+ }
2206
+ cancelRequest = activeCancelRequest;
2207
+ },
2208
+ cancel() {
2209
+ streamClosed = true;
2210
+ activeCancelRequest?.();
2211
+ },
2212
+ } as any);
2213
+
2214
+ const nativeResponse = validateNativeStreamingResponse(
2215
+ await Promise.race([headersPromise, abortPromise])
2216
+ );
2217
+ return {
2218
+ nativeResponse,
2219
+ bodyStream,
2220
+ };
2221
+ }
2222
+
2223
+ const nativeFetchPromise = suppressInternalPromiseRejection(
2224
+ nativeModule.fetch(url, nativeInit, body)
2225
+ ) as CancelableNativeResponsePromise;
2226
+ const nativeCancel = typeof nativeFetchPromise.__exactCancel === 'function'
2227
+ ? nativeFetchPromise.__exactCancel
2228
+ : undefined;
2229
+ if (nativeCancel) {
2230
+ cancelRequest = nativeCancel;
2231
+ }
2232
+ const nativeResponse = validateNativeResponse(
2233
+ await Promise.race([
2234
+ nativeFetchPromise,
2235
+ abortPromise,
2236
+ ])
2237
+ );
2238
+ if (cancelRequest === nativeCancel) {
2239
+ cancelRequest = undefined;
2240
+ }
2241
+ return { nativeResponse };
2242
+ };
2243
+
2244
+ while (true) {
2245
+ // Build headers for this hop
2246
+ const nativeHeaders = buildNativeHeaders(request, requestSource, decompress);
2247
+
2248
+ // Map cache mode to Cache-Control headers
2249
+ if (request.cache === 'no-store' && !hasHeader(nativeHeaders, 'cache-control')) {
2250
+ nativeHeaders.push(['cache-control', 'no-store']);
2251
+ } else if (request.cache === 'no-cache' && !hasHeader(nativeHeaders, 'cache-control')) {
2252
+ nativeHeaders.push(['cache-control', 'no-cache']);
2253
+ } else if (request.cache === 'reload' && !hasHeader(nativeHeaders, 'cache-control')) {
2254
+ nativeHeaders.push(['cache-control', 'no-cache']);
2255
+ if (!hasHeader(nativeHeaders, 'pragma')) {
2256
+ nativeHeaders.push(['pragma', 'no-cache']);
2257
+ }
2258
+ }
2259
+
2260
+ if (redirected) {
2261
+ const cookieIdx = nativeHeaders.findIndex(([n]) => n.toLowerCase() === 'cookie');
2262
+ if (cookieIdx !== -1) {
2263
+ nativeHeaders.splice(cookieIdx, 1);
2264
+ }
2265
+ }
2266
+
2267
+ if (stripSensitiveHeaders) {
2268
+ for (let i = nativeHeaders.length - 1; i >= 0; i--) {
2269
+ if (SENSITIVE_HEADERS.includes(nativeHeaders[i][0].toLowerCase())) {
2270
+ nativeHeaders.splice(i, 1);
2271
+ }
2272
+ }
2273
+ }
2274
+
2275
+ if (shouldProcessCookies(request)) {
2276
+ try {
2277
+ const reqUrl = new URL(currentUrl);
2278
+ const cookieHeader = cookieJar.getCookieHeader(
2279
+ reqUrl,
2280
+ request.credentials,
2281
+ getRuntimeOrigin(),
2282
+ request.mode,
2283
+ currentMethod
2284
+ );
2285
+ if (cookieHeader) {
2286
+ const existingIdx = nativeHeaders.findIndex(([n]) => n.toLowerCase() === 'cookie');
2287
+ if (existingIdx !== -1) {
2288
+ nativeHeaders.splice(existingIdx, 1);
2289
+ }
2290
+ nativeHeaders.push(['cookie', cookieHeader]);
2291
+ }
2292
+ } catch {
2293
+ // URL parse failure — skip cookie injection
2294
+ }
2295
+ }
2296
+
2297
+ const nativeInit: NativeRequestInit = {
2298
+ method: currentMethod,
2299
+ headers: nativeHeaders,
2300
+ credentials: request.credentials,
2301
+ redirect: 'follow',
2302
+ decompress,
2303
+ signal,
2304
+ };
2305
+
2306
+ const hop = await executeHop(
2307
+ currentUrl,
2308
+ nativeInit,
2309
+ currentBody,
2310
+ streamUploadPending && currentMethod !== 'GET' && currentMethod !== 'HEAD'
2311
+ );
2312
+ const nativeResponse = hop.nativeResponse;
2313
+
2314
+ if (shouldProcessCookies(request)) {
2315
+ storeCookiesFromHeaders(nativeResponse.headers, nativeResponse.url || currentUrl);
2316
+ }
2317
+
2318
+ const status = nativeResponse.status;
2319
+ if (!REDIRECT_STATUS_CODES.includes(status)) {
2320
+ const response = finalizeResponse(nativeResponse, hop.bodyStream);
2321
+ if (hop.bodyStream) {
2322
+ cancelRequest = undefined;
2323
+ }
2324
+ return response;
2325
+ }
2326
+
2327
+ if (redirectMode === 'error') {
2328
+ cancelRequest?.();
2329
+ cancelRequest = undefined;
2330
+ const error = new TypeError('Failed to fetch: unexpected redirect') as TypeError & { code?: string };
2331
+ if (isBunCompatEnv()) {
2332
+ error.code = 'UnexpectedRedirect';
2333
+ }
2334
+ throw error;
2335
+ }
2336
+ if (redirectMode === 'manual') {
2337
+ if (isBunCompatEnv()) {
2338
+ const response = finalizeResponse(nativeResponse, hop.bodyStream);
2339
+ if (hop.bodyStream) {
2340
+ cancelRequest = undefined;
2341
+ }
2342
+ return response;
2343
+ }
2344
+ cancelRequest?.();
2345
+ cancelRequest = undefined;
2346
+ return createOpaqueRedirectResponse(currentUrl);
2347
+ }
2348
+
2349
+ redirectCount++;
2350
+ if (redirectCount > MAX_REDIRECTS) {
2351
+ cancelRequest?.();
2352
+ cancelRequest = undefined;
2353
+ throw new TypeError('Failed to fetch: redirect count exceeded');
2354
+ }
2355
+
2356
+ let location: string | null = null;
2357
+ for (const [name, value] of nativeResponse.headers) {
2358
+ if (name.toLowerCase() === 'location') {
2359
+ location = value;
2360
+ break;
2361
+ }
2362
+ }
2363
+
2364
+ if (!location) {
2365
+ const response = finalizeResponse(nativeResponse, hop.bodyStream);
2366
+ if (hop.bodyStream) {
2367
+ cancelRequest = undefined;
2368
+ }
2369
+ return response;
2370
+ }
2371
+
2372
+ let redirectUrl: URL;
2373
+ try {
2374
+ const normalizedLocation = normalizeRedirectLocation(location, currentUrl);
2375
+ if (location.startsWith('://') || normalizedLocation.startsWith('://')) {
2376
+ const schemeMatch = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.exec(currentUrl);
2377
+ const protocol = schemeMatch ? schemeMatch[0] : 'http:';
2378
+ redirectUrl = new URL(protocol + normalizedLocation.slice(1));
2379
+ } else {
2380
+ redirectUrl = new URL(normalizedLocation, currentUrl);
2381
+ }
2382
+ } catch {
2383
+ cancelRequest?.();
2384
+ cancelRequest = undefined;
2385
+ throw new TypeError('Failed to fetch: invalid redirect URL');
2386
+ }
2387
+
2388
+ const newMethod = computeRedirectMethod(status, currentMethod);
2389
+ const previousUrl = currentUrl;
2390
+ cancelRequest?.();
2391
+ cancelRequest = undefined;
2392
+
2393
+ if (newMethod !== currentMethod && (newMethod === 'GET' || newMethod === 'HEAD')) {
2394
+ currentBody = null;
2395
+ streamUploadPending = false;
2396
+ } else if (streamUploadPending) {
2397
+ currentBody = getReplayableUploadBody();
2398
+ streamUploadPending = false;
2399
+ }
2400
+
2401
+ try {
2402
+ if (new URL(previousUrl).origin !== redirectUrl.origin) {
2403
+ stripSensitiveHeaders = true;
2404
+ }
2405
+ if (currentUnixSocketPath && new URL(previousUrl).origin !== redirectUrl.origin) {
2406
+ currentUnixSocketPath = undefined;
2407
+ }
2408
+ } catch {
2409
+ // URL parse failure — keep existing header stripping behavior.
2410
+ }
2411
+
2412
+ currentMethod = newMethod;
2413
+ currentUrl = redirectUrl.href;
2414
+ redirected = true;
2415
+ }
2416
+ } catch (error) {
2417
+ if (cancelled || error instanceof AbortError) {
2418
+ throw error;
2419
+ }
2420
+ if (error instanceof TypeError) {
2421
+ throw error;
2422
+ }
2423
+
2424
+ // Per Fetch spec, network errors should be TypeError
2425
+ if (error instanceof Error) {
2426
+ const bunCompatMessage = normalizeBunCompatNetworkError(error.message);
2427
+ if (bunCompatMessage) {
2428
+ const bunCompatError = new TypeError(bunCompatMessage);
2429
+ copyErrorMetadata(bunCompatError, error);
2430
+ throw bunCompatError;
2431
+ }
2432
+ const typeError = new TypeError(`Failed to fetch: ${error.message}`);
2433
+ copyErrorMetadata(typeError, error);
2434
+ throw typeError;
2435
+ }
2436
+
2437
+ throw new TypeError(`Failed to fetch: ${String(error)}`);
2438
+ } finally {
2439
+ if (cancelRequest) {
2440
+ cancelRequest();
2441
+ }
2442
+ abortCleanup();
2443
+ signalContext.cleanup();
2444
+ }
2445
+ }
2446
+
2447
+ /**
2448
+ * A mock/polyfill fetch implementation for testing and web fallback.
2449
+ * Uses XMLHttpRequest under the hood where native module is not available.
2450
+ */
2451
+ export async function fetchPolyfill(
2452
+ input: RequestInput,
2453
+ init?: RequestInit
2454
+ ): Promise<Response> {
2455
+ // Check capability before proceeding
2456
+ // This throws NotAllowedError if the capability is not granted
2457
+ requireCapability(Capabilities.NETWORK_FETCH);
2458
+
2459
+ // Create Request object
2460
+ // Per spec: if input is a Request, init overrides should still be applied
2461
+ const request = new Request(input, init);
2462
+ const isRequestInput = isRequestInputObject(input);
2463
+ const decompress = init?.decompress !== false;
2464
+ const timeoutMs = resolveTimeoutMs(init);
2465
+
2466
+ // Dispatching a fetch consumes the request body.
2467
+ request.markBodyAsUsedForFetch();
2468
+
2469
+ return new Promise((resolve, reject) => {
2470
+ const signalContext = createEffectiveSignal(request.signal, timeoutMs);
2471
+ const signal = signalContext.signal;
2472
+
2473
+ // Check if aborted
2474
+ if (signal?.aborted) {
2475
+ signalContext.cleanup();
2476
+ reject(new AbortError(signal.reason?.message ?? 'The operation was aborted'));
2477
+ return;
2478
+ }
2479
+
2480
+ // Check if XMLHttpRequest is available (browser/polyfill environment)
2481
+ if (typeof XMLHttpRequest === 'undefined') {
2482
+ signalContext.cleanup();
2483
+ reject(new FetchError('XMLHttpRequest not available'));
2484
+ return;
2485
+ }
2486
+
2487
+ const xhr = new XMLHttpRequest();
2488
+ xhr.responseType = 'arraybuffer';
2489
+
2490
+ // Set up abort handling (only if signal is available)
2491
+ const abortHandler = () => {
2492
+ xhr.abort();
2493
+ signalContext.cleanup();
2494
+ reject(new AbortError(signal?.reason?.message ?? 'The operation was aborted'));
2495
+ };
2496
+ signal?.addEventListener('abort', abortHandler);
2497
+
2498
+ xhr.onload = () => {
2499
+ signal?.removeEventListener('abort', abortHandler);
2500
+ signalContext.cleanup();
2501
+
2502
+ // Parse response headers
2503
+ const headerLines = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
2504
+ const headers: [string, string][] = [];
2505
+ for (const line of headerLines) {
2506
+ const parts = line.split(': ');
2507
+ const name = parts.shift()!;
2508
+ const value = parts.join(': ');
2509
+ if (name) {
2510
+ headers.push([name, value]);
2511
+ }
2512
+ }
2513
+
2514
+ const response = Response.fromNative({
2515
+ status: xhr.status,
2516
+ statusText: xhr.statusText,
2517
+ headers,
2518
+ url: xhr.responseURL || request.url,
2519
+ redirected: xhr.responseURL !== request.url,
2520
+ body: xhr.response,
2521
+ });
2522
+
2523
+ resolve(response);
2524
+ };
2525
+
2526
+ xhr.onerror = () => {
2527
+ signal?.removeEventListener('abort', abortHandler);
2528
+ signalContext.cleanup();
2529
+ reject(new FetchError('Network request failed'));
2530
+ };
2531
+
2532
+ xhr.ontimeout = () => {
2533
+ signal?.removeEventListener('abort', abortHandler);
2534
+ signalContext.cleanup();
2535
+ reject(new FetchError('Request timeout'));
2536
+ };
2537
+
2538
+ // Open connection
2539
+ xhr.open(request.method, request.url, true);
2540
+
2541
+ // Set headers
2542
+ const nativeHeaders = buildNativeHeaders(
2543
+ request,
2544
+ isRequestInput ? 'request-object' : 'input',
2545
+ decompress
2546
+ );
2547
+ for (const [name, value] of nativeHeaders) {
2548
+ // Skip forbidden headers
2549
+ try {
2550
+ xhr.setRequestHeader(name, value);
2551
+ } catch {
2552
+ // Ignore forbidden header errors
2553
+ }
2554
+ }
2555
+
2556
+ // Handle credentials
2557
+ xhr.withCredentials = request.credentials === 'include';
2558
+
2559
+ // Send request with body
2560
+ request.getBodyAsUint8Array().then(
2561
+ (body) => {
2562
+ xhr.send(body as XMLHttpRequestBodyInit | null);
2563
+ },
2564
+ (error) => {
2565
+ signal?.removeEventListener('abort', abortHandler);
2566
+ signalContext.cleanup();
2567
+ reject(error);
2568
+ }
2569
+ );
2570
+ });
2571
+ }
2572
+
2573
+ /**
2574
+ * Install fetch globally.
2575
+ * This sets up the global fetch, Headers, Request, and Response.
2576
+ */
2577
+ export function installFetchGlobals(globalObject: typeof globalThis = globalThis): void {
2578
+ // Use native module if available, otherwise fall back to polyfill
2579
+ const fetchImpl = nativeFetchModule ? fetch : fetchPolyfill;
2580
+ const installedFetch = function fetch(input: RequestInput): Promise<Response> {
2581
+ return fetchImpl(input, arguments.length > 1 ? arguments[1] as RequestInit : undefined);
2582
+ };
2583
+
2584
+ Object.defineProperties(globalObject, {
2585
+ fetch: {
2586
+ value: installedFetch,
2587
+ writable: true,
2588
+ configurable: true,
2589
+ enumerable: true,
2590
+ },
2591
+ Headers: {
2592
+ value: Headers,
2593
+ writable: true,
2594
+ configurable: true,
2595
+ enumerable: true,
2596
+ },
2597
+ Request: {
2598
+ value: Request,
2599
+ writable: true,
2600
+ configurable: true,
2601
+ enumerable: true,
2602
+ },
2603
+ Response: {
2604
+ value: Response,
2605
+ writable: true,
2606
+ configurable: true,
2607
+ enumerable: true,
2608
+ },
2609
+ });
2610
+ }