@socketsecurity/lib 5.11.4 → 5.13.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/CHANGELOG.md +31 -0
- package/dist/external/@socketregistry/packageurl-js.js +3191 -2336
- package/dist/http-request.d.ts +68 -1
- package/dist/http-request.js +113 -22
- package/package.json +4 -3
package/dist/http-request.d.ts
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders, IncomingMessage } from 'http';
|
|
2
|
+
/** IncomingMessage received as a response to a client request (http.request callback). */
|
|
3
|
+
export type IncomingResponse = IncomingMessage;
|
|
4
|
+
/** IncomingMessage received as a request in a server handler (http.createServer callback). */
|
|
5
|
+
export type IncomingRequest = IncomingMessage;
|
|
1
6
|
import type { Logger } from './logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Information passed to the onRequest hook before each request attempt.
|
|
9
|
+
*/
|
|
10
|
+
export interface HttpHookRequestInfo {
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
method: string;
|
|
13
|
+
timeout: number;
|
|
14
|
+
url: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Information passed to the onResponse hook after each request attempt.
|
|
18
|
+
*/
|
|
19
|
+
export interface HttpHookResponseInfo {
|
|
20
|
+
duration: number;
|
|
21
|
+
error?: Error | undefined;
|
|
22
|
+
headers?: IncomingHttpHeaders | undefined;
|
|
23
|
+
method: string;
|
|
24
|
+
status?: number | undefined;
|
|
25
|
+
statusText?: string | undefined;
|
|
26
|
+
url: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Lifecycle hooks for observing HTTP request/response events.
|
|
30
|
+
* Hooks fire per-attempt (retries produce multiple hook calls).
|
|
31
|
+
*/
|
|
32
|
+
export interface HttpHooks {
|
|
33
|
+
onRequest?: ((info: HttpHookRequestInfo) => void) | undefined;
|
|
34
|
+
onResponse?: ((info: HttpHookResponseInfo) => void) | undefined;
|
|
35
|
+
}
|
|
2
36
|
/**
|
|
3
37
|
* Configuration options for HTTP/HTTPS requests.
|
|
4
38
|
*/
|
|
@@ -61,6 +95,11 @@ export interface HttpRequestOptions {
|
|
|
61
95
|
* ```
|
|
62
96
|
*/
|
|
63
97
|
followRedirects?: boolean | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Lifecycle hooks for observing request/response events.
|
|
100
|
+
* Hooks fire per-attempt — retries and redirects each trigger separate hook calls.
|
|
101
|
+
*/
|
|
102
|
+
hooks?: HttpHooks | undefined;
|
|
64
103
|
/**
|
|
65
104
|
* HTTP headers to send with the request.
|
|
66
105
|
* A `User-Agent` header is automatically added if not provided.
|
|
@@ -92,6 +131,14 @@ export interface HttpRequestOptions {
|
|
|
92
131
|
* ```
|
|
93
132
|
*/
|
|
94
133
|
maxRedirects?: number | undefined;
|
|
134
|
+
/**
|
|
135
|
+
* Maximum response body size in bytes. Responses exceeding this limit
|
|
136
|
+
* will be rejected with an error. Prevents memory exhaustion from
|
|
137
|
+
* unexpectedly large responses.
|
|
138
|
+
*
|
|
139
|
+
* @default undefined (no limit)
|
|
140
|
+
*/
|
|
141
|
+
maxResponseSize?: number | undefined;
|
|
95
142
|
/**
|
|
96
143
|
* HTTP method to use for the request.
|
|
97
144
|
*
|
|
@@ -205,7 +252,7 @@ export interface HttpResponse {
|
|
|
205
252
|
* console.log(response.headers['set-cookie']) // May be string[]
|
|
206
253
|
* ```
|
|
207
254
|
*/
|
|
208
|
-
headers:
|
|
255
|
+
headers: IncomingHttpHeaders;
|
|
209
256
|
/**
|
|
210
257
|
* Parse response body as JSON.
|
|
211
258
|
* Type parameter `T` allows specifying the expected JSON structure.
|
|
@@ -270,7 +317,22 @@ export interface HttpResponse {
|
|
|
270
317
|
* ```
|
|
271
318
|
*/
|
|
272
319
|
text(): string;
|
|
320
|
+
/**
|
|
321
|
+
* The underlying Node.js IncomingResponse for advanced use cases
|
|
322
|
+
* (e.g., streaming, custom header inspection). Only available when
|
|
323
|
+
* the response was not consumed by the convenience methods.
|
|
324
|
+
*/
|
|
325
|
+
rawResponse?: IncomingResponse | undefined;
|
|
273
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Read and buffer a client-side IncomingResponse into an HttpResponse.
|
|
329
|
+
*
|
|
330
|
+
* Useful when you have a raw response from code that bypasses
|
|
331
|
+
* `httpRequest()` (e.g., multipart form-data uploads via `http.request()`,
|
|
332
|
+
* or responses from third-party HTTP libraries) and need to convert it
|
|
333
|
+
* into the standard HttpResponse interface.
|
|
334
|
+
*/
|
|
335
|
+
export declare function readIncomingResponse(msg: IncomingResponse): Promise<HttpResponse>;
|
|
274
336
|
/**
|
|
275
337
|
* Configuration options for file downloads.
|
|
276
338
|
*/
|
|
@@ -567,6 +629,11 @@ export interface FetchChecksumsOptions {
|
|
|
567
629
|
* ```
|
|
568
630
|
*/
|
|
569
631
|
export declare function fetchChecksums(url: string, options?: FetchChecksumsOptions | undefined): Promise<Checksums>;
|
|
632
|
+
/**
|
|
633
|
+
* Build an enriched error message based on the error code.
|
|
634
|
+
* Generic guidance (no product-specific branding).
|
|
635
|
+
*/
|
|
636
|
+
export declare function enrichErrorMessage(url: string, method: string, error: NodeJS.ErrnoException): string;
|
|
570
637
|
/**
|
|
571
638
|
* Download a file from a URL to a local path with redirect support, retry logic, and progress callbacks.
|
|
572
639
|
* Uses streaming to avoid loading entire file in memory.
|
package/dist/http-request.js
CHANGED
|
@@ -19,12 +19,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
19
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
20
|
var http_request_exports = {};
|
|
21
21
|
__export(http_request_exports, {
|
|
22
|
+
enrichErrorMessage: () => enrichErrorMessage,
|
|
22
23
|
fetchChecksums: () => fetchChecksums,
|
|
23
24
|
httpDownload: () => httpDownload,
|
|
24
25
|
httpJson: () => httpJson,
|
|
25
26
|
httpRequest: () => httpRequest,
|
|
26
27
|
httpText: () => httpText,
|
|
27
|
-
parseChecksums: () => parseChecksums
|
|
28
|
+
parseChecksums: () => parseChecksums,
|
|
29
|
+
readIncomingResponse: () => readIncomingResponse
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(http_request_exports);
|
|
30
32
|
var import_fs = require("./fs.js");
|
|
@@ -60,6 +62,29 @@ function getHttps() {
|
|
|
60
62
|
}
|
|
61
63
|
return _https;
|
|
62
64
|
}
|
|
65
|
+
async function readIncomingResponse(msg) {
|
|
66
|
+
const chunks = [];
|
|
67
|
+
for await (const chunk of msg) {
|
|
68
|
+
chunks.push(chunk);
|
|
69
|
+
}
|
|
70
|
+
const body = Buffer.concat(chunks);
|
|
71
|
+
const status = msg.statusCode ?? 0;
|
|
72
|
+
const statusText = msg.statusMessage ?? "";
|
|
73
|
+
return {
|
|
74
|
+
arrayBuffer: () => body.buffer.slice(
|
|
75
|
+
body.byteOffset,
|
|
76
|
+
body.byteOffset + body.byteLength
|
|
77
|
+
),
|
|
78
|
+
body,
|
|
79
|
+
headers: msg.headers,
|
|
80
|
+
json: () => JSON.parse(body.toString("utf8")),
|
|
81
|
+
ok: status >= 200 && status < 300,
|
|
82
|
+
rawResponse: msg,
|
|
83
|
+
status,
|
|
84
|
+
statusText,
|
|
85
|
+
text: () => body.toString("utf8")
|
|
86
|
+
};
|
|
87
|
+
}
|
|
63
88
|
function parseChecksums(text) {
|
|
64
89
|
const checksums = { __proto__: null };
|
|
65
90
|
for (const line of text.split("\n")) {
|
|
@@ -238,25 +263,51 @@ async function httpDownloadAttempt(url, destPath, options) {
|
|
|
238
263
|
request.end();
|
|
239
264
|
});
|
|
240
265
|
}
|
|
266
|
+
function enrichErrorMessage(url, method, error) {
|
|
267
|
+
const code = error.code;
|
|
268
|
+
let message = `${method} request failed: ${url}`;
|
|
269
|
+
if (code === "ECONNREFUSED") {
|
|
270
|
+
message += "\n\u2192 Connection refused. Server is unreachable.\n\u2192 Check: Network connectivity and firewall settings.";
|
|
271
|
+
} else if (code === "ENOTFOUND") {
|
|
272
|
+
message += "\n\u2192 DNS lookup failed. Cannot resolve hostname.\n\u2192 Check: Internet connection and DNS settings.";
|
|
273
|
+
} else if (code === "ETIMEDOUT") {
|
|
274
|
+
message += "\n\u2192 Connection timed out. Network or server issue.\n\u2192 Try: Check network connectivity and retry.";
|
|
275
|
+
} else if (code === "ECONNRESET") {
|
|
276
|
+
message += "\n\u2192 Connection reset by server. Possible network interruption.\n\u2192 Try: Retry the request.";
|
|
277
|
+
} else if (code === "EPIPE") {
|
|
278
|
+
message += "\n\u2192 Broken pipe. Server closed connection unexpectedly.\n\u2192 Check: Authentication credentials and permissions.";
|
|
279
|
+
} else if (code === "CERT_HAS_EXPIRED" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") {
|
|
280
|
+
message += "\n\u2192 SSL/TLS certificate error.\n\u2192 Check: System time and date are correct.\n\u2192 Try: Update CA certificates on your system.";
|
|
281
|
+
} else if (code) {
|
|
282
|
+
message += `
|
|
283
|
+
\u2192 Error code: ${code}`;
|
|
284
|
+
}
|
|
285
|
+
return message;
|
|
286
|
+
}
|
|
241
287
|
async function httpRequestAttempt(url, options) {
|
|
242
288
|
const {
|
|
243
289
|
body,
|
|
244
290
|
ca,
|
|
245
291
|
followRedirects = true,
|
|
246
292
|
headers = {},
|
|
293
|
+
hooks,
|
|
247
294
|
maxRedirects = 5,
|
|
295
|
+
maxResponseSize,
|
|
248
296
|
method = "GET",
|
|
249
297
|
timeout = 3e4
|
|
250
298
|
} = { __proto__: null, ...options };
|
|
299
|
+
const startTime = Date.now();
|
|
300
|
+
const mergedHeaders = {
|
|
301
|
+
"User-Agent": "socket-registry/1.0",
|
|
302
|
+
...headers
|
|
303
|
+
};
|
|
304
|
+
hooks?.onRequest?.({ method, url, headers: mergedHeaders, timeout });
|
|
251
305
|
return await new Promise((resolve, reject) => {
|
|
252
306
|
const parsedUrl = new URL(url);
|
|
253
307
|
const isHttps = parsedUrl.protocol === "https:";
|
|
254
308
|
const httpModule = isHttps ? /* @__PURE__ */ getHttps() : /* @__PURE__ */ getHttp();
|
|
255
309
|
const requestOptions = {
|
|
256
|
-
headers:
|
|
257
|
-
"User-Agent": "socket-registry/1.0",
|
|
258
|
-
...headers
|
|
259
|
-
},
|
|
310
|
+
headers: mergedHeaders,
|
|
260
311
|
hostname: parsedUrl.hostname,
|
|
261
312
|
method,
|
|
262
313
|
path: parsedUrl.pathname + parsedUrl.search,
|
|
@@ -266,10 +317,23 @@ async function httpRequestAttempt(url, options) {
|
|
|
266
317
|
if (ca && isHttps) {
|
|
267
318
|
requestOptions["ca"] = ca;
|
|
268
319
|
}
|
|
320
|
+
const emitResponse = (info) => {
|
|
321
|
+
hooks?.onResponse?.({
|
|
322
|
+
duration: Date.now() - startTime,
|
|
323
|
+
method,
|
|
324
|
+
url,
|
|
325
|
+
...info
|
|
326
|
+
});
|
|
327
|
+
};
|
|
269
328
|
const request = httpModule.request(
|
|
270
329
|
requestOptions,
|
|
271
330
|
(res) => {
|
|
272
331
|
if (followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
332
|
+
emitResponse({
|
|
333
|
+
headers: res.headers,
|
|
334
|
+
status: res.statusCode,
|
|
335
|
+
statusText: res.statusMessage
|
|
336
|
+
});
|
|
273
337
|
if (maxRedirects <= 0) {
|
|
274
338
|
reject(
|
|
275
339
|
new Error(
|
|
@@ -294,7 +358,9 @@ async function httpRequestAttempt(url, options) {
|
|
|
294
358
|
ca,
|
|
295
359
|
followRedirects,
|
|
296
360
|
headers,
|
|
361
|
+
hooks,
|
|
297
362
|
maxRedirects: maxRedirects - 1,
|
|
363
|
+
maxResponseSize,
|
|
298
364
|
method,
|
|
299
365
|
timeout
|
|
300
366
|
})
|
|
@@ -302,7 +368,20 @@ async function httpRequestAttempt(url, options) {
|
|
|
302
368
|
return;
|
|
303
369
|
}
|
|
304
370
|
const chunks = [];
|
|
371
|
+
let totalBytes = 0;
|
|
305
372
|
res.on("data", (chunk) => {
|
|
373
|
+
totalBytes += chunk.length;
|
|
374
|
+
if (maxResponseSize && totalBytes > maxResponseSize) {
|
|
375
|
+
res.destroy();
|
|
376
|
+
const sizeMB = (totalBytes / (1024 * 1024)).toFixed(2);
|
|
377
|
+
const maxMB = (maxResponseSize / (1024 * 1024)).toFixed(2);
|
|
378
|
+
const err = new Error(
|
|
379
|
+
`Response exceeds maximum size limit (${sizeMB}MB > ${maxMB}MB)`
|
|
380
|
+
);
|
|
381
|
+
emitResponse({ error: err });
|
|
382
|
+
reject(err);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
306
385
|
chunks.push(chunk);
|
|
307
386
|
});
|
|
308
387
|
res.on("end", () => {
|
|
@@ -321,39 +400,45 @@ async function httpRequestAttempt(url, options) {
|
|
|
321
400
|
return JSON.parse(responseBody.toString("utf8"));
|
|
322
401
|
},
|
|
323
402
|
ok,
|
|
403
|
+
rawResponse: res,
|
|
324
404
|
status: res.statusCode || 0,
|
|
325
405
|
statusText: res.statusMessage || "",
|
|
326
406
|
text() {
|
|
327
407
|
return responseBody.toString("utf8");
|
|
328
408
|
}
|
|
329
409
|
};
|
|
410
|
+
emitResponse({
|
|
411
|
+
headers: res.headers,
|
|
412
|
+
status: res.statusCode,
|
|
413
|
+
statusText: res.statusMessage
|
|
414
|
+
});
|
|
330
415
|
resolve(response);
|
|
331
416
|
});
|
|
332
417
|
res.on("error", (error) => {
|
|
418
|
+
emitResponse({ error });
|
|
333
419
|
reject(error);
|
|
334
420
|
});
|
|
335
421
|
}
|
|
336
422
|
);
|
|
337
423
|
request.on("error", (error) => {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
message += "Request timed out. Check your network or increase the timeout value.";
|
|
347
|
-
} else if (code === "ECONNRESET") {
|
|
348
|
-
message += "Connection reset. The server may have closed the connection unexpectedly.";
|
|
349
|
-
} else {
|
|
350
|
-
message += "Check your network connection and verify the URL is correct.";
|
|
351
|
-
}
|
|
352
|
-
reject(new Error(message, { cause: error }));
|
|
424
|
+
const message = enrichErrorMessage(
|
|
425
|
+
url,
|
|
426
|
+
method,
|
|
427
|
+
error
|
|
428
|
+
);
|
|
429
|
+
const enhanced = new Error(message, { cause: error });
|
|
430
|
+
emitResponse({ error: enhanced });
|
|
431
|
+
reject(enhanced);
|
|
353
432
|
});
|
|
354
433
|
request.on("timeout", () => {
|
|
355
434
|
request.destroy();
|
|
356
|
-
|
|
435
|
+
const err = new Error(
|
|
436
|
+
`${method} request timed out after ${timeout}ms: ${url}
|
|
437
|
+
\u2192 Server did not respond in time.
|
|
438
|
+
\u2192 Try: Increase timeout or check network connectivity.`
|
|
439
|
+
);
|
|
440
|
+
emitResponse({ error: err });
|
|
441
|
+
reject(err);
|
|
357
442
|
});
|
|
358
443
|
if (body) {
|
|
359
444
|
request.write(body);
|
|
@@ -482,7 +567,9 @@ async function httpRequest(url, options) {
|
|
|
482
567
|
ca,
|
|
483
568
|
followRedirects = true,
|
|
484
569
|
headers = {},
|
|
570
|
+
hooks,
|
|
485
571
|
maxRedirects = 5,
|
|
572
|
+
maxResponseSize,
|
|
486
573
|
method = "GET",
|
|
487
574
|
retries = 0,
|
|
488
575
|
retryDelay = 1e3,
|
|
@@ -496,7 +583,9 @@ async function httpRequest(url, options) {
|
|
|
496
583
|
ca,
|
|
497
584
|
followRedirects,
|
|
498
585
|
headers,
|
|
586
|
+
hooks,
|
|
499
587
|
maxRedirects,
|
|
588
|
+
maxResponseSize,
|
|
500
589
|
method,
|
|
501
590
|
timeout
|
|
502
591
|
});
|
|
@@ -542,10 +631,12 @@ async function httpText(url, options) {
|
|
|
542
631
|
}
|
|
543
632
|
// Annotate the CommonJS export names for ESM import in node:
|
|
544
633
|
0 && (module.exports = {
|
|
634
|
+
enrichErrorMessage,
|
|
545
635
|
fetchChecksums,
|
|
546
636
|
httpDownload,
|
|
547
637
|
httpJson,
|
|
548
638
|
httpRequest,
|
|
549
639
|
httpText,
|
|
550
|
-
parseChecksums
|
|
640
|
+
parseChecksums,
|
|
641
|
+
readIncomingResponse
|
|
551
642
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/lib",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.13.0",
|
|
4
4
|
"packageManager": "pnpm@10.33.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Core utilities and infrastructure for Socket.dev security tools",
|
|
@@ -717,6 +717,7 @@
|
|
|
717
717
|
"update": "node scripts/update.mjs"
|
|
718
718
|
},
|
|
719
719
|
"devDependencies": {
|
|
720
|
+
"@anthropic-ai/claude-code": "2.1.89",
|
|
720
721
|
"@babel/core": "7.28.4",
|
|
721
722
|
"@babel/parser": "7.28.4",
|
|
722
723
|
"@babel/traverse": "7.28.4",
|
|
@@ -732,9 +733,9 @@
|
|
|
732
733
|
"@npmcli/package-json": "7.0.0",
|
|
733
734
|
"@npmcli/promise-spawn": "8.0.3",
|
|
734
735
|
"@socketregistry/is-unicode-supported": "1.0.5",
|
|
735
|
-
"@socketregistry/packageurl-js": "1.
|
|
736
|
+
"@socketregistry/packageurl-js": "1.4.1",
|
|
736
737
|
"@socketregistry/yocto-spinner": "1.0.25",
|
|
737
|
-
"@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.
|
|
738
|
+
"@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.12.0",
|
|
738
739
|
"@types/node": "24.9.2",
|
|
739
740
|
"@typescript/native-preview": "7.0.0-dev.20250920.1",
|
|
740
741
|
"@vitest/coverage-v8": "4.0.3",
|