@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/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
- npm install @push.rocks/smartnetwork --save
10
+ pnpm install @push.rocks/smartnetwork --save
11
11
  ```
12
12
 
13
- ## Usage
13
+ ## 🎯 Overview
14
14
 
15
- The `@push.rocks/smartnetwork` package provides a comprehensive suite of network diagnostic tools including speed tests, port availability checks, ping operations, DNS resolution, HTTP endpoint health checks, and more.
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 the package into your project:
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
- ```typescript
28
- const myNetwork = new SmartNetwork();
37
+ // Basic instance
38
+ const network = new SmartNetwork();
29
39
 
30
- // Or with caching enabled (60 seconds TTL)
31
- const myNetworkCached = new SmartNetwork({ cacheTtl: 60000 });
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 download and upload speeds using Cloudflare's speed test infrastructure:
46
+ Measure your network performance using Cloudflare's global infrastructure:
37
47
 
38
48
  ```typescript
39
49
  const speedTest = async () => {
40
- // Basic speed test
41
- const result = await myNetwork.getSpeed();
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 speed test with options
46
- const advancedResult = await myNetwork.getSpeed({
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 specific port is available on your local machine (checks both IPv4 and IPv6):
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 myNetwork.isLocalPortUnused(port);
73
+ const isUnused = await network.isLocalPortUnused(port);
64
74
  if (isUnused) {
65
- console.log(`Port ${port} is available`);
75
+ console.log(`✅ Port ${port} is available`);
66
76
  } else {
67
- console.log(`Port ${port} is in use`);
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 find the first available port within a specified range:
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 myNetwork.findFreePort(3000, 3100);
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(`Found free port: ${freePort}`);
94
+ console.log(`🎉 Found free port: ${freePort}`);
85
95
  } else {
86
- console.log('No free ports available in the specified range');
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
- Verify if a port is open on a remote server:
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 myNetwork.isRemotePortAvailable('example.com:443');
111
+ const isOpen1 = await network.isRemotePortAvailable('example.com:443');
98
112
 
99
113
  // Method 2: Using separate host and port
100
- const isOpen2 = await myNetwork.isRemotePortAvailable('example.com', 443);
114
+ const isOpen2 = await network.isRemotePortAvailable('example.com', 443);
101
115
 
102
- // Method 3: With options (retries, timeout)
103
- const isOpen3 = await myNetwork.isRemotePortAvailable('example.com', {
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 and will throw an error
124
+ // Note: UDP is not supported
111
125
  try {
112
- await myNetwork.isRemotePortAvailable('example.com', {
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
- Send ICMP echo requests to test connectivity and measure latency:
139
+ Test connectivity and measure latency:
126
140
 
127
141
  ```typescript
128
142
  // Simple ping
129
- const pingResult = await myNetwork.ping('google.com');
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
- // Ping with statistics (multiple pings)
134
- const pingStats = await myNetwork.ping('google.com', {
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(`Packet loss: ${pingStats.packetLoss}%`);
140
- console.log(`Min: ${pingStats.min} ms`);
141
- console.log(`Max: ${pingStats.max} ms`);
142
- console.log(`Avg: ${pingStats.avg.toFixed(2)} ms`);
143
- console.log(`Stddev: ${pingStats.stddev.toFixed(2)} ms`);
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
- Perform hop-by-hop network path analysis:
163
+ Analyze network paths hop-by-hop:
149
164
 
150
165
  ```typescript
151
- const hops = await myNetwork.traceroute('google.com', {
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(`${hop.ttl}\t${hop.ip}\t${rtt}`);
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 on the system.
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 records for a hostname:
182
+ Resolve various DNS record types using @push.rocks/smartdns with intelligent resolution strategy:
167
183
 
168
184
  ```typescript
169
- const dnsRecords = await myNetwork.resolveDns('example.com');
185
+ const dnsRecords = await network.resolveDns('example.com');
170
186
 
171
- console.log('A records:', dnsRecords.A); // IPv4 addresses
172
- console.log('AAAA records:', dnsRecords.AAAA); // IPv6 addresses
173
- console.log('MX records:', dnsRecords.MX); // Mail servers
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
- ### HTTP/HTTPS Endpoint Health Checks
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
- Check the health and response time of HTTP/HTTPS endpoints:
205
+ Monitor endpoint availability and response times:
184
206
 
185
207
  ```typescript
186
- const health = await myNetwork.checkEndpoint('https://example.com', {
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(`Status: ${health.status}`);
191
- console.log(`RTT: ${health.rtt} ms`);
192
- console.log('Headers:', health.headers);
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 Gateways
220
+ #### Get All Network Interfaces
198
221
 
199
- List all network interfaces on the system:
222
+ List all network adapters on the system:
200
223
 
201
224
  ```typescript
202
- const gateways = await myNetwork.getGateways();
225
+ const gateways = await network.getGateways();
203
226
 
204
227
  Object.entries(gateways).forEach(([name, interfaces]) => {
205
- console.log(`Interface: ${name}`);
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 system's default network gateway:
239
+ Retrieve the primary network interface:
217
240
 
218
241
  ```typescript
219
- const defaultGateway = await myNetwork.getDefaultGateway();
242
+ const defaultGateway = await network.getDefaultGateway();
220
243
 
221
244
  if (defaultGateway) {
222
- console.log('IPv4 Gateway:', defaultGateway.ipv4.address);
223
- console.log('IPv6 Gateway:', defaultGateway.ipv6.address);
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 IPv4 and IPv6 addresses:
253
+ Discover your public-facing IP addresses:
230
254
 
231
255
  ```typescript
232
- const publicIps = await myNetwork.getPublicIps();
256
+ const publicIps = await network.getPublicIps();
233
257
 
234
- console.log(`Public IPv4: ${publicIps.v4 || 'Not available'}`);
235
- console.log(`Public IPv6: ${publicIps.v6 || 'Not available'}`);
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
- SmartNetwork supports caching for gateway and public IP lookups to reduce repeated network calls:
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
- // These calls will use cached results if called within 60 seconds
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 TTL return cached results
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's functionality with custom plugins:
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 functionality
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 plugin = new (SmartNetwork.pluginsRegistry.get('customPlugin'))(network);
300
+ const PluginClass = SmartNetwork.pluginsRegistry.get('customPlugin');
301
+ const plugin = new PluginClass(network);
275
302
  await plugin.customMethod();
276
303
 
277
- // Unregister when no longer needed
304
+ // Clean up when done
278
305
  SmartNetwork.unregisterPlugin('customPlugin');
279
306
  ```
280
307
 
281
- ### Error Handling
308
+ ### 🚨 Error Handling
282
309
 
283
- The package uses custom `NetworkError` class for network-related errors:
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 myNetwork.isRemotePortAvailable('example.com', { protocol: 'udp' });
316
+ await network.isRemotePortAvailable('example.com', { protocol: 'udp' });
290
317
  } catch (error) {
291
318
  if (error instanceof NetworkError) {
292
- console.error(`Network error: ${error.message}`);
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 full type definitions. Key interfaces include:
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
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnetwork',
6
- version: '4.0.2',
6
+ version: '4.3.0',
7
7
  description: 'A toolkit for network diagnostics including speed tests, port availability checks, and more.'
8
8
  }
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
+ }