@socketsecurity/lib 5.5.2 → 5.6.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 CHANGED
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.6.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.6.0) - 2026-02-08
9
+
10
+ ### Added
11
+
12
+ - **http-request**: Added automatic default headers for JSON and text requests
13
+ - `httpJson()` now automatically sets `Accept: application/json` header
14
+ - `httpJson()` automatically sets `Content-Type: application/json` when body is present
15
+ - `httpText()` now automatically sets `Accept: text/plain` header
16
+ - `httpText()` automatically sets `Content-Type: text/plain` when body is present
17
+ - User-provided headers always override defaults
18
+ - Simplifies API usage - no need to manually set common headers
19
+
20
+ ### Changed
21
+
22
+ - **http-request**: Renamed HTTP helper functions to support all HTTP methods (BREAKING CHANGE)
23
+ - `httpGetJson()` → `httpJson()` - Now supports GET, POST, PUT, DELETE, PATCH, etc.
24
+ - `httpGetText()` → `httpText()` - Now supports all HTTP methods via `method` option
25
+ - Functions now accept `method` parameter in options (defaults to 'GET')
26
+ - More flexible API that matches modern fetch-style conventions
27
+ - **Migration**: Replace `httpGetJson()` calls with `httpJson()` and `httpGetText()` with `httpText()`
28
+
29
+ ### Fixed
30
+
31
+ - **http-request**: Fixed Content-Type header incorrectly sent with empty string body
32
+ - Empty string body (`""`) no longer triggers Content-Type header
33
+ - Changed condition from `if (body !== undefined)` to `if (body)` for semantic correctness
34
+ - Empty string represents "no content" and should not declare a Content-Type
35
+ - Affects `httpJson()` and `httpText()` functions
36
+ - Fixes potential API compatibility issues with servers expecting no Content-Type for empty bodies
37
+ - Added comprehensive test coverage for empty string edge case
38
+
39
+ ## [5.5.3](https://github.com/SocketDev/socket-lib/releases/tag/v5.5.3) - 2026-01-20
40
+
41
+ ### Fixed
42
+
43
+ - **deps**: Added patch for execa@2.1.0 to fix signal-exit v4 compatibility. The package was using default import syntax with signal-exit v4, which now exports onExit as a named export.
44
+
8
45
  ## [5.5.2](https://github.com/SocketDev/socket-lib/releases/tag/v5.5.2) - 2026-01-20
9
46
 
10
47
  ### Changed
