about-system 0.10.8 → 0.10.10

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 (50) hide show
  1. package/dist/about-system-cli.d.ts.map +1 -0
  2. package/dist/about-system-cli.js +1 -1
  3. package/dist/cache/cache-config.d.ts +62 -0
  4. package/dist/cache/cache-config.d.ts.map +1 -0
  5. package/dist/cache/cache.d.ts +53 -0
  6. package/dist/cache/cache.d.ts.map +1 -0
  7. package/dist/index.d.ts +18 -52
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +1 -1
  10. package/dist/info/hardware.d.ts +76 -0
  11. package/dist/info/hardware.d.ts.map +1 -0
  12. package/dist/info/memory.d.ts +59 -0
  13. package/dist/info/memory.d.ts.map +1 -0
  14. package/dist/info/network.d.ts +54 -0
  15. package/dist/info/network.d.ts.map +1 -0
  16. package/dist/info/platform.d.ts +36 -0
  17. package/dist/info/platform.d.ts.map +1 -0
  18. package/dist/info/process.d.ts +23 -0
  19. package/dist/info/process.d.ts.map +1 -0
  20. package/dist/info/settings.d.ts +185 -0
  21. package/dist/info/settings.d.ts.map +1 -0
  22. package/dist/info/software.d.ts +33 -0
  23. package/dist/info/software.d.ts.map +1 -0
  24. package/dist/info/system-status.d.ts +36 -0
  25. package/dist/info/system-status.d.ts.map +1 -0
  26. package/dist/{system-info-api-NgTm8Gzo.js → system-info-api-ByXoHI0m.js} +314 -278
  27. package/dist/system-info-api-ByXoHI0m.js.map +1 -0
  28. package/dist/system-info-api.d.ts +62 -442
  29. package/dist/system-info-api.d.ts.map +1 -0
  30. package/dist/system-info-api.js +4 -5
  31. package/dist/system-info-api.js.map +1 -1
  32. package/dist/types/internal-types.d.ts +14 -0
  33. package/dist/types/internal-types.d.ts.map +1 -0
  34. package/dist/utils/command.d.ts +18 -0
  35. package/dist/utils/command.d.ts.map +1 -0
  36. package/dist/utils/network.d.ts +27 -0
  37. package/dist/utils/network.d.ts.map +1 -0
  38. package/dist/utils/platform.d.ts +11 -0
  39. package/dist/utils/platform.d.ts.map +1 -0
  40. package/dist/web-server.d.ts +21 -0
  41. package/dist/web-server.d.ts.map +1 -0
  42. package/package.json +1 -1
  43. package/src/cache/cache-config.ts +2 -2
  44. package/src/system-info-api.ts +2 -67
  45. package/src/utils/network.ts +104 -4
  46. package/src/utils/utils.test.ts +105 -38
  47. package/dist/system-info-api-NgTm8Gzo.js.map +0 -1
  48. package/dist/web/assets/index--HOsj3jw.js +0 -14
  49. package/dist/web/assets/index-BU_blZyu.css +0 -1
  50. package/dist/web/index.html +0 -13
@@ -17,7 +17,7 @@ export const CACHE_FILE = path.join(os.tmpdir(), "systeminfo-cache.json");
17
17
  * Values are in milliseconds
18
18
  *
19
19
  * @constant
20
- * @property {number} ip - IP information cache (5 minutes)
20
+ * @property {number} ip - IP information cache (10 minutes)
21
21
  * @property {number} cpu - CPU information cache (24 hours)
22
22
  * @property {number} gpu - GPU information cache (24 hours)
23
23
  * @property {number} os - OS information cache (24 hours)
@@ -36,7 +36,7 @@ export const CACHE_FILE = path.join(os.tmpdir(), "systeminfo-cache.json");
36
36
  * @property {number} mount_points - Mount points cache (10 minutes)
37
37
  */
