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