react-native-nitro-net 0.1.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +122 -12
  2. package/android/libs/arm64-v8a/librust_c_net.so +0 -0
  3. package/android/libs/armeabi-v7a/librust_c_net.so +0 -0
  4. package/android/libs/x86/librust_c_net.so +0 -0
  5. package/android/libs/x86_64/librust_c_net.so +0 -0
  6. package/cpp/HybridHttpParser.hpp +67 -0
  7. package/cpp/HybridNetDriver.hpp +74 -0
  8. package/cpp/HybridNetServerDriver.hpp +16 -0
  9. package/cpp/HybridNetSocketDriver.hpp +176 -0
  10. package/cpp/NetBindings.hpp +67 -1
  11. package/ios/Frameworks/RustCNet.xcframework/ios-arm64/RustCNet.framework/RustCNet +0 -0
  12. package/ios/Frameworks/RustCNet.xcframework/ios-arm64_x86_64-simulator/RustCNet.framework/RustCNet +0 -0
  13. package/lib/Net.nitro.d.ts +46 -1
  14. package/lib/Net.nitro.js +3 -1
  15. package/lib/http.d.ts +203 -0
  16. package/lib/http.js +1138 -0
  17. package/lib/https.d.ts +24 -0
  18. package/lib/https.js +144 -0
  19. package/lib/index.d.ts +50 -11
  20. package/lib/index.js +179 -31
  21. package/lib/tls.d.ts +145 -0
  22. package/lib/tls.js +521 -0
  23. package/nitrogen/generated/android/RustCNet+autolinking.cmake +2 -0
  24. package/nitrogen/generated/android/RustCNetOnLoad.cpp +2 -0
  25. package/nitrogen/generated/android/c++/JHybridHttpParserSpec.cpp +54 -0
  26. package/nitrogen/generated/android/c++/JHybridHttpParserSpec.hpp +65 -0
  27. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.cpp +47 -1
  28. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.hpp +9 -0
  29. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.cpp +8 -0
  30. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.hpp +2 -0
  31. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.cpp +79 -0
  32. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.hpp +17 -0
  33. package/nitrogen/generated/android/c++/JNetConfig.hpp +7 -3
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridHttpParserSpec.kt +58 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetDriverSpec.kt +37 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetServerDriverSpec.kt +8 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetSocketDriverSpec.kt +68 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/NetConfig.kt +6 -3
  39. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.cpp +17 -0
  40. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.hpp +118 -41
  41. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Umbrella.hpp +5 -0
  42. package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.cpp +11 -0
  43. package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.hpp +79 -0
  44. package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.hpp +69 -0
  45. package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.hpp +12 -0
  46. package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.hpp +123 -0
  47. package/nitrogen/generated/ios/swift/HybridHttpParserSpec.swift +56 -0
  48. package/nitrogen/generated/ios/swift/HybridHttpParserSpec_cxx.swift +131 -0
  49. package/nitrogen/generated/ios/swift/HybridNetDriverSpec.swift +9 -0
  50. package/nitrogen/generated/ios/swift/HybridNetDriverSpec_cxx.swift +133 -0
  51. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec.swift +2 -0
  52. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec_cxx.swift +36 -0
  53. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec.swift +17 -0
  54. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec_cxx.swift +314 -0
  55. package/nitrogen/generated/ios/swift/NetConfig.swift +19 -1
  56. package/nitrogen/generated/shared/c++/HybridHttpParserSpec.cpp +21 -0
  57. package/nitrogen/generated/shared/c++/HybridHttpParserSpec.hpp +63 -0
  58. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.cpp +9 -0
  59. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.hpp +13 -0
  60. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.cpp +2 -0
  61. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.hpp +2 -0
  62. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.cpp +17 -0
  63. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.hpp +18 -0
  64. package/nitrogen/generated/shared/c++/NetConfig.hpp +6 -2
  65. package/package.json +7 -5
  66. package/react-native-nitro-net.podspec +1 -3
  67. package/src/Net.nitro.ts +44 -1
  68. package/src/http.ts +1304 -0
  69. package/src/https.ts +127 -0
  70. package/src/index.ts +167 -27
  71. package/src/tls.ts +608 -0