38
38
  export const CACHE_DURATION = {
39
- ip: 5 * 60 * 1000,
39
+ ip: 10 * 60 * 1000,
40
40
  cpu: 24 * 60 * 60 * 1000,
41
41
  gpu: 24 * 60 * 60 * 1000,
42
42
  os: 24 * 60 * 60 * 1000,
@@ -11,7 +11,7 @@
11
11
 
12
12
  import os from "os";
13
13
  import fs from "fs";
14
- import https from "https";
14
+ import { fetchIPInfo } from "./utils/network";
15
15
  import path from "path";
16
16
  import type { SystemInfo, SystemInfoOptions } from "./systeminfo-types";
17
17
  import { CACHE_FILE } from "./info/settings"; // Using CACHE_FILE from settings to ensure consistency
@@ -50,7 +50,7 @@ import { shell, packages, containers } from "./info/software";
50
50
  * Values are in milliseconds
51
51
  */
52
52
  const CACHE_DURATION = {
53
- ip: 5 * 60 * 1000,
53
+ ip: 10 * 60 * 1000,
54
54
  cpu: 24 * 60 * 60 * 1000,
55
55
  gpu: 24 * 60 * 60 * 1000,
56
56
  bench: 24 * 60 * 60 * 1000,
@@ -81,41 +81,6 @@ const IS_WINDOWS = os.platform() === "win32";
81
81
  const IS_MAC = os.platform() === "darwin";
82
82
  const IS_LINUX = os.platform() === "linux";
83
83
 
84
- /**
85
- * Default IPInfo.io API token for geolocation
86
- */
87
- const DEFAULT_IPINFO_TOKEN = "da2d6cc4baa5d1";
88
-
89
- /**
90
- * Default network request timeout in milliseconds
91
- */
92
- const DEFAULT_NETWORK_TIMEOUT = 5000;
93
-
94
- /**
95
- * Represents a cached value with timestamp
96
- */
97
- interface CacheEntry {
98
- value: any;
99
- timestamp: number;
100
- }
101
-
102
- /**
103
- * Cache storage structure
104
- */
105
- interface Cache {
106
- [key: string]: CacheEntry;
107
- }
108
-
109
- /**
110
- * IP information from ipinfo.io API
111
- */
112
- interface IPInfo {
113
- ip?: string;
114
- city?: string;
115
- hostname?: string;
116
- org?: string;
117
- }
118
-
119
84
  /**
120
85
  * Context object passed to info collection functions
121
86
  */
@@ -153,36 +118,6 @@ function saveCache(cache: Cache): void {
153
118
  }
154
119
  }
155
120
 
156
- /**
157
- * Fetches IP geolocation information from ipinfo.io API
158
- */
159
- async function fetchIPInfo(
160
- token: string = DEFAULT_IPINFO_TOKEN,
161
- timeout: number = DEFAULT_NETWORK_TIMEOUT
162
- ): Promise<IPInfo> {
163
- return new Promise((resolve) => {
164
- const url = `https://ipinfo.io/json${token ? `?token=${token}` : ""}`;
165
-
166
- const req = https.get(url, (res) => {
167
- let data = "";
168
- res.on("data", (chunk) => (data += chunk));
169
- res.on("end", () => {
170
- try {
171
- resolve(JSON.parse(data));
172
- } catch {
173
- resolve({});
174
- }
175
- });
176
- });
177
-
178
- req.on("error", () => resolve({}));
179
- req.setTimeout(timeout, () => {
180
- req.destroy();
181
- resolve({});
182
- });
183
- });
184
- }
185
-
186
121
  /**
187
122
  * System information collection functions map
188
123
  */
@@ -25,24 +25,86 @@ export interface IPInfo {
25
25
  }
26
26
 
27
27
  /**
28
- * Fetches IP geolocation information from ipinfo.io API
28
+ * IP information from ip-api.com (fallback)
29
+ * @interface IPAPIInfo
30
+ */
31
+ interface IPAPIInfo {
32
+ query?: string;
33
+ city?: string;
34
+ isp?: string;
35
+ org?: string;
36
+ as?: string;
37
+ }
38
+
39
+ /**
40
+ * Fetches IP geolocation information from ipinfo.io lite API
29
41
  * @param {string} token - IPInfo.io API token
30
42
  * @param {number} timeout - Request timeout in milliseconds
31
43
  * @returns {Promise<IPInfo>} IP information object or empty object on error
32
44
  */
33
- export async function fetchIPInfo(
45
+ async function fetchFromIPInfo(
34
46
  token: string = DEFAULT_IPINFO_TOKEN,
35
47
  timeout: number = DEFAULT_NETWORK_TIMEOUT
36
48
  ): Promise<IPInfo> {
37
49
  return new Promise((resolve) => {
38
- const url = `https://ipinfo.io/json${token ? `?token=${token}` : ""}`;
50
+ const url = `https://api.ipinfo.io/lite/8.8.8.8?token=${token}`;
51
+
52
+ const req = https.get(url, (res) => {
53
+ let data = "";
54
+ res.on("data", (chunk) => (data += chunk));
55
+ res.on("end", () => {
56
+ try {
57
+ const parsed = JSON.parse(data);
58
+ // ipinfo lite returns: { ip, city, region, country, org }
59
+ // Map to our standard IPInfo format
60
+ resolve({
61
+ ip: parsed.ip,
62
+ city: parsed.city,
63
+ hostname: parsed.hostname,
64
+ org: parsed.org,
65
+ });
66
+ } catch {
67
+ resolve({});
68
+ }
69
+ });
70
+ });
71
+
72
+ req.on("error", () => resolve({}));
73
+ req.setTimeout(timeout, () => {
74
+ req.destroy();
75
+ resolve({});
76
+ });
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Fetches IP geolocation information from ip-api.com (fallback)
82
+ * @param {number} timeout - Request timeout in milliseconds
83
+ * @returns {Promise<IPInfo>} IP information object or empty object on error
84
+ */
85
+ async function fetchFromIPAPI(
86
+ timeout: number = DEFAULT_NETWORK_TIMEOUT
87
+ ): Promise<IPInfo> {
88
+ return new Promise((resolve) => {
89
+ const url = "http://ip-api.com/json/?fields=status,message,query,city,isp,org,as";
39
90
 
40
91
  const req = https.get(url, (res) => {
41
92
  let data = "";
42
93
  res.on("data", (chunk) => (data += chunk));
43
94
  res.on("end", () => {
44
95
  try {
45
- resolve(JSON.parse(data));
96
+ const parsed: IPAPIInfo = JSON.parse(data);
97
+ if (parsed.status === "success") {
98
+ // Map ip-api response to our standard IPInfo format
99
+ resolve({
100
+ ip: parsed.query,
101
+ city: parsed.city,
102
+ hostname: undefined,
103
+ org: parsed.org || parsed.isp || parsed.as,
104
+ });
105
+ } else {
106
+ resolve({});
107
+ }
46
108
  } catch {
47
109
  resolve({});
48
110
  }
@@ -56,3 +118,41 @@ export async function fetchIPInfo(
56
118
  });
57
119
  });
58
120
  }
121
+
122
+ /**
123
+ * Fetches IP geolocation information using race between ipinfo.io and ip-api.com
124
+ * Uses ipinfo.io lite endpoint first, falls back to ip-api.com if slower or fails
125
+ * @param {string} token - IPInfo.io API token
126
+ * @param {number} timeout - Request timeout in milliseconds
127
+ * @returns {Promise<IPInfo>} IP information object or empty object on error
128
+ */
129
+ export async function fetchIPInfo(
130
+ token: string = DEFAULT_IPINFO_TOKEN,
131
+ timeout: number = DEFAULT_NETWORK_TIMEOUT
132
+ ): Promise<IPInfo> {
133
+ // Race both services, use whichever responds first
134
+ const [ipinfoResult, ipapiResult] = await Promise.allSettled([
135
+ fetchFromIPInfo(token, timeout),
136
+ fetchFromIPAPI(timeout),
137
+ ]);
138
+
139
+ // Prefer ipinfo.io result if successful and has data
140
+ if (ipinfoResult.status === "fulfilled" && ipinfoResult.value && ipinfoResult.value.ip) {
141
+ return ipinfoResult.value;
142
+ }
143
+
144
+ // Fall back to ip-api.com result if successful and has data
145
+ if (ipapiResult.status === "fulfilled" && ipapiResult.value && ipapiResult.value.ip) {
146
+ return ipapiResult.value;
147
+ }
148
+
149
+ // Return whichever has data, or empty object
150
+ if (ipinfoResult.status === "fulfilled" && ipinfoResult.value) {
151
+ return ipinfoResult.value;
152
+ }
153
+ if (ipapiResult.status === "fulfilled" && ipapiResult.value) {
154
+ return ipapiResult.value;
155
+ }
156
+
157
+ return {};
158
+ }
@@ -25,8 +25,8 @@ vi.mock("https", () => ({
25
25
 
26
26
  import { commandExists, execCommand } from "./command";
27
27
  import { IS_LINUX, IS_MAC, IS_WINDOWS } from "./platform";
28
- import { fetchIPInfo } from "./network";
29
- import { DEFAULT_IPINFO_TOKEN } from "../cache/cache-config";
28
+ import { fetchIPInfo, fetchFromIPInfo, fetchFromIPAPI } from "./network";
29
+ import { DEFAULT_IPINFO_TOKEN, DEFAULT_NETWORK_TIMEOUT } from "../cache/cache-config";
30
30
 
31
31
  afterEach(() => {
32
32
  execSync.mockReset();
@@ -142,59 +142,34 @@ const respondWith = (body: string) =>
142
142
  res.emit("end");
143
143
  });
144
144
 
145
- describe("fetchIPInfo", () => {
146
- it("parses the ipinfo.io payload", async () => {
147
- respondWith(JSON.stringify({ ip: "1.2.3.4", city: "San Francisco" }));
145
+ describe("fetchFromIPInfo", () => {
146
+ it("parses the ipinfo.io lite payload", async () => {
147
+ respondWith(JSON.stringify({ ip: "1.2.3.4", city: "San Francisco", org: "AS123 Test ISP" }));
148
148
 
149
- await expect(fetchIPInfo()).resolves.toEqual({
149
+ await expect(fetchFromIPInfo()).resolves.toEqual({
150
150
  ip: "1.2.3.4",
151
151
  city: "San Francisco",
152
+ org: "AS123 Test ISP",
152
153
  });
153
154
  });
154
155
 
155
- it("sends the default token when none is given", async () => {
156
+ it("sends request to ipinfo.io lite endpoint with token", async () => {
156
157
  const get = respondWith("{}");
157
- await fetchIPInfo();
158
+ await fetchFromIPInfo();
158
159
 
159
160
  expect(get.mock.calls[0][0]).toBe(
160
- `https://ipinfo.io/json?token=${DEFAULT_IPINFO_TOKEN}`,
161
+ `https://api.ipinfo.io/lite/8.8.8.8?token=${DEFAULT_IPINFO_TOKEN}`,
161
162
  );
162
163
  });
163
164
 
164
- it("sends a caller-supplied token", async () => {
165
- const get = respondWith("{}");
166
- await fetchIPInfo("my-token");
167
-
168
- expect(get.mock.calls[0][0]).toContain("token=my-token");
169
- });
170
-
171
- it("omits the query string entirely for an empty token", async () => {
172
- const get = respondWith("{}");
173
- await fetchIPInfo("");
174
-
175
- expect(get.mock.calls[0][0]).toBe("https://ipinfo.io/json");
176
- });
177
-
178
- it("reassembles a body that arrives in chunks", async () => {
179
- stubHttpsGet((_req, onResponse) => {
180
- const res = new EventEmitter();
181
- onResponse(res);
182
- res.emit("data", '{"ip":"1.2');
183
- res.emit("data", '.3.4"}');
184
- res.emit("end");
185
- });
186
-
187
- await expect(fetchIPInfo()).resolves.toEqual({ ip: "1.2.3.4" });
188
- });
189
-
190
165
  it("resolves empty rather than rejecting on a malformed body", async () => {
191
166
  respondWith("<html>gateway error</html>");
192
- await expect(fetchIPInfo()).resolves.toEqual({});
167
+ await expect(fetchFromIPInfo()).resolves.toEqual({});
193
168
  });
194
169
 
195
170
  it("resolves empty rather than rejecting when the request errors", async () => {
196
171
  stubHttpsGet((req) => req.emit("error", new Error("ENOTFOUND")));
197
- await expect(fetchIPInfo()).resolves.toEqual({});
172
+ await expect(fetchFromIPInfo()).resolves.toEqual({});
198
173
  });
199
174
 
200
175
  it("arms a timeout that abandons the request", async () => {
@@ -209,10 +184,102 @@ describe("fetchIPInfo", () => {
209
184
  return req;
210
185
  });
211
186
 
212
- const pending = fetchIPInfo("t", 1234);
187
+ const pending = fetchFromIPInfo(DEFAULT_IPINFO_TOKEN, 1234);
213
188
  expect(armed?.ms).toBe(1234);
214
189
 
215
190
  armed?.fire();
216
191
  await expect(pending).resolves.toEqual({});
217
192
  });
218
193
  });
194
+
195
+ describe("fetchFromIPAPI", () => {
196
+ it("parses the ip-api.com payload", async () => {
197
+ respondWith(JSON.stringify({
198
+ status: "success",
199
+ query: "1.2.3.4",
200
+ city: "San Francisco",
201
+ isp: "Test ISP",
202
+ org: "AS123 Test Organization",
203
+ as: "AS123 Test AS"
204
+ }));
205
+
206
+ await expect(fetchFromIPAPI()).resolves.toEqual({
207
+ ip: "1.2.3.4",
208
+ city: "San Francisco",
209
+ org: "AS123 Test Organization",
210
+ });
211
+ });
212
+
213
+ it("sends request to ip-api.com endpoint", async () => {
214
+ const get = respondWith('{"status":"success"}');
215
+ await fetchFromIPAPI();
216
+
217
+ expect(get.mock.calls[0][0]).toBe(
218
+ "http://ip-api.com/json/?fields=status,message,query,city,isp,org,as"
219
+ );
220
+ });
221
+
222
+ it("returns empty object on failed status", async () => {
223
+ respondWith(JSON.stringify({ status: "fail", message: "private range" }));
224
+ await expect(fetchFromIPAPI()).resolves.toEqual({});
225
+ });
226
+
227
+ it("resolves empty rather than rejecting on a malformed body", async () => {
228
+ respondWith("<html>gateway error</html>");
229
+ await expect(fetchFromIPAPI()).resolves.toEqual({});
230
+ });
231
+ });
232
+
233
+ describe("fetchIPInfo (race)", () => {
234
+ it("returns ipinfo result when it responds first with data", async () => {
235
+ let ipinfoResolve: (value: any) => void;
236
+ let ipapiResolve: (value: any) => void;
237
+
238
+ const ipinfoPromise = new Promise((resolve) => { ipinfoResolve = resolve; });
239
+ const ipapiPromise = new Promise((resolve) => { ipapiResolve = resolve; });
240
+
241
+ vi.spyOn(await import("./network"), "fetchFromIPInfo").mockReturnValue(ipinfoPromise);
242
+ vi.spyOn(await import("./network"), "fetchFromIPAPI").mockReturnValue(ipapiPromise);
243
+
244
+ const resultPromise = fetchIPInfo();
245
+
246
+ // Resolve ipinfo first with data
247
+ ipinfoResolve!({ ip: "1.2.3.4", city: "San Francisco" });
248
+ ipapiResolve!({ ip: "5.6.7.8", city: "New York" });
249
+
250
+ await expect(resultPromise).resolves.toEqual({
251
+ ip: "1.2.3.4",
252
+ city: "San Francisco",
253
+ });
254
+ });
255
+
256
+ it("falls back to ip-api when ipinfo fails", async () => {
257
+ let ipinfoResolve: (value: any) => void;
258
+ let ipapiResolve: (value: any) => void;
259
+
260
+ const ipinfoPromise = new Promise((resolve) => { ipinfoResolve = resolve; });
261
+ const ipapiPromise = new Promise((resolve) => { ipapiResolve = resolve; });
262
+
263
+ vi.spyOn(await import("./network"), "fetchFromIPInfo").mockReturnValue(ipinfoPromise);
264
+ vi.spyOn(await import("./network"), "fetchFromIPAPI").mockReturnValue(ipapiPromise);
265
+
266
+ const resultPromise = fetchIPInfo();
267
+
268
+ // Resolve ipinfo with empty, ipapi with data
269
+ ipinfoResolve!({});
270
+ ipapiResolve!({ ip: "5.6.7.8", city: "New York", org: "AS456 Fallback ISP" });
271
+
272
+ await expect(resultPromise).resolves.toEqual({
273
+ ip: "5.6.7.8",
274
+ city: "New York",
275
+ org: "AS456 Fallback ISP",
276
+ });
277
+ });
278
+
279
+ it("returns empty when both fail", async () => {
280
+ vi.spyOn(await import("./network"), "fetchFromIPInfo").mockResolvedValue({});
281
+ vi.spyOn(await import("./network"), "fetchFromIPAPI").mockResolvedValue({});
282
+
283
+ await expect(fetchIPInfo()).resolves.toEqual({});
284
+ });
285
+ });