@@ -10,9 +10,9 @@ export interface DlxBinaryOptions {
10
10
  */
11
11
  name?: string | undefined;
12
12
  /**
13
- * Expected checksum (sha256) for verification.
13
+ * Expected SRI integrity hash (sha512-<base64>) for verification.
14
14
  */
15
- checksum?: string | undefined;
15
+ integrity?: string | undefined;
16
16
  /**
17
17
  * Cache TTL in milliseconds (default: 7 days).
18
18
  */
@@ -47,65 +47,40 @@ export interface DlxBinaryResult {
47
47
  }
48
48
  /**
49
49
  * Metadata structure for cached binaries (.dlx-metadata.json).
50
- * Unified schema shared across TypeScript (dlxBinary) and C++ (socket_macho_decompress).
50
+ * Unified schema shared across TypeScript (dlxBinary) and C++ stub extractor.
51
51
  *
52
- * Core Fields (present in all implementations):
52
+ * Fields:
53
53
  * - version: Schema version (currently "1.0.0")
54
54
  * - cache_key: First 16 chars of SHA-512 hash (matches directory name)
55
55
  * - timestamp: Unix timestamp in milliseconds
56
- * - checksum: Full hash of cached binary (SHA-512 for C++, SHA-256 for TypeScript)
57
- * - checksum_algorithm: "sha512" or "sha256"
58
- * - platform: "darwin" | "linux" | "win32"
59
- * - arch: "x64" | "arm64"
56
+ * - integrity: SRI hash (sha512-<base64>, aligned with npm)
60
57
  * - size: Size of cached binary in bytes
61
58
  * - source: Origin information
62
- * - type: "download" (from URL) or "decompression" (from embedded binary)
59
+ * - type: "download" | "extract" | "package"
63
60
  * - url: Download URL (if type is "download")
64
- * - path: Source binary path (if type is "decompression")
61
+ * - path: Source binary path (if type is "extract")
62
+ * - spec: Package spec (if type is "package")
63
+ * - update_check: Update checking metadata (optional)
64
+ * - last_check: Timestamp of last update check
65
+ * - last_notification: Timestamp of last user notification
66
+ * - latest_known: Latest known version string
65
67
  *
66
- * Extra Fields (implementation-specific):
67
- * - For C++ decompression:
68
- * - compressed_size: Size of compressed data in bytes
69
- * - compression_algorithm: Brotli level (numeric)
70
- * - compression_ratio: original_size / compressed_size
71
- *
72
- * Example (TypeScript download):
68
+ * Example:
73
69
  * ```json
74
70
  * {
75
71
  * "version": "1.0.0",
76
72
  * "cache_key": "a1b2c3d4e5f67890",
77
73
  * "timestamp": 1730332800000,
78
- * "checksum": "sha256-abc123...",
79
- * "checksum_algorithm": "sha256",
80
- * "platform": "darwin",
81
- * "arch": "arm64",
74
+ * "integrity": "sha512-abc123base64...",
82
75
  * "size": 15000000,
83
76
  * "source": {
84
77
  * "type": "download",
85
78
  * "url": "https://example.com/binary"
86
- * }
87
- * }
88
- * ```
89
- *
90
- * Example (C++ decompression):
91
- * ```json
92
- * {
93
- * "version": "1.0.0",
94
- * "cache_key": "0123456789abcdef",
95
- * "timestamp": 1730332800000,
96
- * "checksum": "sha512-def456...",
97
- * "checksum_algorithm": "sha512",
98
- * "platform": "darwin",
99
- * "arch": "arm64",
100
- * "size": 13000000,
101
- * "source": {
102
- * "type": "decompression",
103
- * "path": "/usr/local/bin/socket"
104
79
  * },
105
- * "extra": {
106
- * "compressed_size": 1700000,
107
- * "compression_algorithm": 3,
108
- * "compression_ratio": 7.647
80
+ * "update_check": {
81
+ * "last_check": 1730332800000,
82
+ * "last_notification": 1730246400000,
83
+ * "latest_known": "2.1.0"
109
84
  * }
110
85
  * }
111
86
  * ```
@@ -116,17 +91,19 @@ export interface DlxMetadata {
116
91
  version: string;
117
92
  cache_key: string;
118
93
  timestamp: number;
119
- checksum: string;
120
- checksum_algorithm: string;
121
- platform: string;
122
- arch: string;
94
+ integrity: string;
123
95
  size: number;
124
96
  source?: {
125
- type: 'download' | 'decompression';
97
+ type: 'download' | 'extract' | 'package';
126
98
  url?: string;
127
99
  path?: string;
100
+ spec?: string;
101
+ };
102
+ update_check?: {
103
+ last_check: number;
104
+ last_notification: number;
105
+ latest_known: string;
128
106
  };
129
- extra?: Record<string, unknown>;
130
107
  }
131
108
  /**
132
109
  * Clean expired entries from the DLX cache.
@@ -169,10 +146,8 @@ export declare function getDlxCachePath(): string;
169
146
  */
170
147
  export declare function listDlxCache(): Promise<Array<{
171
148
  age: number;
172
- arch: string;
173
- checksum: string;
149
+ integrity: string;
174
150
  name: string;
175
- platform: string;
176
151
  size: number;
177
152
  url: string;
178
153
  }>>;
@@ -30,7 +30,6 @@ module.exports = __toCommonJS(binary_exports);
30
30
  var import_platform = require("../constants/platform");
31
31
  var import_time = require("../constants/time");
32
32
  var import_cache = require("./cache");
33
- var import_manifest = require("./manifest");
34
33
  var import_http_request = require("../http-request");
35
34
  var import_fs = require("../fs");
36
35
  var import_objects = require("../objects");
@@ -87,7 +86,7 @@ async function isCacheValid(cacheEntryPath, cacheTtl) {
87
86
  return false;
88
87
  }
89
88
  }
90
- async function downloadBinaryFile(url, destPath, checksum) {
89
+ async function downloadBinaryFile(url, destPath, integrity) {
91
90
  const crypto = /* @__PURE__ */ getCrypto();
92
91
  const fs = /* @__PURE__ */ getFs();
93
92
  const path = /* @__PURE__ */ getPath();
@@ -100,9 +99,8 @@ async function downloadBinaryFile(url, destPath, checksum) {
100
99
  const stats = await fs.promises.stat(destPath);
101
100
  if (stats.size > 0) {
102
101
  const fileBuffer2 = await fs.promises.readFile(destPath);
103
- const hasher2 = crypto.createHash("sha256");
104
- hasher2.update(fileBuffer2);
105
- return hasher2.digest("hex");
102
+ const hash2 = crypto.createHash("sha512").update(fileBuffer2).digest("base64");
103
+ return `sha512-${hash2}`;
106
104
  }
107
105
  }
108
106
  try {
@@ -116,19 +114,18 @@ Check your internet connection or verify the URL is accessible.`,
116
114
  );
117
115
  }
118
116
  const fileBuffer = await fs.promises.readFile(destPath);
119
- const hasher = crypto.createHash("sha256");
120
- hasher.update(fileBuffer);
121
- const actualChecksum = hasher.digest("hex");
122
- if (checksum && actualChecksum !== checksum) {
117
+ const hash = crypto.createHash("sha512").update(fileBuffer).digest("base64");
118
+ const actualIntegrity = `sha512-${hash}`;
119
+ if (integrity && actualIntegrity !== integrity) {
123
120
  await (0, import_fs.safeDelete)(destPath);
124
121
  throw new Error(
125
- `Checksum mismatch: expected ${checksum}, got ${actualChecksum}`
122
+ `Integrity mismatch: expected ${integrity}, got ${actualIntegrity}`
126
123
  );
127
124
  }
128
125
  if (!import_platform.WIN32) {
129
126
  await fs.promises.chmod(destPath, 493);
130
127
  }
131
- return actualChecksum;
128
+ return actualIntegrity;
132
129
  },
133
130
  {
134
131
  // Align with npm npx locking strategy.
@@ -137,16 +134,13 @@ Check your internet connection or verify the URL is accessible.`,
137
134
  }
138
135
  );
139
136
  }
