react-native-nitro-net 0.3.0 → 0.3.1

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