@push.rocks/smartnetwork 4.10.0 → 5.0.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.
Files changed (196) hide show
  1. package/.smartconfig.json +19 -8
  2. package/dist_rust/rustnetwork_linux_amd64_musl +0 -0
  3. package/dist_rust/rustnetwork_linux_amd64_musl.tsrust-build.json +14 -0
  4. package/dist_rust/rustnetwork_linux_arm64_musl +0 -0
  5. package/dist_rust/rustnetwork_linux_arm64_musl.tsrust-build.json +14 -0
  6. package/dist_ts/00_commitinfo_data.js +2 -2
  7. package/dist_ts/index.d.ts +1 -1
  8. package/dist_ts/smartnetwork.classes.domainintelligence.d.ts +10 -4
  9. package/dist_ts/smartnetwork.classes.domainintelligence.js +69 -39
  10. package/dist_ts/smartnetwork.classes.ipintelligence.d.ts +8 -2
  11. package/dist_ts/smartnetwork.classes.ipintelligence.js +67 -37
  12. package/dist_ts/smartnetwork.classes.rustbridge.d.ts +12 -8
  13. package/dist_ts/smartnetwork.classes.rustbridge.js +82 -32
  14. package/dist_ts/smartnetwork.classes.smartnetwork.d.ts +46 -8
  15. package/dist_ts/smartnetwork.classes.smartnetwork.js +165 -84
  16. package/native-notices/crates/aho-corasick-1.1.4/copying +3 -0
  17. package/native-notices/crates/aho-corasick-1.1.4/license-mit +21 -0
  18. package/native-notices/crates/aho-corasick-1.1.4/unlicense +24 -0
  19. package/native-notices/crates/anstream-1.0.0/license-apache +202 -0
  20. package/native-notices/crates/anstream-1.0.0/license-mit +19 -0
  21. package/native-notices/crates/anstyle-1.0.14/license-apache +202 -0
  22. package/native-notices/crates/anstyle-1.0.14/license-mit +19 -0
  23. package/native-notices/crates/anstyle-parse-1.0.0/license-apache +202 -0
  24. package/native-notices/crates/anstyle-parse-1.0.0/license-mit +19 -0
  25. package/native-notices/crates/anstyle-query-1.1.5/license-apache +202 -0
  26. package/native-notices/crates/anstyle-query-1.1.5/license-mit +19 -0
  27. package/native-notices/crates/bytes-1.11.1/license +25 -0
  28. package/native-notices/crates/cfg-if-1.0.4/license-apache +201 -0
  29. package/native-notices/crates/cfg-if-1.0.4/license-mit +25 -0
  30. package/native-notices/crates/clap-4.6.1/license-apache +202 -0
  31. package/native-notices/crates/clap-4.6.1/license-mit +19 -0
  32. package/native-notices/crates/clap_builder-4.6.0/license-apache +202 -0
  33. package/native-notices/crates/clap_builder-4.6.0/license-mit +19 -0
  34. package/native-notices/crates/clap_derive-4.6.1/license-apache +202 -0
  35. package/native-notices/crates/clap_derive-4.6.1/license-mit +19 -0
  36. package/native-notices/crates/clap_lex-1.1.0/license-apache +202 -0
  37. package/native-notices/crates/clap_lex-1.1.0/license-mit +19 -0
  38. package/native-notices/crates/colorchoice-1.0.5/license-apache +202 -0
  39. package/native-notices/crates/colorchoice-1.0.5/license-mit +19 -0
  40. package/native-notices/crates/errno-0.3.14/license-apache +201 -0
  41. package/native-notices/crates/errno-0.3.14/license-mit +25 -0
  42. package/native-notices/crates/getrandom-0.3.4/license-apache +201 -0
  43. package/native-notices/crates/getrandom-0.3.4/license-mit +26 -0
  44. package/native-notices/crates/glob-0.3.3/license-apache +201 -0
  45. package/native-notices/crates/glob-0.3.3/license-mit +25 -0
  46. package/native-notices/crates/heck-0.5.0/license-apache +201 -0
  47. package/native-notices/crates/heck-0.5.0/license-mit +25 -0
  48. package/native-notices/crates/hex-0.4.3/license-apache +202 -0
  49. package/native-notices/crates/hex-0.4.3/license-mit +20 -0
  50. package/native-notices/crates/is_terminal_polyfill-1.70.2/license-apache +202 -0
  51. package/native-notices/crates/is_terminal_polyfill-1.70.2/license-mit +19 -0
  52. package/native-notices/crates/itoa-1.0.18/license-apache +176 -0
  53. package/native-notices/crates/itoa-1.0.18/license-mit +23 -0
  54. package/native-notices/crates/lazy_static-1.5.0/license-apache +201 -0
  55. package/native-notices/crates/lazy_static-1.5.0/license-mit +25 -0
  56. package/native-notices/crates/libc-0.2.186/license-apache +176 -0
  57. package/native-notices/crates/libc-0.2.186/license-mit +25 -0
  58. package/native-notices/crates/lock_api-0.4.14/license-apache +201 -0
  59. package/native-notices/crates/lock_api-0.4.14/license-mit +25 -0
  60. package/native-notices/crates/log-0.4.29/license-apache +201 -0
  61. package/native-notices/crates/log-0.4.29/license-mit +25 -0
  62. package/native-notices/crates/matchers-0.2.0/license +19 -0
  63. package/native-notices/crates/memchr-2.8.0/copying +3 -0
  64. package/native-notices/crates/memchr-2.8.0/license-mit +21 -0
  65. package/native-notices/crates/memchr-2.8.0/unlicense +24 -0
  66. package/native-notices/crates/mio-1.2.0/license +19 -0
  67. package/native-notices/crates/no-std-net-0.6.0/license +21 -0
  68. package/native-notices/crates/nu-ansi-term-0.50.3/license +22 -0
  69. package/native-notices/crates/once_cell-1.21.4/license-apache +201 -0
  70. package/native-notices/crates/once_cell-1.21.4/license-mit +23 -0
  71. package/native-notices/crates/parking_lot-0.12.5/license-apache +201 -0
  72. package/native-notices/crates/parking_lot-0.12.5/license-mit +25 -0
  73. package/native-notices/crates/parking_lot_core-0.9.12/license-apache +201 -0
  74. package/native-notices/crates/parking_lot_core-0.9.12/license-mit +25 -0
  75. package/native-notices/crates/pin-project-lite-0.2.17/license-apache +177 -0
  76. package/native-notices/crates/pin-project-lite-0.2.17/license-mit +23 -0
  77. package/native-notices/crates/pnet_base-0.34.0/license-apache +201 -0
  78. package/native-notices/crates/pnet_base-0.34.0/license-mit +25 -0
  79. package/native-notices/crates/pnet_macros-0.34.0/license-apache +201 -0
  80. package/native-notices/crates/pnet_macros-0.34.0/license-mit +25 -0
  81. package/native-notices/crates/pnet_macros_support-0.34.0/license-apache +201 -0
  82. package/native-notices/crates/pnet_macros_support-0.34.0/license-mit +25 -0
  83. package/native-notices/crates/pnet_packet-0.34.0/license-apache +201 -0
  84. package/native-notices/crates/pnet_packet-0.34.0/license-mit +25 -0
  85. package/native-notices/crates/ppv-lite86-0.2.21/license-apache +201 -0
  86. package/native-notices/crates/ppv-lite86-0.2.21/license-mit +25 -0
  87. package/native-notices/crates/proc-macro2-1.0.106/license-apache +176 -0
  88. package/native-notices/crates/proc-macro2-1.0.106/license-mit +23 -0
  89. package/native-notices/crates/quote-1.0.45/license-apache +176 -0
  90. package/native-notices/crates/quote-1.0.45/license-mit +23 -0
  91. package/native-notices/crates/rand-0.9.4/copyright +12 -0
  92. package/native-notices/crates/rand-0.9.4/license-apache +176 -0
  93. package/native-notices/crates/rand-0.9.4/license-mit +26 -0
  94. package/native-notices/crates/rand_chacha-0.9.0/copyright +12 -0
  95. package/native-notices/crates/rand_chacha-0.9.0/license-apache +176 -0
  96. package/native-notices/crates/rand_chacha-0.9.0/license-mit +26 -0
  97. package/native-notices/crates/rand_core-0.9.5/copyright +12 -0
  98. package/native-notices/crates/rand_core-0.9.5/license-apache +187 -0
  99. package/native-notices/crates/rand_core-0.9.5/license-mit +26 -0
  100. package/native-notices/crates/regex-1.12.3/license-apache +201 -0
  101. package/native-notices/crates/regex-1.12.3/license-mit +25 -0
  102. package/native-notices/crates/regex-automata-0.4.14/license-apache +201 -0
  103. package/native-notices/crates/regex-automata-0.4.14/license-mit +25 -0
  104. package/native-notices/crates/regex-syntax-0.8.10/license-apache +201 -0
  105. package/native-notices/crates/regex-syntax-0.8.10/license-mit +25 -0
  106. package/native-notices/crates/scopeguard-1.2.0/license-apache +201 -0
  107. package/native-notices/crates/scopeguard-1.2.0/license-mit +25 -0
  108. package/native-notices/crates/serde-1.0.228/license-apache +176 -0
  109. package/native-notices/crates/serde-1.0.228/license-mit +23 -0
  110. package/native-notices/crates/serde_core-1.0.228/license-apache +176 -0
  111. package/native-notices/crates/serde_core-1.0.228/license-mit +23 -0
  112. package/native-notices/crates/serde_derive-1.0.228/license-apache +176 -0
  113. package/native-notices/crates/serde_derive-1.0.228/license-mit +23 -0
  114. package/native-notices/crates/serde_json-1.0.149/license-apache +176 -0
  115. package/native-notices/crates/serde_json-1.0.149/license-mit +23 -0
  116. package/native-notices/crates/sharded-slab-0.1.7/license +19 -0
  117. package/native-notices/crates/signal-hook-registry-1.4.8/license-apache +201 -0
  118. package/native-notices/crates/signal-hook-registry-1.4.8/license-mit +25 -0
  119. package/native-notices/crates/smallvec-1.15.1/license-apache +201 -0
  120. package/native-notices/crates/smallvec-1.15.1/license-mit +25 -0
  121. package/native-notices/crates/socket2-0.5.10/license-apache +201 -0
  122. package/native-notices/crates/socket2-0.5.10/license-mit +25 -0
  123. package/native-notices/crates/socket2-0.6.3/license-apache +201 -0
  124. package/native-notices/crates/socket2-0.6.3/license-mit +25 -0
  125. package/native-notices/crates/strsim-0.11.1/license +23 -0
  126. package/native-notices/crates/surge-ping-0.8.4/license +21 -0
  127. package/native-notices/crates/syn-2.0.117/license-apache +176 -0
  128. package/native-notices/crates/syn-2.0.117/license-mit +23 -0
  129. package/native-notices/crates/thiserror-1.0.69/license-apache +176 -0
  130. package/native-notices/crates/thiserror-1.0.69/license-mit +23 -0
  131. package/native-notices/crates/thiserror-impl-1.0.69/license-apache +176 -0
  132. package/native-notices/crates/thiserror-impl-1.0.69/license-mit +23 -0
  133. package/native-notices/crates/thread_local-1.1.9/license-apache +201 -0
  134. package/native-notices/crates/thread_local-1.1.9/license-mit +25 -0
  135. package/native-notices/crates/tokio-1.52.3/license +21 -0
  136. package/native-notices/crates/tokio-macros-2.7.0/license +22 -0
  137. package/native-notices/crates/tracing-0.1.44/license +25 -0
  138. package/native-notices/crates/tracing-attributes-0.1.31/license +25 -0
  139. package/native-notices/crates/tracing-core-0.1.36/license +25 -0
  140. package/native-notices/crates/tracing-log-0.2.0/license +25 -0
  141. package/native-notices/crates/tracing-subscriber-0.3.23/license +25 -0
  142. package/native-notices/crates/unicode-ident-1.0.24/license-apache +176 -0
  143. package/native-notices/crates/unicode-ident-1.0.24/license-mit +23 -0
  144. package/native-notices/crates/unicode-ident-1.0.24/license-unicode +39 -0
  145. package/native-notices/crates/utf8parse-0.2.2/license-apache +176 -0
  146. package/native-notices/crates/utf8parse-0.2.2/license-mit +25 -0
  147. package/native-notices/crates/zerocopy-0.8.48/license-apache +202 -0
  148. package/native-notices/crates/zerocopy-0.8.48/license-bsd +24 -0
  149. package/native-notices/crates/zerocopy-0.8.48/license-mit +26 -0
  150. package/native-notices/crates/zmij-1.0.21/license-mit +23 -0
  151. package/native-notices/manifest.json +1890 -0
  152. package/native-notices/readme.md +42 -0
  153. package/native-notices/runtime/gcc/gpl-3.0.txt +674 -0
  154. package/native-notices/runtime/gcc/runtime-library-exception-3.1.txt +73 -0
  155. package/native-notices/runtime/musl-1.2.5/copyright +193 -0
  156. package/native-notices/rust/compiler-builtins-license.txt +275 -0
  157. package/native-notices/rust/copyright-library.html +8266 -0
  158. package/native-notices/rust/crates/addr2line-0.25.1/license-apache +201 -0
  159. package/native-notices/rust/crates/addr2line-0.25.1/license-mit +25 -0
  160. package/native-notices/rust/crates/adler2-2.0.1/license-0bsd +12 -0
  161. package/native-notices/rust/crates/adler2-2.0.1/license-apache +201 -0
  162. package/native-notices/rust/crates/adler2-2.0.1/license-mit +23 -0
  163. package/native-notices/rust/crates/memchr-2.7.6/copying +3 -0
  164. package/native-notices/rust/crates/memchr-2.7.6/license-mit +21 -0
  165. package/native-notices/rust/crates/memchr-2.7.6/unlicense +24 -0
  166. package/native-notices/rust/crates/miniz_oxide-0.8.9/license +25 -0
  167. package/native-notices/rust/crates/miniz_oxide-0.8.9/license-apache.md +177 -0
  168. package/native-notices/rust/crates/miniz_oxide-0.8.9/license-mit.md +24 -0
  169. package/native-notices/rust/crates/miniz_oxide-0.8.9/license-zlib.md +14 -0
  170. package/native-notices/rust/crates/object-0.37.3/license-apache +201 -0
  171. package/native-notices/rust/crates/object-0.37.3/license-mit +25 -0
  172. package/native-notices/rust/licenses/apache-2.0.txt +73 -0
  173. package/native-notices/rust/licenses/bsd-2-clause.txt +9 -0
  174. package/native-notices/rust/licenses/cc-by-sa-4.0.txt +427 -0
  175. package/native-notices/rust/licenses/gcc-exception-3.1.txt +30 -0
  176. package/native-notices/rust/licenses/gpl-2.0-only.txt +133 -0
  177. package/native-notices/rust/licenses/gpl-3.0-or-later.txt +202 -0
  178. package/native-notices/rust/licenses/isc.txt +7 -0
  179. package/native-notices/rust/licenses/llvm-exception.txt +15 -0
  180. package/native-notices/rust/licenses/mit.txt +9 -0
  181. package/native-notices/rust/licenses/ncsa.txt +29 -0
  182. package/native-notices/rust/licenses/ofl-1.1.txt +43 -0
  183. package/native-notices/rust/licenses/unicode-3.0.txt +39 -0
  184. package/package.json +18 -9
  185. package/readme.md +56 -13
  186. package/ts/00_commitinfo_data.ts +1 -1
  187. package/ts/index.ts +1 -1
  188. package/ts/smartnetwork.classes.domainintelligence.ts +73 -40
  189. package/ts/smartnetwork.classes.ipintelligence.ts +71 -37
  190. package/ts/smartnetwork.classes.rustbridge.ts +83 -34
  191. package/ts/smartnetwork.classes.smartnetwork.ts +208 -89
  192. package/dist_rust/rustnetwork_linux_amd64 +0 -0
  193. package/dist_rust/rustnetwork_linux_arm64 +0 -0
  194. package/readme.hints.md +0 -106
  195. package/ts/tsconfig.json +0 -5
  196. /package/{license → license.md} +0 -0
