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.
- package/README.md +1 -1
- package/android/CMakeLists.txt +5 -0
- package/android/build.gradle +1 -1
- package/lib/Driver.d.ts +1 -0
- package/lib/Driver.d.ts.map +1 -0
- package/lib/Driver.js +2 -5
- package/lib/Net.nitro.d.ts +1 -0
- package/lib/Net.nitro.d.ts.map +1 -0
- package/lib/Net.nitro.js +4 -7
- package/lib/http.d.ts +2 -1
- package/lib/http.d.ts.map +1 -0
- package/lib/http.js +56 -61
- package/lib/https.d.ts +1 -0
- package/lib/https.d.ts.map +1 -0
- package/lib/https.js +11 -53
- package/lib/index.d.ts +41 -198
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +11 -928
- package/lib/net.d.ts +197 -0
- package/lib/net.d.ts.map +1 -0
- package/lib/net.js +875 -0
- package/lib/tls.d.ts +2 -1
- package/lib/tls.d.ts.map +1 -0
- package/lib/tls.js +35 -51
- package/nitrogen/generated/android/RustCNet+autolinking.cmake +1 -1
- package/nitrogen/generated/android/RustCNet+autolinking.gradle +1 -1
- package/nitrogen/generated/android/RustCNetOnLoad.cpp +1 -1
- package/nitrogen/generated/android/RustCNetOnLoad.hpp +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_double_std__shared_ptr_ArrayBuffer_.hpp +1 -1
- package/nitrogen/generated/android/c++/JHybridHttpParserSpec.cpp +1 -1
- package/nitrogen/generated/android/c++/JHybridHttpParserSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JHybridNetDriverSpec.cpp +1 -1
- package/nitrogen/generated/android/c++/JHybridNetDriverSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.cpp +1 -1
- package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.cpp +1 -1
- package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JNetConfig.hpp +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/Func_void_double_std__shared_ptr_ArrayBuffer_.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridHttpParserSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetDriverSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetServerDriverSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetSocketDriverSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/NetConfig.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/RustCNetOnLoad.kt +1 -1
- package/nitrogen/generated/ios/RustCNet+autolinking.rb +1 -1
- package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.cpp +1 -1
- package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.hpp +1 -1
- package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Umbrella.hpp +1 -1
- package/nitrogen/generated/ios/RustCNetAutolinking.mm +1 -1
- package/nitrogen/generated/ios/RustCNetAutolinking.swift +1 -1
- package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/swift/Func_void_double_std__shared_ptr_ArrayBuffer_.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridHttpParserSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridHttpParserSpec_cxx.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNetDriverSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNetDriverSpec_cxx.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec_cxx.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec_cxx.swift +1 -1
- package/nitrogen/generated/ios/swift/NetConfig.swift +1 -1
- package/nitrogen/generated/shared/c++/HybridHttpParserSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridHttpParserSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNetDriverSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNetDriverSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNitroBufferSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridNitroBufferSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/NetConfig.hpp +1 -1
- package/package.json +7 -5
- package/src/http.ts +18 -12
- package/src/https.ts +0 -2
- package/src/index.ts +13 -1005
- package/src/net.ts +1005 -0
- package/src/tls.ts +1 -1
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
|
+
};
|