@socketsecurity/lib 5.11.4 → 5.12.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 +17 -0
- package/dist/external/@socketregistry/packageurl-js.js +3191 -2336
- package/dist/http-request.d.ts +55 -1
- package/dist/http-request.js +86 -20
- package/package.json +4 -3
package/dist/http-request.d.ts
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders, IncomingMessage } from 'http';
|
|
1
2
|
import type { Logger } from './logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Information passed to the onRequest hook before each request attempt.
|
|
5
|
+
*/
|
|
6
|
+
export interface HttpHookRequestInfo {
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
method: string;
|
|
9
|
+
timeout: number;
|
|
10
|
+
url: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Information passed to the onResponse hook after each request attempt.
|
|
14
|
+
*/
|
|
15
|
+
export interface HttpHookResponseInfo {
|
|
16
|
+
duration: number;
|
|
17
|
+
error?: Error | undefined;
|
|
18
|
+
headers?: IncomingHttpHeaders | undefined;
|
|
19
|
+
method: string;
|
|
20
|
+
status?: number | undefined;
|
|
21
|
+
statusText?: string | undefined;
|
|
22
|
+
url: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Lifecycle hooks for observing HTTP request/response events.
|
|
26
|
+
* Hooks fire per-attempt (retries produce multiple hook calls).
|
|
27
|
+
*/
|
|
28
|
+
export interface HttpHooks {
|
|
29
|
+
onRequest?: ((info: HttpHookRequestInfo) => void) | undefined;
|
|
30
|
+
onResponse?: ((info: HttpHookResponseInfo) => void) | undefined;
|
|
31
|
+
}
|
|
2
32
|
/**
|
|
3
33
|
* Configuration options for HTTP/HTTPS requests.
|
|
4
34
|
*/
|
|
@@ -61,6 +91,11 @@ export interface HttpRequestOptions {
|
|
|
61
91
|
* ```
|
|
62
92
|
*/
|
|
63
93
|
followRedirects?: boolean | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Lifecycle hooks for observing request/response events.
|
|
96
|
+
* Hooks fire per-attempt — retries and redirects each trigger separate hook calls.
|
|
97
|
+
*/
|
|
98
|
+
hooks?: HttpHooks | undefined;
|
|
64
99
|
/**
|
|
65
100
|
* HTTP headers to send with the request.
|
|
66
101
|
* A `User-Agent` header is automatically added if not provided.
|
|
@@ -92,6 +127,14 @@ export interface HttpRequestOptions {
|
|
|
92
127
|
* ```
|
|
93
128
|
*/
|
|
94
129
|
maxRedirects?: number | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Maximum response body size in bytes. Responses exceeding this limit
|
|
132
|
+
* will be rejected with an error. Prevents memory exhaustion from
|
|
133
|
+
* unexpectedly large responses.
|
|
134
|
+
*
|
|
135
|
+
* @default undefined (no limit)
|
|
136
|
+
*/
|
|
137
|
+
maxResponseSize?: number | undefined;
|
|
95
138
|
/**
|
|
96
139
|
* HTTP method to use for the request.
|
|
97
140
|
*
|
|
@@ -205,7 +248,7 @@ export interface HttpResponse {
|
|
|
205
248
|
* console.log(response.headers['set-cookie']) // May be string[]
|
|
206
249
|
* ```
|
|
207
250
|
*/
|
|
208
|
-
headers:
|
|
251
|
+
headers: IncomingHttpHeaders;
|
|
209
252
|
/**
|
|
210
253
|
* Parse response body as JSON.
|
|
211
254
|
* Type parameter `T` allows specifying the expected JSON structure.
|
|
@@ -270,6 +313,12 @@ export interface HttpResponse {
|
|
|
270
313
|
* ```
|
|
271
314
|
*/
|
|
272
315
|
text(): string;
|
|
316
|
+
/**
|
|
317
|
+
* The underlying Node.js IncomingMessage for advanced use cases
|
|
318
|
+
* (e.g., streaming, custom header inspection). Only available when
|
|
319
|
+
* the response was not consumed by the convenience methods.
|
|
320
|
+
*/
|
|
321
|
+
rawResponse?: IncomingMessage | undefined;
|
|
273
322
|
}
|
|
274
323
|
/**
|
|
275
324
|
* Configuration options for file downloads.
|
|
@@ -567,6 +616,11 @@ export interface FetchChecksumsOptions {
|
|
|
567
616
|
* ```
|
|
568
617
|
*/
|
|
569
618
|
export declare function fetchChecksums(url: string, options?: FetchChecksumsOptions | undefined): Promise<Checksums>;
|
|
619
|
+
/**
|
|
620
|
+
* Build an enriched error message based on the error code.
|
|
621
|
+
* Generic guidance (no product-specific branding).
|
|
622
|
+
*/
|
|
623
|
+
export declare function enrichErrorMessage(url: string, method: string, error: NodeJS.ErrnoException): string;
|
|
570
624
|
/**
|
|
571
625
|
* Download a file from a URL to a local path with redirect support, retry logic, and progress callbacks.
|
|
572
626
|
* Uses streaming to avoid loading entire file in memory.
|
package/dist/http-request.js
CHANGED
|
@@ -19,6 +19,7 @@ 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,
|
|
@@ -238,25 +239,51 @@ async function httpDownloadAttempt(url, destPath, options) {
|
|
|
238
239
|
request.end();
|
|
239
240
|
});
|
|
240
241
|
}
|
|
242
|
+
function enrichErrorMessage(url, method, error) {
|
|
243
|
+
const code = error.code;
|
|
244
|
+
let message = `${method} request failed: ${url}`;
|
|
245
|
+
if (code === "ECONNREFUSED") {
|
|
246
|
+
message += "\n\u2192 Connection refused. Server is unreachable.\n\u2192 Check: Network connectivity and firewall settings.";
|
|
247
|
+
} else if (code === "ENOTFOUND") {
|
|
248
|
+
message += "\n\u2192 DNS lookup failed. Cannot resolve hostname.\n\u2192 Check: Internet connection and DNS settings.";
|
|
249
|
+
} else if (code === "ETIMEDOUT") {
|
|
250
|
+
message += "\n\u2192 Connection timed out. Network or server issue.\n\u2192 Try: Check network connectivity and retry.";
|
|
251
|
+
} else if (code === "ECONNRESET") {
|
|
252
|
+
message += "\n\u2192 Connection reset by server. Possible network interruption.\n\u2192 Try: Retry the request.";
|
|
253
|
+
} else if (code === "EPIPE") {
|
|
254
|
+
message += "\n\u2192 Broken pipe. Server closed connection unexpectedly.\n\u2192 Check: Authentication credentials and permissions.";
|
|
255
|
+
} else if (code === "CERT_HAS_EXPIRED" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") {
|
|
256
|
+
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.";
|
|
257
|
+
} else if (code) {
|
|
258
|
+
message += `
|
|
259
|
+
\u2192 Error code: ${code}`;
|
|
260
|
+
}
|
|
261
|
+
return message;
|
|
262
|
+
}
|
|
241
263
|
async function httpRequestAttempt(url, options) {
|
|
242
264
|
const {
|
|
243
265
|
body,
|
|
244
266
|
ca,
|
|
245
267
|
followRedirects = true,
|
|
246
268
|
headers = {},
|
|
269
|
+
hooks,
|
|
247
270
|
maxRedirects = 5,
|
|
271
|
+
maxResponseSize,
|
|
248
272
|
method = "GET",
|
|
249
273
|
timeout = 3e4
|
|
250
274
|
} = { __proto__: null, ...options };
|
|
275
|
+
const startTime = Date.now();
|
|
276
|
+
const mergedHeaders = {
|
|
277
|
+
"User-Agent": "socket-registry/1.0",
|
|
278
|
+
...headers
|
|
279
|
+
};
|
|
280
|
+
hooks?.onRequest?.({ method, url, headers: mergedHeaders, timeout });
|
|
251
281
|
return await new Promise((resolve, reject) => {
|
|
252
282
|
const parsedUrl = new URL(url);
|
|
253
283
|
const isHttps = parsedUrl.protocol === "https:";
|
|
254
284
|
const httpModule = isHttps ? /* @__PURE__ */ getHttps() : /* @__PURE__ */ getHttp();
|
|
255
285
|
const requestOptions = {
|
|
256
|
-
headers:
|
|
257
|
-
"User-Agent": "socket-registry/1.0",
|
|
258
|
-
...headers
|
|
259
|
-
},
|
|
286
|
+
headers: mergedHeaders,
|
|
260
287
|
hostname: parsedUrl.hostname,
|
|
261
288
|
method,
|
|
262
289
|
path: parsedUrl.pathname + parsedUrl.search,
|
|
@@ -266,10 +293,23 @@ async function httpRequestAttempt(url, options) {
|
|
|
266
293
|
if (ca && isHttps) {
|
|
267
294
|
requestOptions["ca"] = ca;
|
|
268
295
|
}
|
|
296
|
+
const emitResponse = (info) => {
|
|
297
|
+
hooks?.onResponse?.({
|
|
298
|
+
duration: Date.now() - startTime,
|
|
299
|
+
method,
|
|
300
|
+
url,
|
|
301
|
+
...info
|
|
302
|
+
});
|
|
303
|
+
};
|
|
269
304
|
const request = httpModule.request(
|
|
270
305
|
requestOptions,
|
|
271
306
|
(res) => {
|
|
272
307
|
if (followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
308
|
+
emitResponse({
|
|
309
|
+
headers: res.headers,
|
|
310
|
+
status: res.statusCode,
|
|
311
|
+
statusText: res.statusMessage
|
|
312
|
+
});
|
|
273
313
|
if (maxRedirects <= 0) {
|
|
274
314
|
reject(
|
|
275
315
|
new Error(
|
|
@@ -294,7 +334,9 @@ async function httpRequestAttempt(url, options) {
|
|
|
294
334
|
ca,
|
|
295
335
|
followRedirects,
|
|
296
336
|
headers,
|
|
337
|
+
hooks,
|
|
297
338
|
maxRedirects: maxRedirects - 1,
|
|
339
|
+
maxResponseSize,
|
|
298
340
|
method,
|
|
299
341
|
timeout
|
|
300
342
|
})
|
|
@@ -302,7 +344,20 @@ async function httpRequestAttempt(url, options) {
|
|
|
302
344
|
return;
|
|
303
345
|
}
|
|
304
346
|
const chunks = [];
|
|
347
|
+
let totalBytes = 0;
|
|
305
348
|
res.on("data", (chunk) => {
|
|
349
|
+
totalBytes += chunk.length;
|
|
350
|
+
if (maxResponseSize && totalBytes > maxResponseSize) {
|
|
351
|
+
res.destroy();
|
|
352
|
+
const sizeMB = (totalBytes / (1024 * 1024)).toFixed(2);
|
|
353
|
+
const maxMB = (maxResponseSize / (1024 * 1024)).toFixed(2);
|
|
354
|
+
const err = new Error(
|
|
355
|
+
`Response exceeds maximum size limit (${sizeMB}MB > ${maxMB}MB)`
|
|
356
|
+
);
|
|
357
|
+
emitResponse({ error: err });
|
|
358
|
+
reject(err);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
306
361
|
chunks.push(chunk);
|
|
307
362
|
});
|
|
308
363
|
res.on("end", () => {
|
|
@@ -321,39 +376,45 @@ async function httpRequestAttempt(url, options) {
|
|
|
321
376
|
return JSON.parse(responseBody.toString("utf8"));
|
|
322
377
|
},
|
|
323
378
|
ok,
|
|
379
|
+
rawResponse: res,
|
|
324
380
|
status: res.statusCode || 0,
|
|
325
381
|
statusText: res.statusMessage || "",
|
|
326
382
|
text() {
|
|
327
383
|
return responseBody.toString("utf8");
|
|
328
384
|
}
|
|
329
385
|
};
|
|
386
|
+
emitResponse({
|
|
387
|
+
headers: res.headers,
|
|
388
|
+
status: res.statusCode,
|
|
389
|
+
statusText: res.statusMessage
|
|
390
|
+
});
|
|
330
391
|
resolve(response);
|
|
331
392
|
});
|
|
332
393
|
res.on("error", (error) => {
|
|
394
|
+
emitResponse({ error });
|
|
333
395
|
reject(error);
|
|
334
396
|
});
|
|
335
397
|
}
|
|
336
398
|
);
|
|
337
399
|
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 }));
|
|
400
|
+
const message = enrichErrorMessage(
|
|
401
|
+
url,
|
|
402
|
+
method,
|
|
403
|
+
error
|
|
404
|
+
);
|
|
405
|
+
const enhanced = new Error(message, { cause: error });
|
|
406
|
+
emitResponse({ error: enhanced });
|
|
407
|
+
reject(enhanced);
|
|
353
408
|
});
|
|
354
409
|
request.on("timeout", () => {
|
|
355
410
|
request.destroy();
|
|
356
|
-
|
|
411
|
+
const err = new Error(
|
|
412
|
+
`${method} request timed out after ${timeout}ms: ${url}
|
|
413
|
+
\u2192 Server did not respond in time.
|
|
414
|
+
\u2192 Try: Increase timeout or check network connectivity.`
|
|
415
|
+
);
|
|
416
|
+
emitResponse({ error: err });
|
|
417
|
+
reject(err);
|
|
357
418
|
});
|
|
358
419
|
if (body) {
|
|
359
420
|
request.write(body);
|
|
@@ -482,7 +543,9 @@ async function httpRequest(url, options) {
|
|
|
482
543
|
ca,
|
|
483
544
|
followRedirects = true,
|
|
484
545
|
headers = {},
|
|
546
|
+
hooks,
|
|
485
547
|
maxRedirects = 5,
|
|
548
|
+
maxResponseSize,
|
|
486
549
|
method = "GET",
|
|
487
550
|
retries = 0,
|
|
488
551
|
retryDelay = 1e3,
|
|
@@ -496,7 +559,9 @@ async function httpRequest(url, options) {
|
|
|
496
559
|
ca,
|
|
497
560
|
followRedirects,
|
|
498
561
|
headers,
|
|
562
|
+
hooks,
|
|
499
563
|
maxRedirects,
|
|
564
|
+
maxResponseSize,
|
|
500
565
|
method,
|
|
501
566
|
timeout
|
|
502
567
|
});
|
|
@@ -542,6 +607,7 @@ async function httpText(url, options) {
|
|
|
542
607
|
}
|
|
543
608
|
// Annotate the CommonJS export names for ESM import in node:
|
|
544
609
|
0 && (module.exports = {
|
|
610
|
+
enrichErrorMessage,
|
|
545
611
|
fetchChecksums,
|
|
546
612
|
httpDownload,
|
|
547
613
|
httpJson,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/lib",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.12.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.11.
|
|
738
|
+
"@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.11.4",
|
|
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",
|