about-system 0.10.10 → 0.10.11
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 +3 -1
- package/dist/about-system-cli.js +69 -54
- package/dist/about-system-cli.js.map +1 -1
- package/dist/index.d.ts +64 -18
- package/dist/index.js +1 -1
- package/dist/system-info-api-D85U-_0W.js +2473 -0
- package/dist/system-info-api-D85U-_0W.js.map +1 -0
- package/dist/system-info-api.d.ts +432 -62
- package/dist/system-info-api.js +1 -1
- package/dist/web/assets/index--HOsj3jw.js +14 -0
- package/dist/web/assets/index-BU_blZyu.css +1 -0
- package/dist/web/index.html +13 -0
- package/package.json +1 -1
- package/src/about-system-cli.ts +42 -3
- package/src/cache/cache-config.ts +3 -3
- package/src/info/network.ts +14 -47
- package/src/system-info-api.test.ts +106 -0
- package/src/system-info-api.ts +67 -28
- package/src/systeminfo-types.d.ts +6 -0
- package/src/utils/network.ts +113 -94
- package/src/utils/utils.test.ts +84 -75
- package/dist/about-system-cli.d.ts.map +0 -1
- package/dist/cache/cache-config.d.ts +0 -62
- package/dist/cache/cache-config.d.ts.map +0 -1
- package/dist/cache/cache.d.ts +0 -53
- package/dist/cache/cache.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/info/hardware.d.ts +0 -76
- package/dist/info/hardware.d.ts.map +0 -1
- package/dist/info/memory.d.ts +0 -59
- package/dist/info/memory.d.ts.map +0 -1
- package/dist/info/network.d.ts +0 -54
- package/dist/info/network.d.ts.map +0 -1
- package/dist/info/platform.d.ts +0 -36
- package/dist/info/platform.d.ts.map +0 -1
- package/dist/info/process.d.ts +0 -23
- package/dist/info/process.d.ts.map +0 -1
- package/dist/info/settings.d.ts +0 -185
- package/dist/info/settings.d.ts.map +0 -1
- package/dist/info/software.d.ts +0 -33
- package/dist/info/software.d.ts.map +0 -1
- package/dist/info/system-status.d.ts +0 -36
- package/dist/info/system-status.d.ts.map +0 -1
- package/dist/system-info-api-ByXoHI0m.js +0 -2464
- package/dist/system-info-api-ByXoHI0m.js.map +0 -1
- package/dist/system-info-api.d.ts.map +0 -1
- package/dist/types/internal-types.d.ts +0 -14
- package/dist/types/internal-types.d.ts.map +0 -1
- package/dist/utils/command.d.ts +0 -18
- package/dist/utils/command.d.ts.map +0 -1
- package/dist/utils/network.d.ts +0 -27
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/platform.d.ts +0 -11
- package/dist/utils/platform.d.ts.map +0 -1
- package/dist/web-server.d.ts +0 -21
- package/dist/web-server.d.ts.map +0 -1
package/src/utils/network.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @module network
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import http from "http";
|
|
6
7
|
import https from "https";
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_IPINFO_TOKEN,
|
|
@@ -29,6 +30,7 @@ export interface IPInfo {
|
|
|
29
30
|
* @interface IPAPIInfo
|
|
30
31
|
*/
|
|
31
32
|
interface IPAPIInfo {
|
|
33
|
+
status?: string;
|
|
32
34
|
query?: string;
|
|
33
35
|
city?: string;
|
|
34
36
|
isp?: string;
|
|
@@ -37,122 +39,139 @@ interface IPAPIInfo {
|
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @returns {Promise<IPInfo>} IP information object or empty object on error
|
|
42
|
+
* GETs a URL and parses the body as JSON. Never rejects: resolves `null` on
|
|
43
|
+
* any error, on abort, or once `timeout` ms have passed in total — a hard
|
|
44
|
+
* deadline for the whole request, not a socket-idle timeout.
|
|
44
45
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
timeout: number
|
|
48
|
-
|
|
46
|
+
function getJSON(
|
|
47
|
+
url: string,
|
|
48
|
+
timeout: number,
|
|
49
|
+
signal?: AbortSignal
|
|
50
|
+
): Promise<any> {
|
|
49
51
|
return new Promise((resolve) => {
|
|
50
|
-
|
|
52
|
+
if (signal?.aborted) return resolve(null);
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
resolve({});
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
});
|
|
54
|
+
let settled = false;
|
|
55
|
+
let req: http.ClientRequest | undefined;
|
|
56
|
+
const finish = (value: any) => {
|
|
57
|
+
if (settled) return;
|
|
58
|
+
settled = true;
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
signal?.removeEventListener("abort", onAbort);
|
|
61
|
+
resolve(value);
|
|
62
|
+
};
|
|
63
|
+
const onAbort = () => {
|
|
64
|
+
req?.destroy();
|
|
65
|
+
finish(null);
|
|
66
|
+
};
|
|
67
|
+
const timer = setTimeout(onAbort, timeout);
|
|
68
|
+
signal?.addEventListener("abort", onAbort);
|
|
71
69
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
req
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
try {
|
|
71
|
+
const get = url.startsWith("https:") ? https.get : http.get;
|
|
72
|
+
req = get(url, (res) => {
|
|
73
|
+
let data = "";
|
|
74
|
+
res.on("data", (chunk) => (data += chunk));
|
|
75
|
+
res.on("end", () => {
|
|
76
|
+
try {
|
|
77
|
+
finish(JSON.parse(data));
|
|
78
|
+
} catch {
|
|
79
|
+
finish(null);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
res.on("error", () => finish(null));
|
|
83
|
+
});
|
|
84
|
+
req.on("error", () => finish(null));
|
|
85
|
+
} catch {
|
|
86
|
+
finish(null);
|
|
87
|
+
}
|
|
77
88
|
});
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
/**
|
|
81
|
-
* Fetches IP
|
|
82
|
-
* @param {
|
|
92
|
+
* Fetches IP information for this machine from the ipinfo.io lite API
|
|
93
|
+
* @param {string} token - IPInfo.io API token
|
|
94
|
+
* @param {number} timeout - Total request deadline in milliseconds
|
|
95
|
+
* @param {AbortSignal} signal - Abandons the request when aborted
|
|
83
96
|
* @returns {Promise<IPInfo>} IP information object or empty object on error
|
|
84
97
|
*/
|
|
85
|
-
async function
|
|
86
|
-
|
|
98
|
+
export async function fetchFromIPInfo(
|
|
99
|
+
token: string = DEFAULT_IPINFO_TOKEN,
|
|
100
|
+
timeout: number = DEFAULT_NETWORK_TIMEOUT,
|
|
101
|
+
signal?: AbortSignal
|
|
87
102
|
): Promise<IPInfo> {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
105
|
-
} else {
|
|
106
|
-
resolve({});
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
resolve({});
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
});
|
|
103
|
+
const parsed = await getJSON(
|
|
104
|
+
`https://api.ipinfo.io/lite/me?token=${token}`,
|
|
105
|
+
timeout,
|
|
106
|
+
signal
|
|
107
|
+
);
|
|
108
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
109
|
+
// ipinfo lite returns { ip, asn, as_name, country, ... } — no city.
|
|
110
|
+
const org =
|
|
111
|
+
parsed.org ?? (parsed.asn && parsed.as_name ? `${parsed.asn} ${parsed.as_name}` : undefined);
|
|
112
|
+
return {
|
|
113
|
+
ip: parsed.ip,
|
|
114
|
+
city: parsed.city,
|
|
115
|
+
hostname: parsed.hostname,
|
|
116
|
+
org,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Fetches IP geolocation information from ip-api.com (fallback). The free
|
|
122
|
+
* tier is plain HTTP only.
|
|
123
|
+
* @param {number} timeout - Total request deadline in milliseconds
|
|
124
|
+
* @param {AbortSignal} signal - Abandons the request when aborted
|
|
125
|
+
* @returns {Promise<IPInfo>} IP information object or empty object on error
|
|
126
|
+
*/
|
|
127
|
+
export async function fetchFromIPAPI(
|
|
128
|
+
timeout: number = DEFAULT_NETWORK_TIMEOUT,
|
|
129
|
+
signal?: AbortSignal
|
|
130
|
+
): Promise<IPInfo> {
|
|
131
|
+
const parsed: IPAPIInfo | null = await getJSON(
|
|
132
|
+
"http://ip-api.com/json/?fields=status,message,query,city,isp,org,as",
|
|
133
|
+
timeout,
|
|
134
|
+
signal
|
|
135
|
+
);
|
|
136
|
+
if (!parsed || parsed.status !== "success") return {};
|
|
137
|
+
return {
|
|
138
|
+
ip: parsed.query,
|
|
139
|
+
city: parsed.city,
|
|
140
|
+
hostname: undefined,
|
|
141
|
+
org: parsed.org || parsed.isp || parsed.as,
|
|
142
|
+
};
|
|
120
143
|
}
|
|
121
144
|
|
|
122
145
|
/**
|
|
123
|
-
* Fetches IP
|
|
124
|
-
*
|
|
146
|
+
* Fetches IP information from ipinfo.io and ip-api.com in parallel and merges
|
|
147
|
+
* them: ipinfo.io wins per field, ip-api.com fills the gaps (ipinfo lite has
|
|
148
|
+
* no city). Both requests share the same deadline, so this never takes longer
|
|
149
|
+
* than `timeout`.
|
|
125
150
|
* @param {string} token - IPInfo.io API token
|
|
126
|
-
* @param {number} timeout -
|
|
151
|
+
* @param {number} timeout - Total deadline in milliseconds
|
|
152
|
+
* @param {AbortSignal} signal - Abandons both requests when aborted
|
|
127
153
|
* @returns {Promise<IPInfo>} IP information object or empty object on error
|
|
128
154
|
*/
|
|
129
155
|
export async function fetchIPInfo(
|
|
130
156
|
token: string = DEFAULT_IPINFO_TOKEN,
|
|
131
|
-
timeout: number = DEFAULT_NETWORK_TIMEOUT
|
|
157
|
+
timeout: number = DEFAULT_NETWORK_TIMEOUT,
|
|
158
|
+
signal?: AbortSignal
|
|
132
159
|
): Promise<IPInfo> {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
fetchFromIPAPI(timeout),
|
|
160
|
+
const [ipinfo, ipapi] = await Promise.all([
|
|
161
|
+
fetchFromIPInfo(token, timeout, signal),
|
|
162
|
+
fetchFromIPAPI(timeout, signal),
|
|
137
163
|
]);
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
165
|
+
const merged: IPInfo = {};
|
|
166
|
+
for (const source of [ipapi, ipinfo]) {
|
|
167
|
+
for (const [key, value] of Object.entries(source)) {
|
|
168
|
+
if (value) merged[key as keyof IPInfo] = value;
|
|
169
|
+
}
|
|
142
170
|
}
|
|
143
|
-
|
|
144
|
-
//
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Return whichever has data, or empty object
|
|
150
|
-
if (ipinfoResult.status === "fulfilled" && ipinfoResult.value) {
|
|
151
|
-
return ipinfoResult.value;
|
|
171
|
+
// ipinfo answers over IPv6 when the machine has it; the short IPv4 address
|
|
172
|
+
// reads better in a one-line greeting, so keep ip-api's when it has one.
|
|
173
|
+
if (merged.ip?.includes(":") && ipapi.ip && !ipapi.ip.includes(":")) {
|
|
174
|
+
merged.ip = ipapi.ip;
|
|
152
175
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return {};
|
|
158
|
-
}
|
|
176
|
+
return merged;
|
|
177
|
+
}
|
package/src/utils/utils.test.ts
CHANGED
|
@@ -22,6 +22,11 @@ vi.mock("https", () => ({
|
|
|
22
22
|
get: httpsGet,
|
|
23
23
|
default: { get: httpsGet },
|
|
24
24
|
}));
|
|
25
|
+
const httpGet = vi.hoisted(() => vi.fn());
|
|
26
|
+
vi.mock("http", () => ({
|
|
27
|
+
get: httpGet,
|
|
28
|
+
default: { get: httpGet },
|
|
29
|
+
}));
|
|
25
30
|
|
|
26
31
|
import { commandExists, execCommand } from "./command";
|
|
27
32
|
import { IS_LINUX, IS_MAC, IS_WINDOWS } from "./platform";
|
|
@@ -31,7 +36,9 @@ import { DEFAULT_IPINFO_TOKEN, DEFAULT_NETWORK_TIMEOUT } from "../cache/cache-co
|
|
|
31
36
|
afterEach(() => {
|
|
32
37
|
execSync.mockReset();
|
|
33
38
|
httpsGet.mockReset();
|
|
39
|
+
httpGet.mockReset();
|
|
34
40
|
vi.restoreAllMocks();
|
|
41
|
+
vi.useRealTimers();
|
|
35
42
|
});
|
|
36
43
|
|
|
37
44
|
describe("platform flags", () => {
|
|
@@ -118,24 +125,27 @@ describe("commandExists", () => {
|
|
|
118
125
|
});
|
|
119
126
|
});
|
|
120
127
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
type FakeReq = EventEmitter & { destroy: () => void };
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A fake `get` (https or http) whose response a test controls. `behavior`
|
|
132
|
+
* runs on the next microtask; leave it empty to simulate a hung request.
|
|
133
|
+
*/
|
|
134
|
+
function stubGet(
|
|
135
|
+
get: typeof httpsGet,
|
|
136
|
+
behavior: (req: FakeReq, onResponse: (res: EventEmitter) => void) => void = () => {},
|
|
137
|
+
) {
|
|
138
|
+
get.mockImplementation((_url: string, callback: (res: EventEmitter) => void) => {
|
|
139
|
+
const req: FakeReq = Object.assign(new EventEmitter(), { destroy: vi.fn() });
|
|
140
|
+
queueMicrotask(() => behavior(req, callback));
|
|
141
|
+
return req;
|
|
142
|
+
});
|
|
143
|
+
return get;
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
/** Emits a complete response body on the next tick. */
|
|
137
|
-
const respondWith = (body: string) =>
|
|
138
|
-
|
|
147
|
+
const respondWith = (get: typeof httpsGet, body: string) =>
|
|
148
|
+
stubGet(get, (_req, onResponse) => {
|
|
139
149
|
const res = new EventEmitter();
|
|
140
150
|
onResponse(res);
|
|
141
151
|
res.emit("data", body);
|
|
@@ -144,57 +154,61 @@ const respondWith = (body: string) =>
|
|
|
144
154
|
|
|
145
155
|
describe("fetchFromIPInfo", () => {
|
|
146
156
|
it("parses the ipinfo.io lite payload", async () => {
|
|
147
|
-
respondWith(JSON.stringify({ ip: "1.2.3.4",
|
|
157
|
+
respondWith(httpsGet, JSON.stringify({ ip: "1.2.3.4", asn: "AS123", as_name: "Test ISP", country: "US" }));
|
|
148
158
|
|
|
149
159
|
await expect(fetchFromIPInfo()).resolves.toEqual({
|
|
150
160
|
ip: "1.2.3.4",
|
|
151
|
-
city: "San Francisco",
|
|
152
161
|
org: "AS123 Test ISP",
|
|
153
162
|
});
|
|
154
163
|
});
|
|
155
164
|
|
|
156
|
-
it("
|
|
157
|
-
const get = respondWith("{}");
|
|
165
|
+
it("asks about this machine, not a fixed address", async () => {
|
|
166
|
+
const get = respondWith(httpsGet, "{}");
|
|
158
167
|
await fetchFromIPInfo();
|
|
159
168
|
|
|
160
169
|
expect(get.mock.calls[0][0]).toBe(
|
|
161
|
-
`https://api.ipinfo.io/lite/
|
|
170
|
+
`https://api.ipinfo.io/lite/me?token=${DEFAULT_IPINFO_TOKEN}`,
|
|
162
171
|
);
|
|
163
172
|
});
|
|
164
173
|
|
|
165
174
|
it("resolves empty rather than rejecting on a malformed body", async () => {
|
|
166
|
-
respondWith("<html>gateway error</html>");
|
|
175
|
+
respondWith(httpsGet, "<html>gateway error</html>");
|
|
167
176
|
await expect(fetchFromIPInfo()).resolves.toEqual({});
|
|
168
177
|
});
|
|
169
178
|
|
|
170
179
|
it("resolves empty rather than rejecting when the request errors", async () => {
|
|
171
|
-
|
|
180
|
+
stubGet(httpsGet, (req) => req.emit("error", new Error("ENOTFOUND")));
|
|
172
181
|
await expect(fetchFromIPInfo()).resolves.toEqual({});
|
|
173
182
|
});
|
|
174
183
|
|
|
175
|
-
it("
|
|
176
|
-
|
|
177
|
-
httpsGet
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
armed = { ms, fire };
|
|
181
|
-
},
|
|
182
|
-
destroy: vi.fn(),
|
|
183
|
-
});
|
|
184
|
-
return req;
|
|
185
|
-
});
|
|
184
|
+
it("gives up after a hard 3 second deadline by default", async () => {
|
|
185
|
+
vi.useFakeTimers();
|
|
186
|
+
stubGet(httpsGet);
|
|
187
|
+
let done = false;
|
|
188
|
+
const pending = fetchFromIPInfo().then((r) => ((done = true), r));
|
|
186
189
|
|
|
187
|
-
|
|
188
|
-
expect(
|
|
190
|
+
await vi.advanceTimersByTimeAsync(DEFAULT_NETWORK_TIMEOUT - 1);
|
|
191
|
+
expect(done).toBe(false);
|
|
192
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
193
|
+
await expect(pending).resolves.toEqual({});
|
|
194
|
+
expect(DEFAULT_NETWORK_TIMEOUT).toBe(3000);
|
|
195
|
+
expect(httpsGet.mock.results[0].value.destroy).toHaveBeenCalled();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("abandons the request when the signal aborts", async () => {
|
|
199
|
+
stubGet(httpsGet);
|
|
200
|
+
const controller = new AbortController();
|
|
201
|
+
const pending = fetchFromIPInfo(DEFAULT_IPINFO_TOKEN, 60_000, controller.signal);
|
|
189
202
|
|
|
190
|
-
|
|
203
|
+
controller.abort();
|
|
191
204
|
await expect(pending).resolves.toEqual({});
|
|
205
|
+
expect(httpsGet.mock.results[0].value.destroy).toHaveBeenCalled();
|
|
192
206
|
});
|
|
193
207
|
});
|
|
194
208
|
|
|
195
209
|
describe("fetchFromIPAPI", () => {
|
|
196
210
|
it("parses the ip-api.com payload", async () => {
|
|
197
|
-
respondWith(JSON.stringify({
|
|
211
|
+
respondWith(httpGet, JSON.stringify({
|
|
198
212
|
status: "success",
|
|
199
213
|
query: "1.2.3.4",
|
|
200
214
|
city: "San Francisco",
|
|
@@ -210,66 +224,51 @@ describe("fetchFromIPAPI", () => {
|
|
|
210
224
|
});
|
|
211
225
|
});
|
|
212
226
|
|
|
213
|
-
it("
|
|
214
|
-
const get = respondWith('{"status":"success"}');
|
|
227
|
+
it("uses plain http, which is all ip-api.com's free tier serves", async () => {
|
|
228
|
+
const get = respondWith(httpGet, '{"status":"success"}');
|
|
215
229
|
await fetchFromIPAPI();
|
|
216
230
|
|
|
217
231
|
expect(get.mock.calls[0][0]).toBe(
|
|
218
232
|
"http://ip-api.com/json/?fields=status,message,query,city,isp,org,as"
|
|
219
233
|
);
|
|
234
|
+
expect(httpsGet).not.toHaveBeenCalled();
|
|
220
235
|
});
|
|
221
236
|
|
|
222
237
|
it("returns empty object on failed status", async () => {
|
|
223
|
-
respondWith(JSON.stringify({ status: "fail", message: "private range" }));
|
|
238
|
+
respondWith(httpGet, JSON.stringify({ status: "fail", message: "private range" }));
|
|
224
239
|
await expect(fetchFromIPAPI()).resolves.toEqual({});
|
|
225
240
|
});
|
|
226
241
|
|
|
227
242
|
it("resolves empty rather than rejecting on a malformed body", async () => {
|
|
228
|
-
respondWith("<html>gateway error</html>");
|
|
243
|
+
respondWith(httpGet, "<html>gateway error</html>");
|
|
229
244
|
await expect(fetchFromIPAPI()).resolves.toEqual({});
|
|
230
245
|
});
|
|
231
246
|
});
|
|
232
247
|
|
|
233
|
-
describe("fetchIPInfo
|
|
234
|
-
it("
|
|
235
|
-
|
|
236
|
-
|
|
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();
|
|
248
|
+
describe("fetchIPInfo", () => {
|
|
249
|
+
it("prefers ipinfo per field and fills gaps from ip-api", async () => {
|
|
250
|
+
respondWith(httpsGet, JSON.stringify({ ip: "1.2.3.4", asn: "AS123", as_name: "Primary ISP" }));
|
|
251
|
+
respondWith(httpGet, JSON.stringify({ status: "success", query: "5.6.7.8", city: "New York", org: "AS456 Other" }));
|
|
245
252
|
|
|
246
|
-
|
|
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({
|
|
253
|
+
await expect(fetchIPInfo()).resolves.toEqual({
|
|
251
254
|
ip: "1.2.3.4",
|
|
252
|
-
city: "
|
|
255
|
+
city: "New York",
|
|
256
|
+
org: "AS123 Primary ISP",
|
|
253
257
|
});
|
|
254
258
|
});
|
|
255
259
|
|
|
256
|
-
it("
|
|
257
|
-
|
|
258
|
-
|
|
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);
|
|
260
|
+
it("prefers an IPv4 address over ipinfo's IPv6 one", async () => {
|
|
261
|
+
respondWith(httpsGet, JSON.stringify({ ip: "2600:1010::1", asn: "AS123", as_name: "Primary ISP" }));
|
|
262
|
+
respondWith(httpGet, JSON.stringify({ status: "success", query: "5.6.7.8", city: "New York" }));
|
|
265
263
|
|
|
266
|
-
|
|
264
|
+
await expect(fetchIPInfo()).resolves.toMatchObject({ ip: "5.6.7.8" });
|
|
265
|
+
});
|
|
267
266
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
it("falls back to ip-api when ipinfo fails", async () => {
|
|
268
|
+
stubGet(httpsGet, (req) => req.emit("error", new Error("ECONNRESET")));
|
|
269
|
+
respondWith(httpGet, JSON.stringify({ status: "success", query: "5.6.7.8", city: "New York", org: "AS456 Fallback ISP" }));
|
|
271
270
|
|
|
272
|
-
await expect(
|
|
271
|
+
await expect(fetchIPInfo()).resolves.toEqual({
|
|
273
272
|
ip: "5.6.7.8",
|
|
274
273
|
city: "New York",
|
|
275
274
|
org: "AS456 Fallback ISP",
|
|
@@ -277,9 +276,19 @@ describe("fetchIPInfo (race)", () => {
|
|
|
277
276
|
});
|
|
278
277
|
|
|
279
278
|
it("returns empty when both fail", async () => {
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
stubGet(httpsGet, (req) => req.emit("error", new Error("ENOTFOUND")));
|
|
280
|
+
stubGet(httpGet, (req) => req.emit("error", new Error("ENOTFOUND")));
|
|
282
281
|
|
|
283
282
|
await expect(fetchIPInfo()).resolves.toEqual({});
|
|
284
283
|
});
|
|
284
|
+
|
|
285
|
+
it("never waits past the shared deadline when both hang", async () => {
|
|
286
|
+
vi.useFakeTimers();
|
|
287
|
+
stubGet(httpsGet);
|
|
288
|
+
stubGet(httpGet);
|
|
289
|
+
const pending = fetchIPInfo();
|
|
290
|
+
|
|
291
|
+
await vi.advanceTimersByTimeAsync(DEFAULT_NETWORK_TIMEOUT);
|
|
292
|
+
await expect(pending).resolves.toEqual({});
|
|
293
|
+
});
|
|
285
294
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"about-system-cli.d.ts","sourceRoot":"","sources":["../src/about-system-cli.ts"],"names":[],"mappings":";AAoJA,iBAAe,iBAAiB,CAC9B,kBAAkB,GAAE,MAAM,EAAE,EAAE,GAAG,IAAW,GAC3C,OAAO,CAAC,IAAI,CAAC,CAqDf;AA8ED,iBAAS,oBAAoB,IAAI,IAAI,CAoGpC;AAoJD,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache configuration and constants
|
|
3
|
-
* @module cache-config
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Cache file location in system's temporary directory
|
|
7
|
-
* @constant
|
|
8
|
-
*/
|
|
9
|
-
export declare const CACHE_FILE: string;
|
|
10
|
-
/**
|
|
11
|
-
* Cache duration configuration for different system information types
|
|
12
|
-
* Values are in milliseconds
|
|
13
|
-
*
|
|
14
|
-
* @constant
|
|
15
|
-
* @property {number} ip - IP information cache (10 minutes)
|
|
16
|
-
* @property {number} cpu - CPU information cache (24 hours)
|
|
17
|
-
* @property {number} gpu - GPU information cache (24 hours)
|
|
18
|
-
* @property {number} os - OS information cache (24 hours)
|
|
19
|
-
* @property {number} device - Device information cache (24 hours)
|
|
20
|
-
* @property {number} kernel - Kernel information cache (1 hour)
|
|
21
|
-
* @property {number} pacman - Package managers cache (10 minutes)
|
|
22
|
-
* @property {number} ports - Open ports cache (5 minutes)
|
|
23
|
-
* @property {number} containers - Docker containers cache (5 minutes)
|
|
24
|
-
* @property {number} top_process - Top process cache (5 seconds)
|
|
25
|
-
* @property {number} disk_used - Disk usage cache (1 minute)
|
|
26
|
-
* @property {number} ram_used - RAM usage cache (10 seconds)
|
|
27
|
-
* @property {number} services_running - Services cache (5 minutes)
|
|
28
|
-
* @property {number} temperature - Temperature cache (30 seconds)
|
|
29
|
-
* @property {number} battery - Battery status cache (1 minute)
|
|
30
|
-
* @property {number} network_interfaces - Network interfaces cache (5 minutes)
|
|
31
|
-
* @property {number} mount_points - Mount points cache (10 minutes)
|
|
32
|
-
*/
|
|
33
|
-
export declare const CACHE_DURATION: {
|
|
34
|
-
ip: number;
|
|
35
|
-
cpu: number;
|
|
36
|
-
gpu: number;
|
|
37
|
-
os: number;
|
|
38
|
-
device: number;
|
|
39
|
-
kernel: number;
|
|
40
|
-
pacman: number;
|
|
41
|
-
ports: number;
|
|
42
|
-
containers: number;
|
|
43
|
-
top_process: number;
|
|
44
|
-
disk_used: number;
|
|
45
|
-
ram_used: number;
|
|
46
|
-
services_running: number;
|
|
47
|
-
temperature: number;
|
|
48
|
-
battery: number;
|
|
49
|
-
network_interfaces: number;
|
|
50
|
-
mount_points: number;
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* Default IPInfo.io API token for geolocation
|
|
54
|
-
* @constant
|
|
55
|
-
*/
|
|
56
|
-
export declare const DEFAULT_IPINFO_TOKEN = "da2d6cc4baa5d1";
|
|
57
|
-
/**
|
|
58
|
-
* Default network request timeout in milliseconds
|
|
59
|
-
* @constant
|
|
60
|
-
*/
|
|
61
|
-
export declare const DEFAULT_NETWORK_TIMEOUT = 5000;
|
|
62
|
-
//# sourceMappingURL=cache-config.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache-config.d.ts","sourceRoot":"","sources":["../../src/cache/cache-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;GAGG;AACH,eAAO,MAAM,UAAU,QAAkD,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;CAkB1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAErD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC"}
|
package/dist/cache/cache.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache management utilities
|
|
3
|
-
* @module cache
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Represents a cached value with timestamp
|
|
7
|
-
* @interface CacheEntry
|
|
8
|
-
*/
|
|
9
|
-
export interface CacheEntry {
|
|
10
|
-
/** The cached value */
|
|
11
|
-
value: any;
|
|
12
|
-
/** Unix timestamp when the value was cached */
|
|
13
|
-
timestamp: number;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Cache storage structure
|
|
17
|
-
* @interface Cache
|
|
18
|
-
*/
|
|
19
|
-
export interface Cache {
|
|
20
|
-
[key: string]: CacheEntry;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Loads cache from disk
|
|
24
|
-
* @returns {Cache} Cached data or empty object if cache doesn't exist or is corrupted
|
|
25
|
-
*/
|
|
26
|
-
export declare function loadCache(): Cache;
|
|
27
|
-
/**
|
|
28
|
-
* Saves cache to disk
|
|
29
|
-
* @param {Cache} cache - Cache object to save
|
|
30
|
-
*/
|
|
31
|
-
export declare function saveCache(cache: Cache): void;
|
|
32
|
-
/**
|
|
33
|
-
* Checks if a cache entry is still valid based on configured duration
|
|
34
|
-
* @param {CacheEntry} cacheEntry - The cache entry to validate
|
|
35
|
-
* @param {string} key - The key name to determine cache duration
|
|
36
|
-
* @returns {boolean} True if cache is still valid
|
|
37
|
-
*/
|
|
38
|
-
export declare function isCacheValid(cacheEntry: CacheEntry, key: string): boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Retrieves a value from cache if valid
|
|
41
|
-
* @param {Cache} cache - Cache object
|
|
42
|
-
* @param {string} key - Key to retrieve
|
|
43
|
-
* @returns {any} Cached value or null if not found/expired
|
|
44
|
-
*/
|
|
45
|
-
export declare function getCachedValue(cache: Cache, key: string): any;
|
|
46
|
-
/**
|
|
47
|
-
* Stores a value in cache with current timestamp
|
|
48
|
-
* @param {Cache} cache - Cache object
|
|
49
|
-
* @param {string} key - Key to store under
|
|
50
|
-
* @param {any} value - Value to cache
|
|
51
|
-
*/
|
|
52
|
-
export declare function setCachedValue(cache: Cache, key: string, value: any): void;
|
|
53
|
-
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache/cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,uBAAuB;IACvB,KAAK,EAAE,GAAG,CAAC;IACX,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,KAAK;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,KAAK,CASjC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAM5C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAKzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAQ7D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI,CAK1E"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC3E,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,QAAQ,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
|