@push.rocks/smartnetwork 4.1.2 → 4.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/smartnetwork.classes.publicip.d.ts +39 -0
- package/dist_ts/smartnetwork.classes.publicip.js +136 -0
- package/dist_ts/smartnetwork.classes.smartnetwork.d.ts +10 -2
- package/dist_ts/smartnetwork.classes.smartnetwork.js +55 -20
- package/dist_ts/smartnetwork.plugins.d.ts +3 -3
- package/dist_ts/smartnetwork.plugins.js +4 -5
- package/package.json +6 -7
- package/readme.md +180 -96
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -0
- package/ts/smartnetwork.classes.publicip.ts +163 -0
- package/ts/smartnetwork.classes.smartnetwork.ts +70 -20
- package/ts/smartnetwork.plugins.ts +3 -4
package/readme.md
CHANGED
|
@@ -1,49 +1,59 @@
|
|
|
1
|
-
# @push.rocks/smartnetwork
|
|
1
|
+
# @push.rocks/smartnetwork 🌐
|
|
2
2
|
|
|
3
3
|
Comprehensive network diagnostics and utilities for Node.js applications
|
|
4
4
|
|
|
5
|
-
## Install
|
|
5
|
+
## 🚀 Install
|
|
6
6
|
|
|
7
7
|
To install `@push.rocks/smartnetwork`, run the following command in your terminal:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
pnpm install @push.rocks/smartnetwork --save
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## 🎯 Overview
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**@push.rocks/smartnetwork** is your Swiss Army knife for network diagnostics in Node.js. Whether you're building network monitoring tools, implementing health checks, or just need to debug connectivity issues, this library has you covered with a clean, promise-based API.
|
|
16
|
+
|
|
17
|
+
### ✨ Key Features
|
|
18
|
+
|
|
19
|
+
- **🏎️ Speed Testing** - Measure download/upload speeds using Cloudflare's infrastructure
|
|
20
|
+
- **🔌 Port Management** - Check local/remote port availability, find free ports (sequential or random)
|
|
21
|
+
- **📡 Connectivity Testing** - Ping hosts, trace routes, check endpoints
|
|
22
|
+
- **🌍 DNS Operations** - Resolve A, AAAA, and MX records with smart local/remote resolution
|
|
23
|
+
- **🔍 Network Discovery** - Get network interfaces, gateways, public IPs
|
|
24
|
+
- **⚡ Performance Caching** - Built-in caching for expensive operations
|
|
25
|
+
- **🔧 Plugin Architecture** - Extend functionality with custom plugins
|
|
26
|
+
- **📝 Full TypeScript Support** - Complete type definitions included
|
|
27
|
+
|
|
28
|
+
## 💻 Usage
|
|
16
29
|
|
|
17
30
|
### Basic Setup
|
|
18
31
|
|
|
19
|
-
First, import
|
|
32
|
+
First, import and initialize SmartNetwork:
|
|
20
33
|
|
|
21
34
|
```typescript
|
|
22
35
|
import { SmartNetwork } from '@push.rocks/smartnetwork';
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Then, create an instance of `SmartNetwork`:
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
const
|
|
37
|
+
// Basic instance
|
|
38
|
+
const network = new SmartNetwork();
|
|
29
39
|
|
|
30
|
-
//
|
|
31
|
-
const
|
|
40
|
+
// With caching enabled (60 seconds TTL)
|
|
41
|
+
const cachedNetwork = new SmartNetwork({ cacheTtl: 60000 });
|
|
32
42
|
```
|
|
33
43
|
|
|
34
|
-
### Network Speed Testing
|
|
44
|
+
### 🏎️ Network Speed Testing
|
|
35
45
|
|
|
36
|
-
Measure network
|
|
46
|
+
Measure your network performance using Cloudflare's global infrastructure:
|
|
37
47
|
|
|
38
48
|
```typescript
|
|
39
49
|
const speedTest = async () => {
|
|
40
|
-
//
|
|
41
|
-
const result = await
|
|
50
|
+
// Quick speed test
|
|
51
|
+
const result = await network.getSpeed();
|
|
42
52
|
console.log(`Download: ${result.downloadSpeed} Mbps`);
|
|
43
53
|
console.log(`Upload: ${result.uploadSpeed} Mbps`);
|
|
44
54
|
|
|
45
|
-
// Advanced
|
|
46
|
-
const advancedResult = await
|
|
55
|
+
// Advanced configuration
|
|
56
|
+
const advancedResult = await network.getSpeed({
|
|
47
57
|
parallelStreams: 3, // Number of concurrent connections
|
|
48
58
|
duration: 5 // Test duration in seconds
|
|
49
59
|
});
|
|
@@ -52,19 +62,19 @@ const speedTest = async () => {
|
|
|
52
62
|
};
|
|
53
63
|
```
|
|
54
64
|
|
|
55
|
-
### Port Management
|
|
65
|
+
### 🔌 Port Management
|
|
56
66
|
|
|
57
67
|
#### Check Local Port Availability
|
|
58
68
|
|
|
59
|
-
Verify if a
|
|
69
|
+
Verify if a port is available on your local machine (checks both IPv4 and IPv6):
|
|
60
70
|
|
|
61
71
|
```typescript
|
|
62
72
|
const checkLocalPort = async (port: number) => {
|
|
63
|
-
const isUnused = await
|
|
73
|
+
const isUnused = await network.isLocalPortUnused(port);
|
|
64
74
|
if (isUnused) {
|
|
65
|
-
console.log(
|
|
75
|
+
console.log(`✅ Port ${port} is available`);
|
|
66
76
|
} else {
|
|
67
|
-
console.log(
|
|
77
|
+
console.log(`❌ Port ${port} is in use`);
|
|
68
78
|
}
|
|
69
79
|
};
|
|
70
80
|
|
|
@@ -73,136 +83,149 @@ await checkLocalPort(8080);
|
|
|
73
83
|
|
|
74
84
|
#### Find Free Port in Range
|
|
75
85
|
|
|
76
|
-
Automatically
|
|
86
|
+
Automatically discover available ports:
|
|
77
87
|
|
|
78
88
|
```typescript
|
|
79
89
|
const findFreePort = async () => {
|
|
80
|
-
// Find a free port between 3000 and 3100
|
|
81
|
-
const freePort = await
|
|
90
|
+
// Find a free port between 3000 and 3100 (sequential - returns first available)
|
|
91
|
+
const freePort = await network.findFreePort(3000, 3100);
|
|
82
92
|
|
|
83
93
|
if (freePort) {
|
|
84
|
-
console.log(
|
|
94
|
+
console.log(`🎉 Found free port: ${freePort}`);
|
|
85
95
|
} else {
|
|
86
|
-
console.log('No free ports available in
|
|
96
|
+
console.log('😢 No free ports available in range');
|
|
87
97
|
}
|
|
98
|
+
|
|
99
|
+
// Find a random free port in range (useful to avoid port conflicts)
|
|
100
|
+
const randomPort = await network.findFreePort(3000, 3100, { randomize: true });
|
|
101
|
+
console.log(`🎲 Random free port: ${randomPort}`);
|
|
88
102
|
};
|
|
89
103
|
```
|
|
90
104
|
|
|
91
105
|
#### Check Remote Port Availability
|
|
92
106
|
|
|
93
|
-
|
|
107
|
+
Test if services are accessible on remote servers:
|
|
94
108
|
|
|
95
109
|
```typescript
|
|
96
110
|
// Method 1: Using "host:port" syntax
|
|
97
|
-
const isOpen1 = await
|
|
111
|
+
const isOpen1 = await network.isRemotePortAvailable('example.com:443');
|
|
98
112
|
|
|
99
113
|
// Method 2: Using separate host and port
|
|
100
|
-
const isOpen2 = await
|
|
114
|
+
const isOpen2 = await network.isRemotePortAvailable('example.com', 443);
|
|
101
115
|
|
|
102
|
-
// Method 3: With options
|
|
103
|
-
const isOpen3 = await
|
|
116
|
+
// Method 3: With advanced options
|
|
117
|
+
const isOpen3 = await network.isRemotePortAvailable('example.com', {
|
|
104
118
|
port: 443,
|
|
105
119
|
protocol: 'tcp', // Only TCP is supported
|
|
106
120
|
retries: 3, // Number of connection attempts
|
|
107
121
|
timeout: 5000 // Timeout per attempt in ms
|
|
108
122
|
});
|
|
109
123
|
|
|
110
|
-
// Note: UDP is not supported
|
|
124
|
+
// Note: UDP is not supported
|
|
111
125
|
try {
|
|
112
|
-
await
|
|
126
|
+
await network.isRemotePortAvailable('example.com', {
|
|
113
127
|
port: 53,
|
|
114
128
|
protocol: 'udp'
|
|
115
129
|
});
|
|
116
130
|
} catch (e) {
|
|
117
|
-
console.error(e.code); // ENOTSUP
|
|
131
|
+
console.error('UDP not supported:', e.code); // ENOTSUP
|
|
118
132
|
}
|
|
119
133
|
```
|
|
120
134
|
|
|
121
|
-
### Network Connectivity
|
|
135
|
+
### 📡 Network Connectivity
|
|
122
136
|
|
|
123
137
|
#### Ping Operations
|
|
124
138
|
|
|
125
|
-
|
|
139
|
+
Test connectivity and measure latency:
|
|
126
140
|
|
|
127
141
|
```typescript
|
|
128
142
|
// Simple ping
|
|
129
|
-
const pingResult = await
|
|
143
|
+
const pingResult = await network.ping('google.com');
|
|
130
144
|
console.log(`Host alive: ${pingResult.alive}`);
|
|
131
145
|
console.log(`RTT: ${pingResult.time} ms`);
|
|
132
146
|
|
|
133
|
-
//
|
|
134
|
-
const pingStats = await
|
|
147
|
+
// Detailed ping statistics
|
|
148
|
+
const pingStats = await network.ping('google.com', {
|
|
135
149
|
count: 5, // Number of pings
|
|
136
150
|
timeout: 1000 // Timeout per ping in ms
|
|
137
151
|
});
|
|
138
152
|
|
|
139
|
-
console.log(
|
|
140
|
-
console.log(`
|
|
141
|
-
console.log(`
|
|
142
|
-
console.log(`
|
|
143
|
-
console.log(`
|
|
153
|
+
console.log(`📊 Ping Statistics:`);
|
|
154
|
+
console.log(` Packet loss: ${pingStats.packetLoss}%`);
|
|
155
|
+
console.log(` Min: ${pingStats.min} ms`);
|
|
156
|
+
console.log(` Max: ${pingStats.max} ms`);
|
|
157
|
+
console.log(` Avg: ${pingStats.avg.toFixed(2)} ms`);
|
|
158
|
+
console.log(` Stddev: ${pingStats.stddev.toFixed(2)} ms`);
|
|
144
159
|
```
|
|
145
160
|
|
|
146
161
|
#### Traceroute
|
|
147
162
|
|
|
148
|
-
|
|
163
|
+
Analyze network paths hop-by-hop:
|
|
149
164
|
|
|
150
165
|
```typescript
|
|
151
|
-
const hops = await
|
|
166
|
+
const hops = await network.traceroute('google.com', {
|
|
152
167
|
maxHops: 10, // Maximum number of hops
|
|
153
168
|
timeout: 5000 // Timeout in ms
|
|
154
169
|
});
|
|
155
170
|
|
|
171
|
+
console.log('🛤️ Route to destination:');
|
|
156
172
|
hops.forEach(hop => {
|
|
157
173
|
const rtt = hop.rtt === null ? '*' : `${hop.rtt} ms`;
|
|
158
|
-
console.log(
|
|
174
|
+
console.log(` ${hop.ttl}\t${hop.ip}\t${rtt}`);
|
|
159
175
|
});
|
|
160
176
|
```
|
|
161
177
|
|
|
162
|
-
Note: Falls back to a single-hop stub if the `traceroute` binary is unavailable
|
|
178
|
+
*Note: Falls back to a single-hop stub if the `traceroute` binary is unavailable.*
|
|
163
179
|
|
|
164
|
-
### DNS Operations
|
|
180
|
+
### 🌍 DNS Operations
|
|
165
181
|
|
|
166
|
-
Resolve DNS
|
|
182
|
+
Resolve various DNS record types using @push.rocks/smartdns with intelligent resolution strategy:
|
|
167
183
|
|
|
168
184
|
```typescript
|
|
169
|
-
const dnsRecords = await
|
|
185
|
+
const dnsRecords = await network.resolveDns('example.com');
|
|
170
186
|
|
|
171
|
-
console.log('
|
|
172
|
-
console.log('
|
|
173
|
-
console.log('
|
|
187
|
+
console.log('🔍 DNS Records:');
|
|
188
|
+
console.log(' A records:', dnsRecords.A); // IPv4 addresses
|
|
189
|
+
console.log(' AAAA records:', dnsRecords.AAAA); // IPv6 addresses
|
|
174
190
|
|
|
175
191
|
// MX records include priority
|
|
176
192
|
dnsRecords.MX.forEach(mx => {
|
|
177
|
-
console.log(`Mail server: ${mx.exchange} (priority: ${mx.priority})`);
|
|
193
|
+
console.log(` 📧 Mail server: ${mx.exchange} (priority: ${mx.priority})`);
|
|
178
194
|
});
|
|
195
|
+
|
|
196
|
+
// Properly handles local hostnames (localhost, etc.)
|
|
197
|
+
const localDns = await network.resolveDns('localhost');
|
|
198
|
+
console.log(' Localhost:', localDns.A); // Returns ['127.0.0.1']
|
|
179
199
|
```
|
|
180
200
|
|
|
181
|
-
|
|
201
|
+
*DNS resolution uses a `prefer-system` strategy: tries system resolver first (respects /etc/hosts and local DNS), with automatic fallback to Cloudflare DoH for external domains.*
|
|
202
|
+
|
|
203
|
+
### 🏥 HTTP/HTTPS Health Checks
|
|
182
204
|
|
|
183
|
-
|
|
205
|
+
Monitor endpoint availability and response times:
|
|
184
206
|
|
|
185
207
|
```typescript
|
|
186
|
-
const health = await
|
|
208
|
+
const health = await network.checkEndpoint('https://api.example.com/health', {
|
|
187
209
|
timeout: 5000 // Request timeout in ms
|
|
188
210
|
});
|
|
189
211
|
|
|
190
|
-
console.log(
|
|
191
|
-
console.log(`
|
|
192
|
-
console.log(
|
|
212
|
+
console.log(`🩺 Endpoint Health:`);
|
|
213
|
+
console.log(` Status: ${health.status}`);
|
|
214
|
+
console.log(` RTT: ${health.rtt} ms`);
|
|
215
|
+
console.log(` Headers:`, health.headers);
|
|
193
216
|
```
|
|
194
217
|
|
|
195
|
-
### Network Interface Information
|
|
218
|
+
### 🖥️ Network Interface Information
|
|
196
219
|
|
|
197
|
-
#### Get Network
|
|
220
|
+
#### Get All Network Interfaces
|
|
198
221
|
|
|
199
|
-
List all network
|
|
222
|
+
List all network adapters on the system:
|
|
200
223
|
|
|
201
224
|
```typescript
|
|
202
|
-
const gateways = await
|
|
225
|
+
const gateways = await network.getGateways();
|
|
203
226
|
|
|
204
227
|
Object.entries(gateways).forEach(([name, interfaces]) => {
|
|
205
|
-
console.log(
|
|
228
|
+
console.log(`🔌 Interface: ${name}`);
|
|
206
229
|
interfaces.forEach(iface => {
|
|
207
230
|
console.log(` ${iface.family}: ${iface.address}`);
|
|
208
231
|
console.log(` Netmask: ${iface.netmask}`);
|
|
@@ -213,48 +236,50 @@ Object.entries(gateways).forEach(([name, interfaces]) => {
|
|
|
213
236
|
|
|
214
237
|
#### Get Default Gateway
|
|
215
238
|
|
|
216
|
-
Retrieve the
|
|
239
|
+
Retrieve the primary network interface:
|
|
217
240
|
|
|
218
241
|
```typescript
|
|
219
|
-
const defaultGateway = await
|
|
242
|
+
const defaultGateway = await network.getDefaultGateway();
|
|
220
243
|
|
|
221
244
|
if (defaultGateway) {
|
|
222
|
-
console.log('
|
|
223
|
-
console.log('
|
|
245
|
+
console.log('🌐 Default Gateway:');
|
|
246
|
+
console.log(' IPv4:', defaultGateway.ipv4.address);
|
|
247
|
+
console.log(' IPv6:', defaultGateway.ipv6.address);
|
|
224
248
|
}
|
|
225
249
|
```
|
|
226
250
|
|
|
227
|
-
### Public IP Discovery
|
|
251
|
+
### 🌎 Public IP Discovery
|
|
228
252
|
|
|
229
|
-
Discover your public
|
|
253
|
+
Discover your public-facing IP addresses:
|
|
230
254
|
|
|
231
255
|
```typescript
|
|
232
|
-
const publicIps = await
|
|
256
|
+
const publicIps = await network.getPublicIps();
|
|
233
257
|
|
|
234
|
-
console.log(
|
|
235
|
-
console.log(`
|
|
258
|
+
console.log(`🌍 Public IPs:`);
|
|
259
|
+
console.log(` IPv4: ${publicIps.v4 || 'Not available'}`);
|
|
260
|
+
console.log(` IPv6: ${publicIps.v6 || 'Not available'}`);
|
|
236
261
|
```
|
|
237
262
|
|
|
238
|
-
### Caching
|
|
263
|
+
### ⚡ Performance Caching
|
|
239
264
|
|
|
240
|
-
|
|
265
|
+
Reduce network calls with built-in caching:
|
|
241
266
|
|
|
242
267
|
```typescript
|
|
243
268
|
// Create instance with 60-second cache TTL
|
|
244
269
|
const cachedNetwork = new SmartNetwork({ cacheTtl: 60000 });
|
|
245
270
|
|
|
246
|
-
//
|
|
271
|
+
// First call fetches from network
|
|
247
272
|
const gateways1 = await cachedNetwork.getGateways();
|
|
248
273
|
const publicIps1 = await cachedNetwork.getPublicIps();
|
|
249
274
|
|
|
250
|
-
// Subsequent calls within
|
|
251
|
-
const gateways2 = await cachedNetwork.getGateways(); // From cache
|
|
252
|
-
const publicIps2 = await cachedNetwork.getPublicIps(); // From cache
|
|
275
|
+
// Subsequent calls within 60 seconds use cache
|
|
276
|
+
const gateways2 = await cachedNetwork.getGateways(); // From cache ⚡
|
|
277
|
+
const publicIps2 = await cachedNetwork.getPublicIps(); // From cache ⚡
|
|
253
278
|
```
|
|
254
279
|
|
|
255
|
-
### Plugin Architecture
|
|
280
|
+
### 🔧 Plugin Architecture
|
|
256
281
|
|
|
257
|
-
Extend SmartNetwork
|
|
282
|
+
Extend SmartNetwork with custom functionality:
|
|
258
283
|
|
|
259
284
|
```typescript
|
|
260
285
|
// Define your plugin
|
|
@@ -262,7 +287,8 @@ class CustomNetworkPlugin {
|
|
|
262
287
|
constructor(private smartNetwork: SmartNetwork) {}
|
|
263
288
|
|
|
264
289
|
async customMethod() {
|
|
265
|
-
// Your custom network
|
|
290
|
+
// Your custom network logic here
|
|
291
|
+
return 'Custom result';
|
|
266
292
|
}
|
|
267
293
|
}
|
|
268
294
|
|
|
@@ -271,44 +297,102 @@ SmartNetwork.registerPlugin('customPlugin', CustomNetworkPlugin);
|
|
|
271
297
|
|
|
272
298
|
// Use the plugin
|
|
273
299
|
const network = new SmartNetwork();
|
|
274
|
-
const
|
|
300
|
+
const PluginClass = SmartNetwork.pluginsRegistry.get('customPlugin');
|
|
301
|
+
const plugin = new PluginClass(network);
|
|
275
302
|
await plugin.customMethod();
|
|
276
303
|
|
|
277
|
-
//
|
|
304
|
+
// Clean up when done
|
|
278
305
|
SmartNetwork.unregisterPlugin('customPlugin');
|
|
279
306
|
```
|
|
280
307
|
|
|
281
|
-
### Error Handling
|
|
308
|
+
### 🚨 Error Handling
|
|
282
309
|
|
|
283
|
-
|
|
310
|
+
Handle network errors gracefully with custom error types:
|
|
284
311
|
|
|
285
312
|
```typescript
|
|
286
313
|
import { NetworkError } from '@push.rocks/smartnetwork';
|
|
287
314
|
|
|
288
315
|
try {
|
|
289
|
-
await
|
|
316
|
+
await network.isRemotePortAvailable('example.com', { protocol: 'udp' });
|
|
290
317
|
} catch (error) {
|
|
291
318
|
if (error instanceof NetworkError) {
|
|
292
|
-
console.error(
|
|
293
|
-
console.error(`Error code: ${error.code}`);
|
|
319
|
+
console.error(`❌ Network error: ${error.message}`);
|
|
320
|
+
console.error(` Error code: ${error.code}`);
|
|
294
321
|
}
|
|
295
322
|
}
|
|
296
323
|
```
|
|
297
324
|
|
|
298
|
-
### TypeScript Support
|
|
325
|
+
### 📚 TypeScript Support
|
|
299
326
|
|
|
300
|
-
This package is written in TypeScript and provides
|
|
327
|
+
This package is written in TypeScript and provides comprehensive type definitions:
|
|
301
328
|
|
|
302
329
|
```typescript
|
|
303
330
|
interface SmartNetworkOptions {
|
|
304
331
|
cacheTtl?: number; // Cache TTL in milliseconds
|
|
305
332
|
}
|
|
306
333
|
|
|
334
|
+
interface IFindFreePortOptions {
|
|
335
|
+
randomize?: boolean; // If true, returns a random free port instead of the first one
|
|
336
|
+
}
|
|
337
|
+
|
|
307
338
|
interface Hop {
|
|
308
339
|
ttl: number; // Time to live
|
|
309
340
|
ip: string; // IP address of the hop
|
|
310
341
|
rtt: number | null; // Round trip time in ms
|
|
311
342
|
}
|
|
343
|
+
|
|
344
|
+
// ... and many more types for complete type safety
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## 🛠️ Advanced Examples
|
|
348
|
+
|
|
349
|
+
### Building a Network Monitor
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const monitorNetwork = async () => {
|
|
353
|
+
const network = new SmartNetwork({ cacheTtl: 30000 });
|
|
354
|
+
|
|
355
|
+
// Check critical services
|
|
356
|
+
const services = [
|
|
357
|
+
{ name: 'Web Server', host: 'example.com', port: 443 },
|
|
358
|
+
{ name: 'Database', host: 'db.internal', port: 5432 },
|
|
359
|
+
{ name: 'Cache', host: 'redis.internal', port: 6379 }
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
for (const service of services) {
|
|
363
|
+
const isUp = await network.isRemotePortAvailable(service.host, service.port);
|
|
364
|
+
console.log(`${service.name}: ${isUp ? '✅ UP' : '❌ DOWN'}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Check internet connectivity
|
|
368
|
+
const ping = await network.ping('8.8.8.8');
|
|
369
|
+
console.log(`Internet: ${ping.alive ? '✅ Connected' : '❌ Disconnected'}`);
|
|
370
|
+
|
|
371
|
+
// Measure network performance
|
|
372
|
+
const speed = await network.getSpeed();
|
|
373
|
+
console.log(`Speed: ⬇️ ${speed.downloadSpeed} Mbps / ⬆️ ${speed.uploadSpeed} Mbps`);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Run monitor every minute
|
|
377
|
+
setInterval(monitorNetwork, 60000);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Service Discovery
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const discoverServices = async () => {
|
|
384
|
+
const network = new SmartNetwork();
|
|
385
|
+
const commonPorts = [22, 80, 443, 3000, 3306, 5432, 6379, 8080, 9200];
|
|
386
|
+
|
|
387
|
+
console.log('🔍 Scanning local services...');
|
|
388
|
+
|
|
389
|
+
for (const port of commonPorts) {
|
|
390
|
+
const isUsed = !(await network.isLocalPortUnused(port));
|
|
391
|
+
if (isUsed) {
|
|
392
|
+
console.log(` Port ${port}: In use (possible service running)`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
312
396
|
```
|
|
313
397
|
|
|
314
398
|
## License and Legal Information
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './smartnetwork.classes.smartnetwork.js';
|
|
2
2
|
export type { SmartNetworkOptions, Hop } from './smartnetwork.classes.smartnetwork.js';
|
|
3
|
+
export { PublicIp } from './smartnetwork.classes.publicip.js';
|
|
3
4
|
export { setLogger, getLogger } from './logging.js';
|
|
4
5
|
export { NetworkError, TimeoutError } from './errors.js';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { getLogger } from './logging.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Service configuration for IP detection
|
|
5
|
+
*/
|
|
6
|
+
interface IpService {
|
|
7
|
+
name: string;
|
|
8
|
+
v4Url?: string;
|
|
9
|
+
v6Url?: string;
|
|
10
|
+
parseResponse?: (text: string) => string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* PublicIp class for detecting public IPv4 and IPv6 addresses
|
|
15
|
+
* Uses multiple fallback services for reliability
|
|
16
|
+
*/
|
|
17
|
+
export class PublicIp {
|
|
18
|
+
private readonly services: IpService[] = [
|
|
19
|
+
{
|
|
20
|
+
name: 'ipify',
|
|
21
|
+
v4Url: 'https://api.ipify.org?format=text',
|
|
22
|
+
v6Url: 'https://api6.ipify.org?format=text',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'ident.me',
|
|
26
|
+
v4Url: 'https://v4.ident.me',
|
|
27
|
+
v6Url: 'https://v6.ident.me',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'seeip',
|
|
31
|
+
v4Url: 'https://ipv4.seeip.org',
|
|
32
|
+
v6Url: 'https://ipv6.seeip.org',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'icanhazip',
|
|
36
|
+
v4Url: 'https://ipv4.icanhazip.com',
|
|
37
|
+
v6Url: 'https://ipv6.icanhazip.com',
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
private readonly timeout: number;
|
|
42
|
+
private readonly logger = getLogger();
|
|
43
|
+
|
|
44
|
+
constructor(options?: { timeout?: number }) {
|
|
45
|
+
this.timeout = options?.timeout ?? 2000;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get public IPv4 address
|
|
50
|
+
*/
|
|
51
|
+
public async getPublicIpv4(): Promise<string | null> {
|
|
52
|
+
for (const service of this.services) {
|
|
53
|
+
if (!service.v4Url) continue;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const ip = await this.fetchIpFromService(service.v4Url, service.parseResponse);
|
|
57
|
+
if (this.isValidIpv4(ip)) {
|
|
58
|
+
this.logger.info?.(`Got IPv4 from ${service.name}: ${ip}`);
|
|
59
|
+
return ip;
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.logger.debug?.(`Failed to get IPv4 from ${service.name}: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.logger.warn?.('Could not determine public IPv4 address from any service');
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get public IPv6 address
|
|
72
|
+
*/
|
|
73
|
+
public async getPublicIpv6(): Promise<string | null> {
|
|
74
|
+
for (const service of this.services) {
|
|
75
|
+
if (!service.v6Url) continue;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const ip = await this.fetchIpFromService(service.v6Url, service.parseResponse);
|
|
79
|
+
if (this.isValidIpv6(ip)) {
|
|
80
|
+
this.logger.info?.(`Got IPv6 from ${service.name}: ${ip}`);
|
|
81
|
+
return ip;
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.logger.debug?.(`Failed to get IPv6 from ${service.name}: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.logger.warn?.('Could not determine public IPv6 address from any service');
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get both IPv4 and IPv6 addresses
|
|
94
|
+
*/
|
|
95
|
+
public async getPublicIps(): Promise<{ v4: string | null; v6: string | null }> {
|
|
96
|
+
const [v4, v6] = await Promise.all([
|
|
97
|
+
this.getPublicIpv4(),
|
|
98
|
+
this.getPublicIpv6(),
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
return { v4, v6 };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Fetch IP from a service URL
|
|
106
|
+
*/
|
|
107
|
+
private async fetchIpFromService(
|
|
108
|
+
url: string,
|
|
109
|
+
parseResponse?: (text: string) => string
|
|
110
|
+
): Promise<string> {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(url, {
|
|
116
|
+
signal: controller.signal,
|
|
117
|
+
headers: {
|
|
118
|
+
'User-Agent': '@push.rocks/smartnetwork',
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
const ip = parseResponse ? parseResponse(text) : text.trim();
|
|
128
|
+
|
|
129
|
+
return ip;
|
|
130
|
+
} finally {
|
|
131
|
+
clearTimeout(timeoutId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate IPv4 address format
|
|
137
|
+
*/
|
|
138
|
+
private isValidIpv4(ip: string): boolean {
|
|
139
|
+
if (!ip) return false;
|
|
140
|
+
|
|
141
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
142
|
+
if (!ipv4Regex.test(ip)) return false;
|
|
143
|
+
|
|
144
|
+
const parts = ip.split('.');
|
|
145
|
+
return parts.every(part => {
|
|
146
|
+
const num = parseInt(part, 10);
|
|
147
|
+
return num >= 0 && num <= 255;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Validate IPv6 address format
|
|
153
|
+
*/
|
|
154
|
+
private isValidIpv6(ip: string): boolean {
|
|
155
|
+
if (!ip) return false;
|
|
156
|
+
|
|
157
|
+
// Simplified IPv6 validation - checks for colon-separated hex values
|
|
158
|
+
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
159
|
+
const ipv6CompressedRegex = /^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,7}:$/;
|
|
160
|
+
|
|
161
|
+
return ipv6Regex.test(ip) || ipv6CompressedRegex.test(ip);
|
|
162
|
+
}
|
|
163
|
+
}
|