@push.rocks/smartnetwork 4.4.0 → 4.5.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/{npmextra.json → .smartconfig.json} +17 -5
- package/dist_rust/rustnetwork_linux_amd64 +0 -0
- package/dist_rust/rustnetwork_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/helpers/stats.js +1 -1
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/smartnetwork.classes.cloudflarespeed.js +32 -11
- package/dist_ts/smartnetwork.classes.ipintelligence.d.ts +101 -0
- package/dist_ts/smartnetwork.classes.ipintelligence.js +442 -0
- package/dist_ts/smartnetwork.classes.publicip.js +1 -1
- package/dist_ts/smartnetwork.classes.rustbridge.d.ts +100 -0
- package/dist_ts/smartnetwork.classes.rustbridge.js +98 -0
- package/dist_ts/smartnetwork.classes.smartnetwork.d.ts +38 -32
- package/dist_ts/smartnetwork.classes.smartnetwork.js +129 -159
- package/dist_ts/smartnetwork.plugins.d.ts +7 -10
- package/dist_ts/smartnetwork.plugins.js +9 -13
- package/package.json +19 -18
- package/readme.hints.md +60 -34
- package/readme.md +8 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers/stats.ts +1 -1
- package/ts/index.ts +3 -0
- package/ts/smartnetwork.classes.cloudflarespeed.ts +32 -16
- package/ts/smartnetwork.classes.ipintelligence.ts +542 -0
- package/ts/smartnetwork.classes.publicip.ts +2 -2
- package/ts/smartnetwork.classes.rustbridge.ts +181 -0
- package/ts/smartnetwork.classes.smartnetwork.ts +152 -175
- package/ts/smartnetwork.plugins.ts +8 -12
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import * as plugins from './smartnetwork.plugins.js';
|
|
2
2
|
import { CloudflareSpeed } from './smartnetwork.classes.cloudflarespeed.js';
|
|
3
3
|
import { PublicIp } from './smartnetwork.classes.publicip.js';
|
|
4
|
+
import { IpIntelligence, type IIpIntelligenceResult } from './smartnetwork.classes.ipintelligence.js';
|
|
4
5
|
import { getLogger } from './logging.js';
|
|
5
6
|
import { NetworkError } from './errors.js';
|
|
6
|
-
import
|
|
7
|
+
import { RustNetworkBridge } from './smartnetwork.classes.rustbridge.js';
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
* SmartNetwork simplifies actions within the network
|
|
10
|
-
*/
|
|
11
9
|
/**
|
|
12
10
|
* Configuration options for SmartNetwork
|
|
13
11
|
*/
|
|
@@ -35,6 +33,10 @@ export interface IFindFreePortOptions {
|
|
|
35
33
|
exclude?: number[];
|
|
36
34
|
}
|
|
37
35
|
|
|
36
|
+
/**
|
|
37
|
+
* SmartNetwork simplifies actions within the network.
|
|
38
|
+
* Uses a Rust binary for system-dependent operations (ping, traceroute, port scanning, gateway detection).
|
|
39
|
+
*/
|
|
38
40
|
export class SmartNetwork {
|
|
39
41
|
/** Static registry for external plugins */
|
|
40
42
|
public static pluginsRegistry: Map<string, any> = new Map();
|
|
@@ -46,15 +48,51 @@ export class SmartNetwork {
|
|
|
46
48
|
public static unregisterPlugin(name: string): void {
|
|
47
49
|
SmartNetwork.pluginsRegistry.delete(name);
|
|
48
50
|
}
|
|
51
|
+
|
|
49
52
|
private options: SmartNetworkOptions;
|
|
50
53
|
private cache: Map<string, { value: any; expiry: number }>;
|
|
54
|
+
private rustBridge: RustNetworkBridge;
|
|
55
|
+
private bridgeStarted = false;
|
|
56
|
+
private ipIntelligence: IpIntelligence | null = null;
|
|
57
|
+
|
|
51
58
|
constructor(options?: SmartNetworkOptions) {
|
|
52
59
|
this.options = options || {};
|
|
53
60
|
this.cache = new Map();
|
|
61
|
+
this.rustBridge = RustNetworkBridge.getInstance();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Start the Rust binary bridge. Must be called before using ping, traceroute,
|
|
66
|
+
* port scanning, or gateway operations. Safe to call multiple times.
|
|
67
|
+
*/
|
|
68
|
+
public async start(): Promise<void> {
|
|
69
|
+
if (!this.bridgeStarted) {
|
|
70
|
+
await this.rustBridge.start();
|
|
71
|
+
this.bridgeStarted = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Stop the Rust binary bridge.
|
|
77
|
+
*/
|
|
78
|
+
public async stop(): Promise<void> {
|
|
79
|
+
if (this.bridgeStarted) {
|
|
80
|
+
await this.rustBridge.stop();
|
|
81
|
+
this.bridgeStarted = false;
|
|
82
|
+
}
|
|
54
83
|
}
|
|
84
|
+
|
|
55
85
|
/**
|
|
56
|
-
*
|
|
57
|
-
|
|
86
|
+
* Ensure the Rust bridge is running before sending commands.
|
|
87
|
+
*/
|
|
88
|
+
private async ensureBridge(): Promise<void> {
|
|
89
|
+
if (!this.bridgeStarted) {
|
|
90
|
+
await this.start();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get network speed via Cloudflare speed test (pure TS, no Rust needed).
|
|
58
96
|
*/
|
|
59
97
|
public async getSpeed(opts?: { parallelStreams?: number; duration?: number }) {
|
|
60
98
|
const cloudflareSpeedInstance = new CloudflareSpeed(opts);
|
|
@@ -65,35 +103,45 @@ export class SmartNetwork {
|
|
|
65
103
|
* Send ICMP pings to a host. Optionally specify count for multiple pings.
|
|
66
104
|
*/
|
|
67
105
|
public async ping(host: string, opts?: { timeout?: number; count?: number }): Promise<any> {
|
|
68
|
-
|
|
106
|
+
await this.ensureBridge();
|
|
107
|
+
const timeoutMs = opts?.timeout ?? 5000;
|
|
69
108
|
const count = opts?.count && opts.count > 1 ? opts.count : 1;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
109
|
+
|
|
110
|
+
let result: Awaited<ReturnType<typeof this.rustBridge.ping>>;
|
|
111
|
+
try {
|
|
112
|
+
result = await this.rustBridge.ping(host, count, timeoutMs);
|
|
113
|
+
} catch {
|
|
114
|
+
// DNS resolution failure or other error — return dead ping
|
|
115
|
+
if (count === 1) {
|
|
116
|
+
return { alive: false, time: NaN };
|
|
117
|
+
}
|
|
74
118
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
host,
|
|
120
|
+
count,
|
|
121
|
+
times: Array(count).fill(NaN),
|
|
122
|
+
min: NaN,
|
|
123
|
+
max: NaN,
|
|
124
|
+
avg: NaN,
|
|
125
|
+
stddev: NaN,
|
|
126
|
+
packetLoss: 100,
|
|
127
|
+
alive: false,
|
|
77
128
|
};
|
|
78
129
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
|
|
131
|
+
// Map times: replace null with NaN for backward compatibility
|
|
132
|
+
const times = result.times.map((t) => (t === null ? NaN : t));
|
|
133
|
+
const min = result.min === null ? NaN : result.min;
|
|
134
|
+
const max = result.max === null ? NaN : result.max;
|
|
135
|
+
const avg = result.avg === null ? NaN : result.avg;
|
|
136
|
+
const stddev = result.stddev === null ? NaN : result.stddev;
|
|
137
|
+
|
|
138
|
+
if (count === 1) {
|
|
139
|
+
return {
|
|
140
|
+
alive: result.alive,
|
|
141
|
+
time: times[0] ?? NaN,
|
|
142
|
+
};
|
|
90
143
|
}
|
|
91
|
-
|
|
92
|
-
const min = valid.length ? Math.min(...valid) : NaN;
|
|
93
|
-
const max = valid.length ? Math.max(...valid) : NaN;
|
|
94
|
-
const avg = valid.length ? stats.average(valid) : NaN;
|
|
95
|
-
const stddev = valid.length ? Math.sqrt(stats.average(valid.map((v) => (v - avg) ** 2))) : NaN;
|
|
96
|
-
const packetLoss = ((count - aliveCount) / count) * 100;
|
|
144
|
+
|
|
97
145
|
return {
|
|
98
146
|
host,
|
|
99
147
|
count,
|
|
@@ -102,65 +150,22 @@ export class SmartNetwork {
|
|
|
102
150
|
max,
|
|
103
151
|
avg,
|
|
104
152
|
stddev,
|
|
105
|
-
packetLoss,
|
|
106
|
-
alive:
|
|
153
|
+
packetLoss: result.packetLoss,
|
|
154
|
+
alive: result.alive,
|
|
107
155
|
};
|
|
108
156
|
}
|
|
109
157
|
|
|
110
|
-
/**
|
|
111
|
-
* returns a promise with a boolean answer
|
|
112
|
-
* note: false also resolves with false as argument
|
|
113
|
-
* @param port
|
|
114
|
-
*/
|
|
115
158
|
/**
|
|
116
159
|
* Check if a local port is unused (both IPv4 and IPv6)
|
|
117
160
|
*/
|
|
118
161
|
public async isLocalPortUnused(port: number): Promise<boolean> {
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// test IPv4 space
|
|
124
|
-
const ipv4Test = net.createServer();
|
|
125
|
-
ipv4Test.once('error', () => {
|
|
126
|
-
doneIpV4.resolve(false);
|
|
127
|
-
});
|
|
128
|
-
ipv4Test.once('listening', () => {
|
|
129
|
-
ipv4Test.once('close', () => {
|
|
130
|
-
doneIpV4.resolve(true);
|
|
131
|
-
});
|
|
132
|
-
ipv4Test.close();
|
|
133
|
-
});
|
|
134
|
-
ipv4Test.listen(port, '0.0.0.0');
|
|
135
|
-
|
|
136
|
-
await doneIpV4.promise;
|
|
137
|
-
|
|
138
|
-
// test IPv6 space
|
|
139
|
-
const ipv6Test = net.createServer();
|
|
140
|
-
ipv6Test.once('error', () => {
|
|
141
|
-
doneIpV6.resolve(false);
|
|
142
|
-
});
|
|
143
|
-
ipv6Test.once('listening', () => {
|
|
144
|
-
ipv6Test.once('close', () => {
|
|
145
|
-
doneIpV6.resolve(true);
|
|
146
|
-
});
|
|
147
|
-
ipv6Test.close();
|
|
148
|
-
});
|
|
149
|
-
ipv6Test.listen(port, '::');
|
|
150
|
-
|
|
151
|
-
// lets wait for the result
|
|
152
|
-
const resultIpV4 = await doneIpV4.promise;
|
|
153
|
-
const resultIpV6 = await doneIpV6.promise;
|
|
154
|
-
const result = resultIpV4 === true && resultIpV6 === true;
|
|
155
|
-
return result;
|
|
162
|
+
await this.ensureBridge();
|
|
163
|
+
const result = await this.rustBridge.isLocalPortFree(port);
|
|
164
|
+
return result.free;
|
|
156
165
|
}
|
|
157
166
|
|
|
158
167
|
/**
|
|
159
168
|
* Find the first available port within a given range
|
|
160
|
-
* @param startPort The start of the port range (inclusive)
|
|
161
|
-
* @param endPort The end of the port range (inclusive)
|
|
162
|
-
* @param options Optional configuration for port selection behavior
|
|
163
|
-
* @returns The first available port number (or random if options.randomize is true), or null if no ports are available
|
|
164
169
|
*/
|
|
165
170
|
public async findFreePort(startPort: number, endPort: number, options?: IFindFreePortOptions): Promise<number | null> {
|
|
166
171
|
// Validate port range
|
|
@@ -171,66 +176,36 @@ export class SmartNetwork {
|
|
|
171
176
|
throw new NetworkError('Start port must be less than or equal to end port', 'EINVAL');
|
|
172
177
|
}
|
|
173
178
|
|
|
174
|
-
// Create a set of excluded ports for efficient lookup
|
|
175
179
|
const excludedPorts = new Set(options?.exclude || []);
|
|
176
180
|
|
|
177
|
-
// If randomize option is true, collect all available ports and select randomly
|
|
178
181
|
if (options?.randomize) {
|
|
179
182
|
const availablePorts: number[] = [];
|
|
180
|
-
|
|
181
|
-
// Scan the range to find all available ports
|
|
182
183
|
for (let port = startPort; port <= endPort; port++) {
|
|
183
|
-
|
|
184
|
-
if (excludedPorts.has(port)) {
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
|
|
184
|
+
if (excludedPorts.has(port)) continue;
|
|
188
185
|
const isUnused = await this.isLocalPortUnused(port);
|
|
189
186
|
if (isUnused) {
|
|
190
187
|
availablePorts.push(port);
|
|
191
188
|
}
|
|
192
189
|
}
|
|
193
|
-
|
|
194
|
-
// If there are available ports, select one randomly
|
|
195
190
|
if (availablePorts.length > 0) {
|
|
196
191
|
const randomIndex = Math.floor(Math.random() * availablePorts.length);
|
|
197
192
|
return availablePorts[randomIndex];
|
|
198
193
|
}
|
|
199
|
-
|
|
200
|
-
// No free port found in the range
|
|
201
194
|
return null;
|
|
202
195
|
} else {
|
|
203
|
-
// Default behavior: return the first available port (sequential search)
|
|
204
196
|
for (let port = startPort; port <= endPort; port++) {
|
|
205
|
-
|
|
206
|
-
if (excludedPorts.has(port)) {
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
|
|
197
|
+
if (excludedPorts.has(port)) continue;
|
|
210
198
|
const isUnused = await this.isLocalPortUnused(port);
|
|
211
199
|
if (isUnused) {
|
|
212
200
|
return port;
|
|
213
201
|
}
|
|
214
202
|
}
|
|
215
|
-
|
|
216
|
-
// No free port found in the range
|
|
217
203
|
return null;
|
|
218
204
|
}
|
|
219
205
|
}
|
|
220
206
|
|
|
221
|
-
/**
|
|
222
|
-
* checks wether a remote port is available
|
|
223
|
-
* @param domainArg
|
|
224
|
-
*/
|
|
225
207
|
/**
|
|
226
208
|
* Check if a remote port is available
|
|
227
|
-
* @param target host or "host:port"
|
|
228
|
-
* @param opts options including port, protocol (only tcp), retries and timeout
|
|
229
|
-
*/
|
|
230
|
-
/**
|
|
231
|
-
* Check if a remote port is available
|
|
232
|
-
* @param target host or "host:port"
|
|
233
|
-
* @param portOrOpts either a port number (deprecated) or options object
|
|
234
209
|
*/
|
|
235
210
|
public async isRemotePortAvailable(
|
|
236
211
|
target: string,
|
|
@@ -243,7 +218,7 @@ export class SmartNetwork {
|
|
|
243
218
|
let protocol: string = 'tcp';
|
|
244
219
|
let retries = 1;
|
|
245
220
|
let timeout: number | undefined;
|
|
246
|
-
|
|
221
|
+
|
|
247
222
|
if (typeof portOrOpts === 'number') {
|
|
248
223
|
[hostPart] = target.split(':');
|
|
249
224
|
port = portOrOpts;
|
|
@@ -256,47 +231,64 @@ export class SmartNetwork {
|
|
|
256
231
|
const portPart = target.split(':')[1];
|
|
257
232
|
port = opts.port ?? (portPart ? parseInt(portPart, 10) : undefined);
|
|
258
233
|
}
|
|
234
|
+
|
|
259
235
|
if (protocol === 'udp') {
|
|
260
236
|
throw new NetworkError('UDP port check not supported', 'ENOTSUP');
|
|
261
237
|
}
|
|
262
238
|
if (!port) {
|
|
263
239
|
throw new NetworkError('Port not specified', 'EINVAL');
|
|
264
240
|
}
|
|
265
|
-
|
|
241
|
+
|
|
242
|
+
await this.ensureBridge();
|
|
243
|
+
let last = false;
|
|
266
244
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
245
|
+
try {
|
|
246
|
+
const result = await this.rustBridge.tcpPortCheck(hostPart, port, timeout);
|
|
247
|
+
last = result.isOpen;
|
|
248
|
+
if (last) return true;
|
|
249
|
+
} catch {
|
|
250
|
+
// DNS resolution failure or connection error — treat as not available
|
|
251
|
+
last = false;
|
|
252
|
+
}
|
|
274
253
|
}
|
|
275
254
|
return last;
|
|
276
255
|
}
|
|
277
256
|
|
|
278
257
|
/**
|
|
279
|
-
* List network interfaces (gateways)
|
|
258
|
+
* List network interfaces (gateways) — pure TS, no Rust needed.
|
|
280
259
|
*/
|
|
281
260
|
public async getGateways(): Promise<Record<string, plugins.os.NetworkInterfaceInfo[]>> {
|
|
282
|
-
const fetcher = async () => plugins.os.networkInterfaces()
|
|
261
|
+
const fetcher = async () => plugins.os.networkInterfaces() as Record<string, plugins.os.NetworkInterfaceInfo[]>;
|
|
283
262
|
if (this.options.cacheTtl && this.options.cacheTtl > 0) {
|
|
284
263
|
return this.getCached('gateways', fetcher);
|
|
285
264
|
}
|
|
286
265
|
return fetcher();
|
|
287
266
|
}
|
|
288
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Get the default gateway interface and its addresses.
|
|
270
|
+
*/
|
|
289
271
|
public async getDefaultGateway(): Promise<{
|
|
290
272
|
ipv4: plugins.os.NetworkInterfaceInfo;
|
|
291
273
|
ipv6: plugins.os.NetworkInterfaceInfo;
|
|
292
|
-
}> {
|
|
293
|
-
|
|
294
|
-
|
|
274
|
+
} | null> {
|
|
275
|
+
await this.ensureBridge();
|
|
276
|
+
const result = await this.rustBridge.defaultGateway();
|
|
277
|
+
const interfaceName = result.interfaceName;
|
|
278
|
+
|
|
279
|
+
if (!interfaceName) {
|
|
295
280
|
getLogger().warn?.('Cannot determine default gateway');
|
|
296
281
|
return null;
|
|
297
282
|
}
|
|
283
|
+
|
|
284
|
+
// Use os.networkInterfaces() to get rich interface info
|
|
298
285
|
const gateways = await this.getGateways();
|
|
299
|
-
const defaultGateway = gateways[
|
|
286
|
+
const defaultGateway = gateways[interfaceName];
|
|
287
|
+
if (!defaultGateway) {
|
|
288
|
+
getLogger().warn?.(`Interface ${interfaceName} not found in os.networkInterfaces()`);
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
300
292
|
return {
|
|
301
293
|
ipv4: defaultGateway[0],
|
|
302
294
|
ipv6: defaultGateway[1],
|
|
@@ -304,7 +296,7 @@ export class SmartNetwork {
|
|
|
304
296
|
}
|
|
305
297
|
|
|
306
298
|
/**
|
|
307
|
-
* Lookup public IPv4 and IPv6
|
|
299
|
+
* Lookup public IPv4 and IPv6 — pure TS, no Rust needed.
|
|
308
300
|
*/
|
|
309
301
|
public async getPublicIps(): Promise<{ v4: string | null; v6: string | null }> {
|
|
310
302
|
const fetcher = async () => {
|
|
@@ -318,28 +310,25 @@ export class SmartNetwork {
|
|
|
318
310
|
}
|
|
319
311
|
|
|
320
312
|
/**
|
|
321
|
-
* Resolve DNS records (A, AAAA, MX)
|
|
313
|
+
* Resolve DNS records (A, AAAA, MX) — uses smartdns, no Rust needed.
|
|
322
314
|
*/
|
|
323
315
|
public async resolveDns(
|
|
324
316
|
host: string,
|
|
325
317
|
): Promise<{ A: string[]; AAAA: string[]; MX: { exchange: string; priority: number }[] }> {
|
|
326
318
|
try {
|
|
327
319
|
const dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
|
328
|
-
strategy: 'prefer-system',
|
|
320
|
+
strategy: 'prefer-system',
|
|
329
321
|
allowDohFallback: true,
|
|
330
322
|
});
|
|
331
|
-
|
|
323
|
+
|
|
332
324
|
const [aRecords, aaaaRecords, mxRecords] = await Promise.all([
|
|
333
325
|
dnsClient.getRecordsA(host).catch((): any[] => []),
|
|
334
326
|
dnsClient.getRecordsAAAA(host).catch((): any[] => []),
|
|
335
327
|
dnsClient.getRecords(host, 'MX').catch((): any[] => []),
|
|
336
328
|
]);
|
|
337
|
-
|
|
338
|
-
// Extract values from the record objects
|
|
329
|
+
|
|
339
330
|
const A = aRecords.map((record: any) => record.value);
|
|
340
331
|
const AAAA = aaaaRecords.map((record: any) => record.value);
|
|
341
|
-
|
|
342
|
-
// Parse MX records - the value contains "priority exchange"
|
|
343
332
|
const MX = mxRecords.map((record: any) => {
|
|
344
333
|
const parts = record.value.split(' ');
|
|
345
334
|
return {
|
|
@@ -347,7 +336,7 @@ export class SmartNetwork {
|
|
|
347
336
|
exchange: parts[1] || '',
|
|
348
337
|
};
|
|
349
338
|
});
|
|
350
|
-
|
|
339
|
+
|
|
351
340
|
return { A, AAAA, MX };
|
|
352
341
|
} catch (err: any) {
|
|
353
342
|
throw new NetworkError(err.message, err.code);
|
|
@@ -355,20 +344,20 @@ export class SmartNetwork {
|
|
|
355
344
|
}
|
|
356
345
|
|
|
357
346
|
/**
|
|
358
|
-
* Perform a simple HTTP/HTTPS endpoint health check
|
|
347
|
+
* Perform a simple HTTP/HTTPS endpoint health check — pure TS.
|
|
359
348
|
*/
|
|
360
349
|
public async checkEndpoint(
|
|
361
350
|
urlString: string,
|
|
362
|
-
opts?: { timeout?: number },
|
|
351
|
+
opts?: { timeout?: number; rejectUnauthorized?: boolean },
|
|
363
352
|
): Promise<{ status: number; headers: Record<string, string>; rtt: number }> {
|
|
364
353
|
const start = plugins.perfHooks.performance.now();
|
|
365
354
|
try {
|
|
366
355
|
const url = new URL(urlString);
|
|
367
|
-
const lib = url.protocol === 'https:' ? plugins.https : await import('http');
|
|
356
|
+
const lib = url.protocol === 'https:' ? plugins.https : await import('node:http');
|
|
368
357
|
return new Promise((resolve, reject) => {
|
|
369
358
|
const req = lib.request(
|
|
370
359
|
url,
|
|
371
|
-
{ method: 'GET', timeout: opts?.timeout, agent: false },
|
|
360
|
+
{ method: 'GET', timeout: opts?.timeout, agent: false, rejectUnauthorized: opts?.rejectUnauthorized ?? true },
|
|
372
361
|
(res: any) => {
|
|
373
362
|
res.on('data', () => {});
|
|
374
363
|
res.once('end', () => {
|
|
@@ -390,50 +379,38 @@ export class SmartNetwork {
|
|
|
390
379
|
}
|
|
391
380
|
|
|
392
381
|
/**
|
|
393
|
-
* Perform a traceroute: hop-by-hop latency using the
|
|
394
|
-
* Falls back to a single-hop stub if traceroute is unavailable or errors.
|
|
382
|
+
* Perform a traceroute: hop-by-hop latency using the Rust binary.
|
|
395
383
|
*/
|
|
396
384
|
public async traceroute(
|
|
397
385
|
host: string,
|
|
398
386
|
opts?: { maxHops?: number; timeout?: number },
|
|
399
387
|
): Promise<Hop[]> {
|
|
388
|
+
await this.ensureBridge();
|
|
400
389
|
const maxHops = opts?.maxHops ?? 30;
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
ip = parts[1];
|
|
424
|
-
const timePart = parts.find((p, i) => i >= 2 && /^\d+(\.\d+)?$/.test(p));
|
|
425
|
-
rtt = timePart ? parseFloat(timePart) : null;
|
|
426
|
-
}
|
|
427
|
-
hops.push({ ttl, ip, rtt });
|
|
428
|
-
}
|
|
429
|
-
if (hops.length) {
|
|
430
|
-
return hops;
|
|
431
|
-
}
|
|
432
|
-
} catch {
|
|
433
|
-
// traceroute not available or error: fall through to stub
|
|
390
|
+
const timeoutMs = opts?.timeout ?? 5000;
|
|
391
|
+
|
|
392
|
+
const result = await this.rustBridge.traceroute(host, maxHops, timeoutMs);
|
|
393
|
+
return result.hops.map((h) => ({
|
|
394
|
+
ttl: h.ttl,
|
|
395
|
+
ip: h.ip || '*',
|
|
396
|
+
rtt: h.rtt,
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get IP intelligence: ASN, organization, geolocation, and RDAP registration data.
|
|
402
|
+
* Combines RDAP (RIRs), Team Cymru DNS, and MaxMind GeoLite2 — all run in parallel.
|
|
403
|
+
* Pure TS, no Rust needed.
|
|
404
|
+
*/
|
|
405
|
+
public async getIpIntelligence(ip: string): Promise<IIpIntelligenceResult> {
|
|
406
|
+
if (!this.ipIntelligence) {
|
|
407
|
+
this.ipIntelligence = new IpIntelligence();
|
|
408
|
+
}
|
|
409
|
+
const fetcher = () => this.ipIntelligence!.getIntelligence(ip);
|
|
410
|
+
if (this.options.cacheTtl && this.options.cacheTtl > 0) {
|
|
411
|
+
return this.getCached(`ipIntelligence:${ip}`, fetcher);
|
|
434
412
|
}
|
|
435
|
-
|
|
436
|
-
return [{ ttl: 1, ip: host, rtt: null }];
|
|
413
|
+
return fetcher();
|
|
437
414
|
}
|
|
438
415
|
|
|
439
416
|
/**
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
// native scope
|
|
2
|
-
import * as os from 'os';
|
|
3
|
-
import * as https from 'https';
|
|
4
|
-
import * as perfHooks from 'perf_hooks';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as https from 'node:https';
|
|
4
|
+
import * as perfHooks from 'node:perf_hooks';
|
|
5
5
|
|
|
6
6
|
export { os, https, perfHooks };
|
|
7
7
|
|
|
8
8
|
// @pushrocks scope
|
|
9
9
|
import * as smartdns from '@push.rocks/smartdns';
|
|
10
|
-
import * as
|
|
11
|
-
import * as smartpromise from '@push.rocks/smartpromise';
|
|
12
|
-
import * as smartstring from '@push.rocks/smartstring';
|
|
10
|
+
import * as smartrust from '@push.rocks/smartrust';
|
|
13
11
|
|
|
14
|
-
export { smartdns,
|
|
12
|
+
export { smartdns, smartrust };
|
|
15
13
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
import isopen from 'isopen';
|
|
19
|
-
import * as systeminformation from 'systeminformation';
|
|
14
|
+
// third party
|
|
15
|
+
import * as maxmind from 'maxmind';
|
|
20
16
|
|
|
21
|
-
export {
|
|
17
|
+
export { maxmind };
|