package/lib/http.js ADDED
@@ -0,0 +1,1138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClientRequest = exports.globalAgent = exports.Agent = exports.Server = exports.ServerResponse = exports.OutgoingMessage = exports.IncomingMessage = exports.METHODS = exports.STATUS_CODES = void 0;
4
+ exports.createServer = createServer;
5
+ exports.request = request;
6
+ exports.get = get;
7
+ const readable_stream_1 = require("readable-stream");
8
+ const eventemitter3_1 = require("eventemitter3");
9
+ const Driver_1 = require("./Driver");
10
+ const index_1 = require("./index");
11
+ const tls_1 = require("./tls");
12
+ const react_native_nitro_buffer_1 = require("react-native-nitro-buffer");
13
+ function debugLog(message) {
14
+ if ((0, index_1.isVerbose)()) {
15
+ const timestamp = new Date().toISOString().split('T')[1].split('Z')[0];
16
+ console.log(`[HTTP DEBUG ${timestamp}] ${message}`);
17
+ }
18
+ }
19
+ // ========== STATUS_CODES ==========
20
+ exports.STATUS_CODES = {
21
+ 100: 'Continue',
22
+ 101: 'Switching Protocols',
23
+ 102: 'Processing',
24
+ 200: 'OK',
25
+ 201: 'Created',
26
+ 202: 'Accepted',
27
+ 203: 'Non-Authoritative Information',
28
+ 204: 'No Content',
29
+ 205: 'Reset Content',
30
+ 206: 'Partial Content',
31
+ 300: 'Multiple Choices',
32
+ 301: 'Moved Permanently',
33
+ 302: 'Found',
34
+ 303: 'See Other',
35
+ 304: 'Not Modified',
36
+ 307: 'Temporary Redirect',
37
+ 308: 'Permanent Redirect',
38
+ 400: 'Bad Request',
39
+ 401: 'Unauthorized',
40
+ 402: 'Payment Required',
41
+ 403: 'Forbidden',
42
+ 404: 'Not Found',
43
+ 405: 'Method Not Allowed',
44
+ 406: 'Not Acceptable',
45
+ 407: 'Proxy Authentication Required',
46
+ 408: 'Request Timeout',
47
+ 409: 'Conflict',
48
+ 410: 'Gone',
49
+ 411: 'Length Required',
50
+ 412: 'Precondition Failed',
51
+ 413: 'Payload Too Large',
52
+ 414: 'URI Too Long',
53
+ 415: 'Unsupported Media Type',
54
+ 416: 'Range Not Satisfiable',
55
+ 417: 'Expectation Failed',
56
+ 418: "I'm a teapot",
57
+ 421: 'Misdirected Request',
58
+ 422: 'Unprocessable Entity',
59
+ 423: 'Locked',
60
+ 424: 'Failed Dependency',
61
+ 425: 'Too Early',
62
+ 426: 'Upgrade Required',
63
+ 428: 'Precondition Required',
64
+ 429: 'Too Many Requests',
65
+ 431: 'Request Header Fields Too Large',
66
+ 451: 'Unavailable For Legal Reasons',
67
+ 500: 'Internal Server Error',
68
+ 501: 'Not Implemented',
69
+ 502: 'Bad Gateway',
70
+ 503: 'Service Unavailable',
71
+ 504: 'Gateway Timeout',
72
+ 505: 'HTTP Version Not Supported',
73
+ 506: 'Variant Also Negotiates',
74
+ 507: 'Insufficient Storage',
75
+ 508: 'Loop Detected',
76
+ 510: 'Not Extended',
77
+ 511: 'Network Authentication Required',
78
+ };
79
+ exports.METHODS = [
80
+ 'ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE', 'GET', 'HEAD',
81
+ 'LINK', 'LOCK', 'M-SEARCH', 'MERGE', 'MKACTIVITY', 'MKCALENDAR', 'MKCOL',
82
+ 'MOVE', 'NOTIFY', 'OPTIONS', 'PATCH', 'POST', 'PROPFIND', 'PROPPATCH',
83
+ 'PURGE', 'PUT', 'REBIND', 'REPORT', 'SEARCH', 'SOURCE', 'SUBSCRIBE',
84
+ 'TRACE', 'UNBIND', 'UNLINK', 'UNLOCK', 'UNSUBSCRIBE'
85
+ ];
86
+ // ========== IncomingMessage ==========
87
+ class IncomingMessage extends readable_stream_1.Readable {
88
+ constructor(socket) {
89
+ // @ts-ignore
90
+ super({ autoDestroy: false });
91
+ this.httpVersion = '1.1';
92
+ this.httpVersionMajor = 1;
93
+ this.httpVersionMinor = 1;
94
+ this.headers = {};
95
+ this.rawHeaders = [];
96
+ this.aborted = false;
97
+ this.complete = false;
98
+ this.trailers = {};
99
+ this.socket = socket;
100
+ }
101
+ _read() {
102
+ this.socket.resume();
103
+ }
104
+ setTimeout(msecs, callback) {
105
+ this.socket.setTimeout(msecs, callback);
106
+ return this;
107
+ }
108
+ destroy(error) {
109
+ super.destroy(error);
110
+ this.socket.destroy();
111
+ return this;
112
+ }
113
+ setNoDelay(noDelay = true) {
114
+ this.socket.setNoDelay(noDelay);
115
+ }
116
+ setKeepAlive(enable = false, initialDelay = 0) {
117
+ this.socket.setKeepAlive(enable, initialDelay);
118
+ }
119
+ }
120
+ exports.IncomingMessage = IncomingMessage;
121
+ // ========== OutgoingMessage ==========
122
+ class OutgoingMessage extends readable_stream_1.Writable {
123
+ constructor() {
124
+ // @ts-ignore - disable autoDestroy to prevent socket from being destroyed when stream ends
125
+ super({ autoDestroy: false });
126
+ this.headersSent = false;
127
+ this._headers = {};
128
+ this._headerNames = {};
129
+ this.socket = null;
130
+ this.chunkedEncoding = false;
131
+ this._hasBody = true;
132
+ this._sendHeadersSent = false;
133
+ this.aborted = false;
134
+ this._trailers = null;
135
+ this.once('finish', () => {
136
+ this.emit('close');
137
+ });
138
+ }
139
+ destroy(error) {
140
+ super.destroy(error);
141
+ if (this.socket) {
142
+ this.socket.destroy();
143
+ }
144
+ return this;
145
+ }
146
+ setHeader(name, value) {
147
+ if (this.headersSent)
148
+ throw new Error('Cannot set headers after they are sent');
149
+ const key = name.toLowerCase();
150
+ this._headers[key] = value;
151
+ this._headerNames[key] = name;
152
+ return this;
153
+ }
154
+ getHeader(name) {
155
+ return this._headers[name.toLowerCase()];
156
+ }
157
+ removeHeader(name) {
158
+ if (this.headersSent)
159
+ throw new Error('Cannot remove headers after they are sent');
160
+ const key = name.toLowerCase();
161
+ delete this._headers[key];
162
+ delete this._headerNames[key];
163
+ }
164
+ hasHeader(name) {
165
+ return name.toLowerCase() in this._headers;
166
+ }
167
+ getHeaderNames() {
168
+ return Object.values(this._headerNames);
169
+ }
170
+ setTimeout(ms, callback) {
171
+ if (this.socket) {
172
+ this.socket.setTimeout(ms, () => {
173
+ this.emit('timeout');
174
+ if (callback)
175
+ callback();
176
+ });
177
+ }
178
+ else {
179
+ this.once('socket', (s) => {
180
+ s.setTimeout(ms, () => {
181
+ this.emit('timeout');
182
+ if (callback)
183
+ callback();
184
+ });
185
+ });
186
+ }
187
+ return this;
188
+ }
189
+ _renderHeaders(firstLine) {
190
+ let headerStr = firstLine + '\r\n';
191
+ for (const key in this._headers) {
192
+ const name = this._headerNames[key];
193
+ const value = this._headers[key];
194
+ if (Array.isArray(value)) {
195
+ for (const v of value) {
196
+ headerStr += `${name}: ${v}\r\n`;
197
+ }
198
+ }
199
+ else {
200
+ headerStr += `${name}: ${value}\r\n`;
201
+ }
202
+ }
203
+ headerStr += '\r\n';
204
+ return headerStr;
205
+ }
206
+ _sendHeaders(firstLine) {
207
+ if (this.headersSent)
208
+ return;
209
+ // Check for Chunked Encoding
210
+ if (!this.hasHeader('Content-Length') && this._hasBody) {
211
+ this.setHeader('Transfer-Encoding', 'chunked');
212
+ this.chunkedEncoding = true;
213
+ }
214
+ this.headersSent = true;
215
+ const headerStr = this._renderHeaders(firstLine);
216
+ debugLog(`OutgoingMessage._sendHeaders: writing ${headerStr.length} bytes to socket (socket=${!!this.socket})`);
217
+ this.socket.write(react_native_nitro_buffer_1.Buffer.from(headerStr));
218
+ }
219
+ _write(chunk, encoding, callback) {
220
+ if (!this.socket) {
221
+ callback(new Error('Socket not assigned'));
222
+ return;
223
+ }
224
+ if (this.chunkedEncoding) {
225
+ const len = typeof chunk === 'string' ? react_native_nitro_buffer_1.Buffer.byteLength(chunk, encoding) : chunk.length;
226
+ const header = len.toString(16) + '\r\n';
227
+ this.socket.write(react_native_nitro_buffer_1.Buffer.from(header));
228
+ // Note: We don't return the backpressure status here because we are doing multiple writes
229
+ // The final write determines the callback.
230
+ this.socket.write(chunk, encoding, (err) => {
231
+ if (err)
232
+ return callback(err);
233
+ this.socket.write(react_native_nitro_buffer_1.Buffer.from('\r\n'), undefined, callback);
234
+ });
235
+ }
236
+ else {
237
+ this.socket.write(chunk, encoding, callback);
238
+ }
239
+ }
240
+ write(chunk, encoding, callback) {
241
+ const ret = super.write(chunk, encoding, callback);
242
+ // If writableLength is too high, return false
243
+ // But since we are proxying to socket, we should also check socket backpressure
244
+ if (this.socket && this.socket._writableState) {
245
+ // This is a bit hacky but if we have a real Node-like socket, we respect its state
246
+ return ret && !this.socket._writableState.needDrain;
247
+ }
248
+ return ret;
249
+ }
250
+ // _final is called by the stream when all writes are complete before 'finish' event
251
+ _final(callback) {
252
+ if (this.chunkedEncoding && this.socket) {
253
+ let terminator = '0\r\n';
254
+ if (this._trailers) {
255
+ for (const [key, value] of Object.entries(this._trailers)) {
256
+ terminator += `${key}: ${value}\r\n`;
257
+ }
258
+ }
259
+ terminator += '\r\n';
260
+ this.socket.write(react_native_nitro_buffer_1.Buffer.from(terminator), undefined, (err) => {
261
+ callback(err);
262
+ });
263
+ }
264
+ else {
265
+ callback();
266
+ }
267
+ }
268
+ addTrailers(headers) {
269
+ if (this.headersSent && !this.chunkedEncoding) {
270
+ throw new Error('Trailers can only be used with chunked encoding');
271
+ }
272
+ this._trailers = headers;
273
+ }
274
+ end(chunk, encoding, callback) {
275
+ debugLog(`OutgoingMessage.end() called, already ending: ${this._writableState?.ending}, chunk: ${!!chunk}`);
276
+ if (chunk) {
277
+ this.write(chunk, encoding);
278
+ }
279
+ super.end(undefined, undefined, callback);
280
+ return this;
281
+ }
282
+ setNoDelay(noDelay = true) {
283
+ this.socket?.setNoDelay(noDelay);
284
+ }
285
+ setSocketKeepAlive(enable = false, initialDelay = 0) {
286
+ this.socket?.setKeepAlive(enable, initialDelay);
287
+ }
288
+ }
289
+ exports.OutgoingMessage = OutgoingMessage;
290
+ // ========== ServerResponse ==========
291
+ class ServerResponse extends OutgoingMessage {
292
+ constructor(socket) {
293
+ super();
294
+ this.statusCode = 200;
295
+ this.socket = socket;
296
+ }
297
+ writeHead(statusCode, statusMessage, headers) {
298
+ if (this.headersSent)
299
+ throw new Error('Cannot write headers after they are sent');
300
+ this.statusCode = statusCode;
301
+ if (typeof statusMessage === 'object') {
302
+ headers = statusMessage;
303
+ statusMessage = undefined;
304
+ }
305
+ if (statusMessage)
306
+ this.statusMessage = statusMessage;
307
+ if (headers) {
308
+ for (const key in headers) {
309
+ this.setHeader(key, headers[key]);
310
+ }
311
+ }
312
+ // Note: Do NOT send headers here. They will be sent on first write/end
313
+ // when Content-Length can be determined.
314
+ return this;
315
+ }
316
+ _sendResponseHeaders() {
317
+ if (this.headersSent)
318
+ return;
319
+ const firstLine = `HTTP/1.1 ${this.statusCode} ${this.statusMessage || exports.STATUS_CODES[this.statusCode] || 'OK'}`;
320
+ this._sendHeaders(firstLine);
321
+ }
322
+ _write(chunk, encoding, callback) {
323
+ if (!this.headersSent)
324
+ this._sendResponseHeaders();
325
+ super._write(chunk, encoding, callback);
326
+ }
327
+ write(chunk, encoding, callback) {
328
+ return super.write(chunk, encoding, callback);
329
+ }
330
+ end(chunk, encoding, callback) {
331
+ if (!this.headersSent) {
332
+ // If we have a single chunk and no headers sent yet, we can add Content-Length
333
+ // to avoid chunked encoding for simple responses.
334
+ if (chunk) {
335
+ const len = typeof chunk === 'string' ? react_native_nitro_buffer_1.Buffer.byteLength(chunk, encoding) : chunk.length;
336
+ this.setHeader('Content-Length', len);
337
+ }
338
+ else {
339
+ this.setHeader('Content-Length', 0);
340
+ }
341
+ this._sendResponseHeaders();
342
+ }
343
+ // super.end will trigger _write if chunk was provided.
344
+ super.end(chunk, encoding, () => {
345
+ if (callback)
346
+ callback();
347
+ });
348
+ return this;
349
+ }
350
+ }
351
+ exports.ServerResponse = ServerResponse;
352
+ class Server extends eventemitter3_1.EventEmitter {
353
+ constructor(options, requestListener) {
354
+ super();
355
+ this._httpConnections = new Set();
356
+ this.maxHeaderSize = 16384;
357
+ this.maxRequestsPerSocket = 0;
358
+ this.headersTimeout = 60000;
359
+ this.requestTimeout = 300000;
360
+ this.keepAliveTimeout = 5000;
361
+ // Use net.Server from index.ts
362
+ const { Server: NetServer } = require('./index');
363
+ this._netServer = new NetServer();
364
+ let listener;
365
+ if (typeof options === 'function') {
366
+ listener = options;
367
+ }
368
+ else if (options) {
369
+ if (options.keepAliveTimeout !== undefined)
370
+ this.keepAliveTimeout = options.keepAliveTimeout;
371
+ if (options.requestTimeout !== undefined)
372
+ this.requestTimeout = options.requestTimeout;
373
+ if (options.headersTimeout !== undefined)
374
+ this.headersTimeout = options.headersTimeout;
375
+ if (options.maxHeaderSize !== undefined)
376
+ this.maxHeaderSize = options.maxHeaderSize;
377
+ if (options.maxRequestsPerSocket !== undefined)
378
+ this.maxRequestsPerSocket = options.maxRequestsPerSocket;
379
+ listener = requestListener;
380
+ }
381
+ if (listener) {
382
+ this.on('request', listener);
383
+ }
384
+ // Forward net.Server events
385
+ this._netServer.on('listening', () => this.emit('listening'));
386
+ this._netServer.on('close', () => this.emit('close'));
387
+ this._netServer.on('error', (err) => this.emit('error', err));
388
+ this._netServer.on('connection', (socket) => {
389
+ this._setupHttpConnection(socket);
390
+ });
391
+ }
392
+ _setupHttpConnection(socket) {
393
+ this._httpConnections.add(socket);
394
+ let req = null;
395
+ let res = null;
396
+ const parser = Driver_1.Driver.createHttpParser(0); // 0 = Request mode
397
+ // @ts-ignore
398
+ let bodyBytesRead = 0;
399
+ // @ts-ignore
400
+ let contentLength = -1;
401
+ // headersTimeout logic
402
+ let headersTimer = null;
403
+ if (this.headersTimeout > 0) {
404
+ headersTimer = setTimeout(() => {
405
+ debugLog(`Server: headersTimeout reached for socket, destroying`);
406
+ socket.destroy();
407
+ }, this.headersTimeout);
408
+ }
409
+ const onData = (data) => {
410
+ const handleParsedResult = (result) => {
411
+ if (result.startsWith('ERROR:')) {
412
+ if (headersTimer)
413
+ clearTimeout(headersTimer);
414
+ this.emit('error', new Error(result));
415
+ socket.destroy();
416
+ return;
417
+ }
418
+ const parsed = JSON.parse(result);
419
+ if (parsed.is_headers) {
420
+ if (headersTimer) {
421
+ clearTimeout(headersTimer);
422
+ headersTimer = null;
423
+ }
424
+ // Handle CONNECT method (HTTP Tunneling)
425
+ if (parsed.is_connect) {
426
+ const req = new IncomingMessage(socket);
427
+ req.method = parsed.method;
428
+ req.url = parsed.path;
429
+ req.httpVersion = '1.' + parsed.version;
430
+ req.headers = parsed.headers;
431
+ // Remove our data listener to stop feeding the parser
432
+ // The user is responsible for handling the socket data stream from now on
433
+ socket.removeListener('data', onData);
434
+ debugLog(`Server: CONNECT request received, emitting 'connect' event`);
435
+ // TODO: retrieve any remaining body from parser as 'head'
436
+ const head = react_native_nitro_buffer_1.Buffer.alloc(0);
437
+ if (this.listenerCount('connect') > 0) {
438
+ this.emit('connect', req, socket, head);
439
+ }
440
+ else {
441
+ // Default behavior: close connection if no listener
442
+ socket.destroy();
443
+ }
444
+ return;
445
+ }
446
+ const currentReq = new IncomingMessage(socket);
447
+ currentReq.method = parsed.method;
448
+ currentReq.url = parsed.path;
449
+ currentReq.httpVersion = '1.' + parsed.version;
450
+ currentReq.headers = parsed.headers;
451
+ req = currentReq;
452
+ const currentRes = new ServerResponse(socket);
453
+ res = currentRes;
454
+ // Support Keep-Alive: reset state once response is done
455
+ currentRes.on('finish', () => {
456
+ req = null;
457
+ res = null;
458
+ // The parser should already be reset in Rust
459
+ });
460
+ const upgrade = req.headers['upgrade'];
461
+ if (upgrade && this.listenerCount('upgrade') > 0) {
462
+ debugLog(`Server: Upgrade request received, emitting 'upgrade' event`);
463
+ this.emit('upgrade', req, socket, react_native_nitro_buffer_1.Buffer.alloc(0));
464
+ return;
465
+ }
466
+ const expect = req.headers['expect'];
467
+ if (expect && (typeof expect === 'string' && expect.toLowerCase() === '100-continue')) {
468
+ if (this.listenerCount('checkContinue') > 0) {
469
+ this.emit('checkContinue', req, res);
470
+ }
471
+ else {
472
+ socket.write(react_native_nitro_buffer_1.Buffer.from('HTTP/1.1 100 Continue\r\n\r\n'));
473
+ this.emit('request', req, res);
474
+ }
475
+ }
476
+ else {
477
+ debugLog(`Server: Emitting 'request' for ${req.method} ${req.url}`);
478
+ this.emit('request', req, res);
479
+ }
480
+ }
481
+ if (req && parsed.body && parsed.body.length > 0) {
482
+ req.push(react_native_nitro_buffer_1.Buffer.from(parsed.body));
483
+ }
484
+ if (req && parsed.complete) {
485
+ req.complete = true;
486
+ if (parsed.trailers) {
487
+ req.trailers = parsed.trailers;
488
+ }
489
+ req.push(null);
490
+ }
491
+ // For Keep-Alive, try to parse remaining buffer in case of pipelining
492
+ if (parsed.complete && !req) {
493
+ // This case is handled by the feed loop if multiple messages in data
494
+ }
495
+ };
496
+ let input = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
497
+ let iterations = 0;
498
+ const maxIterations = 100; // Safety limit
499
+ while (iterations < maxIterations) {
500
+ iterations++;
501
+ const result = parser.feed(input);
502
+ if (!result || result === '' || result.startsWith('ERROR:')) {
503
+ // Empty result (partial) or error - exit loop
504
+ if (result && result.startsWith('ERROR:')) {
505
+ console.log(`[HTTP] Server: Parser error: ${result}`);
506
+ }
507
+ break;
508
+ }
509
+ handleParsedResult(result);
510
+ input = new ArrayBuffer(0); // Continue with empty input to drain Rust buffer
511
+ }
512
+ };
513
+ socket.on('data', onData);
514
+ // CRITICAL: Ensure server-side socket starts flowing!
515
+ socket.resume();
516
+ socket.on('close', () => {
517
+ if (headersTimer)
518
+ clearTimeout(headersTimer);
519
+ this._httpConnections.delete(socket);
520
+ if (req && !req.readableEnded) {
521
+ req.push(null);
522
+ }
523
+ });
524
+ socket.on('error', (err) => {
525
+ if (req)
526
+ req.emit('error', err);
527
+ else
528
+ this.emit('error', err);
529
+ });
530
+ }
531
+ listen(...args) {
532
+ this._netServer.listen(...args);
533
+ return this;
534
+ }
535
+ close(callback) {
536
+ this._netServer.close(callback);
537
+ return this;
538
+ }
539
+ // @ts-ignore
540
+ async [Symbol.asyncDispose]() {
541
+ return new Promise((resolve) => {
542
+ this.close(() => resolve());
543
+ });
544
+ }
545
+ address() {
546
+ return this._netServer.address();
547
+ }
548
+ get listening() {
549
+ return this._netServer.listening;
550
+ }
551
+ setTimeout(ms, callback) {
552
+ this._netServer.setTimeout(ms, callback);
553
+ return this;
554
+ }
555
+ }
556
+ exports.Server = Server;
557
+ class Agent extends eventemitter3_1.EventEmitter {
558
+ /**
559
+ * Gets the proxy URL for the given request options.
560
+ * Checks HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables.
561
+ *
562
+ * @param options Request options to determine if proxy should be used
563
+ * @returns Proxy URL or null if no proxy should be used
564
+ */
565
+ getProxy(options) {
566
+ // If explicitly set on agent, use that
567
+ if (this.proxy)
568
+ return this.proxy;
569
+ // Check environment variables (React Native may not have process.env)
570
+ const env = typeof process !== 'undefined' && process.env ? process.env : {};
571
+ const isHttps = options.protocol === 'https:';
572
+ const host = options.hostname || options.host || 'localhost';
573
+ // Check NO_PROXY first
574
+ const noProxy = env.NO_PROXY || env.no_proxy;
575
+ if (noProxy) {
576
+ const noProxyList = noProxy.split(',').map(s => s.trim().toLowerCase());
577
+ const hostLower = host.toLowerCase();
578
+ for (const pattern of noProxyList) {
579
+ if (pattern === '*')
580
+ return null;
581
+ if (pattern.startsWith('.') && hostLower.endsWith(pattern))
582
+ return null;
583
+ if (hostLower === pattern)
584
+ return null;
585
+ if (hostLower.endsWith('.' + pattern))
586
+ return null;
587
+ }
588
+ }
589
+ // Get proxy URL based on protocol
590
+ const proxyUrl = isHttps
591
+ ? (env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy)
592
+ : (env.HTTP_PROXY || env.http_proxy);
593
+ return proxyUrl || null;
594
+ }
595
+ constructor(options) {
596
+ super();
597
+ this.maxSockets = Infinity;
598
+ this.maxTotalSockets = Infinity;
599
+ this.maxFreeSockets = 256;
600
+ this.keepAlive = false;
601
+ this.keepAliveMsecs = 1000;
602
+ this.maxCachedSessions = 100;
603
+ this.scheduling = 'lifo';
604
+ this.requests = {};
605
+ this.sockets = {};
606
+ this.freeSockets = {};
607
+ this._totalSockets = 0;
608
+ this.proxy = null;
609
+ if (options?.maxSockets)
610
+ this.maxSockets = options.maxSockets;
611
+ if (options?.maxTotalSockets)
612
+ this.maxTotalSockets = options.maxTotalSockets;
613
+ if (options?.maxFreeSockets)
614
+ this.maxFreeSockets = options.maxFreeSockets;
615
+ if (options?.keepAlive)
616
+ this.keepAlive = options.keepAlive;
617
+ if (options?.keepAliveMsecs)
618
+ this.keepAliveMsecs = options.keepAliveMsecs;
619
+ if (options?.scheduling)
620
+ this.scheduling = options.scheduling;
621
+ if (options?.maxCachedSessions !== undefined)
622
+ this.maxCachedSessions = options.maxCachedSessions;
623
+ }
624
+ getName(options) {
625
+ let name = `${options.host || options.hostname || 'localhost'}:${options.port || (options.protocol === 'https:' ? 443 : 80)}:`;
626
+ if (options.localAddress)
627
+ name += `${options.localAddress}:`;
628
+ if (options.family)
629
+ name += `${options.family}:`;
630
+ return name;
631
+ }
632
+ addRequest(req, options) {
633
+ const name = this.getName(options);
634
+ debugLog(`Agent.addRequest: name=${name}, totalSockets=${this._totalSockets}`);
635
+ // 1. Check if there's an idle socket in freeSockets
636
+ if (this.freeSockets[name] && this.freeSockets[name].length > 0) {
637
+ const socket = this.scheduling === 'lifo'
638
+ ? this.freeSockets[name].pop()
639
+ : this.freeSockets[name].shift();
640
+ if (this.freeSockets[name].length === 0)
641
+ delete this.freeSockets[name];
642
+ // Re-use socket
643
+ if (!this.sockets[name])
644
+ this.sockets[name] = [];
645
+ this.sockets[name].push(socket);
646
+ req.onSocket(socket);
647
+ return;
648
+ }
649
+ // 2. Check if we can create a new connection
650
+ const currentSockets = (this.sockets[name]?.length || 0);
651
+ if (currentSockets < this.maxSockets && this._totalSockets < this.maxTotalSockets) {
652
+ if (!this.sockets[name])
653
+ this.sockets[name] = [];
654
+ // Increment total sockets early
655
+ this._totalSockets++;
656
+ // ClientRequest handles connection but we signal it to proceed
657
+ req.onSocket(null);
658
+ return;
659
+ }
660
+ // 3. Queue the request
661
+ if (!this.requests[name])
662
+ this.requests[name] = [];
663
+ this.requests[name].push(req);
664
+ }
665
+ createConnection(options, callback) {
666
+ const name = this.getName(options);
667
+ const isHttps = options.protocol === 'https:';
668
+ const port = options.port || (isHttps ? 443 : 80);
669
+ const host = options.hostname || options.host || 'localhost';
670
+ debugLog(`Agent.createConnection: name=${name}, isHttps=${isHttps}, host=${host}, port=${port}`);
671
+ // Build clean connection options - DO NOT pass HTTP path as it will be confused with Unix socket path
672
+ const connectOptions = {
673
+ host: host,
674
+ port: port,
675
+ };
676
+ if (isHttps) {
677
+ connectOptions.servername = options.servername || host;
678
+ connectOptions.rejectUnauthorized = options.rejectUnauthorized !== false;
679
+ if (options.ca)
680
+ connectOptions.ca = options.ca;
681
+ if (options.cert)
682
+ connectOptions.cert = options.cert;
683
+ if (options.key)
684
+ connectOptions.key = options.key;
685
+ }
686
+ const socket = isHttps ? new tls_1.TLSSocket(connectOptions) : new index_1.Socket();
687
+ // Re-emit keylog events from TLSSockets
688
+ if (isHttps) {
689
+ socket.on('keylog', (line) => {
690
+ // @ts-ignore - Agent is an EventEmitter via Node-like inheritance or internal use
691
+ this.emit('keylog', line, socket);
692
+ });
693
+ }
694
+ let called = false;
695
+ const onConnected = () => {
696
+ if (called)
697
+ return;
698
+ called = true;
699
+ debugLog(`Agent.createConnection: socket ${isHttps ? 'SECURE_CONNECTED' : 'CONNECTED'} for ${name}`);
700
+ callback(null, socket);
701
+ };
702
+ if (isHttps) {
703
+ socket.on('secureConnect', onConnected);
704
+ }
705
+ else {
706
+ socket.on('connect', onConnected);
707
+ }
708
+ socket.on('error', (err) => {
709
+ if (called)
710
+ return;
711
+ called = true;
712
+ debugLog(`Agent.createConnection: socket ERROR for ${name}: ${err.message}`);
713
+ this._totalSockets--;
714
+ if (this.sockets[name]) {
715
+ const idx = this.sockets[name].indexOf(socket);
716
+ if (idx !== -1)
717
+ this.sockets[name].splice(idx, 1);
718
+ }
719
+ callback(err, null);
720
+ });
721
+ socket.connect(connectOptions);
722
+ if (!this.sockets[name])
723
+ this.sockets[name] = [];
724
+ this.sockets[name].push(socket);
725
+ return socket;
726
+ }
727
+ releaseSocket(socket, options) {
728
+ const name = this.getName(options);
729
+ // Remove from active sockets
730
+ if (this.sockets[name]) {
731
+ const idx = this.sockets[name].indexOf(socket);
732
+ if (idx !== -1)
733
+ this.sockets[name].splice(idx, 1);
734
+ if (this.sockets[name].length === 0)
735
+ delete this.sockets[name];
736
+ }
737
+ const onClose = () => {
738
+ debugLog(`Agent: socket closed while in pool, removing from ${name}`);
739
+ this._removeSocket(socket, name);
740
+ };
741
+ socket.once('close', onClose);
742
+ socket.once('error', onClose);
743
+ socket._agentOnClose = onClose;
744
+ // Check if there are pending requests - ALWAYS reuse if something is waiting
745
+ if (this.requests[name] && this.requests[name].length > 0) {
746
+ const req = this.requests[name].shift();
747
+ if (this.requests[name].length === 0)
748
+ delete this.requests[name];
749
+ if (!this.sockets[name])
750
+ this.sockets[name] = [];
751
+ this.sockets[name].push(socket);
752
+ this.reuseSocket(socket, req);
753
+ return;
754
+ }
755
+ if (this.keepAlive && this.keepSocketAlive(socket)) {
756
+ // Return to free pool
757
+ if (!this.freeSockets[name])
758
+ this.freeSockets[name] = [];
759
+ if (this.freeSockets[name].length < this.maxFreeSockets) {
760
+ this.freeSockets[name].push(socket);
761
+ }
762
+ else {
763
+ this._totalSockets--;
764
+ socket.end();
765
+ }
766
+ }
767
+ else {
768
+ this._totalSockets--;
769
+ socket.end();
770
+ }
771
+ }
772
+ keepSocketAlive(_socket) {
773
+ return true;
774
+ }
775
+ reuseSocket(socket, req) {
776
+ debugLog(`Agent.reuseSocket: reusing socket for ${req.method} ${req.path}`);
777
+ // Remove agent listeners before reusing
778
+ if (socket._agentOnClose) {
779
+ socket.removeListener('close', socket._agentOnClose);
780
+ socket.removeListener('error', socket._agentOnClose);
781
+ delete socket._agentOnClose;
782
+ }
783
+ req.onSocket(socket);
784
+ }
785
+ _removeSocket(socket, name) {
786
+ if (this.sockets[name]) {
787
+ const idx = this.sockets[name].indexOf(socket);
788
+ if (idx !== -1) {
789
+ this.sockets[name].splice(idx, 1);
790
+ this._totalSockets--;
791
+ }
792
+ }
793
+ if (this.freeSockets[name]) {
794
+ const idx = this.freeSockets[name].indexOf(socket);
795
+ if (idx !== -1) {
796
+ this.freeSockets[name].splice(idx, 1);
797
+ this._totalSockets--;
798
+ }
799
+ }
800
+ }
801
+ destroy() {
802
+ for (const name in this.sockets) {
803
+ for (const socket of this.sockets[name]) {
804
+ socket.destroy();
805
+ }
806
+ }
807
+ for (const name in this.freeSockets) {
808
+ for (const socket of this.freeSockets[name]) {
809
+ socket.destroy();
810
+ }
811
+ }
812
+ }
813
+ }
814
+ exports.Agent = Agent;
815
+ exports.globalAgent = new Agent();
816
+ class ClientRequest extends OutgoingMessage {
817
+ constructor(options, callback) {
818
+ super();
819
+ this._connected = false;
820
+ this._pendingWrites = [];
821
+ this._ended = false;
822
+ this._expectContinue = false;
823
+ this._continueReceived = false;
824
+ this._options = options;
825
+ this.method = options.method || 'GET';
826
+ this.path = options.path || '/';
827
+ this.host = options.hostname || options.host || 'localhost';
828
+ if (['GET', 'HEAD'].includes(this.method.toUpperCase())) {
829
+ this._hasBody = false;
830
+ }
831
+ if (options.headers) {
832
+ for (const key in options.headers) {
833
+ this.setHeader(key, options.headers[key]);
834
+ }
835
+ }
836
+ if (callback) {
837
+ this.once('response', callback);
838
+ }
839
+ const expect = this.getHeader('expect');
840
+ if (expect && typeof expect === 'string' && expect.toLowerCase() === '100-continue') {
841
+ this._expectContinue = true;
842
+ }
843
+ if (options.timeout) {
844
+ this.setTimeout(options.timeout);
845
+ }
846
+ const agent = options.agent === false ? new Agent() : (options.agent instanceof Agent ? options.agent : exports.globalAgent);
847
+ // Use setImmediate or setTimeout for React Native compatibility
848
+ const nextTick = typeof setImmediate !== 'undefined' ? setImmediate : (fn) => setTimeout(fn, 0);
849
+ nextTick(() => {
850
+ debugLog(`ClientRequest: nextTick fired for ${this.method} ${this.host}${this.path}`);
851
+ agent.addRequest(this, this._options);
852
+ });
853
+ }
854
+ /** @internal */
855
+ onSocket(socket) {
856
+ if (socket) {
857
+ this.socket = socket;
858
+ this._connected = true;
859
+ this.emit('socket', this.socket);
860
+ this._sendRequest();
861
+ this._flushPendingWrites();
862
+ this._attachSocketListeners();
863
+ }
864
+ else {
865
+ this._connect();
866
+ }
867
+ }
868
+ _connect() {
869
+ const agent = this._options.agent === false ? new Agent() : (this._options.agent instanceof Agent ? this._options.agent : exports.globalAgent);
870
+ const connectCallback = (err, socket) => {
871
+ if (err) {
872
+ debugLog(`ClientRequest._connect: ERROR: ${err.message}`);
873
+ this.emit('error', err);
874
+ return;
875
+ }
876
+ debugLog(`ClientRequest._connect: Socket connected! socket=${!!socket}, socket._driver=${!!socket._driver}`);
877
+ console.log(`[HTTP] _connect: Socket connected!`);
878
+ this.socket = socket;
879
+ this._connected = true;
880
+ this.emit('socket', this.socket);
881
+ debugLog(`ClientRequest._connect: Calling _sendRequest`);
882
+ this._sendRequest();
883
+ this._flushPendingWrites();
884
+ this._attachSocketListeners();
885
+ };
886
+ this.socket = agent.createConnection(this._options, connectCallback);
887
+ }
888
+ _attachSocketListeners() {
889
+ if (!this.socket)
890
+ return;
891
+ const parser = Driver_1.Driver.createHttpParser(1); // 1 = Response mode
892
+ const onData = (data) => {
893
+ const handleParsedResult = (result) => {
894
+ if (result.startsWith('ERROR:')) {
895
+ this.emit('error', new Error(result));
896
+ this.socket.destroy();
897
+ return;
898
+ }
899
+ const parsed = JSON.parse(result);
900
+ console.log(`[HTTP] _connect: Parser result: ${parsed.is_headers ? 'HEADERS' : 'DATA'}${parsed.complete ? ' (COMPLETE)' : ''}`);
901
+ if (parsed.is_headers) {
902
+ const status = parsed.status || 0;
903
+ if (status >= 100 && status < 200 && status !== 101) {
904
+ const info = {
905
+ httpVersion: '1.' + parsed.version,
906
+ httpVersionMajor: 1,
907
+ httpVersionMinor: parsed.version,
908
+ statusCode: status,
909
+ statusMessage: exports.STATUS_CODES[status] || '',
910
+ headers: parsed.headers,
911
+ rawHeaders: []
912
+ };
913
+ if (status === 100) {
914
+ this._continueReceived = true;
915
+ this.emit('continue');
916
+ this._flushPendingWrites();
917
+ }
918
+ else {
919
+ this.emit('information', info);
920
+ }
921
+ return;
922
+ }
923
+ this._res = new IncomingMessage(this.socket);
924
+ this._res.statusCode = status;
925
+ this._res.httpVersion = '1.' + parsed.version;
926
+ this._res.headers = parsed.headers;
927
+ if (status === 101) {
928
+ debugLog(`ClientRequest: 101 Switching Protocols received, detaching parser`);
929
+ this.socket.removeListener('data', onData);
930
+ this.socket.removeListener('error', onError);
931
+ this.emit('upgrade', this._res, this.socket, react_native_nitro_buffer_1.Buffer.alloc(0));
932
+ return;
933
+ }
934
+ // Handle CONNECT method response (HTTP Tunneling)
935
+ if (this.method.toUpperCase() === 'CONNECT' && status >= 200 && status < 300) {
936
+ debugLog(`ClientRequest: CONNECT tunnel established (status=${status}), emitting 'connect' event`);
937
+ this.socket.removeListener('data', onData);
938
+ this.socket.removeListener('error', onError);
939
+ this.emit('connect', this._res, this.socket, react_native_nitro_buffer_1.Buffer.alloc(0));
940
+ return;
941
+ }
942
+ this.emit('response', this._res);
943
+ }
944
+ if (this._res && parsed.body && parsed.body.length > 0) {
945
+ this._res.push(react_native_nitro_buffer_1.Buffer.from(parsed.body));
946
+ }
947
+ if (this._res && parsed.complete) {
948
+ this._res.complete = true;
949
+ if (parsed.trailers) {
950
+ this._res.trailers = parsed.trailers;
951
+ }
952
+ this._res.push(null);
953
+ this._finishResponse();
954
+ }
955
+ };
956
+ let input = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
957
+ let iterations = 0;
958
+ const maxIterations = 100; // Safety limit
959
+ while (iterations < maxIterations) {
960
+ iterations++;
961
+ const result = parser.feed(input);
962
+ if (!result || result === '' || result.startsWith('ERROR:')) {
963
+ // Empty result (partial) or error - exit loop
964
+ if (result && result.startsWith('ERROR:')) {
965
+ console.log(`[HTTP] ClientRequest: Parser error: ${result}`);
966
+ }
967
+ break;
968
+ }
969
+ handleParsedResult(result);
970
+ input = new ArrayBuffer(0); // Continue with empty input to drain Rust buffer
971
+ }
972
+ };
973
+ const onError = (err) => {
974
+ console.log(`[HTTP] _connect: Socket error: ${err.message}`);
975
+ this.emit('error', err);
976
+ this._cleanupSocket();
977
+ };
978
+ const onClose = () => {
979
+ console.log(`[HTTP] _connect: Socket closed`);
980
+ if (this._res && !this._res.readableEnded)
981
+ this._res.push(null);
982
+ this.emit('close');
983
+ this._cleanupSocket();
984
+ };
985
+ this.socket.on('data', onData);
986
+ this.socket.on('error', onError);
987
+ this.socket.on('close', onClose);
988
+ this._socketCleanup = () => {
989
+ this.socket?.removeListener('data', onData);
990
+ this.socket?.removeListener('error', onError);
991
+ this.socket?.removeListener('close', onClose);
992
+ };
993
+ }
994
+ _cleanupSocket() {
995
+ if (this._socketCleanup)
996
+ this._socketCleanup();
997
+ this._socketCleanup = undefined;
998
+ this.socket = null;
999
+ this._connected = false;
1000
+ }
1001
+ _finishResponse() {
1002
+ // Release socket back to agent
1003
+ const agent = this._options.agent === false ? new Agent() : (this._options.agent instanceof Agent ? this._options.agent : exports.globalAgent);
1004
+ const socket = this.socket;
1005
+ this._cleanupSocket();
1006
+ if (socket)
1007
+ agent.releaseSocket(socket, this._options);
1008
+ }
1009
+ _flushPendingWrites() {
1010
+ if (!this.socket)
1011
+ return;
1012
+ if (!this.headersSent)
1013
+ this._sendRequest();
1014
+ // If we are waiting for 100-continue, don't flush yet
1015
+ if (this._expectContinue && !this._continueReceived) {
1016
+ return;
1017
+ }
1018
+ const writes = this._pendingWrites;
1019
+ this._pendingWrites = [];
1020
+ for (const pending of writes) {
1021
+ this._write(pending.chunk, pending.encoding, pending.callback);
1022
+ }
1023
+ if (this._ended) {
1024
+ this._finishRequest();
1025
+ }
1026
+ }
1027
+ _finishRequest() {
1028
+ if (!this._ended)
1029
+ return;
1030
+ super.end();
1031
+ }
1032
+ _sendRequest() {
1033
+ debugLog(`ClientRequest._sendRequest: headersSent=${this.headersSent}, socket=${!!this.socket}`);
1034
+ if (this.headersSent)
1035
+ return;
1036
+ if (!this.hasHeader('host')) {
1037
+ this.setHeader('Host', this.host);
1038
+ }
1039
+ const firstLine = `${this.method} ${this.path} HTTP/1.1`;
1040
+ debugLog(`ClientRequest._sendRequest: sending firstLine=${firstLine}`);
1041
+ this._sendHeaders(firstLine);
1042
+ }
1043
+ _write(chunk, encoding, callback) {
1044
+ if (!this._connected) {
1045
+ this._pendingWrites.push({ chunk, encoding, callback });
1046
+ return;
1047
+ }
1048
+ if (!this.headersSent)
1049
+ this._sendRequest();
1050
+ super._write(chunk, encoding, callback);
1051
+ }
1052
+ write(chunk, encoding, callback) {
1053
+ if (!this._connected) {
1054
+ this._pendingWrites.push({ chunk, encoding, callback });
1055
+ return true;
1056
+ }
1057
+ if (!this.headersSent)
1058
+ this._sendRequest();
1059
+ return super.write(chunk, encoding, callback);
1060
+ }
1061
+ end(chunk, encoding, callback) {
1062
+ debugLog(`ClientRequest.end() called, connected=${this._connected}, headersSent=${this.headersSent}`);
1063
+ if (chunk) {
1064
+ this.write(chunk, encoding);
1065
+ }
1066
+ this._ended = true;
1067
+ // If connected, we can send request and end immediately
1068
+ if (this._connected) {
1069
+ if (!this.headersSent) {
1070
+ this._sendRequest();
1071
+ }
1072
+ // Call super.end only when connected
1073
+ super.end(undefined, undefined, callback);
1074
+ }
1075
+ else {
1076
+ // Socket not connected yet - _flushPendingWrites will handle ending
1077
+ // Store callback if provided
1078
+ if (callback) {
1079
+ this.once('finish', callback);
1080
+ }
1081
+ }
1082
+ return this;
1083
+ }
1084
+ abort() {
1085
+ if (this.aborted)
1086
+ return;
1087
+ this.aborted = true;
1088
+ this.emit('abort');
1089
+ this.destroy();
1090
+ }
1091
+ flushHeaders() {
1092
+ if (this._connected && !this.headersSent) {
1093
+ this._sendRequest();
1094
+ }
1095
+ }
1096
+ }
1097
+ exports.ClientRequest = ClientRequest;
1098
+ function createServer(optionsOrListener, requestListener) {
1099
+ return new Server(optionsOrListener, requestListener);
1100
+ }
1101
+ function request(urlOrOptions, optionsOrCallback, callback) {
1102
+ let opts = {};
1103
+ let cb = callback;
1104
+ if (typeof urlOrOptions === 'string') {
1105
+ const url = new URL(urlOrOptions);
1106
+ opts = {
1107
+ protocol: url.protocol,
1108
+ hostname: url.hostname,
1109
+ path: url.pathname + url.search,
1110
+ port: url.port ? parseInt(url.port) : undefined
1111
+ };
1112
+ }
1113
+ else if (urlOrOptions instanceof URL) {
1114
+ opts = {
1115
+ protocol: urlOrOptions.protocol,
1116
+ hostname: urlOrOptions.hostname,
1117
+ path: urlOrOptions.pathname + urlOrOptions.search,
1118
+ port: urlOrOptions.port ? parseInt(urlOrOptions.port) : undefined
1119
+ };
1120
+ }
1121
+ else {
1122
+ opts = urlOrOptions;
1123
+ }
1124
+ // Handle (url, options, callback) or (url, callback) signatures
1125
+ if (typeof optionsOrCallback === 'function') {
1126
+ cb = optionsOrCallback;
1127
+ }
1128
+ else if (optionsOrCallback) {
1129
+ // Merge options
1130
+ opts = { ...opts, ...optionsOrCallback };
1131
+ }
1132
+ return new ClientRequest(opts, cb);
1133
+ }
1134
+ function get(urlOrOptions, optionsOrCallback, callback) {
1135
+ const req = request(urlOrOptions, optionsOrCallback, callback);
1136
+ req.end();
1137
+ return req;
1138
+ }