react-native-nitro-net 0.3.0 → 0.4.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 (86) hide show
  1. package/README.md +1 -1
  2. package/android/CMakeLists.txt +5 -0
  3. package/android/build.gradle +1 -1
  4. package/lib/Driver.d.ts +1 -0
  5. package/lib/Driver.d.ts.map +1 -0
  6. package/lib/Driver.js +2 -5
  7. package/lib/Net.nitro.d.ts +1 -0
  8. package/lib/Net.nitro.d.ts.map +1 -0
  9. package/lib/Net.nitro.js +4 -7
  10. package/lib/http.d.ts +2 -1
  11. package/lib/http.d.ts.map +1 -0
  12. package/lib/http.js +56 -61
  13. package/lib/https.d.ts +1 -0
  14. package/lib/https.d.ts.map +1 -0
  15. package/lib/https.js +11 -53
  16. package/lib/index.d.ts +41 -198
  17. package/lib/index.d.ts.map +1 -0
  18. package/lib/index.js +11 -928
  19. package/lib/net.d.ts +197 -0
  20. package/lib/net.d.ts.map +1 -0
  21. package/lib/net.js +875 -0
  22. package/lib/tls.d.ts +2 -1
  23. package/lib/tls.d.ts.map +1 -0
  24. package/lib/tls.js +35 -51
  25. package/nitrogen/generated/android/RustCNet+autolinking.cmake +1 -1
  26. package/nitrogen/generated/android/RustCNet+autolinking.gradle +1 -1
  27. package/nitrogen/generated/android/RustCNetOnLoad.cpp +1 -1
  28. package/nitrogen/generated/android/RustCNetOnLoad.hpp +1 -1
  29. package/nitrogen/generated/android/c++/JFunc_void_double_std__shared_ptr_ArrayBuffer_.hpp +1 -1
  30. package/nitrogen/generated/android/c++/JHybridHttpParserSpec.cpp +1 -1
  31. package/nitrogen/generated/android/c++/JHybridHttpParserSpec.hpp +1 -1
  32. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.cpp +1 -1
  33. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.hpp +1 -1
  34. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.cpp +1 -1
  35. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.hpp +1 -1
  36. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.cpp +1 -1
  37. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.hpp +1 -1
  38. package/nitrogen/generated/android/c++/JNetConfig.hpp +1 -1
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/Func_void_double_std__shared_ptr_ArrayBuffer_.kt +1 -1
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridHttpParserSpec.kt +1 -1
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetDriverSpec.kt +1 -1
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetServerDriverSpec.kt +1 -1
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetSocketDriverSpec.kt +1 -1
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/NetConfig.kt +1 -1
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/RustCNetOnLoad.kt +1 -1
  46. package/nitrogen/generated/ios/RustCNet+autolinking.rb +1 -1
  47. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.cpp +1 -1
  48. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.hpp +1 -1
  49. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Umbrella.hpp +1 -1
  50. package/nitrogen/generated/ios/RustCNetAutolinking.mm +1 -1
  51. package/nitrogen/generated/ios/RustCNetAutolinking.swift +1 -1
  52. package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.cpp +1 -1
  53. package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.hpp +1 -1
  54. package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.cpp +1 -1
  55. package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.hpp +1 -1
  56. package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.cpp +1 -1
  57. package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.hpp +1 -1
  58. package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.cpp +1 -1
  59. package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.hpp +1 -1
  60. package/nitrogen/generated/ios/swift/Func_void_double_std__shared_ptr_ArrayBuffer_.swift +1 -1
  61. package/nitrogen/generated/ios/swift/HybridHttpParserSpec.swift +1 -1
  62. package/nitrogen/generated/ios/swift/HybridHttpParserSpec_cxx.swift +1 -1
  63. package/nitrogen/generated/ios/swift/HybridNetDriverSpec.swift +1 -1
  64. package/nitrogen/generated/ios/swift/HybridNetDriverSpec_cxx.swift +1 -1
  65. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec.swift +1 -1
  66. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec_cxx.swift +1 -1
  67. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec.swift +1 -1
  68. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec_cxx.swift +1 -1
  69. package/nitrogen/generated/ios/swift/NetConfig.swift +1 -1
  70. package/nitrogen/generated/shared/c++/HybridHttpParserSpec.cpp +1 -1
  71. package/nitrogen/generated/shared/c++/HybridHttpParserSpec.hpp +1 -1
  72. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.cpp +1 -1
  73. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.hpp +1 -1
  74. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.cpp +1 -1
  75. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.hpp +1 -1
  76. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.cpp +1 -1
  77. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.hpp +1 -1
  78. package/nitrogen/generated/shared/c++/HybridNitroBufferSpec.cpp +1 -1
  79. package/nitrogen/generated/shared/c++/HybridNitroBufferSpec.hpp +1 -1
  80. package/nitrogen/generated/shared/c++/NetConfig.hpp +1 -1
  81. package/package.json +7 -5
  82. package/src/http.ts +18 -12
  83. package/src/https.ts +0 -2
  84. package/src/index.ts +13 -1005
  85. package/src/net.ts +1005 -0
  86. package/src/tls.ts +1 -1