@@ -5,7 +5,10 @@ import { IpIntelligence, type IIpIntelligenceResult } from './smartnetwork.class
5
5
  import { DomainIntelligence, type IDomainIntelligenceResult } from './smartnetwork.classes.domainintelligence.js';
6
6
  import { getLogger } from './logging.js';
7
7
  import { NetworkError } from './errors.js';
8
- import { RustNetworkBridge } from './smartnetwork.classes.rustbridge.js';
8
+ import {
9
+ RustNetworkBridge,
10
+ RustNetworkBridgeActivationCleanupError,
11
+ } from './smartnetwork.classes.rustbridge.js';
9
12
 
10
13
  /** Type alias for the shared Smartdns client instance */
11
14
  type TSmartdnsClient = InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns>;
@@ -29,6 +32,37 @@ export interface Hop {
29
32
  rtt: number | null;
30
33
  }
31
34
 
35
+ /**
36
+ * Options for the ping method
37
+ */
38
+ export interface IPingOptions {
39
+ /** Time to wait for each echo reply in milliseconds. Default: 5000 */
40
+ timeout?: number;
41
+ /** Number of echo requests to send. Default: 1 */
42
+ count?: number;
43
+ }
44
+
45
+ /**
46
+ * Result of the ping method. Round-trip times are in milliseconds and `null`
47
+ * where no reply arrived.
48
+ */
49
+ export interface IPingResult {
50
+ host: string;
51
+ count: number;
52
+ /** True when at least one echo reply arrived */
53
+ alive: boolean;
54
+ /** Round-trip time of the first reply */
55
+ time: number | null;
56
+ /** Round-trip time per echo request */
57
+ times: (number | null)[];
58
+ min: number | null;
59
+ max: number | null;
60
+ avg: number | null;
61
+ stddev: number | null;
62
+ /** Lost echo requests in percent */
63
+ packetLoss: number;
64
+ }
65
+
32
66
  /**
33
67
  * Options for the findFreePort method
34
68
  */
