react-native-nitro-net 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -12
- package/android/libs/arm64-v8a/librust_c_net.so +0 -0
- package/android/libs/armeabi-v7a/librust_c_net.so +0 -0
- package/android/libs/x86/librust_c_net.so +0 -0
- package/android/libs/x86_64/librust_c_net.so +0 -0
- package/cpp/HybridHttpParser.hpp +67 -0
- package/cpp/HybridNetDriver.hpp +6 -0
- package/cpp/HybridNetServerDriver.hpp +7 -0
- package/cpp/HybridNetSocketDriver.hpp +27 -0
- package/cpp/NetBindings.hpp +15 -0
- package/ios/Frameworks/RustCNet.xcframework/Info.plist +5 -5
- package/ios/Frameworks/RustCNet.xcframework/ios-arm64/RustCNet.framework/RustCNet +0 -0
- package/ios/Frameworks/RustCNet.xcframework/ios-arm64_x86_64-simulator/RustCNet.framework/RustCNet +0 -0
- package/lib/Net.nitro.d.ts +19 -0
- package/lib/http.d.ts +203 -0
- package/lib/http.js +1138 -0
- package/lib/https.d.ts +24 -0
- package/lib/https.js +144 -0
- package/lib/index.d.ts +46 -8
- package/lib/index.js +133 -26
- package/lib/tls.d.ts +21 -0
- package/lib/tls.js +74 -4
- package/nitrogen/generated/android/RustCNet+autolinking.cmake +2 -0
- package/nitrogen/generated/android/RustCNetOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JHybridHttpParserSpec.cpp +54 -0
- package/nitrogen/generated/android/c++/JHybridHttpParserSpec.hpp +65 -0
- package/nitrogen/generated/android/c++/JHybridNetDriverSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JHybridNetDriverSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.cpp +4 -0
- package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.hpp +2 -0
- package/nitrogen/generated/android/c++/JNetConfig.hpp +7 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridHttpParserSpec.kt +58 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetDriverSpec.kt +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetServerDriverSpec.kt +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetSocketDriverSpec.kt +8 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/NetConfig.kt +6 -3
- package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.hpp +26 -0
- package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Umbrella.hpp +5 -0
- package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.hpp +79 -0
- package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.hpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.hpp +6 -0
- package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.hpp +14 -0
- package/nitrogen/generated/ios/swift/HybridHttpParserSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridHttpParserSpec_cxx.swift +131 -0
- package/nitrogen/generated/ios/swift/HybridNetDriverSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridNetDriverSpec_cxx.swift +15 -0
- package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec_cxx.swift +11 -0
- package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec_cxx.swift +36 -0
- package/nitrogen/generated/ios/swift/NetConfig.swift +19 -1
- package/nitrogen/generated/shared/c++/HybridHttpParserSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridHttpParserSpec.hpp +63 -0
- package/nitrogen/generated/shared/c++/HybridNetDriverSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNetDriverSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.hpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.hpp +2 -0
- package/nitrogen/generated/shared/c++/NetConfig.hpp +6 -2
- package/package.json +3 -3
- package/src/Net.nitro.ts +17 -0
- package/src/http.ts +1304 -0
- package/src/https.ts +127 -0
- package/src/index.ts +149 -18
- package/src/tls.ts +82 -6
package/src/https.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as http from './http'
|
|
2
|
+
import * as tls from './tls'
|
|
3
|
+
import { Driver } from './Driver'
|
|
4
|
+
import { Buffer } from 'react-native-nitro-buffer'
|
|
5
|
+
import { IncomingMessage } from './http'
|
|
6
|
+
|
|
7
|
+
// ========== Server ==========
|
|
8
|
+
|
|
9
|
+
export class Server extends tls.Server {
|
|
10
|
+
private _httpConnections = new Set<any>();
|
|
11
|
+
public maxHeaderSize: number = 16384;
|
|
12
|
+
public maxRequestsPerSocket: number = 0;
|
|
13
|
+
public headersTimeout: number = 60000;
|
|
14
|
+
public requestTimeout: number = 300000;
|
|
15
|
+
public keepAliveTimeout: number = 5000;
|
|
16
|
+
|
|
17
|
+
constructor(options?: any, requestListener?: (req: http.IncomingMessage, res: http.ServerResponse) => void) {
|
|
18
|
+
if (typeof options === 'function') {
|
|
19
|
+
requestListener = options;
|
|
20
|
+
options = {};
|
|
21
|
+
}
|
|
22
|
+
super(options);
|
|
23
|
+
|
|
24
|
+
if (requestListener) {
|
|
25
|
+
this.on('request', requestListener);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Initialize HTTP connection setup for secure connections
|
|
29
|
+
this.on('secureConnection', (socket: any) => {
|
|
30
|
+
// @ts-ignore - access internal http logic
|
|
31
|
+
(http.Server.prototype as any)._setupHttpConnection.call(this, socket);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public setTimeout(ms: number, callback?: () => void): this {
|
|
36
|
+
// @ts-ignore - access netServer via super's internal or cast
|
|
37
|
+
(this as any)._netServer.setTimeout(ms, callback);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createServer(options?: any, requestListener?: (req: http.IncomingMessage, res: http.ServerResponse) => void): Server {
|
|
43
|
+
return new Server(options, requestListener);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ========== ClientRequest ==========
|
|
47
|
+
|
|
48
|
+
export class ClientRequest extends http.ClientRequest {
|
|
49
|
+
constructor(options: any, callback?: (res: http.IncomingMessage) => void) {
|
|
50
|
+
if (typeof options === 'string') {
|
|
51
|
+
options = new URL(options);
|
|
52
|
+
}
|
|
53
|
+
if (options instanceof URL) {
|
|
54
|
+
options = {
|
|
55
|
+
protocol: options.protocol,
|
|
56
|
+
hostname: options.hostname,
|
|
57
|
+
path: options.pathname + options.search,
|
|
58
|
+
port: options.port ? parseInt(options.port) : 443
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
options.protocol = 'https:';
|
|
62
|
+
super(options, callback);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function request(
|
|
67
|
+
urlOrOptions: string | URL | http.RequestOptions,
|
|
68
|
+
optionsOrCallback?: http.RequestOptions | ((res: http.IncomingMessage) => void),
|
|
69
|
+
callback?: (res: http.IncomingMessage) => void
|
|
70
|
+
): ClientRequest {
|
|
71
|
+
let opts: http.RequestOptions = {};
|
|
72
|
+
let cb: ((res: http.IncomingMessage) => void) | undefined = callback;
|
|
73
|
+
|
|
74
|
+
if (typeof urlOrOptions === 'string') {
|
|
75
|
+
const url = new URL(urlOrOptions);
|
|
76
|
+
opts = {
|
|
77
|
+
protocol: url.protocol,
|
|
78
|
+
hostname: url.hostname,
|
|
79
|
+
path: url.pathname + url.search,
|
|
80
|
+
port: url.port ? parseInt(url.port) : 443
|
|
81
|
+
};
|
|
82
|
+
} else if (urlOrOptions instanceof URL) {
|
|
83
|
+
opts = {
|
|
84
|
+
protocol: urlOrOptions.protocol,
|
|
85
|
+
hostname: urlOrOptions.hostname,
|
|
86
|
+
path: urlOrOptions.pathname + urlOrOptions.search,
|
|
87
|
+
port: urlOrOptions.port ? parseInt(urlOrOptions.port) : 443
|
|
88
|
+
};
|
|
89
|
+
} else {
|
|
90
|
+
opts = { ...urlOrOptions };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof optionsOrCallback === 'function') {
|
|
94
|
+
cb = optionsOrCallback;
|
|
95
|
+
} else if (optionsOrCallback) {
|
|
96
|
+
opts = { ...opts, ...optionsOrCallback };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
opts.protocol = 'https:';
|
|
100
|
+
return new ClientRequest(opts, cb);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function get(
|
|
104
|
+
urlOrOptions: string | URL | http.RequestOptions,
|
|
105
|
+
optionsOrCallback?: http.RequestOptions | ((res: http.IncomingMessage) => void),
|
|
106
|
+
callback?: (res: http.IncomingMessage) => void
|
|
107
|
+
): ClientRequest {
|
|
108
|
+
const req = request(urlOrOptions, optionsOrCallback, callback);
|
|
109
|
+
req.end();
|
|
110
|
+
return req;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ========== Agent ==========
|
|
114
|
+
|
|
115
|
+
export class Agent extends http.Agent {
|
|
116
|
+
constructor(options?: any) {
|
|
117
|
+
super(options);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const globalAgent = new Agent({
|
|
122
|
+
keepAlive: true,
|
|
123
|
+
scheduling: 'lifo',
|
|
124
|
+
timeout: 5000,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export { IncomingMessage };
|
package/src/index.ts
CHANGED
|
@@ -33,16 +33,21 @@ let _autoSelectFamilyDefault = 4; // Node default is usually 4/6 independent, bu
|
|
|
33
33
|
let _isVerbose = false;
|
|
34
34
|
let _isInitialized = false;
|
|
35
35
|
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
console.log(`[NET DEBUG] ${message}`);
|
|
39
|
-
}
|
|
36
|
+
function isVerbose(): boolean {
|
|
37
|
+
return _isVerbose;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
function setVerbose(enabled: boolean): void {
|
|
43
41
|
_isVerbose = enabled;
|
|
44
42
|
}
|
|
45
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
|
+
|
|
46
51
|
function getDefaultAutoSelectFamily(): number {
|
|
47
52
|
return _autoSelectFamilyDefault;
|
|
48
53
|
}
|
|
@@ -79,6 +84,9 @@ function ensureInitialized(): void {
|
|
|
79
84
|
*/
|
|
80
85
|
function initWithConfig(config: NetConfig): void {
|
|
81
86
|
_isInitialized = true;
|
|
87
|
+
if (config.debug !== undefined) {
|
|
88
|
+
setVerbose(config.debug);
|
|
89
|
+
}
|
|
82
90
|
Driver.initWithConfig(config);
|
|
83
91
|
}
|
|
84
92
|
|
|
@@ -89,17 +97,69 @@ function initWithConfig(config: NetConfig): void {
|
|
|
89
97
|
// SocketAddress
|
|
90
98
|
// -----------------------------------------------------------------------------
|
|
91
99
|
|
|
100
|
+
export interface SocketAddressOptions {
|
|
101
|
+
address?: string;
|
|
102
|
+
family?: 'ipv4' | 'ipv6';
|
|
103
|
+
port?: number;
|
|
104
|
+
flowlabel?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
export class SocketAddress {
|
|
93
108
|
readonly address: string;
|
|
94
109
|
readonly family: 'ipv4' | 'ipv6';
|
|
95
110
|
readonly port: number;
|
|
96
111
|
readonly flowlabel: number;
|
|
97
112
|
|
|
98
|
-
constructor(options:
|
|
99
|
-
this.address = options.address;
|
|
100
|
-
this.family = options.family || (isIPv6(
|
|
101
|
-
this.port = options.port;
|
|
102
|
-
this.flowlabel = options.flowlabel
|
|
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 });
|
|
103
163
|
}
|
|
104
164
|
}
|
|
105
165
|
|
|
@@ -107,9 +167,31 @@ export class SocketAddress {
|
|
|
107
167
|
// BlockList
|
|
108
168
|
// -----------------------------------------------------------------------------
|
|
109
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
|
+
|
|
110
179
|
export class BlockList {
|
|
111
180
|
private _rules: Array<{ type: 'address' | 'range' | 'subnet', data: any }> = [];
|
|
112
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
|
+
|
|
113
195
|
addAddress(address: string, family?: 'ipv4' | 'ipv6'): void {
|
|
114
196
|
this._rules.push({ type: 'address', data: { address, family: family || (isIPv6(address) ? 'ipv6' : 'ipv4') } });
|
|
115
197
|
}
|
|
@@ -143,6 +225,37 @@ export class BlockList {
|
|
|
143
225
|
}
|
|
144
226
|
return false;
|
|
145
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
|
+
}
|
|
146
259
|
}
|
|
147
260
|
|
|
148
261
|
function ipv4ToLong(ip: string): number {
|
|
@@ -178,6 +291,7 @@ export class Socket extends Duplex {
|
|
|
178
291
|
public bytesWritten: number = 0;
|
|
179
292
|
public autoSelectFamilyAttemptedAddresses: string[] = [];
|
|
180
293
|
private _autoSelectFamily: boolean = false;
|
|
294
|
+
private _timeout: number = 0;
|
|
181
295
|
|
|
182
296
|
get localFamily(): string {
|
|
183
297
|
return this.localAddress && this.localAddress.includes(':') ? 'IPv6' : 'IPv4';
|
|
@@ -203,7 +317,9 @@ export class Socket extends Duplex {
|
|
|
203
317
|
super({
|
|
204
318
|
allowHalfOpen: options?.allowHalfOpen ?? false,
|
|
205
319
|
readable: options?.readable ?? true,
|
|
206
|
-
writable: options?.writable ?? true
|
|
320
|
+
writable: options?.writable ?? true,
|
|
321
|
+
// @ts-ignore
|
|
322
|
+
autoDestroy: false
|
|
207
323
|
});
|
|
208
324
|
|
|
209
325
|
if (options?.socketDriver) {
|
|
@@ -211,6 +327,8 @@ export class Socket extends Duplex {
|
|
|
211
327
|
this._driver = options.socketDriver;
|
|
212
328
|
this._connected = true;
|
|
213
329
|
this._setupEvents();
|
|
330
|
+
// Enable noDelay by default
|
|
331
|
+
this._driver.setNoDelay(true);
|
|
214
332
|
// Resume the socket since it starts paused on server-accept
|
|
215
333
|
this.resume();
|
|
216
334
|
// Emit connect for server-side socket? No, it's already connected.
|
|
@@ -219,8 +337,10 @@ export class Socket extends Duplex {
|
|
|
219
337
|
ensureInitialized();
|
|
220
338
|
this._driver = Driver.createSocket();
|
|
221
339
|
this._setupEvents();
|
|
222
|
-
//
|
|
223
|
-
this.
|
|
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()
|
|
224
344
|
}
|
|
225
345
|
|
|
226
346
|
this.on('finish', () => {
|
|
@@ -261,6 +381,8 @@ export class Socket extends Duplex {
|
|
|
261
381
|
this.connecting = false;
|
|
262
382
|
this._connected = true;
|
|
263
383
|
this._updateAddresses();
|
|
384
|
+
// Now that we're connected, start receiving data
|
|
385
|
+
this.resume();
|
|
264
386
|
this.emit('connect');
|
|
265
387
|
this.emit('ready');
|
|
266
388
|
break;
|
|
@@ -409,6 +531,7 @@ export class Socket extends Duplex {
|
|
|
409
531
|
this._autoSelectFamily = true;
|
|
410
532
|
}
|
|
411
533
|
|
|
534
|
+
debugLog(`Socket.connect: target=${host}:${port}, autoSelectFamily=${this._autoSelectFamily}`);
|
|
412
535
|
return this._connect(port, host, connectionListener, options.signal);
|
|
413
536
|
}
|
|
414
537
|
|
|
@@ -431,6 +554,7 @@ export class Socket extends Duplex {
|
|
|
431
554
|
this.once('close', () => signal.removeEventListener('abort', abortHandler));
|
|
432
555
|
}
|
|
433
556
|
|
|
557
|
+
debugLog(`Socket._connect: Calling driver.connect(${host}, ${port})`);
|
|
434
558
|
this._driver?.connect(host, port);
|
|
435
559
|
return this;
|
|
436
560
|
}
|
|
@@ -511,6 +635,7 @@ export class Socket extends Duplex {
|
|
|
511
635
|
|
|
512
636
|
// Standard net.Socket methods
|
|
513
637
|
setTimeout(msecs: number, callback?: () => void): this {
|
|
638
|
+
this._timeout = msecs;
|
|
514
639
|
if (this._driver) {
|
|
515
640
|
this._driver.setTimeout(msecs);
|
|
516
641
|
}
|
|
@@ -534,12 +659,13 @@ export class Socket extends Duplex {
|
|
|
534
659
|
* Resume reading after a call to pause().
|
|
535
660
|
*/
|
|
536
661
|
resume(): this {
|
|
537
|
-
const
|
|
538
|
-
|
|
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}`);
|
|
539
665
|
super.resume();
|
|
540
|
-
if (
|
|
666
|
+
if (driver) {
|
|
541
667
|
debugLog(`Socket.resume() calling driver.resume(), id: ${id}`);
|
|
542
|
-
|
|
668
|
+
driver.resume();
|
|
543
669
|
}
|
|
544
670
|
return this;
|
|
545
671
|
}
|
|
@@ -569,8 +695,8 @@ export class Socket extends Duplex {
|
|
|
569
695
|
return this;
|
|
570
696
|
}
|
|
571
697
|
|
|
572
|
-
get timeout(): number
|
|
573
|
-
return
|
|
698
|
+
get timeout(): number {
|
|
699
|
+
return this._timeout;
|
|
574
700
|
}
|
|
575
701
|
|
|
576
702
|
get bufferSize(): number {
|
|
@@ -848,6 +974,8 @@ export function createServer(options?: any, connectionListener?: (socket: Socket
|
|
|
848
974
|
}
|
|
849
975
|
|
|
850
976
|
export * as tls from './tls'
|
|
977
|
+
export * as http from './http'
|
|
978
|
+
export * as https from './https'
|
|
851
979
|
|
|
852
980
|
export {
|
|
853
981
|
isIP,
|
|
@@ -855,6 +983,7 @@ export {
|
|
|
855
983
|
isIPv6,
|
|
856
984
|
getDefaultAutoSelectFamily,
|
|
857
985
|
setDefaultAutoSelectFamily,
|
|
986
|
+
isVerbose,
|
|
858
987
|
setVerbose,
|
|
859
988
|
initWithConfig,
|
|
860
989
|
};
|
|
@@ -876,4 +1005,6 @@ export default {
|
|
|
876
1005
|
setDefaultAutoSelectFamily,
|
|
877
1006
|
setVerbose,
|
|
878
1007
|
initWithConfig,
|
|
1008
|
+
http: require('./http'),
|
|
1009
|
+
https: require('./https'),
|
|
879
1010
|
};
|
package/src/tls.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { Socket, Server as NetServer, SocketOptions } from './index'
|
|
1
|
+
import { Socket, Server as NetServer, SocketOptions, isVerbose } from './index'
|
|
2
2
|
import { Driver } from './Driver'
|
|
3
3
|
import { NetSocketDriver } from './Net.nitro'
|
|
4
4
|
|
|
5
|
+
function debugLog(message: string) {
|
|
6
|
+
if (isVerbose()) {
|
|
7
|
+
const timestamp = new Date().toISOString().split('T')[1].split('Z')[0];
|
|
8
|
+
console.log(`[NET DEBUG ${timestamp}] ${message}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
export interface PeerCertificate {
|
|
6
13
|
subject: { [key: string]: string }
|
|
7
14
|
issuer: { [key: string]: string }
|
|
@@ -26,6 +33,13 @@ export interface ConnectionOptions extends SocketOptions {
|
|
|
26
33
|
pfx?: string | ArrayBuffer
|
|
27
34
|
passphrase?: string
|
|
28
35
|
keylog?: boolean // Enable keylogging (SSLKEYLOGFILE format)
|
|
36
|
+
/**
|
|
37
|
+
* Custom hostname verification function.
|
|
38
|
+
* If provided, it will be called after the TLS handshake to verify the peer certificate.
|
|
39
|
+
* Return `undefined` if valid, or an `Error` if invalid.
|
|
40
|
+
* If not provided, the default `checkServerIdentity` is used.
|
|
41
|
+
*/
|
|
42
|
+
checkServerIdentity?: (hostname: string, cert: PeerCertificate) => Error | undefined
|
|
29
43
|
}
|
|
30
44
|
|
|
31
45
|
export interface SecureContextOptions {
|
|
@@ -179,7 +193,11 @@ export class TLSSocket extends Socket {
|
|
|
179
193
|
|
|
180
194
|
renegotiate(options: any, callback: (err: Error | null) => void): boolean {
|
|
181
195
|
if (callback) {
|
|
182
|
-
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
const err = new Error('Renegotiation is not supported by rustls');
|
|
198
|
+
(err as any).code = 'ERR_TLS_RENEGOTIATION_DISABLED';
|
|
199
|
+
callback(err);
|
|
200
|
+
}, 0);
|
|
183
201
|
}
|
|
184
202
|
return false;
|
|
185
203
|
}
|
|
@@ -188,6 +206,39 @@ export class TLSSocket extends Socket {
|
|
|
188
206
|
// No-op, already effectively disabled
|
|
189
207
|
}
|
|
190
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Enables trace output for this socket.
|
|
211
|
+
*/
|
|
212
|
+
enableTrace(): void {
|
|
213
|
+
const driver = (this as any)._driver as NetSocketDriver
|
|
214
|
+
if (driver) {
|
|
215
|
+
driver.enableTrace()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Exports keying material for use by external protocols.
|
|
221
|
+
*
|
|
222
|
+
* @param length The number of bytes to return.
|
|
223
|
+
* @param label A label identifying the keying material.
|
|
224
|
+
* @param context An optional context.
|
|
225
|
+
* @returns Buffer containing keying material.
|
|
226
|
+
* @throws Error if export fails (e.g., TLS not connected).
|
|
227
|
+
*/
|
|
228
|
+
exportKeyingMaterial(length: number, label: string, context?: Buffer): Buffer {
|
|
229
|
+
const driver = (this as any)._driver as NetSocketDriver
|
|
230
|
+
if (driver) {
|
|
231
|
+
const ctx = context ? new Uint8Array(context).buffer as ArrayBuffer : undefined
|
|
232
|
+
const result = driver.exportKeyingMaterial(length, label, ctx)
|
|
233
|
+
if (result) {
|
|
234
|
+
return Buffer.from(result)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const err = new Error('exportKeyingMaterial failed: TLS connection may not be established')
|
|
238
|
+
; (err as any).code = 'ERR_TLS_EXPORT_KEYING_MATERIAL'
|
|
239
|
+
throw err
|
|
240
|
+
}
|
|
241
|
+
|
|
191
242
|
constructor(socket: Socket, options?: ConnectionOptions)
|
|
192
243
|
constructor(options: ConnectionOptions)
|
|
193
244
|
constructor(socketOrOptions: Socket | ConnectionOptions, options?: ConnectionOptions) {
|
|
@@ -230,6 +281,21 @@ export class TLSSocket extends Socket {
|
|
|
230
281
|
if (connectionListener) this.once('secureConnect', connectionListener);
|
|
231
282
|
|
|
232
283
|
this.once('connect', () => {
|
|
284
|
+
// After the native TLS handshake, perform hostname verification
|
|
285
|
+
if (rejectUnauthorized !== false) {
|
|
286
|
+
const cert = this.getPeerCertificate() as PeerCertificate;
|
|
287
|
+
if (cert && Object.keys(cert).length > 0) {
|
|
288
|
+
const verifyFn = (typeof options === 'object' && options.checkServerIdentity)
|
|
289
|
+
? options.checkServerIdentity
|
|
290
|
+
: checkServerIdentity;
|
|
291
|
+
const verifyErr = verifyFn(servername, cert);
|
|
292
|
+
if (verifyErr) {
|
|
293
|
+
this.emit('error', verifyErr);
|
|
294
|
+
this.destroy(verifyErr);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
233
299
|
this.emit('secureConnect')
|
|
234
300
|
})
|
|
235
301
|
|
|
@@ -255,14 +321,18 @@ export class TLSSocket extends Socket {
|
|
|
255
321
|
|
|
256
322
|
if (path) {
|
|
257
323
|
if (secureContextId !== undefined) {
|
|
324
|
+
debugLog(`TLSSocket.connect: Calling driver.connectUnixTLSWithContext(${path}, ${servername}, ctx=${secureContextId})`);
|
|
258
325
|
driver.connectUnixTLSWithContext(path, servername, rejectUnauthorized, secureContextId)
|
|
259
326
|
} else {
|
|
327
|
+
debugLog(`TLSSocket.connect: Calling driver.connectUnixTLS(${path}, ${servername})`);
|
|
260
328
|
driver.connectUnixTLS(path, servername, rejectUnauthorized)
|
|
261
329
|
}
|
|
262
330
|
} else {
|
|
263
331
|
if (secureContextId !== undefined) {
|
|
332
|
+
debugLog(`TLSSocket.connect: Calling driver.connectTLSWithContext(${host}, ${port}, ${servername}, ctx=${secureContextId})`);
|
|
264
333
|
driver.connectTLSWithContext(host, port, servername, rejectUnauthorized, secureContextId)
|
|
265
334
|
} else {
|
|
335
|
+
debugLog(`TLSSocket.connect: Calling driver.connectTLS(${host}, ${port}, ${servername})`);
|
|
266
336
|
driver.connectTLS(host, port, servername, rejectUnauthorized)
|
|
267
337
|
}
|
|
268
338
|
}
|
|
@@ -323,6 +393,9 @@ export class Server extends NetServer {
|
|
|
323
393
|
key: options.key,
|
|
324
394
|
ca: options.ca
|
|
325
395
|
}).id;
|
|
396
|
+
} else {
|
|
397
|
+
// Create empty secure context to allow late configuration (addContext)
|
|
398
|
+
this._secureContextId = createSecureContext().id;
|
|
326
399
|
}
|
|
327
400
|
|
|
328
401
|
this.on('connection', (socket: Socket) => {
|
|
@@ -408,12 +481,15 @@ export class Server extends NetServer {
|
|
|
408
481
|
|
|
409
482
|
const driver = (this as any)._driver;
|
|
410
483
|
|
|
411
|
-
if (
|
|
412
|
-
|
|
484
|
+
if (_path) {
|
|
485
|
+
driver.listenTLSUnix(_path, this._secureContextId, _backlog);
|
|
486
|
+
} else if (handle) {
|
|
487
|
+
console.warn("TLS over handles not fully implemented yet");
|
|
488
|
+
driver.listenTLS(_port || 0, this._secureContextId, _backlog, ipv6Only, reusePort);
|
|
489
|
+
} else {
|
|
490
|
+
driver.listenTLS(_port || 0, this._secureContextId, _backlog, ipv6Only, reusePort);
|
|
413
491
|
}
|
|
414
492
|
|
|
415
|
-
driver.listenTLS(_port || 0, this._secureContextId, _backlog, ipv6Only, reusePort);
|
|
416
|
-
|
|
417
493
|
return this;
|
|
418
494
|
}
|
|
419
495
|
}
|