package/src/index.ts CHANGED
@@ -1,1010 +1,18 @@
1
- import { Duplex, DuplexOptions } from 'readable-stream'
2
- import { EventEmitter } from 'eventemitter3'
3
- import { Driver } from './Driver'
4
- import type { NetSocketDriver, NetServerDriver, NetConfig } from './Net.nitro'
5
- import { NetSocketEvent, NetServerEvent } from './Net.nitro'
6
- import { Buffer } from 'react-native-nitro-buffer'
7
-
8
- // -----------------------------------------------------------------------------
9
- // Utils
10
- // -----------------------------------------------------------------------------
11
-
12
- function isIP(input: string): number {
13
- // Simple regex check
14
- if (/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(input)) return 4;
15
- // Basic IPv6 check allowing double colons
16
- if (/^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/.test(input)) return 6;
17
- if (/^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$/.test(input)) return 6;
18
- return 0;
19
- }
20
-
21
- function isIPv4(input: string): boolean {
22
- return isIP(input) === 4;
23
- }
24
-
25
- function isIPv6(input: string): boolean {
26
- return isIP(input) === 6;
27
- }
28
- // -----------------------------------------------------------------------------
29
- // Global Configuration
30
- // -----------------------------------------------------------------------------
31
-
32
- let _autoSelectFamilyDefault = 4; // Node default is usually 4/6 independent, but we mock it.
33
- let _isVerbose = false;
34
- let _isInitialized = false;
35
-
36
- function isVerbose(): boolean {
37
- return _isVerbose;
38
- }
39
-
40
- function setVerbose(enabled: boolean): void {
41
- _isVerbose = enabled;
42
- }
43
-
44
- function debugLog(message: string) {
45
- if (_isVerbose) {
46
- const timestamp = new Date().toISOString().split('T')[1].split('Z')[0];
47
- console.log(`[NET DEBUG ${timestamp}] ${message}`);
48
- }
49
- }
50
-
51
- function getDefaultAutoSelectFamily(): number {
52
- return _autoSelectFamilyDefault;
53
- }
54
-
55
- function setDefaultAutoSelectFamily(family: number): void {
56
- if (family !== 4 && family !== 6) throw new Error('Family must be 4 or 6');
57
- _autoSelectFamilyDefault = family;
58
- }
59
-
60
- /**
61
- * Ensures that the network module is initialized.
62
- * If initWithConfig hasn't been called, it will be called with default options.
63
- */
64
- function ensureInitialized(): void {
65
- if (!_isInitialized) {
66
- initWithConfig({});
67
- }
68
- }
69
-
70
- /**
71
- * Initialize the network module with custom configuration.
72
- * Must be called before any socket/server operations, or the config will be ignored.
73
- *
74
- * @param config Configuration options
75
- * @param config.workerThreads Number of worker threads (0 = use CPU core count)
76
- *
77
- * @example
78
- * ```ts
79
- * import { initWithConfig } from 'react-native-nitro-net';
80
- *
81
- * // Initialize with 4 worker threads
82
- * initWithConfig({ workerThreads: 4 });
83
- * ```
84
- */
85
- function initWithConfig(config: NetConfig): void {
86
- _isInitialized = true;
87
- if (config.debug !== undefined) {
88
- setVerbose(config.debug);
89
- }
90
- Driver.initWithConfig(config);
91
- }
92
-
93
- // -----------------------------------------------------------------------------
94
- // SocketAddress
95
-
96
- // -----------------------------------------------------------------------------
97
- // SocketAddress
98
- // -----------------------------------------------------------------------------
99
-
100
- export interface SocketAddressOptions {
101
- address?: string;
102
- family?: 'ipv4' | 'ipv6';
103
- port?: number;
104
- flowlabel?: number;
105
- }
106
-
107
- export class SocketAddress {
108
- readonly address: string;
109
- readonly family: 'ipv4' | 'ipv6';
110
- readonly port: number;
111
- readonly flowlabel: number;
112
-
113
- constructor(options: SocketAddressOptions = {}) {
114
- this.address = options.address ?? (options.family === 'ipv6' ? '::' : '127.0.0.1');
115
- this.family = options.family || (isIPv6(this.address) ? 'ipv6' : 'ipv4');
116
- this.port = options.port ?? 0;
117
- this.flowlabel = options.flowlabel ?? 0;
118
- }
119
-
120
- /**
121
- * Attempts to parse a string containing a socket address.
122
- * Returns a SocketAddress if successful, or undefined if not.
123
- *
124
- * Supported formats:
125
- * - `ip:port` (e.g., `127.0.0.1:8080`, `[::1]:8080`)
126
- * - `ip` only (port defaults to 0)
127
- */
128
- static parse(input: string): SocketAddress | undefined {
129
- if (!input || typeof input !== 'string') return undefined;
130
- let address: string;
131
- let port = 0;
132
-
133
- // Handle IPv6 bracket notation: [::1]:port
134
- const ipv6Match = input.match(/^\[([^\]]+)\]:?(\d*)$/);
135
- if (ipv6Match) {
136
- address = ipv6Match[1];
137
- port = ipv6Match[2] ? parseInt(ipv6Match[2], 10) : 0;
138
- if (!isIPv6(address)) return undefined;
139
- return new SocketAddress({ address, port, family: 'ipv6' });
140
- }
141
-
142
- // Handle IPv4 or IPv6 without brackets
143
- const lastColon = input.lastIndexOf(':');
144
- if (lastColon === -1) {
145
- // No port, just IP
146
- address = input;
147
- } else {
148
- // Determine if the colon is a port separator or part of IPv6
149
- const potentialPort = input.slice(lastColon + 1);
150
- const potentialAddr = input.slice(0, lastColon);
151
- if (/^\d+$/.test(potentialPort) && (isIPv4(potentialAddr) || isIPv6(potentialAddr))) {
152
- address = potentialAddr;
153
- port = parseInt(potentialPort, 10);
154
- } else {
155
- // It's an IPv6 address without port
156
- address = input;
157
- }
158
- }
159
-
160
- const family = isIPv6(address) ? 'ipv6' : (isIPv4(address) ? 'ipv4' : undefined);
161
- if (!family) return undefined;
162
- return new SocketAddress({ address, port, family });
163
- }
164
- }
165
-
166
- // -----------------------------------------------------------------------------
167
- // BlockList
168
- // -----------------------------------------------------------------------------
169
-
170
- export interface BlockListRule {
171
- type: 'address' | 'range' | 'subnet';
172
- address?: string;
173
- start?: string;
174
- end?: string;
175
- prefix?: number;
176
- family: 'ipv4' | 'ipv6';
177
- }
178
-
179
- export class BlockList {
180
- private _rules: Array<{ type: 'address' | 'range' | 'subnet', data: any }> = [];
181
-
182
- /** Returns an array of rules added to the blocklist. */
183
- get rules(): BlockListRule[] {
184
- return this._rules.map(r => {
185
- if (r.type === 'address') {
186
- return { type: 'address' as const, address: r.data.address, family: r.data.family };
187
- } else if (r.type === 'range') {
188
- return { type: 'range' as const, start: r.data.start, end: r.data.end, family: r.data.family };
189
- } else {
190
- return { type: 'subnet' as const, address: r.data.net, prefix: r.data.prefix, family: r.data.family };
191
- }
192
- });
193
- }
194
-
195
- addAddress(address: string, family?: 'ipv4' | 'ipv6'): void {
196
- this._rules.push({ type: 'address', data: { address, family: family || (isIPv6(address) ? 'ipv6' : 'ipv4') } });
197
- }
198
-
199
- addRange(start: string, end: string, family?: 'ipv4' | 'ipv6'): void {
200
- this._rules.push({ type: 'range', data: { start, end, family: family || (isIPv6(start) ? 'ipv6' : 'ipv4') } });
201
- }
202
-
203
- addSubnet(net: string, prefix: number, family?: 'ipv4' | 'ipv6'): void {
204
- this._rules.push({ type: 'subnet', data: { net, prefix, family: family || (isIPv6(net) ? 'ipv6' : 'ipv4') } });
205
- }
206
-
207
- check(address: string, family?: 'ipv4' | 'ipv6'): boolean {
208
- const addrFamily = family || (isIPv6(address) ? 'ipv6' : 'ipv4');
209
- const addrNum = addrFamily === 'ipv4' ? ipv4ToLong(address) : null;
210
-
211
- for (const rule of this._rules) {
212
- if (rule.data.family !== addrFamily) continue;
213
-
214
- if (rule.type === 'address') {
215
- if (rule.data.address === address) return true;
216
- } else if (rule.type === 'range' && addrNum !== null) {
217
- const start = ipv4ToLong(rule.data.start);
218
- const end = ipv4ToLong(rule.data.end);
219
- if (addrNum >= start && addrNum <= end) return true;
220
- } else if (rule.type === 'subnet' && addrNum !== null) {
221
- const net = ipv4ToLong(rule.data.net);
222
- const mask = ~(Math.pow(2, 32 - rule.data.prefix) - 1);
223
- if ((addrNum & mask) === (net & mask)) return true;
224
- }
225
- }
226
- return false;
227
- }
228
-
229
- /**
230
- * Serializes the BlockList to a JSON-compatible format.
231
- */
232
- toJSON(): BlockListRule[] {
233
- return this.rules;
234
- }
235
-
236
- /**
237
- * Creates a BlockList from a JSON array of rules.
238
- */
239
- static fromJSON(json: BlockListRule[]): BlockList {
240
- const list = new BlockList();
241
- for (const rule of json) {
242
- if (rule.type === 'address' && rule.address) {
243
- list.addAddress(rule.address, rule.family);
244
- } else if (rule.type === 'range' && rule.start && rule.end) {
245
- list.addRange(rule.start, rule.end, rule.family);
246
- } else if (rule.type === 'subnet' && rule.address && rule.prefix !== undefined) {
247
- list.addSubnet(rule.address, rule.prefix, rule.family);
248
- }
249
- }
250
- return list;
251
- }
252
-
253
- /**
254
- * Checks if a given value is a BlockList instance.
255
- */
256
- static isBlockList(value: unknown): value is BlockList {
257
- return value instanceof BlockList;
258
- }
259
- }
260
-
261
- function ipv4ToLong(ip: string): number {
262
- return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
263
- }
264
-
265
- // -----------------------------------------------------------------------------
266
- // Socket
267
- // -----------------------------------------------------------------------------
268
-
269
- export interface SocketOptions extends DuplexOptions {
270
- fd?: any;
271
- allowHalfOpen?: boolean;
272
- readable?: boolean;
273
- writable?: boolean;
274
- path?: string;
275
- // Extension for internal use
276
- socketDriver?: NetSocketDriver;
277
- remoteFamily?: string;
278
- }
279
-
280
- export class Socket extends Duplex {
281
- protected _driver: NetSocketDriver | undefined;
282
- public connecting: boolean = false; // Changed from private _connecting
283
- protected _connected: boolean = false;
284
- protected _hadError: boolean = false; // Added
285
- public remoteAddress?: string;
286
- public remotePort?: number;
287
- public remoteFamily?: string;
288
- public localAddress?: string;
289
- public localPort?: number;
290
- public bytesRead: number = 0;
291
- public bytesWritten: number = 0;
292
- public autoSelectFamilyAttemptedAddresses: string[] = [];
293
- private _autoSelectFamily: boolean = false;
294
- private _timeout: number = 0;
295
-
296
- get localFamily(): string {
297
- return this.localAddress && this.localAddress.includes(':') ? 'IPv6' : 'IPv4';
298
- }
299
-
300
- get readyState(): string {
301
- if (this.connecting) return 'opening';
302
- if (this._connected) {
303
- // @ts-ignore
304
- if (this.writable && this.readable) return 'open';
305
- // @ts-ignore
306
- if (this.writable) return 'writeOnly';
307
- // @ts-ignore
308
- if (this.readable) return 'readOnly';
309
- }
310
- return 'closed';
311
- }
312
-
313
- get pending(): boolean {
314
- return this.connecting;
315
- }
316
- constructor(options?: SocketOptions) {
317
- super({
318
- allowHalfOpen: options?.allowHalfOpen ?? false,
319
- readable: options?.readable ?? true,
320
- writable: options?.writable ?? true,
321
- // @ts-ignore
322
- autoDestroy: false
323
- });
324
-
325
- if (options?.socketDriver) {
326
- // Wrapping existing socket (from Server)
327
- this._driver = options.socketDriver;
328
- this._connected = true;
329
- this._setupEvents();
330
- // Enable noDelay by default
331
- this._driver.setNoDelay(true);
332
- // Resume the socket since it starts paused on server-accept
333
- this.resume();
334
- // Emit connect for server-side socket? No, it's already connected.
335
- } else {
336
- // New client socket
337
- ensureInitialized();
338
- this._driver = Driver.createSocket();
339
- this._setupEvents();
340
- // Enable noDelay by default to match Node.js and reduce latency for small writes
341
- this._driver.setNoDelay(true);
342
- // Do NOT resume here - socket is not connected yet!
343
- // resume() will be called after 'connect' event in _connect()
344
- }
345
-
346
- this.on('finish', () => {
347
- // Writable side finished
348
- });
349
- }
350
-
351
- on(event: string | symbol, listener: (...args: any[]) => void): this {
352
- if (event === 'connect' && this._connected) {
353
- process.nextTick(listener);
354
- return this;
355
- }
356
- const ret = super.on(event, listener);
357
- if (event === 'data' && !this.isPaused() && (this as any).readableFlowing !== true) {
358
- debugLog(`Socket on('data'), flowing: ${(this as any).readableFlowing}`);
359
- this.resume();
360
- }
361
- return ret;
362
- }
363
-
364
- private _setupEvents() {
365
- if (!this._driver) return;
366
- const id = (this._driver as any).id ?? (this._driver as any)._id;
367
- this._driver.onEvent = (eventType: number, data: ArrayBuffer) => {
368
- this.emit('event', eventType, data);
369
- if (eventType === 3) { // ERROR
370
- const msg = new TextDecoder().decode(data);
371
- debugLog(`Socket (id: ${id}) NATIVE ERROR: ${msg}`);
372
- }
373
- if (eventType === 9) { // SESSION/DEBUG
374
- debugLog(`Socket (id: ${id}) NATIVE SESSION EVENT RECEIVED`);
375
- this.emit('session', data);
376
- return;
377
- }
378
- debugLog(`Socket (id: ${id}, localPort: ${this.localPort}) Event TYPE: ${eventType}, data len: ${data?.byteLength}`);
379
- switch (eventType) {
380
- case NetSocketEvent.CONNECT:
381
- this.connecting = false;
382
- this._connected = true;
383
- this._updateAddresses();
384
- // Now that we're connected, start receiving data
385
- this.resume();
386
- this.emit('connect');
387
- this.emit('ready');
388
- break;
389
- case NetSocketEvent.DATA:
390
- debugLog(`Socket onEvent(DATA), len: ${data?.byteLength}, flowing: ${(this as any).readableFlowing}`);
391
- if (data && data.byteLength > 0) {
392
- const buffer = Buffer.from(data);
393
- this.bytesRead += buffer.length;
394
- if (!this.push(buffer)) {
395
- this.pause();
396
- }
397
- }
398
- break;
399
- case NetSocketEvent.ERROR: {
400
- this._hadError = true;
401
- const errorMsg = data ? Buffer.from(data).toString() : 'Unknown socket error';
402
- const error = new Error(errorMsg);
403
-
404
- if (this.connecting && this._autoSelectFamily) {
405
- // If we were connecting, this is a connection attempt failure
406
- // We attempt to get the last attempted address if available
407
- const lastAttempt = this.autoSelectFamilyAttemptedAddresses[this.autoSelectFamilyAttemptedAddresses.length - 1];
408
- if (lastAttempt) {
409
- const [ip, port] = lastAttempt.split(':'); // distinct if ipv6?
410
- // Simple parsing for event emission
411
- const family = ip.includes(':') ? 6 : 4;
412
- this.emit('connectionAttemptFailed', ip, parseInt(port || '0', 10), family, error);
413
- }
414
- }
415
-
416
- this.emit('error', error);
417
- this.destroy();
418
- break;
419
- }
420
- case NetSocketEvent.CLOSE:
421
- this._connected = false;
422
- this.connecting = false;
423
- this.push(null); // EOF
424
- this.emit('close', this._hadError);
425
- break;
426
- case NetSocketEvent.DRAIN:
427
- this.emit('drain');
428
- break;
429
- case NetSocketEvent.TIMEOUT:
430
- if (this.connecting && this._autoSelectFamily) {
431
- const lastAttempt = this.autoSelectFamilyAttemptedAddresses[this.autoSelectFamilyAttemptedAddresses.length - 1];
432
- if (lastAttempt) {
433
- const [ip, port] = lastAttempt.split(':');
434
- const family = ip.includes(':') ? 6 : 4;
435
- this.emit('connectionAttemptTimeout', ip, parseInt(port || '0', 10), family);
436
- }
437
- }
438
- this.emit('timeout');
439
- break;
440
- case NetSocketEvent.LOOKUP: {
441
- if (data) {
442
- const lookupStr = Buffer.from(data).toString();
443
- const parts = lookupStr.split(',');
444
- if (parts.length >= 2) {
445
- const [ip, family] = parts;
446
- this.remoteAddress = ip;
447
- this.remoteFamily = family === '6' ? 'IPv6' : 'IPv4';
448
-
449
- // Emit connectionAttempt
450
- // We don't have the port in LOOKUP data usually, but we stored it in this.remotePort (dest)
451
- // actually remotePort might not be set yet if we used _connect with port arg.
452
- // But _connect sets this.remotePort = port.
453
- const port = this.remotePort || 0;
454
- const fam = family === '6' ? 6 : 4;
455
- if (this._autoSelectFamily) {
456
- this.emit('connectionAttempt', ip, port, fam);
457
- }
458
- this.autoSelectFamilyAttemptedAddresses.push(`${ip}:${port}`);
459
- }
460
- const host = parts.length > 2 ? parts[2] : undefined;
461
- this.emit('lookup', null, parts[0], parts[1] ? parseInt(parts[1], 10) : undefined, host);
462
- }
463
- break;
464
- }
465
- }
466
- };
467
- }
468
-
469
-
470
- private _updateAddresses() {
471
- try {
472
- const local = this._driver?.getLocalAddress();
473
- if (local) {
474
- const parts = local.split(':');
475
- if (parts.length >= 2) {
476
- this.localPort = parseInt(parts[parts.length - 1], 10);
477
- this.localAddress = parts.slice(0, parts.length - 1).join(':').replace(/[\[\]]/g, '');
478
- }
479
- }
480
- const remote = this._driver?.getRemoteAddress();
481
- if (remote) {
482
- const parts = remote.split(':');
483
- if (parts.length >= 2) {
484
- this.remotePort = parseInt(parts[parts.length - 1], 10);
485
- this.remoteAddress = parts.slice(0, parts.length - 1).join(':').replace(/[\[\]]/g, '');
486
- this.remoteFamily = this.remoteAddress.includes(':') ? 'IPv6' : 'IPv4';
487
- }
488
- }
489
- } catch (e) {
490
- // Ignore errors for now
491
- }
492
- }
493
-
494
- address(): { port: number; family: string; address: string } | null {
495
- if (!this.localAddress) return null;
496
- return {
497
- port: this.localPort || 0,
498
- family: this.localAddress.includes(':') ? 'IPv6' : 'IPv4',
499
- address: this.localAddress
500
- };
501
- }
502
-
503
- connect(options: any, connectionListener?: () => void): this {
504
- if (typeof options === 'string') {
505
- // Path?
506
- if (isNaN(Number(options))) {
507
- return this._connectUnix(options, connectionListener);
508
- }
509
- }
510
-
511
- if (typeof options === 'number' || typeof options === 'string') {
512
- const port = Number(options);
513
- const host = (arguments.length > 1 && typeof arguments[1] === 'string') ? arguments[1] : 'localhost';
514
- const cb = typeof arguments[1] === 'function' ? arguments[1] : connectionListener;
515
- // Default: Node 20 defaults autoSelectFamily to true
516
- this._autoSelectFamily = true;
517
- return this._connect(port, host, cb || arguments[2]);
518
- }
519
-
520
- if (options.path) {
521
- return this._connectUnix(options.path, connectionListener, options.signal);
522
- }
523
-
524
- const port = options.port;
525
- const host = options.host || 'localhost';
526
-
527
- // Handle autoSelectFamily option
528
- if (typeof options.autoSelectFamily === 'boolean') {
529
- this._autoSelectFamily = options.autoSelectFamily;
530
- } else {
531
- this._autoSelectFamily = true;
532
- }
533
-
534
- debugLog(`Socket.connect: target=${host}:${port}, autoSelectFamily=${this._autoSelectFamily}`);
535
- return this._connect(port, host, connectionListener, options.signal);
536
- }
537
-
538
- private _connect(port: number, host: string, listener?: () => void, signal?: AbortSignal): this {
539
- this.remotePort = port; // Store intended remote port
540
- if (this.connecting || this._connected) return this;
541
- if (signal?.aborted) {
542
- process.nextTick(() => this.emit('error', new Error('The operation was aborted')));
543
- return this;
544
- }
545
- this.connecting = true;
546
- if (listener) this.once('connect', listener);
547
-
548
- if (signal) {
549
- const abortHandler = () => {
550
- this.destroy(new Error('The operation was aborted'));
551
- };
552
- signal.addEventListener('abort', abortHandler, { once: true });
553
- this.once('connect', () => signal.removeEventListener('abort', abortHandler));
554
- this.once('close', () => signal.removeEventListener('abort', abortHandler));
555
- }
556
-
557
- debugLog(`Socket._connect: Calling driver.connect(${host}, ${port})`);
558
- this._driver?.connect(host, port);
559
- return this;
560
- }
561
-
562
- private _connectUnix(path: string, listener?: () => void, signal?: AbortSignal): this {
563
- if (this.connecting || this._connected) return this;
564
- if (signal?.aborted) {
565
- process.nextTick(() => this.emit('error', new Error('The operation was aborted')));
566
- return this;
567
- }
568
- this.connecting = true;
569
- if (listener) this.once('connect', listener);
570
-
571
- if (signal) {
572
- const abortHandler = () => {
573
- this.destroy(new Error('The operation was aborted'));
574
- };
575
- signal.addEventListener('abort', abortHandler, { once: true });
576
- this.once('connect', () => signal.removeEventListener('abort', abortHandler));
577
- this.once('close', () => signal.removeEventListener('abort', abortHandler));
578
- }
579
-
580
- this._driver?.connectUnix(path);
581
- return this;
582
- }
583
-
584
- end(cb?: () => void): this;
585
- end(chunk: any, cb?: () => void): this;
586
- end(chunk: any, encoding: string, cb?: () => void): this;
587
- end(chunk?: any, encoding?: any, cb?: any): this {
588
- debugLog(`Socket (localPort: ${this.localPort}) .end() called`);
589
- return super.end(chunk, encoding, cb);
590
- }
591
-
592
- _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void {
593
- if (!this._driver) {
594
- return callback(new Error('Socket not connected'));
595
- }
596
- try {
597
- const buffer = (chunk instanceof Buffer) ? chunk : Buffer.from(chunk, encoding as any);
598
- this.bytesWritten += buffer.length;
599
- const ab = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
600
- debugLog(`Socket _write, len: ${ab.byteLength}`);
601
- this._driver.write(ab);
602
- callback(null);
603
- } catch (err: any) {
604
- callback(err);
605
- }
606
- }
607
-
608
- _read(size: number): void {
609
- if (this._driver) this._driver.resume();
610
- }
611
-
612
- _final(callback: (error?: Error | null) => void): void {
613
- if (this._driver) {
614
- this._driver.shutdown();
615
- }
616
- callback(null);
617
- }
618
-
619
- destroy(reason?: Error): this {
620
- debugLog(`Socket (localPort: ${this.localPort}) .destroy() called, reason: ${reason?.message}`);
621
- return super.destroy(reason);
622
- }
623
-
624
- _destroy(err: Error | null, callback: (error: Error | null) => void) {
625
- debugLog(`Socket (localPort: ${this.localPort}) ._destroy() called`);
626
- this._connected = false;
627
- this.connecting = false;
628
- this.destroyed = true;
629
- if (this._driver) {
630
- this._driver.destroy();
631
- this._driver = undefined;
632
- }
633
- callback(err);
634
- }
635
-
636
- // Standard net.Socket methods
637
- setTimeout(msecs: number, callback?: () => void): this {
638
- this._timeout = msecs;
639
- if (this._driver) {
640
- this._driver.setTimeout(msecs);
641
- }
642
- if (callback) this.once('timeout', callback);
643
- return this;
644
- }
645
-
646
- /**
647
- * Pause the reading of data. That is, 'data' events will not be emitted.
648
- * Useful to throttle back an upload.
649
- */
650
- pause(): this {
651
- super.pause();
652
- if (this._driver) {
653
- this._driver.pause();
654
- }
655
- return this;
656
- }
657
-
658
- /**
659
- * Resume reading after a call to pause().
660
- */
661
- resume(): this {
662
- const driver = this._driver as any;
663
- const id = driver?.id;
664
- debugLog(`Socket.resume() called, id: ${id === undefined ? 'none' : id}, destroyed: ${this.destroyed}`);
665
- super.resume();
666
- if (driver) {
667
- debugLog(`Socket.resume() calling driver.resume(), id: ${id}`);
668
- driver.resume();
669
- }
670
- return this;
671
- }
672
-
673
- /**
674
- * Enable/disable the use of Nagle's algorithm.
675
- */
676
- setNoDelay(noDelay?: boolean): this {
677
- this._driver?.setNoDelay(noDelay !== false);
678
- return this;
679
- }
680
-
681
- setKeepAlive(enable?: boolean, initialDelay?: number): this {
682
- this._driver?.setKeepAlive(enable !== false, initialDelay || 0);
683
- return this;
684
- }
685
-
686
- ref(): this { return this; }
687
- unref(): this { return this; }
688
-
689
- /**
690
- * Set the encoding for the socket as a Readable Stream.
691
- * Use 'utf8', 'hex', etc.
692
- */
693
- setEncoding(encoding: BufferEncoding): this {
694
- super.setEncoding(encoding);
695
- return this;
696
- }
697
-
698
- get timeout(): number {
699
- return this._timeout;
700
- }
701
-
702
- get bufferSize(): number {
703
- return 0; // Deprecated but often accessed
704
- }
705
-
706
- resetAndDestroy(): this {
707
- if (this._driver) {
708
- this._driver.resetAndDestroy();
709
- this._driver = undefined;
710
- }
711
- this._connected = false;
712
- this.connecting = false;
713
- this.destroyed = true;
714
- return this;
715
- }
716
- }
717
-
718
- // -----------------------------------------------------------------------------
719
- // Server
720
- // -----------------------------------------------------------------------------
721
-
722
- export class Server extends EventEmitter {
723
- private _driver: NetServerDriver;
724
- private _sockets = new Set<Socket>();
725
- private _connections: number = 0;
726
-
727
- private _maxConnections: number = 0;
728
- private _dropMaxConnection: boolean = false;
729
-
730
- get maxConnections(): number {
731
- return this._maxConnections;
732
- }
733
-
734
- set maxConnections(value: number) {
735
- this._maxConnections = value;
736
- // We handle maxConnections in JS to support 'drop' event.
737
- // Disable native limit to ensure we receive the connection attempt.
738
- this._driver.maxConnections = 0;
739
- }
740
-
741
- get dropMaxConnection(): boolean {
742
- return this._dropMaxConnection;
743
- }
744
-
745
- set dropMaxConnection(value: boolean) {
746
- this._dropMaxConnection = value;
747
- }
748
-
749
- get listening(): boolean {
750
- // If we have a driver and we assume it's listening if it has been started?
751
- // Actually, checking _driver state might be hard if not exposed.
752
- // But typically 'listening' is true after 'listening' event.
753
- // We can track it with a private flag or by checking address() which returns null if not listening.
754
- return !!this.address();
755
- }
756
-
757
- constructor(options?: any, connectionListener?: (socket: Socket) => void) {
758
- super();
759
- ensureInitialized();
760
- this._driver = Driver.createServer();
761
-
762
- if (typeof options === 'function') {
763
- connectionListener = options;
764
- options = {};
765
- }
766
-
767
- if (connectionListener) {
768
- this.on('connection', connectionListener);
769
- }
770
-
771
- this._driver.onEvent = (eventType: number, data: ArrayBuffer) => {
772
- switch (eventType) {
773
- case NetServerEvent.CONNECTION: {
774
- const payload = data ? Buffer.from(data).toString() : '';
775
- if (payload === 'success') {
776
- this.emit('listening');
777
- } else {
778
- const clientId = payload;
779
- debugLog(`Server connection clientId: '${clientId}', current connections: ${this._connections}, max: ${this._maxConnections}`);
780
- if (clientId) {
781
- // Check maxConnections
782
- if (this._maxConnections > 0 && this._connections >= this._maxConnections) {
783
- debugLog(`Server maxConnections reached (${this._connections} >= ${this._maxConnections}). Dropping connection. clientId: ${clientId}`);
784
-
785
- const socketDriver = Driver.createSocket(clientId);
786
- const socket = new Socket({
787
- socketDriver: socketDriver,
788
- readable: true,
789
- writable: true
790
- });
791
- // @ts-ignore
792
- socket._updateAddresses();
793
-
794
- this.emit('drop', {
795
- localAddress: socket.localAddress,
796
- localPort: socket.localPort,
797
- localFamily: socket.localFamily,
798
- remoteAddress: socket.remoteAddress,
799
- remotePort: socket.remotePort,
800
- remoteFamily: socket.remoteFamily
801
- });
802
-
803
- socket.destroy();
804
- return;
805
- }
806
-
807
- const socketDriver = Driver.createSocket(clientId);
808
- const socket = new Socket({
809
- socketDriver: socketDriver,
810
- readable: true,
811
- writable: true
812
- });
813
-
814
- // Initialize addresses immediately for server-side socket
815
- // @ts-ignore
816
- socket._updateAddresses();
817
- debugLog(`Socket initialized addresses: local=${socket.localAddress}:${socket.localPort}, remote=${socket.remoteAddress}:${socket.remotePort}`);
818
-
819
- // Keep reference to prevent GC
820
- this._sockets.add(socket);
821
- this._connections++;
822
- socket.on('close', () => {
823
- this._connections--;
824
- this._sockets.delete(socket);
825
- });
826
- this.emit('connection', socket);
827
- }
828
- }
829
- break;
830
- }
831
- case NetServerEvent.ERROR:
832
- this.emit('error', new Error(data ? Buffer.from(data).toString() : 'Unknown server error'));
833
- break;
834
- case NetServerEvent.DEBUG: {
835
- debugLog(`Server NATIVE SESSION/DEBUG EVENT RECEIVED`);
836
- this.emit('session', data);
837
- break;
838
- }
839
- case NetServerEvent.CLOSE:
840
- this.emit('close');
841
- break;
842
- }
843
- };
844
- }
845
-
846
-
847
- ref(): this { return this; }
848
- unref(): this { return this; }
849
-
850
- // @ts-ignore
851
- [Symbol.asyncDispose](): Promise<void> {
852
- return new Promise((resolve) => {
853
- this.close(() => resolve());
854
- });
855
- }
856
- listen(port?: any, host?: any, backlog?: any, callback?: any): this {
857
- let _port = 0;
858
- let _host: string | undefined;
859
- let _backlog: number | undefined;
860
- let _path: string | undefined;
861
- let _callback: (() => void) | undefined;
862
- let signal: AbortSignal | undefined;
863
- let ipv6Only = false;
864
- let reusePort = false;
865
- let handle: { fd?: number } | undefined;
866
-
867
- if (typeof port === 'object' && port !== null) {
868
- // Check if it's a handle object with fd property
869
- if (typeof port.fd === 'number') {
870
- handle = port;
871
- _backlog = port.backlog;
872
- _callback = host; // listen(handle, cb)
873
- } else {
874
- _port = port.port;
875
- _host = port.host;
876
- _backlog = port.backlog;
877
- _path = port.path;
878
- signal = port.signal;
879
- ipv6Only = port.ipv6Only === true;
880
- reusePort = port.reusePort === true;
881
- _callback = host; // listen(options, cb)
882
- }
883
- } else {
884
- _port = typeof port === 'number' ? port : (typeof port === 'string' && !isNaN(Number(port)) ? Number(port) : 0);
885
- if (typeof port === 'string' && isNaN(Number(port))) _path = port;
886
-
887
- if (typeof host === 'string') _host = host;
888
- else if (typeof host === 'function') _callback = host;
889
-
890
- if (typeof backlog === 'number') _backlog = backlog;
891
- else if (typeof backlog === 'function') _callback = backlog;
892
-
893
- if (typeof callback === 'function') _callback = callback;
894
- }
895
-
896
- if (_callback) this.once('listening', _callback);
897
-
898
- if (signal?.aborted) {
899
- process.nextTick(() => this.emit('error', new Error('The operation was aborted')));
900
- return this;
901
- }
902
-
903
- if (signal) {
904
- const abortHandler = () => {
905
- this.close();
906
- this.emit('error', new Error('The operation was aborted'));
907
- };
908
- signal.addEventListener('abort', abortHandler, { once: true });
909
- this.once('listening', () => signal.removeEventListener('abort', abortHandler));
910
- this.once('close', () => signal.removeEventListener('abort', abortHandler));
911
- }
912
-
913
- if (handle && typeof handle.fd === 'number') {
914
- // Listen on an existing file descriptor (handle)
915
- this._driver.listenHandle(handle.fd, _backlog);
916
- } else if (_path) {
917
- this._driver.listenUnix(_path, _backlog);
918
- } else {
919
- this._driver.listen(_port || 0, _backlog, ipv6Only, reusePort);
920
- }
921
-
922
- return this;
923
- }
924
-
925
- close(callback?: (err?: Error) => void): this {
926
- // Destroy all active connections first
927
- for (const socket of this._sockets) {
928
- socket.destroy();
929
- }
930
- this._sockets.clear();
931
- this._connections = 0;
932
-
933
- this._driver.close();
934
- if (callback) this.once('close', callback);
935
- return this;
936
- }
937
-
938
- address(): { port: number; family: string; address: string } | null {
939
- try {
940
- const addr = this._driver.getLocalAddress();
941
- if (addr) {
942
- const parts = addr.split(':');
943
- if (parts.length >= 2) {
944
- const port = parseInt(parts[parts.length - 1], 10);
945
- const address = parts.slice(0, parts.length - 1).join(':').replace(/[\[\]]/g, '');
946
- const family = address.includes(':') ? 'IPv6' : 'IPv4';
947
- return { port, family, address };
948
- }
949
- }
950
- } catch (e) {
951
- // Ignore
952
- }
953
- return null;
954
- }
955
-
956
- getConnections(cb: (err: Error | null, count: number) => void): void {
957
- cb(null, this._connections);
958
- }
959
- }
960
-
961
- // -----------------------------------------------------------------------------
962
- // Exports
963
- // -----------------------------------------------------------------------------
964
-
965
- export function createConnection(options: any, connectionListener?: () => void): Socket {
966
- const socket = new Socket(options);
967
- return socket.connect(options, connectionListener);
968
- }
969
-
970
- export const connect = createConnection;
971
-
972
- export function createServer(options?: any, connectionListener?: (socket: Socket) => void): Server {
973
- return new Server(options, connectionListener);
974
- }
975
-
976
- export * as tls from './tls'
977
- export * as http from './http'
978
- export * as https from './https'
1
+ import * as net from './net'
2
+ import * as tls from './tls'
3
+ import * as http from './http'
4
+ import * as https from './https'
979
5
 
6
+ export * from './net'
980
7
  export {
981
- isIP,
982
- isIPv4,
983
- isIPv6,
984
- getDefaultAutoSelectFamily,
985
- setDefaultAutoSelectFamily,
986
- isVerbose,
987
- setVerbose,
988
- initWithConfig,
989
- };
990
-
991
- export type { NetConfig };
8
+ tls,
9
+ http,
10
+ https
11
+ }
992
12
 
993
13
  export default {
994
- Socket,
995
- Server,
996
- SocketAddress,
997
- BlockList,
998
- createConnection,
999
- createServer,
1000
- connect,
1001
- isIP,
1002
- isIPv4,
1003
- isIPv6,
1004
- getDefaultAutoSelectFamily,
1005
- setDefaultAutoSelectFamily,
1006
- setVerbose,
1007
- initWithConfig,
1008
- http: require('./http'),
1009
- https: require('./https'),
14
+ ...net,
15
+ tls,
16
+ http,
17
+ https,
1010
18
  };