@@ -39,6 +73,8 @@ export interface IFindFreePortOptions {
39
73
  exclude?: number[];
40
74
  }
41
75
 
76
+ type TBridgeLifecycleState = 'idle' | 'active' | 'release-failed';
77
+
42
78
  /**
43
79
  * SmartNetwork simplifies actions within the network.
44
80
  * Uses a Rust binary for system-dependent operations (ping, traceroute, port scanning, gateway detection).
@@ -58,7 +94,11 @@ export class SmartNetwork {
58
94
  private options: SmartNetworkOptions;
59
95
  private cache: Map<string, { value: any; expiry: number }>;
60
96
  private rustBridge: RustNetworkBridge;
61
- private bridgeStarted = false;
97
+ private bridgeLifecycleState: TBridgeLifecycleState = 'idle';
98
+ private bridgeLifecycleQueue: Promise<void> = Promise.resolve();
99
+ private activeDnsOperations = 0;
100
+ private dnsDrainPromise: Promise<void> | null = null;
101
+ private resolveDnsDrain: (() => void) | null = null;
62
102
  private ipIntelligence: IpIntelligence | null = null;
63
103
  private domainIntelligence: DomainIntelligence | null = null;
64
104
  private dnsClient: TSmartdnsClient | null = null;
@@ -73,11 +113,21 @@ export class SmartNetwork {
73
113
  * Start the Rust binary bridge. Must be called before using ping, traceroute,
74
114
  * port scanning, or gateway operations. Safe to call multiple times.
75
115
  */