140
- async function writeMetadata(cacheEntryPath, cacheKey, url, binaryName, checksum, size) {
137
+ async function writeMetadata(cacheEntryPath, cacheKey, url, integrity, size) {
141
138
  const metaPath = getMetadataPath(cacheEntryPath);
142
139
  const metadata = {
143
140
  version: "1.0.0",
144
141
  cache_key: cacheKey,
145
142
  timestamp: Date.now(),
146
- checksum,
147
- checksum_algorithm: "sha256",
148
- platform: (0, import_platform.getPlatform)(),
149
- arch: (0, import_platform.getArch)(),
143
+ integrity,
150
144
  size,
151
145
  source: {
152
146
  type: "download",
@@ -155,21 +149,6 @@ async function writeMetadata(cacheEntryPath, cacheKey, url, binaryName, checksum
155
149
  };
156
150
  const fs = /* @__PURE__ */ getFs();
157
151
  await fs.promises.writeFile(metaPath, JSON.stringify(metadata, null, 2));
158
- try {
159
- const spec = `${url}:${binaryName}`;
160
- await import_manifest.dlxManifest.setBinaryEntry(spec, cacheKey, {
161
- checksum,
162
- checksum_algorithm: metadata.checksum_algorithm,
163
- platform: metadata.platform,
164
- arch: metadata.arch,
165
- size,
166
- source: {
167
- type: "download",
168
- url
169
- }
170
- });
171
- } catch {
172
- }
173
152
  }
174
153
  async function cleanDlxCache(maxAge = import_time.DLX_BINARY_CACHE_TTL) {
175
154
  const cacheDir = getDlxCachePath();
@@ -214,8 +193,8 @@ async function cleanDlxCache(maxAge = import_time.DLX_BINARY_CACHE_TTL) {
214
193
  async function dlxBinary(args, options, spawnExtra) {
215
194
  const {
216
195
  cacheTtl = import_time.DLX_BINARY_CACHE_TTL,
217
- checksum,
218
196
  force: userForce = false,
197
+ integrity,
219
198
  name,
220
199
  spawnOptions,
221
200
  url,
@@ -231,13 +210,13 @@ async function dlxBinary(args, options, spawnExtra) {
231
210
  const cacheEntryDir = path.join(cacheDir, cacheKey);
232
211
  const binaryPath = (0, import_normalize.normalizePath)(path.join(cacheEntryDir, binaryName));
233
212
  let downloaded = false;
234
- let computedChecksum = checksum;
213
+ let computedIntegrity = integrity;
235
214
  if (!force && fs.existsSync(cacheEntryDir) && await isCacheValid(cacheEntryDir, cacheTtl)) {
236
215
  try {
237
216
  const metaPath = getMetadataPath(cacheEntryDir);
238
217
  const metadata = await (0, import_fs.readJson)(metaPath, { throws: false });
239
- if (metadata && typeof metadata === "object" && !Array.isArray(metadata) && typeof metadata["checksum"] === "string") {
240
- computedChecksum = metadata["checksum"];
218
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata) && typeof metadata["integrity"] === "string") {
219
+ computedIntegrity = metadata["integrity"];
241
220
  } else {
242
221
  downloaded = true;
243
222
  }
@@ -271,14 +250,13 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
271
250
  { cause: e }
272
251
  );
273
252
  }
274
- computedChecksum = await downloadBinaryFile(url, binaryPath, checksum);
253
+ computedIntegrity = await downloadBinaryFile(url, binaryPath, integrity);
275
254
  const stats = await fs.promises.stat(binaryPath);
276
255
  await writeMetadata(
277
256
  cacheEntryDir,
278
257
  cacheKey,
279
258
  url,
280
- binaryName,
281
- computedChecksum || "",
259
+ computedIntegrity || "",
282
260
  stats.size
283
261
  );
284
262
  }
@@ -301,8 +279,8 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
301
279
  async function downloadBinary(options) {
302
280
  const {
303
281
  cacheTtl = import_time.DLX_BINARY_CACHE_TTL,
304
- checksum,
305
282
  force = false,
283
+ integrity,
306
284
  name,
307
285
  url
308
286
  } = { __proto__: null, ...options };
@@ -341,14 +319,17 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
341
319
  { cause: e }
342
320
  );
343
321
  }
344
- const computedChecksum = await downloadBinaryFile(url, binaryPath, checksum);
322
+ const computedIntegrity = await downloadBinaryFile(
323
+ url,
324
+ binaryPath,
325
+ integrity
326
+ );
345
327
  const stats = await fs.promises.stat(binaryPath);
346
328
  await writeMetadata(
347
329
  cacheEntryDir,
348
330
  cacheKey,
349
331
  url,
350
- binaryName,
351
- computedChecksum || "",
332
+ computedIntegrity || "",
352
333
  stats.size
353
334
  );
354
335
  downloaded = true;
@@ -406,10 +387,8 @@ async function listDlxCache() {
406
387
  const binaryStats = await fs.promises.stat(binaryPath);
407
388
  results.push({
408
389
  age: now - (metaObj["timestamp"] || 0),
409
- arch: metaObj["arch"] || "unknown",
410
- checksum: metaObj["checksum"] || "",
390
+ integrity: metaObj["integrity"] || "",
411
391
  name: binaryFile,
412
- platform: metaObj["platform"] || "unknown",
413
392
  size: binaryStats.size,
414
393
  url
415
394
  });
@@ -14,17 +14,23 @@ export interface PackageDetails {
14
14
  * Details for binary download entries.
15
15
  */
16
16
  export interface BinaryDetails {
17
- checksum: string;
18
- checksum_algorithm: ChecksumAlgorithm;
17
+ /** SRI integrity hash (sha512-<base64>, aligned with npm). */
18
+ integrity: string;
19
19
  platform: string;
20
20
  arch: string;
21
21
  size: number;
22
22
  source: {
23
- type: 'download';
24
- url: string;
23
+ type: 'download' | 'extract';
24
+ url?: string;
25
+ path?: string;
26
+ };
27
+ /** Update check metadata (same structure as packages). */
28
+ update_check?: {
29
+ last_check: number;
30
+ last_notification: number;
31
+ latest_known: string;
25
32
  };
26
33
  }
27
- export type ChecksumAlgorithm = 'sha256' | 'sha512';
28
34
  /**
29
35
  * Unified manifest entry for all cached items (packages and binaries).
30
36
  * Shared fields at root, type-specific fields in details.
@@ -11,9 +11,9 @@ var __commonJS = (cb, mod) => function __require() {
11
11
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
12
  };
13
13
 
14
- // node_modules/.pnpm/libnpmexec@10.1.11_supports-color@10.0.0/node_modules/libnpmexec/lib/get-bin-from-manifest.js
14
+ // node_modules/.pnpm/libnpmexec@10.2.0_supports-color@10.0.0/node_modules/libnpmexec/lib/get-bin-from-manifest.js
15
15
  var require_get_bin_from_manifest = __commonJS({
16
- "node_modules/.pnpm/libnpmexec@10.1.11_supports-color@10.0.0/node_modules/libnpmexec/lib/get-bin-from-manifest.js"(exports2, module2) {
16
+ "node_modules/.pnpm/libnpmexec@10.2.0_supports-color@10.0.0/node_modules/libnpmexec/lib/get-bin-from-manifest.js"(exports2, module2) {
17
17
  var getBinFromManifest2 = /* @__PURE__ */ __name((mani) => {
18
18
  const bin = mani.bin || {};
19
19
  if (new Set(Object.values(bin)).size === 1) {
@@ -435,46 +435,6 @@ export interface HttpDownloadResult {
435
435
  */
436
436
  size: number;
437
437
  }
438
- /**
439
- * Make an HTTP/HTTPS request with retry logic and redirect support.
440
- * Provides a fetch-like API using Node.js native http/https modules.
441
- *
442
- * This is the main entry point for making HTTP requests. It handles retries,
443
- * redirects, timeouts, and provides a fetch-compatible response interface.
444
- *
445
- * @param url - The URL to request (must start with http:// or https://)
446
- * @param options - Request configuration options
447
- * @returns Promise resolving to response object with `.json()`, `.text()`, etc.
448
- * @throws {Error} When all retries are exhausted, timeout occurs, or non-retryable error happens
449
- *
450
- * @example
451
- * ```ts
452
- * // Simple GET request
453
- * const response = await httpRequest('https://api.example.com/data')
454
- * const data = response.json()
455
- *
456
- * // POST with JSON body
457
- * const response = await httpRequest('https://api.example.com/users', {
458
- * method: 'POST',
459
- * headers: { 'Content-Type': 'application/json' },
460
- * body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
461
- * })
462
- *
463
- * // With retries and timeout
464
- * const response = await httpRequest('https://api.example.com/data', {
465
- * retries: 3,
466
- * retryDelay: 1000,
467
- * timeout: 60000
468
- * })
469
- *
470
- * // Don't follow redirects
471
- * const response = await httpRequest('https://example.com/redirect', {
472
- * followRedirects: false
473
- * })
474
- * console.log(response.status) // 301, 302, etc.
475
- * ```
476
- */
477
- export declare function httpRequest(url: string, options?: HttpRequestOptions | undefined): Promise<HttpResponse>;
478
438
  /**
479
439
  * Download a file from a URL to a local path with redirect support, retry logic, and progress callbacks.
480
440
  * Uses streaming to avoid loading entire file in memory.
@@ -533,8 +493,12 @@ export declare function httpRequest(url: string, options?: HttpRequestOptions |
533
493
  */
534
494
  export declare function httpDownload(url: string, destPath: string, options?: HttpDownloadOptions | undefined): Promise<HttpDownloadResult>;
535
495
  /**
536
- * Perform a GET request and parse JSON response.
537
- * Convenience wrapper around `httpRequest` for common JSON API calls.
496
+ * Perform an HTTP request and parse JSON response.
497
+ * Convenience wrapper around `httpRequest` for JSON API calls.
498
+ * Automatically sets appropriate headers for JSON requests:
499
+ * - `Accept: application/json` (always)
500
+ * - `Content-Type: application/json` (when body is present)
501
+ * User-provided headers override these defaults.
538
502
  *
539
503
  * @template T - Expected JSON response type (defaults to `unknown`)
540
504
  * @param url - The URL to request (must start with http:// or https://)
@@ -544,34 +508,79 @@ export declare function httpDownload(url: string, destPath: string, options?: Ht
544
508
  *
545
509
  * @example
546
510
  * ```ts
547
- * // Simple JSON GET
548
- * const data = await httpGetJson('https://api.example.com/data')
511
+ * // Simple JSON GET (automatically sets Accept: application/json)
512
+ * const data = await httpJson('https://api.example.com/data')
549
513
  * console.log(data)
550
514
  *
551
515
  * // With type safety
552
516
  * interface User { id: number; name: string; email: string }
553
- * const user = await httpGetJson<User>('https://api.example.com/user/123')
517
+ * const user = await httpJson<User>('https://api.example.com/user/123')
554
518
  * console.log(user.name, user.email)
555
519
  *
556
- * // With custom headers
557
- * const data = await httpGetJson('https://api.example.com/data', {
558
- * headers: {
559
- * 'Authorization': 'Bearer token123',
560
- * 'Accept': 'application/json'
561
- * }
520
+ * // POST with JSON body (automatically sets Content-Type: application/json)
521
+ * const result = await httpJson('https://api.example.com/users', {
522
+ * method: 'POST',
523
+ * body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
562
524
  * })
563
525
  *
564
- * // With retries
565
- * const data = await httpGetJson('https://api.example.com/data', {
526
+ * // With custom headers and retries
527
+ * const data = await httpJson('https://api.example.com/data', {
528
+ * headers: {
529
+ * 'Authorization': 'Bearer token123'
530
+ * },
566
531
  * retries: 3,
567
532
  * retryDelay: 1000
568
533
  * })
569
534
  * ```
570
535
  */
571
- export declare function httpGetJson<T = unknown>(url: string, options?: HttpRequestOptions | undefined): Promise<T>;
536
+ export declare function httpJson<T = unknown>(url: string, options?: HttpRequestOptions | undefined): Promise<T>;
537
+ /**
538
+ * Make an HTTP/HTTPS request with retry logic and redirect support.
539
+ * Provides a fetch-like API using Node.js native http/https modules.
540
+ *
541
+ * This is the main entry point for making HTTP requests. It handles retries,
542
+ * redirects, timeouts, and provides a fetch-compatible response interface.
543
+ *
544
+ * @param url - The URL to request (must start with http:// or https://)
545
+ * @param options - Request configuration options
546
+ * @returns Promise resolving to response object with `.json()`, `.text()`, etc.
547
+ * @throws {Error} When all retries are exhausted, timeout occurs, or non-retryable error happens
548
+ *
549
+ * @example
550
+ * ```ts
551
+ * // Simple GET request
552
+ * const response = await httpRequest('https://api.example.com/data')
553
+ * const data = response.json()
554
+ *
555
+ * // POST with JSON body
556
+ * const response = await httpRequest('https://api.example.com/users', {
557
+ * method: 'POST',
558
+ * headers: { 'Content-Type': 'application/json' },
559
+ * body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
560
+ * })
561
+ *
562
+ * // With retries and timeout
563
+ * const response = await httpRequest('https://api.example.com/data', {
564
+ * retries: 3,
565
+ * retryDelay: 1000,
566
+ * timeout: 60000
567
+ * })
568
+ *
569
+ * // Don't follow redirects
570
+ * const response = await httpRequest('https://example.com/redirect', {
571
+ * followRedirects: false
572
+ * })
573
+ * console.log(response.status) // 301, 302, etc.
574
+ * ```
575
+ */
576
+ export declare function httpRequest(url: string, options?: HttpRequestOptions | undefined): Promise<HttpResponse>;
572
577
  /**
573
- * Perform a GET request and return text response.
578
+ * Perform an HTTP request and return text response.
574
579
  * Convenience wrapper around `httpRequest` for fetching text content.
580
+ * Automatically sets appropriate headers for text requests:
581
+ * - `Accept: text/plain` (always)
582
+ * - `Content-Type: text/plain` (when body is present)
583
+ * User-provided headers override these defaults.
575
584
  *
576
585
  * @param url - The URL to request (must start with http:// or https://)
577
586
  * @param options - Request configuration options
@@ -580,25 +589,32 @@ export declare function httpGetJson<T = unknown>(url: string, options?: HttpRequ
580
589
  *
581
590
  * @example
582
591
  * ```ts
583
- * // Fetch HTML
584
- * const html = await httpGetText('https://example.com')
592
+ * // Fetch HTML (automatically sets Accept: text/plain)
593
+ * const html = await httpText('https://example.com')
585
594
  * console.log(html.includes('<!DOCTYPE html>'))
586
595
  *
587
596
  * // Fetch plain text
588
- * const text = await httpGetText('https://example.com/file.txt')
597
+ * const text = await httpText('https://example.com/file.txt')
589
598
  * console.log(text)
590
599
  *
591
- * // With custom headers
592
- * const text = await httpGetText('https://example.com/data.txt', {
600
+ * // POST with text body (automatically sets Content-Type: text/plain)
601
+ * const result = await httpText('https://example.com/api', {
602
+ * method: 'POST',
603
+ * body: 'raw text data'
604
+ * })
605
+ *
606
+ * // With custom headers (override defaults)
607
+ * const text = await httpText('https://example.com/data.txt', {
593
608
  * headers: {
594
- * 'Authorization': 'Bearer token123'
609
+ * 'Authorization': 'Bearer token123',
610
+ * 'Accept': 'text/html' // Override default Accept header
595
611
  * }
596
612
  * })
597
613
  *
598
614
  * // With timeout
599
- * const text = await httpGetText('https://example.com/large-file.txt', {
615
+ * const text = await httpText('https://example.com/large-file.txt', {
600
616
  * timeout: 60000 // 1 minute
601
617
  * })
602
618
  * ```
603
619
  */
604
- export declare function httpGetText(url: string, options?: HttpRequestOptions | undefined): Promise<string>;
620
+ export declare function httpText(url: string, options?: HttpRequestOptions | undefined): Promise<string>;
@@ -20,9 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var http_request_exports = {};
21
21
  __export(http_request_exports, {
22
22
  httpDownload: () => httpDownload,
23
- httpGetJson: () => httpGetJson,
24
- httpGetText: () => httpGetText,
25
- httpRequest: () => httpRequest
23
+ httpJson: () => httpJson,
24
+ httpRequest: () => httpRequest,
25
+ httpText: () => httpText
26
26
  });
27
27
  module.exports = __toCommonJS(http_request_exports);
28
28
  let _fs;
@@ -49,38 +49,131 @@ function getHttps() {
49
49
  }
50
50
  return _https;
51
51
  }
52
- async function httpRequest(url, options) {
52
+ async function httpDownloadAttempt(url, destPath, options) {
53
53
  const {
54
- body,
55
54
  followRedirects = true,
56
55
  headers = {},
57
56
  maxRedirects = 5,
58
- method = "GET",
59
- retries = 0,
60
- retryDelay = 1e3,
61
- timeout = 3e4
57
+ onProgress,
58
+ timeout = 12e4
62
59
  } = { __proto__: null, ...options };
63
- let lastError;
64
- for (let attempt = 0; attempt <= retries; attempt++) {
65
- try {
66
- return await httpRequestAttempt(url, {
67
- body,
68
- followRedirects,
69
- headers,
70
- maxRedirects,
71
- method,
72
- timeout
73
- });
74
- } catch (e) {
75
- lastError = e;
76
- if (attempt === retries) {
77
- break;
60
+ return await new Promise((resolve, reject) => {
61
+ const parsedUrl = new URL(url);
62
+ const isHttps = parsedUrl.protocol === "https:";
63
+ const httpModule = isHttps ? /* @__PURE__ */ getHttps() : /* @__PURE__ */ getHttp();
64
+ const requestOptions = {
65
+ headers: {
66
+ "User-Agent": "socket-registry/1.0",
67
+ ...headers
68
+ },
69
+ hostname: parsedUrl.hostname,
70
+ method: "GET",
71
+ path: parsedUrl.pathname + parsedUrl.search,
72
+ port: parsedUrl.port,
73
+ timeout
74
+ };
75
+ const { createWriteStream } = /* @__PURE__ */ getFs();
76
+ let fileStream;
77
+ let streamClosed = false;
78
+ const closeStream = () => {
79
+ if (!streamClosed && fileStream) {
80
+ streamClosed = true;
81
+ fileStream.close();
78
82
  }
79
- const delayMs = retryDelay * 2 ** attempt;
80
- await new Promise((resolve) => setTimeout(resolve, delayMs));
81
- }
82
- }
83
- throw lastError || new Error("Request failed after retries");
83
+ };
84
+ const request = httpModule.request(
85
+ requestOptions,
86
+ (res) => {
87
+ if (followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
88
+ if (maxRedirects <= 0) {
89
+ reject(
90
+ new Error(
91
+ `Too many redirects (exceeded maximum: ${maxRedirects})`
92
+ )
93
+ );
94
+ return;
95
+ }
96
+ const redirectUrl = res.headers.location.startsWith("http") ? res.headers.location : new URL(res.headers.location, url).toString();
97
+ resolve(
98
+ httpDownloadAttempt(redirectUrl, destPath, {
99
+ followRedirects,
100
+ headers,
101
+ maxRedirects: maxRedirects - 1,
102
+ onProgress,
103
+ timeout
104
+ })
105
+ );
106
+ return;
107
+ }
108
+ if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
109
+ closeStream();
110
+ reject(
111
+ new Error(
112
+ `Download failed: HTTP ${res.statusCode} ${res.statusMessage}`
113
+ )
114
+ );
115
+ return;
116
+ }
117
+ const totalSize = Number.parseInt(
118
+ res.headers["content-length"] || "0",
119
+ 10
120
+ );
121
+ let downloadedSize = 0;
122
+ fileStream = createWriteStream(destPath);
123
+ fileStream.on("error", (error) => {
124
+ closeStream();
125
+ const err = new Error(`Failed to write file: ${error.message}`, {
126
+ cause: error
127
+ });
128
+ reject(err);
129
+ });
130
+ res.on("data", (chunk) => {
131
+ downloadedSize += chunk.length;
132
+ if (onProgress && totalSize > 0) {
133
+ onProgress(downloadedSize, totalSize);
134
+ }
135
+ });
136
+ res.on("end", () => {
137
+ fileStream?.close(() => {
138
+ streamClosed = true;
139
+ resolve({
140
+ path: destPath,
141
+ size: downloadedSize
142
+ });
143
+ });
144
+ });
145
+ res.on("error", (error) => {
146
+ closeStream();
147
+ reject(error);
148
+ });
149
+ res.pipe(fileStream);
150
+ }
151
+ );
152
+ request.on("error", (error) => {
153
+ closeStream();
154
+ const code = error.code;
155
+ let message = `HTTP download failed for ${url}: ${error.message}
156
+ `;
157
+ if (code === "ENOTFOUND") {
158
+ message += "DNS lookup failed. Check the hostname and your network connection.";
159
+ } else if (code === "ECONNREFUSED") {
160
+ message += "Connection refused. Verify the server is running and accessible.";
161
+ } else if (code === "ETIMEDOUT") {
162
+ message += "Request timed out. Check your network or increase the timeout value.";
163
+ } else if (code === "ECONNRESET") {
164
+ message += "Connection reset. The server may have closed the connection unexpectedly.";
165
+ } else {
166
+ message += "Check your network connection and verify the URL is correct.";
167
+ }
168
+ reject(new Error(message, { cause: error }));
169
+ });
170
+ request.on("timeout", () => {
171
+ request.destroy();
172
+ closeStream();
173
+ reject(new Error(`Download timed out after ${timeout}ms`));
174
+ });
175
+ request.end();
176
+ });
84
177
  }
85
178
  async function httpRequestAttempt(url, options) {
86
179
  const {
@@ -236,134 +329,30 @@ async function httpDownload(url, destPath, options) {
236
329
  }
237
330
  throw lastError || new Error("Download failed after retries");
238
331
  }
239
- async function httpDownloadAttempt(url, destPath, options) {
332
+ async function httpJson(url, options) {
240
333
  const {
241
- followRedirects = true,
334
+ body,
242
335
  headers = {},
243
- maxRedirects = 5,
244
- onProgress,
245
- timeout = 12e4
246
- } = { __proto__: null, ...options };
247
- return await new Promise((resolve, reject) => {
248
- const parsedUrl = new URL(url);
249
- const isHttps = parsedUrl.protocol === "https:";
250
- const httpModule = isHttps ? /* @__PURE__ */ getHttps() : /* @__PURE__ */ getHttp();
251
- const requestOptions = {
252
- headers: {
253
- "User-Agent": "socket-registry/1.0",
254
- ...headers
255
- },
256
- hostname: parsedUrl.hostname,
257
- method: "GET",
258
- path: parsedUrl.pathname + parsedUrl.search,
259
- port: parsedUrl.port,
260
- timeout
261
- };
262
- const { createWriteStream } = /* @__PURE__ */ getFs();
263
- let fileStream;
264
- let streamClosed = false;
265
- const closeStream = () => {
266
- if (!streamClosed && fileStream) {
267
- streamClosed = true;
268
- fileStream.close();
269
- }
270
- };
271
- const request = httpModule.request(
272
- requestOptions,
273
- (res) => {
274
- if (followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
275
- if (maxRedirects <= 0) {
276
- reject(
277
- new Error(
278
- `Too many redirects (exceeded maximum: ${maxRedirects})`
279
- )
280
- );
281
- return;
282
- }
283
- const redirectUrl = res.headers.location.startsWith("http") ? res.headers.location : new URL(res.headers.location, url).toString();
284
- resolve(
285
- httpDownloadAttempt(redirectUrl, destPath, {
286
- followRedirects,
287
- headers,
288
- maxRedirects: maxRedirects - 1,
289
- onProgress,
290
- timeout
291
- })
292
- );
293
- return;
294
- }
295
- if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
296
- closeStream();
297
- reject(
298
- new Error(
299
- `Download failed: HTTP ${res.statusCode} ${res.statusMessage}`
300
- )
301
- );
302
- return;
303
- }
304
- const totalSize = Number.parseInt(
305
- res.headers["content-length"] || "0",
306
- 10
307
- );
308
- let downloadedSize = 0;
309
- fileStream = createWriteStream(destPath);
310
- fileStream.on("error", (error) => {
311
- closeStream();
312
- const err = new Error(`Failed to write file: ${error.message}`, {
313
- cause: error
314
- });
315
- reject(err);
316
- });
317
- res.on("data", (chunk) => {
318
- downloadedSize += chunk.length;
319
- if (onProgress && totalSize > 0) {
320
- onProgress(downloadedSize, totalSize);
321
- }
322
- });
323
- res.on("end", () => {
324
- fileStream?.close(() => {
325
- streamClosed = true;
326
- resolve({
327
- path: destPath,
328
- size: downloadedSize
329
- });
330
- });
331
- });
332
- res.on("error", (error) => {
333
- closeStream();
334
- reject(error);
335
- });
336
- res.pipe(fileStream);
337
- }
338
- );
339
- request.on("error", (error) => {
340
- closeStream();
341
- const code = error.code;
342
- let message = `HTTP download failed for ${url}: ${error.message}
343
- `;
344
- if (code === "ENOTFOUND") {
345
- message += "DNS lookup failed. Check the hostname and your network connection.";
346
- } else if (code === "ECONNREFUSED") {
347
- message += "Connection refused. Verify the server is running and accessible.";
348
- } else if (code === "ETIMEDOUT") {
349
- message += "Request timed out. Check your network or increase the timeout value.";
350
- } else if (code === "ECONNRESET") {
351
- message += "Connection reset. The server may have closed the connection unexpectedly.";
352
- } else {
353
- message += "Check your network connection and verify the URL is correct.";
354
- }
355
- reject(new Error(message, { cause: error }));
356
- });
357
- request.on("timeout", () => {
358
- request.destroy();
359
- closeStream();
360
- reject(new Error(`Download timed out after ${timeout}ms`));
361
- });
362
- request.end();
336
+ ...restOptions
337
+ } = {
338
+ __proto__: null,
339
+ ...options
340
+ };
341
+ const defaultHeaders = {
342
+ Accept: "application/json"
343
+ };
344
+ if (body) {
345
+ defaultHeaders["Content-Type"] = "application/json";
346
+ }
347
+ const mergedHeaders = {
348
+ ...defaultHeaders,
349
+ ...headers
350
+ };
351
+ const response = await httpRequest(url, {
352
+ body,
353
+ headers: mergedHeaders,
354
+ ...restOptions
363
355
  });
364
- }
365
- async function httpGetJson(url, options) {
366
- const response = await httpRequest(url, { ...options, method: "GET" });
367
356
  if (!response.ok) {
368
357
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
369
358
  }
@@ -373,8 +362,63 @@ async function httpGetJson(url, options) {
373
362
  throw new Error("Failed to parse JSON response", { cause: e });
374
363
  }
375
364
  }
376
- async function httpGetText(url, options) {
377
- const response = await httpRequest(url, { ...options, method: "GET" });
365
+ async function httpRequest(url, options) {
366
+ const {
367
+ body,
368
+ followRedirects = true,
369
+ headers = {},
370
+ maxRedirects = 5,
371
+ method = "GET",
372
+ retries = 0,
373
+ retryDelay = 1e3,
374
+ timeout = 3e4
375
+ } = { __proto__: null, ...options };
376
+ let lastError;
377
+ for (let attempt = 0; attempt <= retries; attempt++) {
378
+ try {
379
+ return await httpRequestAttempt(url, {
380
+ body,
381
+ followRedirects,
382
+ headers,
383
+ maxRedirects,
384
+ method,
385
+ timeout
386
+ });
387
+ } catch (e) {
388
+ lastError = e;
389
+ if (attempt === retries) {
390
+ break;
391
+ }
392
+ const delayMs = retryDelay * 2 ** attempt;
393
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
394
+ }
395
+ }
396
+ throw lastError || new Error("Request failed after retries");
397
+ }
398
+ async function httpText(url, options) {
399
+ const {
400
+ body,
401
+ headers = {},
402
+ ...restOptions
403
+ } = {
404
+ __proto__: null,
405
+ ...options
406
+ };
407
+ const defaultHeaders = {
408
+ Accept: "text/plain"
409
+ };
410
+ if (body) {
411
+ defaultHeaders["Content-Type"] = "text/plain";
412
+ }
413
+ const mergedHeaders = {
414
+ ...defaultHeaders,
415
+ ...headers
416
+ };
417
+ const response = await httpRequest(url, {
418
+ body,
419
+ headers: mergedHeaders,
420
+ ...restOptions
421
+ });
378
422
  if (!response.ok) {
379
423
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
380
424
  }
@@ -383,7 +427,7 @@ async function httpGetText(url, options) {
383
427
  // Annotate the CommonJS export names for ESM import in node:
384
428
  0 && (module.exports = {
385
429
  httpDownload,
386
- httpGetJson,
387
- httpGetText,
388
- httpRequest
430
+ httpJson,
431
+ httpRequest,
432
+ httpText
389
433
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "5.5.2",
4
- "packageManager": "pnpm@10.28.0",
3
+ "version": "5.6.0",
4
+ "packageManager": "pnpm@10.29.1",
5
5
  "license": "MIT",
6
6
  "description": "Core utilities and infrastructure for Socket.dev security tools",
7
7
  "keywords": [
@@ -730,7 +730,7 @@
730
730
  "@socketregistry/is-unicode-supported": "1.0.5",
731
731
  "@socketregistry/packageurl-js": "1.3.5",
732
732
  "@socketregistry/yocto-spinner": "1.0.25",
733
- "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.5.0",
733
+ "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.5.3",
734
734
  "@types/node": "24.9.2",
735
735
  "@typescript/native-preview": "7.0.0-dev.20250920.1",
736
736
  "@vitest/coverage-v8": "4.0.3",
@@ -754,7 +754,7 @@
754
754
  "globals": "16.4.0",
755
755
  "has-flag": "5.0.1",
756
756
  "husky": "9.1.7",
757
- "libnpmexec": "^10.1.11",
757
+ "libnpmexec": "^10.2.0",
758
758
  "libnpmpack": "9.0.9",
759
759
  "lint-staged": "15.2.11",
760
760
  "magic-string": "0.30.17",
@@ -818,8 +818,8 @@
818
818
  "patchedDependencies": {
819
819
  "@npmcli/run-script@10.0.0": "patches/@npmcli__run-script@10.0.0.patch",
820
820
  "@sigstore/sign@4.1.0": "patches/@sigstore__sign@4.1.0.patch",
821
- "node-gyp@11.5.0": "patches/node-gyp@11.5.0.patch",
822
- "execa@5.1.1": "patches/execa@5.1.1.patch"
821
+ "execa@5.1.1": "patches/execa@5.1.1.patch",
822
+ "node-gyp@11.5.0": "patches/node-gyp@11.5.0.patch"
823
823
  }
824
824
  }
825
825
  }