76
- public async start(): Promise<void> {
77
- if (!this.bridgeStarted) {
78
- await this.rustBridge.start();
79
- this.bridgeStarted = true;
80
- }
116
+ public start(): Promise<void> {
117
+ return this.enqueueBridgeLifecycle(async () => {
118
+ if (this.bridgeLifecycleState === 'release-failed') {
119
+ throw new Error('SmartNetwork cleanup must succeed before it can start again');
120
+ }
121
+ try {
122
+ await this.rustBridge.start(this);
123
+ this.bridgeLifecycleState = 'active';
124
+ } catch (error) {
125
+ if (error instanceof RustNetworkBridgeActivationCleanupError) {
126
+ this.bridgeLifecycleState = 'release-failed';
127
+ }
128
+ throw error;
129
+ }
130
+ });
81
131
  }
82
132
 
83
133
  /**
@@ -86,35 +136,100 @@ export class SmartNetwork {
86
136
  * Rust-backed features, otherwise the smartdns Rust backend may keep
87
137
  * the event loop alive.
88
138
  */
89
- public async stop(): Promise<void> {
90
- if (this.bridgeStarted) {
91
- await this.rustBridge.stop();
92
- this.bridgeStarted = false;
139
+ public stop(): Promise<void> {
140
+ return this.enqueueBridgeLifecycle(async () => {
141
+ await this.waitForDnsOperations();
142
+
143
+ let bridgeError: unknown;
144
+ if (this.bridgeLifecycleState !== 'idle') {
145
+ try {
146
+ await this.rustBridge.stop(this);
147
+ } catch (error) {
148
+ bridgeError = error;
149
+ }
150
+ }
151
+
152
+ let dnsError: unknown;
153
+ if (this.dnsClient) {
154
+ try {
155
+ await this.dnsClient.terminate();
156
+ this.dnsClient = null;
157
+ // Intelligence instances hold a stale reference to the terminated
158
+ // client; drop them so the next call rebuilds with a fresh one.
159
+ this.ipIntelligence = null;
160
+ this.domainIntelligence = null;
161
+ } catch (error) {
162
+ dnsError = error;
163
+ }
164
+ }
165
+
166
+ if (bridgeError || dnsError) {
167
+ this.bridgeLifecycleState = 'release-failed';
168
+ } else {
169
+ this.bridgeLifecycleState = 'idle';
170
+ }
171
+
172
+ if (bridgeError && dnsError) {
173
+ throw new AggregateError([bridgeError, dnsError], 'SmartNetwork cleanup failed');
174
+ }
175
+ if (bridgeError) throw bridgeError;
176
+ if (dnsError) throw dnsError;
177
+ });
178
+ }
179
+
180
+ private enqueueBridgeLifecycle(operationArg: () => Promise<void>): Promise<void> {
181
+ const result = this.bridgeLifecycleQueue.then(operationArg);
182
+ this.bridgeLifecycleQueue = result.catch(() => {});
183
+ return result;
184
+ }
185
+
186
+ private async withDnsClient<T>(
187
+ operationArg: (dnsClientArg: TSmartdnsClient) => Promise<T>,
188
+ ): Promise<T> {
189
+ let dnsClient: TSmartdnsClient;
190
+ await this.enqueueBridgeLifecycle(async () => {
191
+ if (this.bridgeLifecycleState === 'release-failed') {
192
+ throw new Error('SmartNetwork cleanup must succeed before DNS can be used again');
193
+ }
194
+ dnsClient = this.ensureDnsClient();
195
+ this.activeDnsOperations++;
196
+ });
197
+
198
+ try {
199
+ return await operationArg(dnsClient!);
200
+ } finally {
201
+ this.activeDnsOperations--;
202
+ if (this.activeDnsOperations === 0 && this.resolveDnsDrain) {
203
+ const resolve = this.resolveDnsDrain;
204
+ this.resolveDnsDrain = null;
205
+ this.dnsDrainPromise = null;
206
+ resolve();
207
+ }
93
208
  }
94
- if (this.dnsClient) {
95
- this.dnsClient.destroy();
96
- this.dnsClient = null;
97
- // Intelligence instances hold a stale reference to the destroyed
98
- // client; drop them so the next call rebuilds with a fresh one.
99
- this.ipIntelligence = null;
100
- this.domainIntelligence = null;
209
+ }
210
+
211
+ private waitForDnsOperations(): Promise<void> {
212
+ if (this.activeDnsOperations === 0) return Promise.resolve();
213
+ if (!this.dnsDrainPromise) {
214
+ this.dnsDrainPromise = new Promise<void>((resolve) => {
215
+ this.resolveDnsDrain = resolve;
216
+ });
101
217
  }
218
+ return this.dnsDrainPromise;
102
219
  }
103
220
 
104
221
  /**
105
222
  * Ensure the Rust bridge is running before sending commands.
106
223
  */
107
224
  private async ensureBridge(): Promise<void> {
108
- if (!this.bridgeStarted) {
109
- await this.start();
110
- }
225
+ await this.start();
111
226
  }
112
227
 
113
228
  /**
114
229
  * Lazily create the shared Smartdns client. The Rust backend inside
115
230
  * Smartdns is only spawned on first query that requires it (NS/MX/SOA
116
231
  * with prefer-system strategy, or any query with doh/udp strategy).
117
- * The client is destroyed by stop().
232
+ * The client is terminated by stop().
118
233
  */
119
234
  private ensureDnsClient(): TSmartdnsClient {
120
235
  if (!this.dnsClient) {
@@ -136,9 +251,14 @@ export class SmartNetwork {
136
251
  }
137
252
 
138
253
  /**
139
- * Send ICMP pings to a host. Optionally specify count for multiple pings.
254
+ * Send ICMP echo requests to a host.
255
+ *
256
+ * A host that does not answer or cannot be resolved yields `alive: false`.
257
+ * Rejects with a `NetworkError` with code `EPERM` when the process may not
258
+ * open an ICMP socket (Linux needs `CAP_NET_RAW` or a `net.ipv4.ping_group_range`
259
+ * that includes the process group), because no probe was sent at all.
140
260
  */
141
- public async ping(host: string, opts?: { timeout?: number; count?: number }): Promise<any> {
261
+ public async ping(host: string, opts?: IPingOptions): Promise<IPingResult> {
142
262
  await this.ensureBridge();
143
263
  const timeoutMs = opts?.timeout ?? 5000;
144
264
  const count = opts?.count && opts.count > 1 ? opts.count : 1;
@@ -146,48 +266,43 @@ export class SmartNetwork {
146
266
  let result: Awaited<ReturnType<typeof this.rustBridge.ping>>;
147
267
  try {
148
268
  result = await this.rustBridge.ping(host, count, timeoutMs);
149
- } catch {
150
- // DNS resolution failure or other error — return dead ping
151
- if (count === 1) {
152
- return { alive: false, time: NaN };
269
+ } catch (error) {
270
+ if (error instanceof plugins.smartrust.RustBridgeRequestError) {
271
+ if (error.responseErrorCode === 'ERESOLVE') {
272
+ return {
273
+ host,
274
+ count,
275
+ alive: false,
276
+ time: null,
277
+ times: Array<number | null>(count).fill(null),
278
+ min: null,
279
+ max: null,
280
+ avg: null,
281
+ stddev: null,
282
+ packetLoss: 100,
283
+ };
284
+ }
285
+ if (error.responseErrorCode === 'EPERM') {
286
+ throw new NetworkError(
287
+ `ICMP ping is not permitted for this process (needs CAP_NET_RAW or a net.ipv4.ping_group_range that includes its group): ${error.message}`,
288
+ 'EPERM',
289
+ );
290
+ }
153
291
  }
154
- return {
155
- host,
156
- count,
157
- times: Array(count).fill(NaN),
158
- min: NaN,
159
- max: NaN,
160
- avg: NaN,
161
- stddev: NaN,
162
- packetLoss: 100,
163
- alive: false,
164
- };
165
- }
166
-
167
- // Map times: replace null with NaN for backward compatibility
168
- const times = result.times.map((t) => (t === null ? NaN : t));
169
- const min = result.min === null ? NaN : result.min;
170
- const max = result.max === null ? NaN : result.max;
171
- const avg = result.avg === null ? NaN : result.avg;
172
- const stddev = result.stddev === null ? NaN : result.stddev;
173
-
174
- if (count === 1) {
175
- return {
176
- alive: result.alive,
177
- time: times[0] ?? NaN,
178
- };
292
+ throw error;
179
293
  }
180
294
 
181
295
  return {
182
296
  host,
183
297
  count,
184
- times,
185
- min,
186
- max,
187
- avg,
188
- stddev,
189
- packetLoss: result.packetLoss,
190
298
  alive: result.alive,
299
+ time: result.times.find((time) => time !== null) ?? null,
300
+ times: result.times,
301
+ min: result.min,
302
+ max: result.max,
303
+ avg: result.avg,
304
+ stddev: result.stddev,
305
+ packetLoss: result.packetLoss,
191
306
  };
192
307
  }
193
308
 
@@ -348,31 +463,31 @@ export class SmartNetwork {
348
463
  /**
349
464
  * Resolve DNS records (A, AAAA, MX) via the shared smartdns client.
350
465
  * The client is lifecycle-managed by start()/stop() — MX queries spawn
351
- * the smartdns Rust bridge, which is torn down by stop().
466
+ * the smartdns Rust bridge, whose termination is confirmed by stop().
352
467
  */
353
468
  public async resolveDns(
354
469
  host: string,
355
470
  ): Promise<{ A: string[]; AAAA: string[]; MX: { exchange: string; priority: number }[] }> {
356
471
  try {
357
- const dnsClient = this.ensureDnsClient();
358
-
359
- const [aRecords, aaaaRecords, mxRecords] = await Promise.all([
360
- dnsClient.getRecordsA(host).catch((): any[] => []),
361
- dnsClient.getRecordsAAAA(host).catch((): any[] => []),
362
- dnsClient.getRecords(host, 'MX').catch((): any[] => []),
363
- ]);
364
-
365
- const A = aRecords.map((record: any) => record.value);
366
- const AAAA = aaaaRecords.map((record: any) => record.value);
367
- const MX = mxRecords.map((record: any) => {
368
- const parts = record.value.split(' ');
369
- return {
370
- priority: parseInt(parts[0], 10),
371
- exchange: parts[1] || '',
372
- };
472
+ return await this.withDnsClient(async (dnsClient) => {
473
+ const [aRecords, aaaaRecords, mxRecords] = await Promise.all([
474
+ dnsClient.getRecordsA(host).catch((): any[] => []),
475
+ dnsClient.getRecordsAAAA(host).catch((): any[] => []),
476
+ dnsClient.getRecords(host, 'MX').catch((): any[] => []),
477
+ ]);
478
+
479
+ const A = aRecords.map((record: any) => record.value);
480
+ const AAAA = aaaaRecords.map((record: any) => record.value);
481
+ const MX = mxRecords.map((record: any) => {
482
+ const parts = record.value.split(' ');
483
+ return {
484
+ priority: parseInt(parts[0], 10),
485
+ exchange: parts[1] || '',
486
+ };
487
+ });
488
+
489
+ return { A, AAAA, MX };
373
490
  });
374
-
375
- return { A, AAAA, MX };
376
491
  } catch (err: any) {
377
492
  throw new NetworkError(err.message, err.code);
378
493
  }
@@ -438,13 +553,15 @@ export class SmartNetwork {
438
553
  * Pure TS, no Rust needed.
439
554
  */
440
555
  public async getIpIntelligence(ip: string): Promise<IIpIntelligenceResult> {
441
- if (!this.ipIntelligence) {
442
- this.ipIntelligence = new IpIntelligence({
443
- dnsClient: this.ensureDnsClient(),
444
- timeout: this.options.ipIntelligenceTimeout,
445
- });
446
- }
447
- const fetcher = () => this.ipIntelligence!.getIntelligence(ip);
556
+ const fetcher = () => this.withDnsClient(async (dnsClient) => {
557
+ if (!this.ipIntelligence) {
558
+ this.ipIntelligence = new IpIntelligence({
559
+ dnsClient,
560
+ timeout: this.options.ipIntelligenceTimeout,
561
+ });
562
+ }
563
+ return this.ipIntelligence.getIntelligence(ip);
564
+ });
448
565
  if (this.options.cacheTtl && this.options.cacheTtl > 0) {
449
566
  return this.getCached(`ipIntelligence:${ip}`, fetcher);
450
567
  }
@@ -457,10 +574,12 @@ export class SmartNetwork {
457
574
  * Rust needed.
458
575
  */
459
576
  public async getDomainIntelligence(domain: string): Promise<IDomainIntelligenceResult> {
460
- if (!this.domainIntelligence) {
461
- this.domainIntelligence = new DomainIntelligence({ dnsClient: this.ensureDnsClient() });
462
- }
463
- const fetcher = () => this.domainIntelligence!.getIntelligence(domain);
577
+ const fetcher = () => this.withDnsClient(async (dnsClient) => {
578
+ if (!this.domainIntelligence) {
579
+ this.domainIntelligence = new DomainIntelligence({ dnsClient });
580
+ }
581
+ return this.domainIntelligence.getIntelligence(domain);
582
+ });
464
583
  if (this.options.cacheTtl && this.options.cacheTtl > 0) {
465
584
  return this.getCached(`domainIntelligence:${domain}`, fetcher);
466
585
  }
Binary file
Binary file
package/readme.hints.md DELETED
@@ -1,106 +0,0 @@
1
- # Project Analysis
2
-
3
- ## Architecture Overview
4
- This is a comprehensive network diagnostics toolkit. The main entry point is the `SmartNetwork` class which orchestrates all functionality. System-dependent operations are delegated to a Rust binary via JSON-over-stdin/stdout IPC using `@push.rocks/smartrust`.
5
-
6
- Key features:
7
- - Speed testing via Cloudflare (parallelizable with duration support) — pure TS
8
- - ICMP ping with statistics — Rust binary (surge-ping)
9
- - Port availability checks (local and remote) — Rust binary (tokio TCP / socket2)
10
- - Network gateway discovery — Rust binary (parses /proc/net/route on Linux)
11
- - Public IP retrieval — pure TS
12
- - DNS resolution via smartdns — pure TS
13
- - HTTP endpoint health checks — pure TS
14
- - Traceroute — Rust binary (raw ICMP sockets via socket2)
15
-
16
- ## Rust Binary Architecture (v4.5.0+)
17
-
18
- ### Binary: `rustnetwork`
19
- - Located in `rust/crates/rustnetwork/`
20
- - Cross-compiled for linux_amd64 and linux_arm64 via `@git.zone/tsrust`
21
- - Output: `dist_rust/rustnetwork_linux_amd64`, `dist_rust/rustnetwork_linux_arm64`
22
- - IPC mode: `--management` flag for JSON-over-stdin/stdout
23
-
24
- ### IPC Commands
25
- | Method | Description |
26
- |--------|-------------|
27
- | `healthPing` | Liveness check (returns `{ pong: true }`) |
28
- | `ping` | ICMP ping with count/timeout, returns stats |
29
- | `traceroute` | Hop-by-hop latency via raw ICMP sockets |
30
- | `tcpPortCheck` | TCP connect probe to remote host:port |
31
- | `isLocalPortFree` | Bind test on IPv4 + IPv6 |
32
- | `defaultGateway` | Parse /proc/net/route for default interface |
33
-
34
- ### TypeScript Bridge
35
- - `ts/smartnetwork.classes.rustbridge.ts` — Singleton `RustNetworkBridge` wrapping `smartrust.RustBridge`
36
- - `SmartNetwork` class has `start()`/`stop()` lifecycle for the bridge
37
- - `ensureBridge()` auto-starts on first use
38
-
39
- ### Build Pipeline
40
- - `pnpm build` = `tsbuild tsfolders && tsrust`
41
- - Targets configured in `.smartconfig.json` under `@git.zone/tsrust`
42
-
43
- ## Key Components
44
-
45
- ### SmartNetwork Class
46
- - Central orchestrator for all network operations
47
- - Requires `start()` before using ping/traceroute/port/gateway operations
48
- - Supports caching via `cacheTtl` option for gateway and public IP lookups
49
- - Plugin architecture for extensibility
50
-
51
- ### RustNetworkBridge Class
52
- - Singleton pattern via `getInstance()`
53
- - Binary search: dist_rust/ (platform-suffixed) > rust/target/release/ > rust/target/debug/
54
- - `searchSystemPath: false` — only looks in local paths
55
-
56
- ### CloudflareSpeed Class
57
- - Handles internet speed testing using Cloudflare's infrastructure
58
- - Supports parallel streams and customizable test duration
59
- - Upload speed measurement uses server-timing header with client-side timing fallback
60
-
61
- ### Error Handling
62
- - Custom `NetworkError` and `TimeoutError` classes for better error context
63
- - Error codes follow Node.js conventions (ENOTSUP, EINVAL, ETIMEOUT)
64
- - Ping/port methods gracefully degrade on errors (return alive=false / false)
65
-
66
- ### Logging
67
- - Global logger interface for consistent logging across the codebase
68
- - Replaceable logger implementation (defaults to console)
69
-
70
- ### Statistics Helpers
71
- - Utility functions for statistical calculations (average, median, quartile, jitter)
72
- - Used by speed testing; ping statistics now computed server-side in Rust
73
-
74
- ## Dependencies
75
- - **Runtime**: `@push.rocks/smartrust` (IPC bridge), `@push.rocks/smartdns` (DNS resolution)
76
- - **Removed in v4.5.0**: `@push.rocks/smartping`, `@push.rocks/smartstring`, `isopen`, `systeminformation`
77
- - **Dev**: `@git.zone/tsrust` (Rust cross-compilation)
78
-
79
- ## Testing
80
- - `test/test.ts` — Core smoke tests (speed, ports, gateways, public IPs)
81
- - `test/test.ping.ts` — ICMP ping tests (requires CAP_NET_RAW for alive=true)
82
- - `test/test.ports.ts` — Comprehensive port testing (27 tests)
83
- - `test/test.features.ts` — DNS, HTTP health check, traceroute, multi-ping, plugins, caching
84
- - All tests require `start()`/`stop()` lifecycle for the Rust bridge
85
-
86
- ## Technical Details
87
- - ESM-only package (module type)
88
- - TypeScript with strict typing
89
- - Node built-in imports use `node:` prefix throughout
90
- - Uses native Node.js modules for HTTP/HTTPS and os.networkInterfaces()
91
- - Rust binary requires no elevated privileges for port checks; ICMP ping needs CAP_NET_RAW or appropriate ping_group_range
92
-
93
- ## Design Patterns
94
- - Singleton pattern for RustNetworkBridge
95
- - Factory pattern for plugin registration
96
- - Caching pattern with TTL for expensive operations
97
- - Promise-based async/await throughout
98
- - Error propagation with custom error types
99
- - Graceful degradation when ICMP permissions unavailable
100
-
101
- ## Static Rust Binaries
102
-
103
- - The Rust binaries in `dist_rust/` are statically linked (static-pie) via `"static": true` in the `@git.zone/tsrust` block of `.smartconfig.json` (tsrust >= 1.4.1). They run on both glibc (Debian/Ubuntu) and musl (Alpine) systems.
104
- - tsrust injects `RUSTFLAGS="-C target-feature=+crt-static"` only into its own per-target cargo invocations and verifies the result (no `PT_INTERP` ELF header). Keep `rust/.cargo/config.toml` free of `rustflags` entries: the injected env variable would replace them, and a repo-wide `rustflags` would also break plain `cargo test`/`cargo check` (proc-macros cannot build with `+crt-static` on linux-gnu without an explicit `--target`).
105
- - `pnpm run test:rust` runs the Rust unit tests; plain `cargo test` in `rust/` works as well.
106
- - Verify linkage manually with `ldd dist_rust/<binary>_linux_amd64` → "statically linked".
package/ts/tsconfig.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "noImplicitAny": true
4
- }
5
- }
File without changes