@socketsecurity/sdk 3.0.26 → 3.0.28
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 +12 -0
- package/README.md +48 -13
- package/dist/index.mjs +3530 -57
- package/dist/testing.mjs +250 -1
- package/package.json +4 -4
- package/types/api.d.ts +31 -1
package/dist/index.mjs
CHANGED
|
@@ -1,63 +1,3536 @@
|
|
|
1
1
|
/* Socket SDK ESM - Built with esbuild */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
|
|
3
|
+
// package.json
|
|
4
|
+
var package_default = {
|
|
5
|
+
name: "@socketsecurity/sdk",
|
|
6
|
+
version: "3.0.28",
|
|
7
|
+
license: "MIT",
|
|
8
|
+
description: "SDK for the Socket API client",
|
|
9
|
+
author: {
|
|
10
|
+
name: "Socket Inc",
|
|
11
|
+
email: "eng@socket.dev",
|
|
12
|
+
url: "https://socket.dev"
|
|
13
|
+
},
|
|
14
|
+
homepage: "https://github.com/SocketDev/socket-sdk-js",
|
|
15
|
+
repository: {
|
|
16
|
+
type: "git",
|
|
17
|
+
url: "git://github.com/SocketDev/socket-sdk-js.git"
|
|
18
|
+
},
|
|
19
|
+
type: "module",
|
|
20
|
+
main: "./dist/index.mjs",
|
|
21
|
+
types: "./dist/index.d.ts",
|
|
22
|
+
exports: {
|
|
23
|
+
".": {
|
|
24
|
+
types: "./dist/index.d.ts",
|
|
25
|
+
default: "./dist/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
"./testing": {
|
|
29
|
+
types: "./dist/testing.d.ts",
|
|
30
|
+
default: "./dist/testing.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./types/api": {
|
|
33
|
+
types: "./types/api.d.ts",
|
|
34
|
+
default: "./types/api.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./types/api-helpers": {
|
|
37
|
+
types: "./types/api-helpers.d.ts",
|
|
38
|
+
default: "./types/api-helpers.d.ts"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
scripts: {
|
|
42
|
+
build: "node scripts/build.mjs",
|
|
43
|
+
bump: "node scripts/bump.mjs",
|
|
44
|
+
check: "node scripts/check.mjs",
|
|
45
|
+
clean: "node scripts/clean.mjs",
|
|
46
|
+
cover: "node scripts/cover.mjs",
|
|
47
|
+
fix: "node scripts/lint.mjs --fix",
|
|
48
|
+
"generate-sdk": "node scripts/generate-sdk.mjs",
|
|
49
|
+
lint: "node scripts/lint.mjs",
|
|
50
|
+
precommit: "pnpm run check --lint --staged",
|
|
51
|
+
prepare: "husky",
|
|
52
|
+
prepublishOnly: "echo 'ERROR: Use GitHub Actions workflow for publishing' && exit 1",
|
|
53
|
+
publish: "node scripts/publish.mjs",
|
|
54
|
+
claude: "node scripts/claude.mjs",
|
|
55
|
+
test: "node scripts/test.mjs",
|
|
56
|
+
type: "tsgo --noEmit -p .config/tsconfig.check.json",
|
|
57
|
+
update: "node scripts/update.mjs"
|
|
58
|
+
},
|
|
59
|
+
dependencies: {
|
|
60
|
+
"@socketsecurity/lib": "3.0.3"
|
|
61
|
+
},
|
|
62
|
+
devDependencies: {
|
|
63
|
+
"@babel/parser": "7.26.3",
|
|
64
|
+
"@babel/traverse": "7.26.4",
|
|
65
|
+
"@babel/types": "7.26.3",
|
|
66
|
+
"@biomejs/biome": "2.2.4",
|
|
67
|
+
"@dotenvx/dotenvx": "1.49.0",
|
|
68
|
+
"@eslint/compat": "1.3.2",
|
|
69
|
+
"@eslint/js": "9.35.0",
|
|
70
|
+
"@types/node": "24.9.2",
|
|
71
|
+
"@typescript/native-preview": "7.0.0-dev.20250926.1",
|
|
72
|
+
"@vitest/coverage-v8": "4.0.3",
|
|
73
|
+
del: "8.0.1",
|
|
74
|
+
"dev-null-cli": "2.0.0",
|
|
75
|
+
esbuild: "0.25.11",
|
|
76
|
+
eslint: "9.35.0",
|
|
77
|
+
"eslint-import-resolver-typescript": "4.4.4",
|
|
78
|
+
"eslint-plugin-import-x": "4.16.1",
|
|
79
|
+
"eslint-plugin-jsdoc": "57.0.8",
|
|
80
|
+
"eslint-plugin-n": "17.23.1",
|
|
81
|
+
"eslint-plugin-sort-destructure-keys": "2.0.0",
|
|
82
|
+
"eslint-plugin-unicorn": "56.0.1",
|
|
83
|
+
"fast-glob": "3.3.3",
|
|
84
|
+
globals: "16.4.0",
|
|
85
|
+
"http2-wrapper": "2.2.1",
|
|
86
|
+
husky: "9.1.7",
|
|
87
|
+
"magic-string": "0.30.14",
|
|
88
|
+
nock: "14.0.10",
|
|
89
|
+
"npm-run-all2": "8.0.4",
|
|
90
|
+
"openapi-typescript": "6.7.6",
|
|
91
|
+
semver: "7.7.2",
|
|
92
|
+
taze: "19.6.0",
|
|
93
|
+
"type-coverage": "2.29.7",
|
|
94
|
+
"typescript-eslint": "8.44.1",
|
|
95
|
+
vitest: "4.0.3",
|
|
96
|
+
"yoctocolors-cjs": "2.1.3"
|
|
97
|
+
},
|
|
98
|
+
pnpm: {
|
|
99
|
+
ignoredBuiltDependencies: [
|
|
100
|
+
"esbuild",
|
|
101
|
+
"unrs-resolver"
|
|
102
|
+
],
|
|
103
|
+
overrides: {
|
|
104
|
+
vite: "7.1.12"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
engines: {
|
|
108
|
+
node: ">=18",
|
|
109
|
+
pnpm: ">=10.16.0"
|
|
110
|
+
},
|
|
111
|
+
files: [
|
|
112
|
+
"CHANGELOG.md",
|
|
113
|
+
"data/*.json",
|
|
114
|
+
"dist/*.d.ts",
|
|
115
|
+
"dist/*.js",
|
|
116
|
+
"dist/*.mjs",
|
|
117
|
+
"types/*.d.ts"
|
|
118
|
+
],
|
|
119
|
+
typeCoverage: {
|
|
120
|
+
cache: true,
|
|
121
|
+
atLeast: 99,
|
|
122
|
+
ignoreAsAssertion: true,
|
|
123
|
+
ignoreCatch: true,
|
|
124
|
+
ignoreEmptyType: true,
|
|
125
|
+
"ignore-non-null-assertion": true,
|
|
126
|
+
"ignore-type-assertion": true,
|
|
127
|
+
"ignore-files": "test/*",
|
|
128
|
+
strict: true
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/user-agent.ts
|
|
133
|
+
function createUserAgentFromPkgJson(pkgData) {
|
|
134
|
+
const { homepage } = pkgData;
|
|
135
|
+
const name = pkgData.name.replace("@", "").replace("/", "-");
|
|
136
|
+
return `${name}/${pkgData.version}${homepage ? ` (${homepage})` : ""}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/constants.ts
|
|
140
|
+
import {
|
|
141
|
+
SOCKET_API_TOKENS_URL,
|
|
142
|
+
SOCKET_CONTACT_URL,
|
|
143
|
+
SOCKET_DASHBOARD_URL
|
|
144
|
+
} from "@socketsecurity/lib/constants/socket";
|
|
145
|
+
var DEFAULT_USER_AGENT = createUserAgentFromPkgJson(package_default);
|
|
146
|
+
var DEFAULT_HTTP_TIMEOUT = 3e4;
|
|
147
|
+
var DEFAULT_RETRIES = 3;
|
|
148
|
+
var DEFAULT_RETRY_DELAY = 1e3;
|
|
149
|
+
var MAX_HTTP_TIMEOUT = 5 * 60 * 1e3;
|
|
150
|
+
var MIN_HTTP_TIMEOUT = 5e3;
|
|
151
|
+
var MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
|
|
152
|
+
var MAX_STREAM_SIZE = 100 * 1024 * 1024;
|
|
153
|
+
var SOCKET_PUBLIC_BLOB_STORE_URL = "https://socketusercontent.com";
|
|
154
|
+
var httpAgentNames = /* @__PURE__ */ new Set(["http", "https", "http2"]);
|
|
155
|
+
var publicPolicy = /* @__PURE__ */ new Map([
|
|
156
|
+
// error (1):
|
|
157
|
+
["malware", "error"],
|
|
158
|
+
// warn (7):
|
|
159
|
+
["criticalCVE", "warn"],
|
|
160
|
+
["didYouMean", "warn"],
|
|
161
|
+
["gitDependency", "warn"],
|
|
162
|
+
["httpDependency", "warn"],
|
|
163
|
+
["licenseSpdxDisj", "warn"],
|
|
164
|
+
["obfuscatedFile", "warn"],
|
|
165
|
+
["troll", "warn"],
|
|
166
|
+
// monitor (7):
|
|
167
|
+
["deprecated", "monitor"],
|
|
168
|
+
["mediumCVE", "monitor"],
|
|
169
|
+
["mildCVE", "monitor"],
|
|
170
|
+
["shrinkwrap", "monitor"],
|
|
171
|
+
["telemetry", "monitor"],
|
|
172
|
+
["unpopularPackage", "monitor"],
|
|
173
|
+
["unstableOwnership", "monitor"],
|
|
174
|
+
// ignore (85):
|
|
175
|
+
["ambiguousClassifier", "ignore"],
|
|
176
|
+
["badEncoding", "ignore"],
|
|
177
|
+
["badSemver", "ignore"],
|
|
178
|
+
["badSemverDependency", "ignore"],
|
|
179
|
+
["bidi", "ignore"],
|
|
180
|
+
["binScriptConfusion", "ignore"],
|
|
181
|
+
["chromeContentScript", "ignore"],
|
|
182
|
+
["chromeHostPermission", "ignore"],
|
|
183
|
+
["chromePermission", "ignore"],
|
|
184
|
+
["chromeWildcardHostPermission", "ignore"],
|
|
185
|
+
["chronoAnomaly", "ignore"],
|
|
186
|
+
["compromisedSSHKey", "ignore"],
|
|
187
|
+
["copyleftLicense", "ignore"],
|
|
188
|
+
["cve", "ignore"],
|
|
189
|
+
["debugAccess", "ignore"],
|
|
190
|
+
["deprecatedLicense", "ignore"],
|
|
191
|
+
["deprecatedException", "ignore"],
|
|
192
|
+
["dynamicRequire", "ignore"],
|
|
193
|
+
["emptyPackage", "ignore"],
|
|
194
|
+
["envVars", "ignore"],
|
|
195
|
+
["explicitlyUnlicensedItem", "ignore"],
|
|
196
|
+
["extraneousDependency", "ignore"],
|
|
197
|
+
["fileDependency", "ignore"],
|
|
198
|
+
["filesystemAccess", "ignore"],
|
|
199
|
+
["floatingDependency", "ignore"],
|
|
200
|
+
["gitHubDependency", "ignore"],
|
|
201
|
+
["gptAnomaly", "ignore"],
|
|
202
|
+
["gptDidYouMean", "ignore"],
|
|
203
|
+
["gptMalware", "ignore"],
|
|
204
|
+
["gptSecurity", "ignore"],
|
|
205
|
+
["hasNativeCode", "ignore"],
|
|
206
|
+
["highEntropyStrings", "ignore"],
|
|
207
|
+
["homoglyphs", "ignore"],
|
|
208
|
+
["installScripts", "ignore"],
|
|
209
|
+
["invalidPackageJSON", "ignore"],
|
|
210
|
+
["invisibleChars", "ignore"],
|
|
211
|
+
["licenseChange", "ignore"],
|
|
212
|
+
["licenseException", "ignore"],
|
|
213
|
+
["longStrings", "ignore"],
|
|
214
|
+
["majorRefactor", "ignore"],
|
|
215
|
+
["manifestConfusion", "ignore"],
|
|
216
|
+
["minifiedFile", "ignore"],
|
|
217
|
+
["miscLicenseIssues", "ignore"],
|
|
218
|
+
["missingAuthor", "ignore"],
|
|
219
|
+
["missingDependency", "ignore"],
|
|
220
|
+
["missingLicense", "ignore"],
|
|
221
|
+
["missingTarball", "ignore"],
|
|
222
|
+
["mixedLicense", "ignore"],
|
|
223
|
+
["modifiedException", "ignore"],
|
|
224
|
+
["modifiedLicense", "ignore"],
|
|
225
|
+
["networkAccess", "ignore"],
|
|
226
|
+
["newAuthor", "ignore"],
|
|
227
|
+
["noAuthorData", "ignore"],
|
|
228
|
+
["noBugTracker", "ignore"],
|
|
229
|
+
["noLicenseFound", "ignore"],
|
|
230
|
+
["noREADME", "ignore"],
|
|
231
|
+
["noRepository", "ignore"],
|
|
232
|
+
["noTests", "ignore"],
|
|
233
|
+
["noV1", "ignore"],
|
|
234
|
+
["noWebsite", "ignore"],
|
|
235
|
+
["nonOSILicense", "ignore"],
|
|
236
|
+
["nonSPDXLicense", "ignore"],
|
|
237
|
+
["nonpermissiveLicense", "ignore"],
|
|
238
|
+
["notice", "ignore"],
|
|
239
|
+
["obfuscatedRequire", "ignore"],
|
|
240
|
+
["peerDependency", "ignore"],
|
|
241
|
+
["potentialVulnerability", "ignore"],
|
|
242
|
+
["semverAnomaly", "ignore"],
|
|
243
|
+
["shellAccess", "ignore"],
|
|
244
|
+
["shellScriptOverride", "ignore"],
|
|
245
|
+
["socketUpgradeAvailable", "ignore"],
|
|
246
|
+
["suspiciousStarActivity", "ignore"],
|
|
247
|
+
["suspiciousString", "ignore"],
|
|
248
|
+
["trivialPackage", "ignore"],
|
|
249
|
+
["typeModuleCompatibility", "ignore"],
|
|
250
|
+
["uncaughtOptionalDependency", "ignore"],
|
|
251
|
+
["unclearLicense", "ignore"],
|
|
252
|
+
["unidentifiedLicense", "ignore"],
|
|
253
|
+
["unmaintained", "ignore"],
|
|
254
|
+
["unpublished", "ignore"],
|
|
255
|
+
["unresolvedRequire", "ignore"],
|
|
256
|
+
["unsafeCopyright", "ignore"],
|
|
257
|
+
["unusedDependency", "ignore"],
|
|
258
|
+
["urlStrings", "ignore"],
|
|
259
|
+
["usesEval", "ignore"],
|
|
260
|
+
["zeroWidth", "ignore"]
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
// src/utils.ts
|
|
264
|
+
import path from "node:path";
|
|
265
|
+
import { memoize } from "@socketsecurity/lib/memoization";
|
|
266
|
+
import { normalizePath } from "@socketsecurity/lib/path";
|
|
267
|
+
var normalizeBaseUrl = memoize(
|
|
268
|
+
(baseUrl) => {
|
|
269
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
270
|
+
},
|
|
271
|
+
{ name: "normalizeBaseUrl" }
|
|
272
|
+
);
|
|
273
|
+
function promiseWithResolvers() {
|
|
274
|
+
if (Promise.withResolvers) {
|
|
275
|
+
return Promise.withResolvers();
|
|
276
|
+
}
|
|
277
|
+
const obj = {};
|
|
278
|
+
obj.promise = new Promise((resolver, reject) => {
|
|
279
|
+
obj.resolve = resolver;
|
|
280
|
+
obj.reject = reject;
|
|
281
|
+
});
|
|
282
|
+
return obj;
|
|
283
|
+
}
|
|
284
|
+
function queryToSearchParams(init) {
|
|
285
|
+
const params = new URLSearchParams(
|
|
286
|
+
init
|
|
287
|
+
);
|
|
288
|
+
const normalized = { __proto__: null };
|
|
289
|
+
const entries = params.entries();
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
let key = entry[0];
|
|
292
|
+
const value = entry[1];
|
|
293
|
+
if (key === "defaultBranch") {
|
|
294
|
+
key = "default_branch";
|
|
295
|
+
} else if (key === "perPage") {
|
|
296
|
+
key = "per_page";
|
|
297
|
+
}
|
|
298
|
+
if (value) {
|
|
299
|
+
normalized[key] = value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return new URLSearchParams(normalized);
|
|
303
|
+
}
|
|
304
|
+
function resolveAbsPaths(filepaths, pathsRelativeTo) {
|
|
305
|
+
const basePath = resolveBasePath(pathsRelativeTo);
|
|
306
|
+
return filepaths.map((p) => normalizePath(path.resolve(basePath, p)));
|
|
307
|
+
}
|
|
308
|
+
function resolveBasePath(pathsRelativeTo = ".") {
|
|
309
|
+
return normalizePath(path.resolve(process.cwd(), pathsRelativeTo));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/file-upload.ts
|
|
313
|
+
import events from "node:events";
|
|
314
|
+
import { createReadStream } from "node:fs";
|
|
315
|
+
import path2 from "node:path";
|
|
316
|
+
import { Readable } from "node:stream";
|
|
317
|
+
import { normalizePath as normalizePath2 } from "@socketsecurity/lib/path";
|
|
318
|
+
|
|
319
|
+
// src/http-client.ts
|
|
320
|
+
import http from "node:http";
|
|
321
|
+
import https from "node:https";
|
|
322
|
+
import { debugLog } from "@socketsecurity/lib/debug";
|
|
323
|
+
import { jsonParse } from "@socketsecurity/lib/json";
|
|
324
|
+
import { perfTimer } from "@socketsecurity/lib/performance";
|
|
325
|
+
var ResponseError = class _ResponseError extends Error {
|
|
326
|
+
response;
|
|
327
|
+
/**
|
|
328
|
+
* Create a new ResponseError from an HTTP response.
|
|
329
|
+
* Automatically formats error message with status code and message.
|
|
330
|
+
*/
|
|
331
|
+
constructor(response, message = "") {
|
|
332
|
+
const statusCode = response.statusCode ?? "unknown";
|
|
333
|
+
const statusMessage = response.statusMessage ?? "No status message";
|
|
334
|
+
super(
|
|
335
|
+
/* c8 ignore next - fallback empty message if not provided */
|
|
336
|
+
`Socket API ${message || "Request failed"} (${statusCode}): ${statusMessage}`
|
|
337
|
+
);
|
|
338
|
+
this.name = "ResponseError";
|
|
339
|
+
this.response = response;
|
|
340
|
+
Error.captureStackTrace(this, _ResponseError);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
async function createDeleteRequest(baseUrl, urlPath, options) {
|
|
344
|
+
const req = getHttpModule(baseUrl).request(`${baseUrl}${urlPath}`, {
|
|
345
|
+
method: "DELETE",
|
|
346
|
+
...options
|
|
347
|
+
}).end();
|
|
348
|
+
return await getResponse(req);
|
|
349
|
+
}
|
|
350
|
+
async function createGetRequest(baseUrl, urlPath, options) {
|
|
351
|
+
const stopTimer = perfTimer("http:get", { urlPath });
|
|
352
|
+
try {
|
|
353
|
+
const req = getHttpModule(baseUrl).request(`${baseUrl}${urlPath}`, {
|
|
354
|
+
method: "GET",
|
|
355
|
+
...options
|
|
356
|
+
}).end();
|
|
357
|
+
const response = await getResponse(req);
|
|
358
|
+
stopTimer({ statusCode: response.statusCode });
|
|
359
|
+
return response;
|
|
360
|
+
} catch (error) {
|
|
361
|
+
stopTimer({ error: true });
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async function createRequestWithJson(method, baseUrl, urlPath, json, options) {
|
|
366
|
+
const stopTimer = perfTimer(`http:${method.toLowerCase()}`, {
|
|
367
|
+
urlPath
|
|
368
|
+
});
|
|
369
|
+
try {
|
|
370
|
+
const body = JSON.stringify(json);
|
|
371
|
+
const req = getHttpModule(baseUrl).request(`${baseUrl}${urlPath}`, {
|
|
372
|
+
method,
|
|
373
|
+
...options,
|
|
374
|
+
headers: {
|
|
375
|
+
...options.headers,
|
|
376
|
+
"Content-Length": Buffer.byteLength(body, "utf8"),
|
|
377
|
+
"Content-Type": "application/json"
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
req.write(body);
|
|
381
|
+
req.end();
|
|
382
|
+
const response = await getResponse(req);
|
|
383
|
+
stopTimer({ statusCode: response.statusCode });
|
|
384
|
+
return response;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
stopTimer({ error: true });
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async function getErrorResponseBody(response) {
|
|
391
|
+
return await new Promise((resolve, reject) => {
|
|
392
|
+
let body = "";
|
|
393
|
+
let totalBytes = 0;
|
|
394
|
+
response.setEncoding("utf8");
|
|
395
|
+
response.on("data", (chunk) => {
|
|
396
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf8");
|
|
397
|
+
totalBytes += chunkBytes;
|
|
398
|
+
if (totalBytes > MAX_RESPONSE_SIZE) {
|
|
399
|
+
response.destroy();
|
|
400
|
+
const sizeMB = (totalBytes / (1024 * 1024)).toFixed(2);
|
|
401
|
+
const maxMB = (MAX_RESPONSE_SIZE / (1024 * 1024)).toFixed(2);
|
|
402
|
+
const message = [
|
|
403
|
+
`Response exceeds maximum size limit (${sizeMB}MB > ${maxMB}MB)`,
|
|
404
|
+
"\u2192 The API response is too large to process safely.",
|
|
405
|
+
"\u2192 Try: Use pagination parameters (limit, offset) to reduce response size.",
|
|
406
|
+
"\u2192 Try: Request specific fields instead of full objects.",
|
|
407
|
+
"\u2192 Contact support if you need to process larger responses."
|
|
408
|
+
].join("\n");
|
|
409
|
+
reject(new Error(message));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
body += chunk;
|
|
413
|
+
});
|
|
414
|
+
response.on("end", () => resolve(body));
|
|
415
|
+
response.on("error", (e) => reject(e));
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
function getHttpModule(url) {
|
|
419
|
+
return url.startsWith("https:") ? https : http;
|
|
420
|
+
}
|
|
421
|
+
async function getResponse(req) {
|
|
422
|
+
return await new Promise((resolve, reject) => {
|
|
423
|
+
let timedOut = false;
|
|
424
|
+
req.on("response", (response) => {
|
|
425
|
+
if (timedOut) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
resolve(response);
|
|
429
|
+
});
|
|
430
|
+
req.on("timeout", () => {
|
|
431
|
+
timedOut = true;
|
|
432
|
+
req.destroy();
|
|
433
|
+
const method = req.method || "REQUEST";
|
|
434
|
+
const path3 = req.path || "unknown";
|
|
435
|
+
const timeout = req.timeout || "configured timeout";
|
|
436
|
+
const message = [
|
|
437
|
+
`${method} request timed out after ${timeout}ms: ${path3}`,
|
|
438
|
+
"\u2192 The Socket API did not respond in time.",
|
|
439
|
+
"\u2192 Try: Increase timeout option or check network connectivity.",
|
|
440
|
+
"\u2192 If problem persists, Socket API may be experiencing issues."
|
|
441
|
+
].join("\n");
|
|
442
|
+
reject(new Error(message));
|
|
443
|
+
});
|
|
444
|
+
req.on("error", (e) => {
|
|
445
|
+
if (!timedOut) {
|
|
446
|
+
const err = e;
|
|
447
|
+
const method = req.method || "REQUEST";
|
|
448
|
+
const path3 = req.path || "unknown";
|
|
449
|
+
let message = `${method} request failed: ${path3}`;
|
|
450
|
+
if (err.code === "ECONNREFUSED") {
|
|
451
|
+
message += [
|
|
452
|
+
"",
|
|
453
|
+
"\u2192 Connection refused. Socket API server is unreachable.",
|
|
454
|
+
"\u2192 Check: Network connectivity and firewall settings.",
|
|
455
|
+
"\u2192 Verify: Base URL is correct (default: https://api.socket.dev)"
|
|
456
|
+
].join("\n");
|
|
457
|
+
} else if (err.code === "ENOTFOUND") {
|
|
458
|
+
message += [
|
|
459
|
+
"",
|
|
460
|
+
"\u2192 DNS lookup failed. Cannot resolve hostname.",
|
|
461
|
+
"\u2192 Check: Internet connection and DNS settings.",
|
|
462
|
+
"\u2192 Verify: Base URL hostname is correct."
|
|
463
|
+
].join("\n");
|
|
464
|
+
} else if (err.code === "ETIMEDOUT") {
|
|
465
|
+
message += [
|
|
466
|
+
"",
|
|
467
|
+
"\u2192 Connection timed out. Network or server issue.",
|
|
468
|
+
"\u2192 Try: Check network connectivity and retry.",
|
|
469
|
+
"\u2192 If using proxy, verify proxy configuration."
|
|
470
|
+
].join("\n");
|
|
471
|
+
} else if (err.code === "ECONNRESET") {
|
|
472
|
+
message += [
|
|
473
|
+
"",
|
|
474
|
+
"\u2192 Connection reset by server. Possible network interruption.",
|
|
475
|
+
"\u2192 Try: Retry the request. Enable retries option if not set."
|
|
476
|
+
].join("\n");
|
|
477
|
+
} else if (err.code === "EPIPE") {
|
|
478
|
+
message += [
|
|
479
|
+
"",
|
|
480
|
+
"\u2192 Broken pipe. Server closed connection unexpectedly.",
|
|
481
|
+
"\u2192 Possible: Authentication issue or server error.",
|
|
482
|
+
"\u2192 Check: API token is valid and has required permissions."
|
|
483
|
+
].join("\n");
|
|
484
|
+
} else if (err.code === "CERT_HAS_EXPIRED" || err.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") {
|
|
485
|
+
message += [
|
|
486
|
+
"",
|
|
487
|
+
"\u2192 SSL/TLS certificate error.",
|
|
488
|
+
"\u2192 Check: System time and date are correct.",
|
|
489
|
+
"\u2192 Try: Update CA certificates on your system."
|
|
490
|
+
].join("\n");
|
|
491
|
+
} else if (err.code) {
|
|
492
|
+
message += `
|
|
493
|
+
\u2192 Error code: ${err.code}`;
|
|
494
|
+
}
|
|
495
|
+
const enhancedError = new Error(message, { cause: e });
|
|
496
|
+
reject(enhancedError);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
async function getResponseJson(response, method) {
|
|
502
|
+
const stopTimer = perfTimer("http:parse-json");
|
|
503
|
+
try {
|
|
504
|
+
if (!isResponseOk(response)) {
|
|
505
|
+
throw new ResponseError(
|
|
506
|
+
response,
|
|
507
|
+
method ? `${method} Request failed` : void 0
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
const responseBody = await getErrorResponseBody(response);
|
|
511
|
+
if (responseBody === "") {
|
|
512
|
+
debugLog("API response: empty response treated as {}");
|
|
513
|
+
stopTimer({ success: true });
|
|
514
|
+
return {};
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
const responseJson = jsonParse(responseBody);
|
|
518
|
+
debugLog("API response:", responseJson);
|
|
519
|
+
stopTimer({ success: true });
|
|
520
|
+
return responseJson;
|
|
521
|
+
} catch (e) {
|
|
522
|
+
stopTimer({ error: true });
|
|
523
|
+
if (e instanceof SyntaxError) {
|
|
524
|
+
const contentType = response.headers["content-type"];
|
|
525
|
+
const preview = responseBody.length > 200 ? `${responseBody.slice(0, 200)}...` : responseBody;
|
|
526
|
+
const messageParts = [
|
|
527
|
+
"Socket API returned invalid JSON response",
|
|
528
|
+
`\u2192 Response preview: ${preview}`,
|
|
529
|
+
`\u2192 Parse error: ${e.message}`
|
|
530
|
+
];
|
|
531
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
532
|
+
messageParts.push(
|
|
533
|
+
`\u2192 Unexpected Content-Type: ${contentType} (expected application/json)`,
|
|
534
|
+
"\u2192 The API may have returned an error page instead of JSON."
|
|
535
|
+
);
|
|
536
|
+
} else if (responseBody.startsWith("<")) {
|
|
537
|
+
messageParts.push(
|
|
538
|
+
"\u2192 Response appears to be HTML, not JSON.",
|
|
539
|
+
"\u2192 This may indicate an API endpoint error or network interception."
|
|
540
|
+
);
|
|
541
|
+
} else if (responseBody.length === 0) {
|
|
542
|
+
messageParts.push("\u2192 Response body is empty when JSON was expected.");
|
|
543
|
+
} else if (responseBody.includes("502 Bad Gateway") || responseBody.includes("503 Service")) {
|
|
544
|
+
messageParts.push(
|
|
545
|
+
"\u2192 Response indicates a server error.",
|
|
546
|
+
"\u2192 The Socket API may be temporarily unavailable."
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
const enhancedError = new Error(messageParts.join("\n"), {
|
|
550
|
+
cause: e
|
|
551
|
+
});
|
|
552
|
+
enhancedError.name = "SyntaxError";
|
|
553
|
+
enhancedError.originalResponse = responseBody;
|
|
554
|
+
Object.setPrototypeOf(enhancedError, SyntaxError.prototype);
|
|
555
|
+
throw enhancedError;
|
|
556
|
+
}
|
|
557
|
+
if (e instanceof Error) {
|
|
558
|
+
throw e;
|
|
559
|
+
}
|
|
560
|
+
const unknownError = new Error("Unknown JSON parsing error", {
|
|
561
|
+
cause: e
|
|
562
|
+
});
|
|
563
|
+
unknownError.name = "SyntaxError";
|
|
564
|
+
unknownError.originalResponse = responseBody;
|
|
565
|
+
Object.setPrototypeOf(unknownError, SyntaxError.prototype);
|
|
566
|
+
throw unknownError;
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
stopTimer({ error: true });
|
|
570
|
+
throw error;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function isResponseOk(response) {
|
|
574
|
+
const { statusCode } = response;
|
|
575
|
+
return statusCode ? statusCode >= 200 && statusCode < 300 : false;
|
|
576
|
+
}
|
|
577
|
+
function reshapeArtifactForPublicPolicy(data, isAuthenticated, actions) {
|
|
578
|
+
if (!isAuthenticated) {
|
|
579
|
+
const allowedActions = actions ? actions.split(",") : void 0;
|
|
580
|
+
const reshapeArtifact = (artifact) => ({
|
|
581
|
+
name: artifact.name,
|
|
582
|
+
version: artifact.version,
|
|
583
|
+
size: artifact.size,
|
|
584
|
+
author: artifact.author,
|
|
585
|
+
type: artifact.type,
|
|
586
|
+
supplyChainRisk: artifact.supplyChainRisk,
|
|
587
|
+
scorecards: artifact.scorecards,
|
|
588
|
+
topLevelAncestors: artifact.topLevelAncestors,
|
|
589
|
+
// Compact the alerts array to reduce response size for non-authenticated
|
|
590
|
+
// requests.
|
|
591
|
+
alerts: artifact.alerts?.filter((alert) => {
|
|
592
|
+
if (alert.severity === "low") {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
if (allowedActions && alert.action && !allowedActions.includes(alert.action)) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
}).map((alert) => ({
|
|
600
|
+
type: alert.type,
|
|
601
|
+
severity: alert.severity,
|
|
602
|
+
key: alert.key
|
|
603
|
+
}))
|
|
604
|
+
});
|
|
605
|
+
if (data["artifacts"]) {
|
|
606
|
+
const artifacts = data["artifacts"];
|
|
607
|
+
return {
|
|
608
|
+
...data,
|
|
609
|
+
artifacts: Array.isArray(artifacts) ? artifacts.map(reshapeArtifact) : artifacts
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
if (data["alerts"]) {
|
|
613
|
+
return reshapeArtifact(
|
|
614
|
+
data
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return data;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/file-upload.ts
|
|
622
|
+
function createRequestBodyForFilepaths(filepaths, basePath) {
|
|
623
|
+
const requestBody = [];
|
|
624
|
+
for (const absPath of filepaths) {
|
|
625
|
+
const relPath = normalizePath2(path2.relative(basePath, absPath));
|
|
626
|
+
const filename = path2.basename(absPath);
|
|
627
|
+
let stream;
|
|
628
|
+
try {
|
|
629
|
+
stream = createReadStream(absPath, { highWaterMark: 1024 * 1024 });
|
|
630
|
+
} catch (error) {
|
|
631
|
+
const err = error;
|
|
632
|
+
let message = `Failed to read file: ${absPath}`;
|
|
633
|
+
if (err.code === "ENOENT") {
|
|
634
|
+
message += "\n\u2192 File does not exist. Check the file path and try again.";
|
|
635
|
+
} else if (err.code === "EACCES") {
|
|
636
|
+
message += `
|
|
637
|
+
\u2192 Permission denied. Run: chmod +r "${absPath}"`;
|
|
638
|
+
} else if (err.code === "EISDIR") {
|
|
639
|
+
message += "\n\u2192 Expected a file but found a directory.";
|
|
640
|
+
} else if (err.code) {
|
|
641
|
+
message += `
|
|
642
|
+
\u2192 Error code: ${err.code}`;
|
|
643
|
+
}
|
|
644
|
+
throw new Error(message, { cause: error });
|
|
645
|
+
}
|
|
646
|
+
requestBody.push([
|
|
647
|
+
`Content-Disposition: form-data; name="${relPath}"; filename="${filename}"\r
|
|
648
|
+
`,
|
|
649
|
+
"Content-Type: application/octet-stream\r\n\r\n",
|
|
650
|
+
stream
|
|
651
|
+
]);
|
|
652
|
+
}
|
|
653
|
+
return requestBody;
|
|
654
|
+
}
|
|
655
|
+
function createRequestBodyForJson(jsonData, basename = "data.json") {
|
|
656
|
+
const ext = path2.extname(basename);
|
|
657
|
+
const name = path2.basename(basename, ext);
|
|
658
|
+
return [
|
|
659
|
+
`Content-Disposition: form-data; name="${name}"; filename="${basename}"\r
|
|
20
660
|
Content-Type: application/json\r
|
|
21
661
|
\r
|
|
22
|
-
`,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
662
|
+
`,
|
|
663
|
+
Readable.from(JSON.stringify(jsonData), { highWaterMark: 1024 * 1024 }),
|
|
664
|
+
"\r\n"
|
|
665
|
+
];
|
|
666
|
+
}
|
|
667
|
+
async function createUploadRequest(baseUrl, urlPath, requestBodyNoBoundaries, options) {
|
|
668
|
+
return await new Promise(async (pass, fail) => {
|
|
669
|
+
const boundary = `NodeMultipartBoundary${Date.now()}`;
|
|
670
|
+
const boundarySep = `--${boundary}\r
|
|
671
|
+
`;
|
|
672
|
+
const finalBoundary = `--${boundary}--\r
|
|
673
|
+
`;
|
|
674
|
+
const requestBody = [
|
|
675
|
+
...requestBodyNoBoundaries.flatMap((part) => [
|
|
676
|
+
boundarySep,
|
|
677
|
+
/* c8 ignore next - Array.isArray branch for part is defensive coding for edge cases. */
|
|
678
|
+
...Array.isArray(part) ? part : [part]
|
|
679
|
+
]),
|
|
680
|
+
finalBoundary
|
|
681
|
+
];
|
|
682
|
+
const url = new URL(urlPath, baseUrl);
|
|
683
|
+
const req = getHttpModule(baseUrl).request(url, {
|
|
684
|
+
method: "POST",
|
|
685
|
+
...options,
|
|
686
|
+
headers: {
|
|
687
|
+
...options?.headers,
|
|
688
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
req.flushHeaders();
|
|
692
|
+
getResponse(req).then(pass, fail);
|
|
693
|
+
let aborted = false;
|
|
694
|
+
req.on("error", () => aborted = true);
|
|
695
|
+
req.on("close", () => aborted = true);
|
|
696
|
+
try {
|
|
697
|
+
for (const part of requestBody) {
|
|
698
|
+
if (aborted) {
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
if (typeof part === "string") {
|
|
702
|
+
if (!req.write(part)) {
|
|
703
|
+
await events.once(req, "drain");
|
|
704
|
+
}
|
|
705
|
+
} else if (typeof part?.pipe === "function") {
|
|
706
|
+
const stream = part;
|
|
707
|
+
try {
|
|
708
|
+
for await (const chunk of stream) {
|
|
709
|
+
if (aborted) {
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
if (!req.write(chunk)) {
|
|
713
|
+
await events.once(req, "drain");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
} catch (streamError) {
|
|
717
|
+
const err = streamError;
|
|
718
|
+
let message = "Failed to read file during upload";
|
|
719
|
+
if (err.code === "ENOENT") {
|
|
720
|
+
message += "\n\u2192 File was deleted during upload. Ensure files remain accessible during the upload process.";
|
|
721
|
+
} else if (err.code === "EACCES") {
|
|
722
|
+
message += "\n\u2192 Permission denied while reading file. Check file permissions.";
|
|
723
|
+
} else if (err.code) {
|
|
724
|
+
message += `
|
|
725
|
+
\u2192 Error code: ${err.code}`;
|
|
726
|
+
}
|
|
727
|
+
throw new Error(message, { cause: streamError });
|
|
728
|
+
}
|
|
729
|
+
if (!aborted && !req.write("\r\n")) {
|
|
730
|
+
await events.once(req, "drain");
|
|
731
|
+
}
|
|
732
|
+
if (typeof part.destroy === "function") {
|
|
733
|
+
part.destroy();
|
|
734
|
+
}
|
|
735
|
+
} else {
|
|
736
|
+
throw new TypeError('Expected "string" or "stream" type');
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
} catch (e) {
|
|
740
|
+
req.destroy(e);
|
|
741
|
+
fail(e);
|
|
742
|
+
} finally {
|
|
743
|
+
if (!aborted) {
|
|
744
|
+
req.end();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/quota-utils.ts
|
|
751
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
752
|
+
import { join } from "node:path";
|
|
753
|
+
import { memoize as memoize2, once } from "@socketsecurity/lib/memoization";
|
|
754
|
+
var loadRequirements = once(() => {
|
|
755
|
+
try {
|
|
756
|
+
const requirementsPath = join(
|
|
757
|
+
__dirname,
|
|
758
|
+
"..",
|
|
759
|
+
"data",
|
|
760
|
+
"api-method-quota-and-permissions.json"
|
|
761
|
+
);
|
|
762
|
+
if (!existsSync(requirementsPath)) {
|
|
763
|
+
throw new Error(`Requirements file not found at: ${requirementsPath}`);
|
|
764
|
+
}
|
|
765
|
+
const data = readFileSync(requirementsPath, "utf8");
|
|
766
|
+
return JSON.parse(data);
|
|
767
|
+
} catch (e) {
|
|
768
|
+
throw new Error("Failed to load SDK method requirements", { cause: e });
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
function calculateTotalQuotaCost(methodNames) {
|
|
772
|
+
return methodNames.reduce((total, methodName) => {
|
|
773
|
+
return total + getQuotaCost(methodName);
|
|
774
|
+
}, 0);
|
|
775
|
+
}
|
|
776
|
+
function getAllMethodRequirements() {
|
|
777
|
+
const reqs = loadRequirements();
|
|
778
|
+
const result = {};
|
|
779
|
+
Object.entries(reqs.api).forEach(([methodName, requirement]) => {
|
|
780
|
+
result[methodName] = {
|
|
781
|
+
permissions: [...requirement.permissions],
|
|
782
|
+
quota: requirement.quota
|
|
783
|
+
};
|
|
784
|
+
});
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
var getMethodRequirements = memoize2(
|
|
788
|
+
(methodName) => {
|
|
789
|
+
const reqs = loadRequirements();
|
|
790
|
+
const requirement = reqs.api[methodName];
|
|
791
|
+
if (!requirement) {
|
|
792
|
+
throw new Error(`Unknown SDK method: "${String(methodName)}"`);
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
permissions: [...requirement.permissions],
|
|
796
|
+
quota: requirement.quota
|
|
797
|
+
};
|
|
798
|
+
},
|
|
799
|
+
{ name: "getMethodRequirements" }
|
|
800
|
+
);
|
|
801
|
+
var getMethodsByPermissions = memoize2(
|
|
802
|
+
(permissions) => {
|
|
803
|
+
const reqs = loadRequirements();
|
|
804
|
+
return Object.entries(reqs.api).filter(([, requirement]) => {
|
|
805
|
+
return permissions.some(
|
|
806
|
+
(permission) => requirement.permissions.includes(permission)
|
|
807
|
+
);
|
|
808
|
+
}).map(([methodName]) => methodName).sort();
|
|
809
|
+
},
|
|
810
|
+
{ name: "getMethodsByPermissions" }
|
|
811
|
+
);
|
|
812
|
+
var getMethodsByQuotaCost = memoize2(
|
|
813
|
+
(quotaCost) => {
|
|
814
|
+
const reqs = loadRequirements();
|
|
815
|
+
return Object.entries(reqs.api).filter(([, requirement]) => requirement.quota === quotaCost).map(([methodName]) => methodName).sort();
|
|
816
|
+
},
|
|
817
|
+
{ name: "getMethodsByQuotaCost" }
|
|
818
|
+
);
|
|
819
|
+
var getQuotaCost = memoize2(
|
|
820
|
+
(methodName) => {
|
|
821
|
+
const reqs = loadRequirements();
|
|
822
|
+
const requirement = reqs.api[methodName];
|
|
823
|
+
if (!requirement) {
|
|
824
|
+
throw new Error(`Unknown SDK method: "${String(methodName)}"`);
|
|
825
|
+
}
|
|
826
|
+
return requirement.quota;
|
|
827
|
+
},
|
|
828
|
+
{ name: "getQuotaCost" }
|
|
829
|
+
);
|
|
830
|
+
var getQuotaUsageSummary = memoize2(
|
|
831
|
+
() => {
|
|
832
|
+
const reqs = loadRequirements();
|
|
833
|
+
const summary = {};
|
|
834
|
+
Object.entries(reqs.api).forEach(([methodName, requirement]) => {
|
|
835
|
+
const costKey = `${requirement.quota} units`;
|
|
836
|
+
if (!summary[costKey]) {
|
|
837
|
+
summary[costKey] = [];
|
|
838
|
+
}
|
|
839
|
+
summary[costKey].push(methodName);
|
|
840
|
+
});
|
|
841
|
+
Object.keys(summary).forEach((costKey) => {
|
|
842
|
+
summary[costKey]?.sort();
|
|
843
|
+
});
|
|
844
|
+
return summary;
|
|
845
|
+
},
|
|
846
|
+
{ name: "getQuotaUsageSummary" }
|
|
847
|
+
);
|
|
848
|
+
var getRequiredPermissions = memoize2(
|
|
849
|
+
(methodName) => {
|
|
850
|
+
const reqs = loadRequirements();
|
|
851
|
+
const requirement = reqs.api[methodName];
|
|
852
|
+
if (!requirement) {
|
|
853
|
+
throw new Error(`Unknown SDK method: "${String(methodName)}"`);
|
|
854
|
+
}
|
|
855
|
+
return [...requirement.permissions];
|
|
856
|
+
},
|
|
857
|
+
{ name: "getRequiredPermissions" }
|
|
858
|
+
);
|
|
859
|
+
function hasQuotaForMethods(availableQuota, methodNames) {
|
|
860
|
+
const totalCost = calculateTotalQuotaCost(methodNames);
|
|
861
|
+
return availableQuota >= totalCost;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/socket-sdk-class.ts
|
|
865
|
+
import { createWriteStream } from "node:fs";
|
|
866
|
+
import readline from "node:readline";
|
|
867
|
+
import { createTtlCache } from "@socketsecurity/lib/cache-with-ttl";
|
|
868
|
+
import { UNKNOWN_ERROR } from "@socketsecurity/lib/constants/core";
|
|
869
|
+
import { getAbortSignal } from "@socketsecurity/lib/constants/process";
|
|
870
|
+
import { SOCKET_PUBLIC_API_TOKEN } from "@socketsecurity/lib/constants/socket";
|
|
871
|
+
import { debugLog as debugLog2, isDebugNs } from "@socketsecurity/lib/debug";
|
|
872
|
+
import { validateFiles } from "@socketsecurity/lib/fs";
|
|
873
|
+
import { jsonParse as jsonParse2 } from "@socketsecurity/lib/json";
|
|
874
|
+
import { getOwn, isObjectObject } from "@socketsecurity/lib/objects";
|
|
875
|
+
import { pRetry } from "@socketsecurity/lib/promises";
|
|
876
|
+
import { setMaxEventTargetListeners } from "@socketsecurity/lib/suppress-warnings";
|
|
877
|
+
import { urlSearchParamAsBoolean } from "@socketsecurity/lib/url";
|
|
878
|
+
var abortSignal = getAbortSignal();
|
|
879
|
+
var SocketSdk = class {
|
|
880
|
+
#apiToken;
|
|
881
|
+
#baseUrl;
|
|
882
|
+
#cache;
|
|
883
|
+
#onFileValidation;
|
|
884
|
+
#reqOptions;
|
|
885
|
+
#retries;
|
|
886
|
+
#retryDelay;
|
|
887
|
+
/**
|
|
888
|
+
* Initialize Socket SDK with API token and configuration options.
|
|
889
|
+
* Sets up authentication, base URL, HTTP client options, retry behavior, and caching.
|
|
890
|
+
*/
|
|
891
|
+
constructor(apiToken, options) {
|
|
892
|
+
const MAX_API_TOKEN_LENGTH = 1024;
|
|
893
|
+
if (typeof apiToken !== "string") {
|
|
894
|
+
throw new TypeError('"apiToken" is required and must be a string');
|
|
895
|
+
}
|
|
896
|
+
const trimmedToken = apiToken.trim();
|
|
897
|
+
if (!trimmedToken) {
|
|
898
|
+
throw new Error('"apiToken" cannot be empty or whitespace-only');
|
|
899
|
+
}
|
|
900
|
+
if (trimmedToken.length > MAX_API_TOKEN_LENGTH) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`"apiToken" exceeds maximum length of ${MAX_API_TOKEN_LENGTH} characters`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const {
|
|
906
|
+
agent: agentOrObj,
|
|
907
|
+
baseUrl = "https://api.socket.dev/v0/",
|
|
908
|
+
cache = false,
|
|
909
|
+
cacheTtl = 5 * 60 * 1e3,
|
|
910
|
+
onFileValidation,
|
|
911
|
+
retries = DEFAULT_RETRIES,
|
|
912
|
+
retryDelay = DEFAULT_RETRY_DELAY,
|
|
913
|
+
timeout = DEFAULT_HTTP_TIMEOUT,
|
|
914
|
+
userAgent
|
|
915
|
+
} = { __proto__: null, ...options };
|
|
916
|
+
if (timeout !== void 0) {
|
|
917
|
+
if (typeof timeout !== "number" || timeout < MIN_HTTP_TIMEOUT || timeout > MAX_HTTP_TIMEOUT) {
|
|
918
|
+
throw new TypeError(
|
|
919
|
+
`"timeout" must be a number between ${MIN_HTTP_TIMEOUT} and ${MAX_HTTP_TIMEOUT} milliseconds`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const agentKeys = agentOrObj ? Object.keys(agentOrObj) : [];
|
|
924
|
+
const agentAsGotOptions = agentOrObj;
|
|
925
|
+
const agent = agentKeys.length && agentKeys.every((k) => httpAgentNames.has(k)) ? (
|
|
926
|
+
/* c8 ignore next 3 - Got-style agent options compatibility layer */
|
|
927
|
+
agentAsGotOptions.https || agentAsGotOptions.http || agentAsGotOptions.http2
|
|
928
|
+
) : agentOrObj;
|
|
929
|
+
this.#apiToken = trimmedToken;
|
|
930
|
+
this.#baseUrl = normalizeBaseUrl(baseUrl);
|
|
931
|
+
this.#cache = cache ? createTtlCache({
|
|
932
|
+
memoize: true,
|
|
933
|
+
prefix: "socket-sdk",
|
|
934
|
+
ttl: cacheTtl
|
|
935
|
+
}) : (
|
|
936
|
+
/* c8 ignore next - cache disabled by default */
|
|
937
|
+
void 0
|
|
938
|
+
);
|
|
939
|
+
this.#onFileValidation = onFileValidation;
|
|
940
|
+
this.#retries = retries;
|
|
941
|
+
this.#retryDelay = retryDelay;
|
|
942
|
+
this.#reqOptions = {
|
|
943
|
+
...agent ? { agent } : {},
|
|
944
|
+
headers: {
|
|
945
|
+
Authorization: `Basic ${btoa(`${trimmedToken}:`)}`,
|
|
946
|
+
"User-Agent": userAgent ?? DEFAULT_USER_AGENT
|
|
947
|
+
},
|
|
948
|
+
signal: abortSignal,
|
|
949
|
+
/* c8 ignore next - Optional timeout parameter, tested implicitly through method calls */
|
|
950
|
+
...timeout ? { timeout } : {}
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Parse Retry-After header value and return delay in milliseconds.
|
|
955
|
+
* Supports both delay-seconds (integer) and HTTP-date formats.
|
|
956
|
+
*/
|
|
957
|
+
#parseRetryAfter(retryAfterValue) {
|
|
958
|
+
if (!retryAfterValue) {
|
|
959
|
+
return void 0;
|
|
960
|
+
}
|
|
961
|
+
const value = Array.isArray(retryAfterValue) ? retryAfterValue[0] : retryAfterValue;
|
|
962
|
+
if (!value) {
|
|
963
|
+
return void 0;
|
|
964
|
+
}
|
|
965
|
+
const seconds = Number.parseInt(value, 10);
|
|
966
|
+
if (!Number.isNaN(seconds) && seconds >= 0) {
|
|
967
|
+
return seconds * 1e3;
|
|
968
|
+
}
|
|
969
|
+
const date = new Date(value);
|
|
970
|
+
if (!Number.isNaN(date.getTime())) {
|
|
971
|
+
const delayMs = date.getTime() - Date.now();
|
|
972
|
+
if (delayMs > 0) {
|
|
973
|
+
return delayMs;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return void 0;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Execute an HTTP request with retry logic.
|
|
980
|
+
* Internal method for wrapping HTTP operations with exponential backoff.
|
|
981
|
+
*/
|
|
982
|
+
async #executeWithRetry(operation) {
|
|
983
|
+
const result = await pRetry(operation, {
|
|
984
|
+
baseDelayMs: this.#retryDelay,
|
|
985
|
+
onRetry: (_attempt, error, _delay) => {
|
|
986
|
+
if (!(error instanceof ResponseError)) {
|
|
987
|
+
return void 0;
|
|
988
|
+
}
|
|
989
|
+
const { statusCode } = error.response;
|
|
990
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
991
|
+
throw error;
|
|
992
|
+
}
|
|
993
|
+
if (statusCode === 429) {
|
|
994
|
+
const retryAfter = this.#parseRetryAfter(
|
|
995
|
+
error.response.headers["retry-after"]
|
|
996
|
+
);
|
|
997
|
+
if (retryAfter !== void 0) {
|
|
998
|
+
return retryAfter;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return void 0;
|
|
1002
|
+
},
|
|
1003
|
+
onRetryRethrow: true,
|
|
1004
|
+
retries: this.#retries
|
|
1005
|
+
});
|
|
1006
|
+
if (result === void 0) {
|
|
1007
|
+
throw new Error("Request aborted");
|
|
1008
|
+
}
|
|
1009
|
+
return result;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Execute a GET request with optional caching.
|
|
1013
|
+
* Internal method for handling cached GET requests with retry logic.
|
|
1014
|
+
*/
|
|
1015
|
+
async #getCached(cacheKey, fetcher) {
|
|
1016
|
+
if (!this.#cache) {
|
|
1017
|
+
return await this.#executeWithRetry(fetcher);
|
|
1018
|
+
}
|
|
1019
|
+
return await this.#cache.getOrFetch(cacheKey, async () => {
|
|
1020
|
+
return await this.#executeWithRetry(fetcher);
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Create async generator for streaming batch package URL processing.
|
|
1025
|
+
* Internal method for handling chunked PURL responses with error handling.
|
|
1026
|
+
*/
|
|
1027
|
+
async *#createBatchPurlGenerator(componentsObj, queryParams) {
|
|
1028
|
+
let res;
|
|
1029
|
+
try {
|
|
1030
|
+
res = await this.#executeWithRetry(
|
|
1031
|
+
() => this.#createBatchPurlRequest(componentsObj, queryParams)
|
|
1032
|
+
);
|
|
1033
|
+
} catch (e) {
|
|
1034
|
+
yield await this.#handleApiError(e);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (!res) {
|
|
1038
|
+
throw new Error("Failed to get response from batch PURL request");
|
|
1039
|
+
}
|
|
1040
|
+
const rli = readline.createInterface({
|
|
1041
|
+
input: res,
|
|
1042
|
+
crlfDelay: Number.POSITIVE_INFINITY,
|
|
1043
|
+
signal: abortSignal
|
|
1044
|
+
});
|
|
1045
|
+
const isPublicToken = this.#apiToken === SOCKET_PUBLIC_API_TOKEN;
|
|
1046
|
+
for await (const line of rli) {
|
|
1047
|
+
const trimmed = line.trim();
|
|
1048
|
+
const artifact = trimmed ? jsonParse2(line, { throws: false }) : (
|
|
1049
|
+
/* c8 ignore next - Empty line handling in batch streaming response parsing. */
|
|
1050
|
+
null
|
|
1051
|
+
);
|
|
1052
|
+
if (isObjectObject(artifact)) {
|
|
1053
|
+
yield this.#handleApiSuccess(
|
|
1054
|
+
/* c8 ignore next 7 - Public token artifact reshaping branch for policy compliance. */
|
|
1055
|
+
isPublicToken ? reshapeArtifactForPublicPolicy(
|
|
1056
|
+
artifact,
|
|
1057
|
+
false,
|
|
1058
|
+
queryParams?.["actions"]
|
|
1059
|
+
) : artifact
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Create HTTP request for batch package URL processing.
|
|
1066
|
+
* Internal method for handling PURL batch API calls with retry logic.
|
|
1067
|
+
*/
|
|
1068
|
+
async #createBatchPurlRequest(componentsObj, queryParams) {
|
|
1069
|
+
const req = getHttpModule(this.#baseUrl).request(`${this.#baseUrl}purl?${queryToSearchParams(queryParams)}`, {
|
|
1070
|
+
method: "POST",
|
|
1071
|
+
...this.#reqOptions
|
|
1072
|
+
}).end(JSON.stringify(componentsObj));
|
|
1073
|
+
const response = await getResponse(req);
|
|
1074
|
+
if (!isResponseOk(response)) {
|
|
1075
|
+
throw new ResponseError(response);
|
|
1076
|
+
}
|
|
1077
|
+
return response;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Create standardized error result from query operation exceptions.
|
|
1081
|
+
* Internal error handling for non-throwing query API methods.
|
|
1082
|
+
*/
|
|
1083
|
+
#createQueryErrorResult(e) {
|
|
1084
|
+
if (e instanceof SyntaxError) {
|
|
1085
|
+
const enhancedError = e;
|
|
1086
|
+
let responseText = enhancedError.originalResponse || "";
|
|
1087
|
+
if (!responseText) {
|
|
1088
|
+
const match = e.message.match(/Invalid JSON response:\n([\s\S]*?)\n→/);
|
|
1089
|
+
responseText = match?.[1] || "";
|
|
1090
|
+
}
|
|
1091
|
+
const preview = responseText.slice(0, 100) || "";
|
|
1092
|
+
return {
|
|
1093
|
+
cause: `Please report this. JSON.parse threw an error over the following response: \`${preview.trim()}${responseText.length > 100 ? "\u2026" : ""}\``,
|
|
1094
|
+
data: void 0,
|
|
1095
|
+
error: "Server returned invalid JSON",
|
|
1096
|
+
status: 0,
|
|
1097
|
+
success: false
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
const errStr = e ? String(e).trim() : "";
|
|
1101
|
+
return {
|
|
1102
|
+
cause: errStr || UNKNOWN_ERROR,
|
|
1103
|
+
data: void 0,
|
|
1104
|
+
error: "API request failed",
|
|
1105
|
+
status: 0,
|
|
1106
|
+
success: false
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Extract text content from HTTP response stream.
|
|
1111
|
+
* Internal method with size limits to prevent memory exhaustion.
|
|
1112
|
+
*/
|
|
1113
|
+
/* c8 ignore start - unused utility method reserved for future text response handling */
|
|
1114
|
+
async #getResponseText(response) {
|
|
1115
|
+
const chunks = [];
|
|
1116
|
+
let size = 0;
|
|
1117
|
+
const MAX = 50 * 1024 * 1024;
|
|
1118
|
+
for await (const chunk of response) {
|
|
1119
|
+
size += chunk.length;
|
|
1120
|
+
if (size > MAX) {
|
|
1121
|
+
throw new Error("Response body exceeds maximum size limit");
|
|
1122
|
+
}
|
|
1123
|
+
chunks.push(chunk);
|
|
1124
|
+
}
|
|
1125
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1126
|
+
}
|
|
1127
|
+
/* c8 ignore stop */
|
|
1128
|
+
/**
|
|
1129
|
+
* Handle API error responses and convert to standardized error result.
|
|
1130
|
+
* Internal error handling with status code analysis and message formatting.
|
|
1131
|
+
*/
|
|
1132
|
+
async #handleApiError(error) {
|
|
1133
|
+
if (!(error instanceof ResponseError)) {
|
|
1134
|
+
throw new Error("Unexpected Socket API error", {
|
|
1135
|
+
cause: error
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
const { statusCode } = error.response;
|
|
1139
|
+
if (statusCode && statusCode >= 500) {
|
|
1140
|
+
throw new Error(`Socket API server error (${statusCode})`, {
|
|
1141
|
+
cause: error
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
const bodyStr = await getErrorResponseBody(error.response);
|
|
1145
|
+
let body;
|
|
1146
|
+
try {
|
|
1147
|
+
const parsed = JSON.parse(bodyStr);
|
|
1148
|
+
if (typeof parsed?.error?.message === "string") {
|
|
1149
|
+
body = parsed.error.message;
|
|
1150
|
+
if (parsed.error.details) {
|
|
1151
|
+
const detailsStr = typeof parsed.error.details === "string" ? parsed.error.details : JSON.stringify(parsed.error.details);
|
|
1152
|
+
body = `${body} - Details: ${detailsStr}`;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
} catch {
|
|
1156
|
+
body = bodyStr;
|
|
1157
|
+
}
|
|
1158
|
+
let errorMessage = error.message ?? /* c8 ignore next - fallback for missing error message */
|
|
1159
|
+
UNKNOWN_ERROR;
|
|
1160
|
+
const trimmedBody = body?.trim();
|
|
1161
|
+
if (trimmedBody && !errorMessage.includes(trimmedBody)) {
|
|
1162
|
+
const statusMessage = error.response?.statusMessage;
|
|
1163
|
+
if (statusMessage && errorMessage.includes(statusMessage)) {
|
|
1164
|
+
errorMessage = errorMessage.replace(statusMessage, trimmedBody);
|
|
1165
|
+
} else {
|
|
1166
|
+
errorMessage = `${errorMessage}: ${trimmedBody}`;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
let actionableGuidance;
|
|
1170
|
+
if (statusCode === 401) {
|
|
1171
|
+
actionableGuidance = [
|
|
1172
|
+
"\u2192 Authentication failed. API token is invalid or expired.",
|
|
1173
|
+
"\u2192 Check: Your API token is correct and active.",
|
|
1174
|
+
`\u2192 Generate a new token at: ${SOCKET_API_TOKENS_URL}`
|
|
1175
|
+
].join("\n");
|
|
1176
|
+
} else if (statusCode === 403) {
|
|
1177
|
+
actionableGuidance = [
|
|
1178
|
+
"\u2192 Authorization failed. Insufficient permissions.",
|
|
1179
|
+
"\u2192 Check: Your API token has required permissions for this operation.",
|
|
1180
|
+
"\u2192 Check: You have access to the specified organization/repository.",
|
|
1181
|
+
`\u2192 Verify: Organization settings at ${SOCKET_DASHBOARD_URL}`
|
|
1182
|
+
].join("\n");
|
|
1183
|
+
} else if (statusCode === 404) {
|
|
1184
|
+
actionableGuidance = [
|
|
1185
|
+
"\u2192 Resource not found.",
|
|
1186
|
+
"\u2192 Verify: Package name, version, or resource ID is correct.",
|
|
1187
|
+
"\u2192 Check: Organization or repository exists and is accessible."
|
|
1188
|
+
].join("\n");
|
|
1189
|
+
} else if (statusCode === 429) {
|
|
1190
|
+
const retryAfter = error.response.headers["retry-after"];
|
|
1191
|
+
const retryMsg = retryAfter ? `Retry after ${retryAfter} seconds.` : "Wait before retrying.";
|
|
1192
|
+
actionableGuidance = [
|
|
1193
|
+
"\u2192 Rate limit exceeded. Too many requests.",
|
|
1194
|
+
`\u2192 ${retryMsg}`,
|
|
1195
|
+
"\u2192 Try: Implement exponential backoff or enable SDK retry option.",
|
|
1196
|
+
`\u2192 Contact support to increase rate limits: ${SOCKET_CONTACT_URL}`
|
|
1197
|
+
].join("\n");
|
|
1198
|
+
} else if (statusCode === 400) {
|
|
1199
|
+
actionableGuidance = [
|
|
1200
|
+
"\u2192 Bad request. Invalid parameters or request body.",
|
|
1201
|
+
"\u2192 Check: All required parameters are provided and correctly formatted.",
|
|
1202
|
+
"\u2192 Verify: Package URLs (PURLs) follow correct format."
|
|
1203
|
+
].join("\n");
|
|
1204
|
+
} else if (statusCode === 413) {
|
|
1205
|
+
actionableGuidance = [
|
|
1206
|
+
"\u2192 Payload too large. Request exceeds size limits.",
|
|
1207
|
+
"\u2192 Try: Reduce the number of files or packages in a single request.",
|
|
1208
|
+
"\u2192 Try: Use batch operations with smaller chunks."
|
|
1209
|
+
].join("\n");
|
|
1210
|
+
}
|
|
1211
|
+
const causeWithGuidance = actionableGuidance ? [trimmedBody, "", actionableGuidance].filter(Boolean).join("\n") : body;
|
|
1212
|
+
return {
|
|
1213
|
+
cause: causeWithGuidance,
|
|
1214
|
+
data: void 0,
|
|
1215
|
+
error: errorMessage,
|
|
1216
|
+
/* c8 ignore next - fallback for missing status code in edge cases. */
|
|
1217
|
+
status: statusCode ?? 0,
|
|
1218
|
+
success: false
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Handle successful API responses and convert to standardized success result.
|
|
1223
|
+
* Internal success handling with consistent response formatting.
|
|
1224
|
+
*/
|
|
1225
|
+
#handleApiSuccess(data) {
|
|
1226
|
+
return {
|
|
1227
|
+
cause: void 0,
|
|
1228
|
+
data,
|
|
1229
|
+
error: void 0,
|
|
1230
|
+
// Use generic 200 OK status for all successful API responses.
|
|
1231
|
+
status: 200,
|
|
1232
|
+
success: true
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Handle query API response data based on requested response type.
|
|
1237
|
+
* Internal method for processing different response formats (json, text, response).
|
|
1238
|
+
*/
|
|
1239
|
+
async #handleQueryResponseData(response, responseType) {
|
|
1240
|
+
if (responseType === "response") {
|
|
1241
|
+
return response;
|
|
1242
|
+
}
|
|
1243
|
+
if (responseType === "text") {
|
|
1244
|
+
return await this.#getResponseText(response);
|
|
1245
|
+
}
|
|
1246
|
+
if (responseType === "json") {
|
|
1247
|
+
return await getResponseJson(response);
|
|
1248
|
+
}
|
|
1249
|
+
return response;
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Fetch package analysis data for multiple packages in a single batch request.
|
|
1253
|
+
* Returns all results at once after processing is complete.
|
|
1254
|
+
*
|
|
1255
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1256
|
+
*/
|
|
1257
|
+
async batchPackageFetch(componentsObj, queryParams) {
|
|
1258
|
+
let res;
|
|
1259
|
+
try {
|
|
1260
|
+
res = await this.#createBatchPurlRequest(componentsObj, queryParams);
|
|
1261
|
+
} catch (e) {
|
|
1262
|
+
return await this.#handleApiError(e);
|
|
1263
|
+
}
|
|
1264
|
+
if (!res) {
|
|
1265
|
+
throw new Error("Failed to get response from batch PURL request");
|
|
1266
|
+
}
|
|
1267
|
+
const rli = readline.createInterface({
|
|
1268
|
+
input: res,
|
|
1269
|
+
crlfDelay: Number.POSITIVE_INFINITY,
|
|
1270
|
+
signal: abortSignal
|
|
1271
|
+
});
|
|
1272
|
+
const isPublicToken = this.#apiToken === SOCKET_PUBLIC_API_TOKEN;
|
|
1273
|
+
const results = [];
|
|
1274
|
+
for await (const line of rli) {
|
|
1275
|
+
const trimmed = line.trim();
|
|
1276
|
+
const artifact = trimmed ? jsonParse2(line, { throws: false }) : (
|
|
1277
|
+
/* c8 ignore next - Empty line handling in batch parsing. */
|
|
1278
|
+
null
|
|
1279
|
+
);
|
|
1280
|
+
if (isObjectObject(artifact)) {
|
|
1281
|
+
results.push(
|
|
1282
|
+
/* c8 ignore next 7 - Public token artifact reshaping for policy compliance. */
|
|
1283
|
+
isPublicToken ? reshapeArtifactForPublicPolicy(
|
|
1284
|
+
artifact,
|
|
1285
|
+
false,
|
|
1286
|
+
queryParams?.["actions"]
|
|
1287
|
+
) : artifact
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const compact = urlSearchParamAsBoolean(
|
|
1292
|
+
getOwn(queryParams, "compact")
|
|
1293
|
+
);
|
|
1294
|
+
return this.#handleApiSuccess(
|
|
1295
|
+
compact ? results : results
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Stream package analysis data for multiple packages with chunked processing and concurrency control.
|
|
1300
|
+
* Returns results as they become available via async generator.
|
|
1301
|
+
*
|
|
1302
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1303
|
+
*/
|
|
1304
|
+
async *batchPackageStream(componentsObj, options) {
|
|
1305
|
+
const {
|
|
1306
|
+
chunkSize = 100,
|
|
1307
|
+
concurrencyLimit = 10,
|
|
1308
|
+
queryParams
|
|
1309
|
+
} = {
|
|
1310
|
+
__proto__: null,
|
|
1311
|
+
...options
|
|
1312
|
+
};
|
|
1313
|
+
const neededMaxListeners = concurrencyLimit * 2;
|
|
1314
|
+
setMaxEventTargetListeners(abortSignal, neededMaxListeners);
|
|
1315
|
+
const { components } = componentsObj;
|
|
1316
|
+
const { length: componentsCount } = components;
|
|
1317
|
+
const running = [];
|
|
1318
|
+
let index = 0;
|
|
1319
|
+
const enqueueGen = () => {
|
|
1320
|
+
if (index >= componentsCount) {
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
const generator = this.#createBatchPurlGenerator(
|
|
1324
|
+
{
|
|
1325
|
+
// Chunk components.
|
|
1326
|
+
components: components.slice(index, index + chunkSize)
|
|
1327
|
+
},
|
|
1328
|
+
queryParams
|
|
1329
|
+
);
|
|
1330
|
+
continueGen(generator);
|
|
1331
|
+
index += chunkSize;
|
|
1332
|
+
};
|
|
1333
|
+
const continueGen = (generator) => {
|
|
1334
|
+
const {
|
|
1335
|
+
promise,
|
|
1336
|
+
reject: rejectFn,
|
|
1337
|
+
resolve: resolveFn
|
|
1338
|
+
} = promiseWithResolvers();
|
|
1339
|
+
running.push({
|
|
1340
|
+
generator,
|
|
1341
|
+
promise
|
|
1342
|
+
});
|
|
1343
|
+
void generator.next().then(
|
|
1344
|
+
(iteratorResult) => resolveFn({ generator, iteratorResult }),
|
|
1345
|
+
rejectFn
|
|
1346
|
+
);
|
|
1347
|
+
};
|
|
1348
|
+
while (running.length < concurrencyLimit && index < componentsCount) {
|
|
1349
|
+
enqueueGen();
|
|
1350
|
+
}
|
|
1351
|
+
while (running.length > 0) {
|
|
1352
|
+
const { generator, iteratorResult } = await Promise.race(
|
|
1353
|
+
running.map((entry) => entry.promise)
|
|
1354
|
+
);
|
|
1355
|
+
const index2 = running.findIndex((entry) => entry.generator === generator);
|
|
1356
|
+
if (index2 === -1) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
running.splice(index2, 1);
|
|
1360
|
+
if (iteratorResult.value) {
|
|
1361
|
+
yield iteratorResult.value;
|
|
1362
|
+
}
|
|
1363
|
+
if (iteratorResult.done) {
|
|
1364
|
+
enqueueGen();
|
|
1365
|
+
} else {
|
|
1366
|
+
continueGen(generator);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Create a snapshot of project dependencies by uploading manifest files.
|
|
1372
|
+
* Analyzes dependency files to generate a comprehensive security report.
|
|
1373
|
+
*
|
|
1374
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1375
|
+
*/
|
|
1376
|
+
async createDependenciesSnapshot(filepaths, options) {
|
|
1377
|
+
const { pathsRelativeTo = ".", queryParams } = {
|
|
1378
|
+
__proto__: null,
|
|
1379
|
+
...options
|
|
1380
|
+
};
|
|
1381
|
+
const basePath = resolveBasePath(pathsRelativeTo);
|
|
1382
|
+
const absFilepaths = resolveAbsPaths(filepaths, basePath);
|
|
1383
|
+
const { invalidPaths, validPaths } = validateFiles(absFilepaths);
|
|
1384
|
+
if (this.#onFileValidation && invalidPaths.length > 0) {
|
|
1385
|
+
const result = await this.#onFileValidation(validPaths, invalidPaths, {
|
|
1386
|
+
operation: "createDependenciesSnapshot"
|
|
1387
|
+
});
|
|
1388
|
+
if (!result.shouldContinue) {
|
|
1389
|
+
return {
|
|
1390
|
+
cause: result.errorCause,
|
|
1391
|
+
data: void 0,
|
|
1392
|
+
error: result.errorMessage ?? "File validation failed",
|
|
1393
|
+
status: 400,
|
|
1394
|
+
success: false
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (!this.#onFileValidation && invalidPaths.length > 0) {
|
|
1399
|
+
const samplePaths = invalidPaths.slice(0, 3).join("\n - ");
|
|
1400
|
+
const remaining = invalidPaths.length > 3 ? `
|
|
1401
|
+
... and ${invalidPaths.length - 3} more` : "";
|
|
1402
|
+
console.warn(
|
|
1403
|
+
`Warning: ${invalidPaths.length} files skipped (unreadable):
|
|
1404
|
+
- ${samplePaths}${remaining}
|
|
40
1405
|
\u2192 This may occur with Yarn Berry PnP or pnpm symlinks.
|
|
41
|
-
\u2192 Try: Run installation command to ensure files are accessible.`
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
1406
|
+
\u2192 Try: Run installation command to ensure files are accessible.`
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
if (validPaths.length === 0) {
|
|
1410
|
+
const samplePaths = invalidPaths.slice(0, 5).join("\n - ");
|
|
1411
|
+
const remaining = invalidPaths.length > 5 ? `
|
|
1412
|
+
... and ${invalidPaths.length - 5} more` : "";
|
|
1413
|
+
return {
|
|
1414
|
+
cause: [
|
|
1415
|
+
`All ${invalidPaths.length} files failed validation:`,
|
|
1416
|
+
` - ${samplePaths}${remaining}`,
|
|
1417
|
+
"",
|
|
1418
|
+
"\u2192 Common causes:",
|
|
1419
|
+
" \u2022 Yarn Berry PnP virtual filesystem (files are not on disk)",
|
|
1420
|
+
" \u2022 pnpm symlinks pointing to inaccessible locations",
|
|
1421
|
+
" \u2022 Incorrect file permissions",
|
|
1422
|
+
" \u2022 Files were deleted after discovery",
|
|
1423
|
+
"",
|
|
1424
|
+
"\u2192 Solutions:",
|
|
1425
|
+
" \u2022 Yarn Berry: Use `nodeLinker: node-modules` in .yarnrc.yml",
|
|
1426
|
+
" \u2022 pnpm: Use `node-linker=hoisted` in .npmrc",
|
|
1427
|
+
" \u2022 Check file permissions with: ls -la <file>",
|
|
1428
|
+
" \u2022 Run package manager install command"
|
|
1429
|
+
].join("\n"),
|
|
1430
|
+
data: void 0,
|
|
1431
|
+
error: "No readable manifest files found",
|
|
1432
|
+
status: 400,
|
|
1433
|
+
success: false
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
try {
|
|
1437
|
+
const data = await this.#executeWithRetry(
|
|
1438
|
+
async () => await getResponseJson(
|
|
1439
|
+
await createUploadRequest(
|
|
1440
|
+
this.#baseUrl,
|
|
1441
|
+
`dependencies/upload?${queryToSearchParams(queryParams)}`,
|
|
1442
|
+
createRequestBodyForFilepaths(validPaths, basePath),
|
|
1443
|
+
this.#reqOptions
|
|
1444
|
+
)
|
|
1445
|
+
)
|
|
1446
|
+
);
|
|
1447
|
+
return this.#handleApiSuccess(data);
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
return await this.#handleApiError(e);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Create a diff scan from two full scan IDs.
|
|
1454
|
+
* Compares two existing full scans to identify changes.
|
|
1455
|
+
*
|
|
1456
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1457
|
+
*/
|
|
1458
|
+
async createOrgDiffScanFromIds(orgSlug, queryParams) {
|
|
1459
|
+
try {
|
|
1460
|
+
const data = await this.#executeWithRetry(
|
|
1461
|
+
async () => await getResponseJson(
|
|
1462
|
+
await createRequestWithJson(
|
|
1463
|
+
"POST",
|
|
1464
|
+
this.#baseUrl,
|
|
1465
|
+
`orgs/${encodeURIComponent(orgSlug)}/diff-scans?${queryToSearchParams(queryParams)}`,
|
|
1466
|
+
{},
|
|
1467
|
+
this.#reqOptions
|
|
1468
|
+
)
|
|
1469
|
+
)
|
|
1470
|
+
);
|
|
1471
|
+
return this.#handleApiSuccess(data);
|
|
1472
|
+
} catch (e) {
|
|
1473
|
+
return await this.#handleApiError(e);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Create a full security scan for an organization.
|
|
1478
|
+
*
|
|
1479
|
+
* Uploads project manifest files and initiates full security analysis.
|
|
1480
|
+
* Returns scan metadata with guaranteed required fields.
|
|
1481
|
+
*
|
|
1482
|
+
* @param orgSlug - Organization identifier
|
|
1483
|
+
* @param filepaths - Array of file paths to upload (package.json, package-lock.json, etc.)
|
|
1484
|
+
* @param options - Scan configuration including repository, branch, and commit details
|
|
1485
|
+
* @returns Full scan metadata including ID and URLs
|
|
1486
|
+
*
|
|
1487
|
+
* @example
|
|
1488
|
+
* ```typescript
|
|
1489
|
+
* const result = await sdk.createFullScan('my-org',
|
|
1490
|
+
* ['package.json', 'package-lock.json'],
|
|
1491
|
+
* {
|
|
1492
|
+
* repo: 'my-repo',
|
|
1493
|
+
* branch: 'main',
|
|
1494
|
+
* commit_message: 'Update dependencies',
|
|
1495
|
+
* commit_hash: 'abc123',
|
|
1496
|
+
* pathsRelativeTo: './my-project'
|
|
1497
|
+
* }
|
|
1498
|
+
* )
|
|
1499
|
+
*
|
|
1500
|
+
* if (result.success) {
|
|
1501
|
+
* console.log('Scan ID:', result.data.id)
|
|
1502
|
+
* console.log('Report URL:', result.data.html_report_url)
|
|
1503
|
+
* }
|
|
1504
|
+
* ```
|
|
1505
|
+
*
|
|
1506
|
+
* @see https://docs.socket.dev/reference/createorgfullscan
|
|
1507
|
+
* @apiEndpoint POST /orgs/{org_slug}/full-scans
|
|
1508
|
+
* @quota 1 unit
|
|
1509
|
+
* @scopes full-scans:create
|
|
1510
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1511
|
+
*/
|
|
1512
|
+
async createFullScan(orgSlug, filepaths, options) {
|
|
1513
|
+
const { pathsRelativeTo = ".", ...queryParams } = {
|
|
1514
|
+
__proto__: null,
|
|
1515
|
+
...options
|
|
1516
|
+
};
|
|
1517
|
+
const basePath = resolveBasePath(pathsRelativeTo);
|
|
1518
|
+
const absFilepaths = resolveAbsPaths(filepaths, basePath);
|
|
1519
|
+
const { invalidPaths, validPaths } = validateFiles(absFilepaths);
|
|
1520
|
+
if (this.#onFileValidation && invalidPaths.length > 0) {
|
|
1521
|
+
const result = await this.#onFileValidation(validPaths, invalidPaths, {
|
|
1522
|
+
operation: "createOrgFullScan",
|
|
1523
|
+
orgSlug
|
|
1524
|
+
});
|
|
1525
|
+
if (!result.shouldContinue) {
|
|
1526
|
+
return {
|
|
1527
|
+
cause: result.errorCause,
|
|
1528
|
+
data: void 0,
|
|
1529
|
+
error: result.errorMessage ?? "File validation failed",
|
|
1530
|
+
status: 400,
|
|
1531
|
+
success: false
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (!this.#onFileValidation && invalidPaths.length > 0) {
|
|
1536
|
+
const samplePaths = invalidPaths.slice(0, 3).join("\n - ");
|
|
1537
|
+
const remaining = invalidPaths.length > 3 ? `
|
|
1538
|
+
... and ${invalidPaths.length - 3} more` : "";
|
|
1539
|
+
console.warn(
|
|
1540
|
+
`Warning: ${invalidPaths.length} files skipped (unreadable):
|
|
1541
|
+
- ${samplePaths}${remaining}
|
|
48
1542
|
\u2192 This may occur with Yarn Berry PnP or pnpm symlinks.
|
|
49
|
-
\u2192 Try: Run installation command to ensure files are accessible.`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`),data:void 0,error:"No readable manifest files found",status:400,success:!1}}try{return{cause:void 0,data:await this.#r(async()=>await d(await v(this.#e,`orgs/${encodeURIComponent(t)}/full-scans?${k(n)}`,F(u,a),this.#t))),error:void 0,status:200,success:!0}}catch(l){let h=await this.#s(l);return{cause:h.cause,data:void 0,error:h.error,status:h.status,success:!1}}}async createRepository(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/repos`,e,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async createRepositoryLabel(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/repos/labels`,e,this.#t))),error:void 0,status:201,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async deleteOrgDiffScan(t,e){try{let s=await this.#r(async()=>await d(await A(this.#e,`orgs/${encodeURIComponent(t)}/diff-scans/${encodeURIComponent(e)}`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async deleteFullScan(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await A(this.#e,`orgs/${encodeURIComponent(t)}/full-scans/${encodeURIComponent(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async deleteRepository(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await A(this.#e,`orgs/${encodeURIComponent(t)}/repos/${encodeURIComponent(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async deleteRepositoryLabel(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await A(this.#e,`orgs/${encodeURIComponent(t)}/repos/labels/${encodeURIComponent(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async exportCDX(t,e){try{let s=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/full-scans/${encodeURIComponent(e)}/sbom/export/cdx`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async exportSPDX(t,e){try{let s=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/full-scans/${encodeURIComponent(e)}/sbom/export/spdx`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async getApi(t,e){let{responseType:s="response",throws:r=!0}={__proto__:null,...e};try{let n=await m(this.#e,t,this.#t);if(!O(n)){if(r)throw new R(n);let i=await this.#s(new R(n));return{cause:i.cause,data:void 0,error:i.error,status:i.status,success:!1}}let a=await this.#f(n,s);return r?a:{cause:void 0,data:a,error:void 0,status:n.statusCode??200,success:!0}}catch(n){if(r)throw n;if(n instanceof R){let a=await this.#s(n);return{cause:a.cause,data:void 0,error:a.error,status:a.status,success:!1}}return this.#g(n)}}async getAPITokens(t){try{let e=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/tokens`,this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async getAuditLogEvents(t,e){try{let s=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/audit-log?${k(e)}`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async getDiffScanById(t,e){try{let s=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/diff-scans/${encodeURIComponent(e)}`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async getEnabledEntitlements(t){return((await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/entitlements`,this.#t))))?.items||[]).filter(r=>r&&r.enabled===!0&&r.key).map(r=>r.key)}async getEntitlements(t){return(await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/entitlements`,this.#t))))?.items||[]}async getIssuesByNpmPackage(t,e){try{let s=await this.#r(async()=>await d(await m(this.#e,`npm/${encodeURIComponent(t)}/${encodeURIComponent(e)}/issues`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async getOrgAnalytics(t){try{let e=await this.#r(async()=>await d(await m(this.#e,`analytics/org/${encodeURIComponent(t)}`,this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async listOrganizations(){try{return{cause:void 0,data:await this.#d("organizations",async()=>await d(await m(this.#e,"organizations",this.#t))),error:void 0,status:200,success:!0}}catch(t){let e=await this.#s(t);return{cause:e.cause,data:void 0,error:e.error,status:e.status,success:!1}}}async getFullScan(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/full-scans/${encodeURIComponent(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async listFullScans(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/full-scans?${k(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async getFullScanMetadata(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/full-scans/${encodeURIComponent(e)}/metadata`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async getOrgLicensePolicy(t){try{let e=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/settings/license-policy`,this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async getRepository(t,e){let s=encodeURIComponent(t),r=encodeURIComponent(e);try{return{cause:void 0,data:await this.#r(async()=>await d(await m(this.#e,`orgs/${s}/repos/${r}`,this.#t))),error:void 0,status:200,success:!0}}catch(n){let a=await this.#s(n);return{cause:a.cause,data:void 0,error:a.error,status:a.status,success:!1}}}async getRepositoryLabel(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/repos/labels/${encodeURIComponent(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async listRepositoryLabels(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/repos/labels?${k(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async listRepositories(t,e){try{return{cause:void 0,data:await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/repos?${k(e)}`,this.#t))),error:void 0,status:200,success:!0}}catch(s){let r=await this.#s(s);return{cause:r.cause,data:void 0,error:r.error,status:r.status,success:!1}}}async getOrgSecurityPolicy(t){try{let e=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/settings/security-policy`,this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async getOrgTriage(t){try{let e=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/triage`,this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async getQuota(){try{let t=await this.#d("quota",async()=>await d(await m(this.#e,"quota",this.#t)));return this.#n(t)}catch(t){return await this.#s(t)}}async getRepoAnalytics(t,e){try{let s=await this.#r(async()=>await d(await m(this.#e,`analytics/repo/${encodeURIComponent(t)}/${encodeURIComponent(e)}`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async getScoreByNpmPackage(t,e){try{let s=await this.#r(async()=>await d(await m(this.#e,`npm/${encodeURIComponent(t)}/${encodeURIComponent(e)}/score`,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async getSupportedScanFiles(){try{let t=await this.#r(async()=>await d(await m(this.#e,"report/supported",this.#t)));return this.#n(t)}catch(t){return await this.#s(t)}}async listOrgDiffScans(t){try{let e=await this.#r(async()=>await d(await m(this.#e,`orgs/${encodeURIComponent(t)}/diff-scans`,this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async postAPIToken(t,e){try{let s=await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/tokens`,e,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async postAPITokensRevoke(t,e){try{let s=await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/tokens/${encodeURIComponent(e)}/revoke`,{},this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async postAPITokensRotate(t,e){try{let s=await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/tokens/${encodeURIComponent(e)}/rotate`,{},this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async postAPITokenUpdate(t,e,s){try{let r=await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/tokens/${encodeURIComponent(e)}/update`,s,this.#t)));return this.#n(r)}catch(r){return await this.#s(r)}}async postSettings(t){try{let e=await this.#r(async()=>await d(await y("POST",this.#e,"settings",{json:t},this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async searchDependencies(t){try{let e=await this.#r(async()=>await d(await y("POST",this.#e,"dependencies/search",t,this.#t)));return this.#n(e)}catch(e){return await this.#s(e)}}async sendApi(t,e){let{body:s,method:r="POST",throws:n=!0}={__proto__:null,...e};try{let a=await y(r,this.#e,t,s,this.#t),i=await d(a);return n?i:{cause:void 0,data:i,error:void 0,status:a.statusCode??200,success:!0}}catch(a){if(n)throw a;if(a instanceof R){let c=await this.#s(a);return{cause:c.cause,data:void 0,error:c.error,status:c.status,success:!1}}return{cause:(a?String(a).trim():"")||K,data:void 0,error:"API request failed",status:0,success:!1}}}async streamFullScan(t,e,s){let{output:r}={__proto__:null,...s};try{let n=P(this.#e).request(`${this.#e}orgs/${encodeURIComponent(t)}/full-scans/${encodeURIComponent(e)}`,{method:"GET",...this.#t}).end(),a=await T(n);if(!O(a))throw new R(a);if(typeof r=="string"){let i=Me(r),c=0;a.on("data",u=>{if(c+=u.length,c>$)throw a.destroy(),i.destroy(),new Error(`Response exceeds maximum stream size of ${$} bytes`)}),a.pipe(i),i.on("error",u=>{throw new Error(`Failed to write to file: ${r}`,{cause:u})})}else if(r===!0){let i=0;a.on("data",c=>{if(i+=c.length,i>$)throw a.destroy(),new Error(`Response exceeds maximum stream size of ${$} bytes`)}),a.pipe(process.stdout),process.stdout.on("error",c=>{throw new Error("Failed to write to stdout",{cause:c})})}return this.#n(a)}catch(n){return await this.#s(n)}}async streamPatchesFromScan(t,e){let s=await this.#r(async()=>await m(this.#e,`orgs/${encodeURIComponent(t)}/patches/scan?scan_id=${encodeURIComponent(e)}`,this.#t));if(!O(s))throw new R(s,"GET Request failed");let r=H.createInterface({input:s,crlfDelay:Number.POSITIVE_INFINITY});return new ReadableStream({async start(n){try{for await(let a of r){let i=a.trim();if(i)try{let c=JSON.parse(i);n.enqueue(c)}catch(c){ge("streamPatchesFromScan",`Failed to parse line: ${c}`)}}}catch(a){n.error(a)}finally{n.close()}}})}async updateOrgAlertTriage(t,e,s){try{let r=await this.#r(async()=>await d(await y("PUT",this.#e,`orgs/${encodeURIComponent(t)}/triage/${encodeURIComponent(e)}`,s,this.#t)));return this.#n(r)}catch(r){return await this.#s(r)}}async updateOrgLicensePolicy(t,e,s){try{let r=await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/settings/license-policy?${k(s)}`,e,this.#t)));return this.#n(r)}catch(r){return await this.#s(r)}}async updateRepository(t,e,s){try{return{cause:void 0,data:await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/repos/${encodeURIComponent(e)}`,s,this.#t))),error:void 0,status:200,success:!0}}catch(r){let n=await this.#s(r);return{cause:n.cause,data:void 0,error:n.error,status:n.status,success:!1}}}async updateRepositoryLabel(t,e,s){try{return{cause:void 0,data:await this.#r(async()=>await d(await y("PUT",this.#e,`orgs/${encodeURIComponent(t)}/repos/labels/${encodeURIComponent(e)}`,s,this.#t))),error:void 0,status:200,success:!0}}catch(r){let n=await this.#s(r);return{cause:n.cause,data:void 0,error:n.error,status:n.status,success:!1}}}async updateOrgSecurityPolicy(t,e){try{let s=await this.#r(async()=>await d(await y("POST",this.#e,`orgs/${encodeURIComponent(t)}/settings/security-policy`,e,this.#t)));return this.#n(s)}catch(s){return await this.#s(s)}}async uploadManifestFiles(t,e,s){let{pathsRelativeTo:r="."}={__proto__:null,...s},n=I(r),a=U(e,n),{invalidPaths:i,validPaths:c}=V(a);if(this.#o&&i.length>0){let u=await this.#o(c,i,{operation:"uploadManifestFiles",orgSlug:t});if(!u.shouldContinue)return{error:u.errorMessage??"File validation failed",status:400,success:!1,...u.errorCause?{cause:u.errorCause}:{}}}if(!this.#o&&i.length>0){let u=i.slice(0,3).join(`
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
1543
|
+
\u2192 Try: Run installation command to ensure files are accessible.`
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
if (validPaths.length === 0) {
|
|
1547
|
+
const samplePaths = invalidPaths.slice(0, 5).join("\n - ");
|
|
1548
|
+
const remaining = invalidPaths.length > 5 ? `
|
|
1549
|
+
... and ${invalidPaths.length - 5} more` : "";
|
|
1550
|
+
return {
|
|
1551
|
+
cause: [
|
|
1552
|
+
`All ${invalidPaths.length} files failed validation:`,
|
|
1553
|
+
` - ${samplePaths}${remaining}`,
|
|
1554
|
+
"",
|
|
1555
|
+
"\u2192 Common causes:",
|
|
1556
|
+
" \u2022 Yarn Berry PnP virtual filesystem (files are not on disk)",
|
|
1557
|
+
" \u2022 pnpm symlinks pointing to inaccessible locations",
|
|
1558
|
+
" \u2022 Incorrect file permissions",
|
|
1559
|
+
" \u2022 Files were deleted after discovery",
|
|
1560
|
+
"",
|
|
1561
|
+
"\u2192 Solutions:",
|
|
1562
|
+
" \u2022 Yarn Berry: Use `nodeLinker: node-modules` in .yarnrc.yml",
|
|
1563
|
+
" \u2022 pnpm: Use `node-linker=hoisted` in .npmrc",
|
|
1564
|
+
" \u2022 Check file permissions with: ls -la <file>",
|
|
1565
|
+
" \u2022 Run package manager install command"
|
|
1566
|
+
].join("\n"),
|
|
1567
|
+
data: void 0,
|
|
1568
|
+
error: "No readable manifest files found",
|
|
1569
|
+
status: 400,
|
|
1570
|
+
success: false
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
const data = await this.#executeWithRetry(
|
|
1575
|
+
async () => await getResponseJson(
|
|
1576
|
+
await createUploadRequest(
|
|
1577
|
+
this.#baseUrl,
|
|
1578
|
+
`orgs/${encodeURIComponent(orgSlug)}/full-scans?${queryToSearchParams(queryParams)}`,
|
|
1579
|
+
createRequestBodyForFilepaths(validPaths, basePath),
|
|
1580
|
+
this.#reqOptions
|
|
1581
|
+
)
|
|
1582
|
+
)
|
|
1583
|
+
);
|
|
1584
|
+
return {
|
|
1585
|
+
cause: void 0,
|
|
1586
|
+
data,
|
|
1587
|
+
error: void 0,
|
|
1588
|
+
status: 200,
|
|
1589
|
+
success: true
|
|
1590
|
+
};
|
|
1591
|
+
} catch (e) {
|
|
1592
|
+
const errorResult = await this.#handleApiError(e);
|
|
1593
|
+
return {
|
|
1594
|
+
cause: errorResult.cause,
|
|
1595
|
+
data: void 0,
|
|
1596
|
+
error: errorResult.error,
|
|
1597
|
+
status: errorResult.status,
|
|
1598
|
+
success: false
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Create a new repository in an organization.
|
|
1604
|
+
*
|
|
1605
|
+
* Registers a repository for monitoring and security scanning.
|
|
1606
|
+
*
|
|
1607
|
+
* @param orgSlug - Organization identifier
|
|
1608
|
+
* @param params - Repository configuration (name, description, homepage, etc.)
|
|
1609
|
+
* @returns Created repository details
|
|
1610
|
+
*
|
|
1611
|
+
* @example
|
|
1612
|
+
* ```typescript
|
|
1613
|
+
* const result = await sdk.createRepository('my-org', {
|
|
1614
|
+
* name: 'my-repo',
|
|
1615
|
+
* description: 'My project repository',
|
|
1616
|
+
* homepage: 'https://example.com'
|
|
1617
|
+
* })
|
|
1618
|
+
*
|
|
1619
|
+
* if (result.success) {
|
|
1620
|
+
* console.log('Repository created:', result.data.id)
|
|
1621
|
+
* }
|
|
1622
|
+
* ```
|
|
1623
|
+
*
|
|
1624
|
+
* @see https://docs.socket.dev/reference/createorgrepo
|
|
1625
|
+
* @apiEndpoint POST /orgs/{org_slug}/repos
|
|
1626
|
+
* @quota 1 unit
|
|
1627
|
+
* @scopes repo:write
|
|
1628
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1629
|
+
*/
|
|
1630
|
+
async createRepository(orgSlug, params) {
|
|
1631
|
+
try {
|
|
1632
|
+
const data = await this.#executeWithRetry(
|
|
1633
|
+
async () => await getResponseJson(
|
|
1634
|
+
await createRequestWithJson(
|
|
1635
|
+
"POST",
|
|
1636
|
+
this.#baseUrl,
|
|
1637
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos`,
|
|
1638
|
+
params,
|
|
1639
|
+
this.#reqOptions
|
|
1640
|
+
)
|
|
1641
|
+
)
|
|
1642
|
+
);
|
|
1643
|
+
return {
|
|
1644
|
+
cause: void 0,
|
|
1645
|
+
data,
|
|
1646
|
+
error: void 0,
|
|
1647
|
+
status: 200,
|
|
1648
|
+
success: true
|
|
1649
|
+
};
|
|
1650
|
+
} catch (e) {
|
|
1651
|
+
const errorResult = await this.#handleApiError(e);
|
|
1652
|
+
return {
|
|
1653
|
+
cause: errorResult.cause,
|
|
1654
|
+
data: void 0,
|
|
1655
|
+
error: errorResult.error,
|
|
1656
|
+
status: errorResult.status,
|
|
1657
|
+
success: false
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Create a new repository label for an organization.
|
|
1663
|
+
*
|
|
1664
|
+
* Labels can be used to group and organize repositories and apply security/license policies.
|
|
1665
|
+
*
|
|
1666
|
+
* @param orgSlug - Organization identifier
|
|
1667
|
+
* @param labelData - Label configuration (must include name property)
|
|
1668
|
+
* @returns Created label with guaranteed id and name fields
|
|
1669
|
+
*
|
|
1670
|
+
* @example
|
|
1671
|
+
* ```typescript
|
|
1672
|
+
* const result = await sdk.createRepositoryLabel('my-org', { name: 'production' })
|
|
1673
|
+
*
|
|
1674
|
+
* if (result.success) {
|
|
1675
|
+
* console.log('Label created:', result.data.id)
|
|
1676
|
+
* console.log('Label name:', result.data.name)
|
|
1677
|
+
* }
|
|
1678
|
+
* ```
|
|
1679
|
+
*
|
|
1680
|
+
* @see https://docs.socket.dev/reference/createorgrepolabel
|
|
1681
|
+
* @apiEndpoint POST /orgs/{org_slug}/repos/labels
|
|
1682
|
+
* @quota 1 unit
|
|
1683
|
+
* @scopes repo-label:create
|
|
1684
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1685
|
+
*/
|
|
1686
|
+
async createRepositoryLabel(orgSlug, labelData) {
|
|
1687
|
+
try {
|
|
1688
|
+
const data = await this.#executeWithRetry(
|
|
1689
|
+
async () => await getResponseJson(
|
|
1690
|
+
await createRequestWithJson(
|
|
1691
|
+
"POST",
|
|
1692
|
+
this.#baseUrl,
|
|
1693
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos/labels`,
|
|
1694
|
+
labelData,
|
|
1695
|
+
this.#reqOptions
|
|
1696
|
+
)
|
|
1697
|
+
)
|
|
1698
|
+
);
|
|
1699
|
+
return {
|
|
1700
|
+
cause: void 0,
|
|
1701
|
+
data,
|
|
1702
|
+
error: void 0,
|
|
1703
|
+
status: 201,
|
|
1704
|
+
success: true
|
|
1705
|
+
};
|
|
1706
|
+
} catch (e) {
|
|
1707
|
+
const errorResult = await this.#handleApiError(e);
|
|
1708
|
+
return {
|
|
1709
|
+
cause: errorResult.cause,
|
|
1710
|
+
data: void 0,
|
|
1711
|
+
error: errorResult.error,
|
|
1712
|
+
status: errorResult.status,
|
|
1713
|
+
success: false
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Delete a diff scan from an organization.
|
|
1719
|
+
* Permanently removes diff scan data and results.
|
|
1720
|
+
*
|
|
1721
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1722
|
+
*/
|
|
1723
|
+
async deleteOrgDiffScan(orgSlug, diffScanId) {
|
|
1724
|
+
try {
|
|
1725
|
+
const data = await this.#executeWithRetry(
|
|
1726
|
+
async () => await getResponseJson(
|
|
1727
|
+
await createDeleteRequest(
|
|
1728
|
+
this.#baseUrl,
|
|
1729
|
+
`orgs/${encodeURIComponent(orgSlug)}/diff-scans/${encodeURIComponent(diffScanId)}`,
|
|
1730
|
+
this.#reqOptions
|
|
1731
|
+
)
|
|
1732
|
+
)
|
|
1733
|
+
);
|
|
1734
|
+
return this.#handleApiSuccess(data);
|
|
1735
|
+
} catch (e) {
|
|
1736
|
+
return await this.#handleApiError(e);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Delete a full scan from an organization.
|
|
1741
|
+
*
|
|
1742
|
+
* Permanently removes scan data and results.
|
|
1743
|
+
*
|
|
1744
|
+
* @param orgSlug - Organization identifier
|
|
1745
|
+
* @param scanId - Full scan identifier to delete
|
|
1746
|
+
* @returns Success confirmation
|
|
1747
|
+
*
|
|
1748
|
+
* @example
|
|
1749
|
+
* ```typescript
|
|
1750
|
+
* const result = await sdk.deleteFullScan('my-org', 'scan_123')
|
|
1751
|
+
*
|
|
1752
|
+
* if (result.success) {
|
|
1753
|
+
* console.log('Scan deleted successfully')
|
|
1754
|
+
* }
|
|
1755
|
+
* ```
|
|
1756
|
+
*
|
|
1757
|
+
* @see https://docs.socket.dev/reference/deleteorgfullscan
|
|
1758
|
+
* @apiEndpoint DELETE /orgs/{org_slug}/full-scans/{full_scan_id}
|
|
1759
|
+
* @quota 1 unit
|
|
1760
|
+
* @scopes full-scans:delete
|
|
1761
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1762
|
+
*/
|
|
1763
|
+
async deleteFullScan(orgSlug, scanId) {
|
|
1764
|
+
try {
|
|
1765
|
+
const data = await this.#executeWithRetry(
|
|
1766
|
+
async () => await getResponseJson(
|
|
1767
|
+
await createDeleteRequest(
|
|
1768
|
+
this.#baseUrl,
|
|
1769
|
+
`orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(scanId)}`,
|
|
1770
|
+
this.#reqOptions
|
|
1771
|
+
)
|
|
1772
|
+
)
|
|
1773
|
+
);
|
|
1774
|
+
return {
|
|
1775
|
+
cause: void 0,
|
|
1776
|
+
data,
|
|
1777
|
+
error: void 0,
|
|
1778
|
+
status: 200,
|
|
1779
|
+
success: true
|
|
1780
|
+
};
|
|
1781
|
+
} catch (e) {
|
|
1782
|
+
const errorResult = await this.#handleApiError(e);
|
|
1783
|
+
return {
|
|
1784
|
+
cause: errorResult.cause,
|
|
1785
|
+
data: void 0,
|
|
1786
|
+
error: errorResult.error,
|
|
1787
|
+
status: errorResult.status,
|
|
1788
|
+
success: false
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Delete a repository from an organization.
|
|
1794
|
+
*
|
|
1795
|
+
* Removes repository monitoring and associated scan data.
|
|
1796
|
+
*
|
|
1797
|
+
* @param orgSlug - Organization identifier
|
|
1798
|
+
* @param repoSlug - Repository slug/name to delete
|
|
1799
|
+
* @returns Success confirmation
|
|
1800
|
+
*
|
|
1801
|
+
* @example
|
|
1802
|
+
* ```typescript
|
|
1803
|
+
* const result = await sdk.deleteRepository('my-org', 'old-repo')
|
|
1804
|
+
*
|
|
1805
|
+
* if (result.success) {
|
|
1806
|
+
* console.log('Repository deleted')
|
|
1807
|
+
* }
|
|
1808
|
+
* ```
|
|
1809
|
+
*
|
|
1810
|
+
* @see https://docs.socket.dev/reference/deleteorgrepo
|
|
1811
|
+
* @apiEndpoint DELETE /orgs/{org_slug}/repos/{repo_slug}
|
|
1812
|
+
* @quota 1 unit
|
|
1813
|
+
* @scopes repo:write
|
|
1814
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1815
|
+
*/
|
|
1816
|
+
async deleteRepository(orgSlug, repoSlug) {
|
|
1817
|
+
try {
|
|
1818
|
+
const data = await this.#executeWithRetry(
|
|
1819
|
+
async () => await getResponseJson(
|
|
1820
|
+
await createDeleteRequest(
|
|
1821
|
+
this.#baseUrl,
|
|
1822
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos/${encodeURIComponent(repoSlug)}`,
|
|
1823
|
+
this.#reqOptions
|
|
1824
|
+
)
|
|
1825
|
+
)
|
|
1826
|
+
);
|
|
1827
|
+
return {
|
|
1828
|
+
cause: void 0,
|
|
1829
|
+
data,
|
|
1830
|
+
error: void 0,
|
|
1831
|
+
status: 200,
|
|
1832
|
+
success: true
|
|
1833
|
+
};
|
|
1834
|
+
} catch (e) {
|
|
1835
|
+
const errorResult = await this.#handleApiError(e);
|
|
1836
|
+
return {
|
|
1837
|
+
cause: errorResult.cause,
|
|
1838
|
+
data: void 0,
|
|
1839
|
+
error: errorResult.error,
|
|
1840
|
+
status: errorResult.status,
|
|
1841
|
+
success: false
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Delete a repository label from an organization.
|
|
1847
|
+
*
|
|
1848
|
+
* Removes label and all its associations (repositories, security policy, license policy, etc.).
|
|
1849
|
+
*
|
|
1850
|
+
* @param orgSlug - Organization identifier
|
|
1851
|
+
* @param labelId - Label identifier
|
|
1852
|
+
* @returns Deletion confirmation
|
|
1853
|
+
*
|
|
1854
|
+
* @example
|
|
1855
|
+
* ```typescript
|
|
1856
|
+
* const result = await sdk.deleteRepositoryLabel('my-org', 'label-id-123')
|
|
1857
|
+
*
|
|
1858
|
+
* if (result.success) {
|
|
1859
|
+
* console.log('Label deleted:', result.data.status)
|
|
1860
|
+
* }
|
|
1861
|
+
* ```
|
|
1862
|
+
*
|
|
1863
|
+
* @see https://docs.socket.dev/reference/deleteorgrepolabel
|
|
1864
|
+
* @apiEndpoint DELETE /orgs/{org_slug}/repos/labels/{label_id}
|
|
1865
|
+
* @quota 1 unit
|
|
1866
|
+
* @scopes repo-label:delete
|
|
1867
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1868
|
+
*/
|
|
1869
|
+
async deleteRepositoryLabel(orgSlug, labelId) {
|
|
1870
|
+
try {
|
|
1871
|
+
const data = await this.#executeWithRetry(
|
|
1872
|
+
async () => await getResponseJson(
|
|
1873
|
+
await createDeleteRequest(
|
|
1874
|
+
this.#baseUrl,
|
|
1875
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos/labels/${encodeURIComponent(labelId)}`,
|
|
1876
|
+
this.#reqOptions
|
|
1877
|
+
)
|
|
1878
|
+
)
|
|
1879
|
+
);
|
|
1880
|
+
return {
|
|
1881
|
+
cause: void 0,
|
|
1882
|
+
data,
|
|
1883
|
+
error: void 0,
|
|
1884
|
+
status: 200,
|
|
1885
|
+
success: true
|
|
1886
|
+
};
|
|
1887
|
+
} catch (e) {
|
|
1888
|
+
const errorResult = await this.#handleApiError(e);
|
|
1889
|
+
return {
|
|
1890
|
+
cause: errorResult.cause,
|
|
1891
|
+
data: void 0,
|
|
1892
|
+
error: errorResult.error,
|
|
1893
|
+
status: errorResult.status,
|
|
1894
|
+
success: false
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Delete a legacy scan report permanently.
|
|
1900
|
+
/**
|
|
1901
|
+
* Export scan results in CycloneDX SBOM format.
|
|
1902
|
+
* Returns Software Bill of Materials compliant with CycloneDX standard.
|
|
1903
|
+
*
|
|
1904
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1905
|
+
*/
|
|
1906
|
+
async exportCDX(orgSlug, fullScanId) {
|
|
1907
|
+
try {
|
|
1908
|
+
const data = await this.#executeWithRetry(
|
|
1909
|
+
async () => await getResponseJson(
|
|
1910
|
+
await createGetRequest(
|
|
1911
|
+
this.#baseUrl,
|
|
1912
|
+
`orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(fullScanId)}/sbom/export/cdx`,
|
|
1913
|
+
this.#reqOptions
|
|
1914
|
+
)
|
|
1915
|
+
)
|
|
1916
|
+
);
|
|
1917
|
+
return this.#handleApiSuccess(data);
|
|
1918
|
+
} catch (e) {
|
|
1919
|
+
return await this.#handleApiError(e);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Export scan results in SPDX SBOM format.
|
|
1924
|
+
* Returns Software Bill of Materials compliant with SPDX standard.
|
|
1925
|
+
*
|
|
1926
|
+
* @throws {Error} When server returns 5xx status codes
|
|
1927
|
+
*/
|
|
1928
|
+
async exportSPDX(orgSlug, fullScanId) {
|
|
1929
|
+
try {
|
|
1930
|
+
const data = await this.#executeWithRetry(
|
|
1931
|
+
async () => await getResponseJson(
|
|
1932
|
+
await createGetRequest(
|
|
1933
|
+
this.#baseUrl,
|
|
1934
|
+
`orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(fullScanId)}/sbom/export/spdx`,
|
|
1935
|
+
this.#reqOptions
|
|
1936
|
+
)
|
|
1937
|
+
)
|
|
1938
|
+
);
|
|
1939
|
+
return this.#handleApiSuccess(data);
|
|
1940
|
+
} catch (e) {
|
|
1941
|
+
return await this.#handleApiError(e);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Execute a raw GET request to any API endpoint with configurable response type.
|
|
1946
|
+
* Supports both throwing (default) and non-throwing modes.
|
|
1947
|
+
* @param urlPath - API endpoint path (e.g., 'organizations')
|
|
1948
|
+
* @param options - Request options including responseType and throws behavior
|
|
1949
|
+
* @returns Raw response, parsed data, or SocketSdkGenericResult based on options
|
|
1950
|
+
*/
|
|
1951
|
+
async getApi(urlPath, options) {
|
|
1952
|
+
const { responseType = "response", throws = true } = {
|
|
1953
|
+
__proto__: null,
|
|
1954
|
+
...options
|
|
1955
|
+
};
|
|
1956
|
+
try {
|
|
1957
|
+
const response = await createGetRequest(
|
|
1958
|
+
this.#baseUrl,
|
|
1959
|
+
urlPath,
|
|
1960
|
+
this.#reqOptions
|
|
1961
|
+
);
|
|
1962
|
+
if (!isResponseOk(response)) {
|
|
1963
|
+
if (throws) {
|
|
1964
|
+
throw new ResponseError(response);
|
|
1965
|
+
}
|
|
1966
|
+
const errorResult = await this.#handleApiError(
|
|
1967
|
+
new ResponseError(response)
|
|
1968
|
+
);
|
|
1969
|
+
return {
|
|
1970
|
+
cause: errorResult.cause,
|
|
1971
|
+
data: void 0,
|
|
1972
|
+
error: errorResult.error,
|
|
1973
|
+
status: errorResult.status,
|
|
1974
|
+
success: false
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
const data = await this.#handleQueryResponseData(
|
|
1978
|
+
response,
|
|
1979
|
+
responseType
|
|
1980
|
+
);
|
|
1981
|
+
if (throws) {
|
|
1982
|
+
return data;
|
|
1983
|
+
}
|
|
1984
|
+
return {
|
|
1985
|
+
cause: void 0,
|
|
1986
|
+
data,
|
|
1987
|
+
error: void 0,
|
|
1988
|
+
/* c8 ignore next - Defensive fallback: response.statusCode is always defined in Node.js http/https */
|
|
1989
|
+
status: response.statusCode ?? 200,
|
|
1990
|
+
success: true
|
|
1991
|
+
};
|
|
1992
|
+
} catch (e) {
|
|
1993
|
+
if (throws) {
|
|
1994
|
+
throw e;
|
|
1995
|
+
}
|
|
1996
|
+
if (e instanceof ResponseError) {
|
|
1997
|
+
const errorResult = await this.#handleApiError(e);
|
|
1998
|
+
return {
|
|
1999
|
+
cause: errorResult.cause,
|
|
2000
|
+
data: void 0,
|
|
2001
|
+
error: errorResult.error,
|
|
2002
|
+
status: errorResult.status,
|
|
2003
|
+
success: false
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
return this.#createQueryErrorResult(e);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Get list of API tokens for an organization.
|
|
2011
|
+
* Returns organization API tokens with metadata and permissions.
|
|
2012
|
+
*
|
|
2013
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2014
|
+
*/
|
|
2015
|
+
async getAPITokens(orgSlug) {
|
|
2016
|
+
try {
|
|
2017
|
+
const data = await this.#executeWithRetry(
|
|
2018
|
+
async () => await getResponseJson(
|
|
2019
|
+
await createGetRequest(
|
|
2020
|
+
this.#baseUrl,
|
|
2021
|
+
`orgs/${encodeURIComponent(orgSlug)}/tokens`,
|
|
2022
|
+
this.#reqOptions
|
|
2023
|
+
)
|
|
2024
|
+
)
|
|
2025
|
+
);
|
|
2026
|
+
return this.#handleApiSuccess(data);
|
|
2027
|
+
} catch (e) {
|
|
2028
|
+
return await this.#handleApiError(e);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Retrieve audit log events for an organization.
|
|
2033
|
+
* Returns chronological log of security and administrative actions.
|
|
2034
|
+
*
|
|
2035
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2036
|
+
*/
|
|
2037
|
+
async getAuditLogEvents(orgSlug, queryParams) {
|
|
2038
|
+
try {
|
|
2039
|
+
const data = await this.#executeWithRetry(
|
|
2040
|
+
async () => await getResponseJson(
|
|
2041
|
+
await createGetRequest(
|
|
2042
|
+
this.#baseUrl,
|
|
2043
|
+
`orgs/${encodeURIComponent(orgSlug)}/audit-log?${queryToSearchParams(queryParams)}`,
|
|
2044
|
+
this.#reqOptions
|
|
2045
|
+
)
|
|
2046
|
+
)
|
|
2047
|
+
);
|
|
2048
|
+
return this.#handleApiSuccess(data);
|
|
2049
|
+
} catch (e) {
|
|
2050
|
+
return await this.#handleApiError(e);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Get details for a specific diff scan.
|
|
2055
|
+
* Returns comparison between two full scans with artifact changes.
|
|
2056
|
+
*
|
|
2057
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2058
|
+
*/
|
|
2059
|
+
async getDiffScanById(orgSlug, diffScanId) {
|
|
2060
|
+
try {
|
|
2061
|
+
const data = await this.#executeWithRetry(
|
|
2062
|
+
async () => await getResponseJson(
|
|
2063
|
+
await createGetRequest(
|
|
2064
|
+
this.#baseUrl,
|
|
2065
|
+
`orgs/${encodeURIComponent(orgSlug)}/diff-scans/${encodeURIComponent(diffScanId)}`,
|
|
2066
|
+
this.#reqOptions
|
|
2067
|
+
)
|
|
2068
|
+
)
|
|
2069
|
+
);
|
|
2070
|
+
return this.#handleApiSuccess(data);
|
|
2071
|
+
} catch (e) {
|
|
2072
|
+
return await this.#handleApiError(e);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Retrieve the enabled entitlements for an organization.
|
|
2077
|
+
*
|
|
2078
|
+
* This method fetches the organization's entitlements and filters for only* the enabled ones, returning their keys. Entitlements represent Socket
|
|
2079
|
+
* Products that the organization has access to use.
|
|
2080
|
+
*/
|
|
2081
|
+
async getEnabledEntitlements(orgSlug) {
|
|
2082
|
+
const data = await this.#executeWithRetry(
|
|
2083
|
+
async () => await getResponseJson(
|
|
2084
|
+
await createGetRequest(
|
|
2085
|
+
this.#baseUrl,
|
|
2086
|
+
`orgs/${encodeURIComponent(orgSlug)}/entitlements`,
|
|
2087
|
+
this.#reqOptions
|
|
2088
|
+
)
|
|
2089
|
+
)
|
|
2090
|
+
);
|
|
2091
|
+
const items = data?.items || [];
|
|
2092
|
+
return items.filter((item) => item && item.enabled === true && item.key).map((item) => item.key);
|
|
2093
|
+
}
|
|
2094
|
+
/**
|
|
2095
|
+
* Retrieve all entitlements for an organization.
|
|
2096
|
+
*
|
|
2097
|
+
* This method fetches all entitlements (both enabled and disabled) for
|
|
2098
|
+
* an organization, returning the complete list with their status.
|
|
2099
|
+
*/
|
|
2100
|
+
async getEntitlements(orgSlug) {
|
|
2101
|
+
const data = await this.#executeWithRetry(
|
|
2102
|
+
async () => await getResponseJson(
|
|
2103
|
+
await createGetRequest(
|
|
2104
|
+
this.#baseUrl,
|
|
2105
|
+
`orgs/${encodeURIComponent(orgSlug)}/entitlements`,
|
|
2106
|
+
this.#reqOptions
|
|
2107
|
+
)
|
|
2108
|
+
)
|
|
2109
|
+
);
|
|
2110
|
+
return data?.items || [];
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Get security issues for a specific npm package and version.
|
|
2114
|
+
* Returns detailed vulnerability and security alert information.
|
|
2115
|
+
*
|
|
2116
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2117
|
+
*/
|
|
2118
|
+
async getIssuesByNpmPackage(pkgName, version) {
|
|
2119
|
+
try {
|
|
2120
|
+
const data = await this.#executeWithRetry(
|
|
2121
|
+
async () => await getResponseJson(
|
|
2122
|
+
await createGetRequest(
|
|
2123
|
+
this.#baseUrl,
|
|
2124
|
+
`npm/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/issues`,
|
|
2125
|
+
this.#reqOptions
|
|
2126
|
+
)
|
|
2127
|
+
)
|
|
2128
|
+
);
|
|
2129
|
+
return this.#handleApiSuccess(data);
|
|
2130
|
+
} catch (e) {
|
|
2131
|
+
return await this.#handleApiError(e);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Get analytics data for organization usage patterns and security metrics.
|
|
2136
|
+
* Returns statistical analysis for specified time period.
|
|
2137
|
+
*
|
|
2138
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2139
|
+
*/
|
|
2140
|
+
async getOrgAnalytics(time) {
|
|
2141
|
+
try {
|
|
2142
|
+
const data = await this.#executeWithRetry(
|
|
2143
|
+
async () => await getResponseJson(
|
|
2144
|
+
await createGetRequest(
|
|
2145
|
+
this.#baseUrl,
|
|
2146
|
+
`analytics/org/${encodeURIComponent(time)}`,
|
|
2147
|
+
this.#reqOptions
|
|
2148
|
+
)
|
|
2149
|
+
)
|
|
2150
|
+
);
|
|
2151
|
+
return this.#handleApiSuccess(data);
|
|
2152
|
+
} catch (e) {
|
|
2153
|
+
return await this.#handleApiError(e);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* List all organizations accessible to the current user.
|
|
2158
|
+
*
|
|
2159
|
+
* Returns organization details and access permissions with guaranteed required fields.
|
|
2160
|
+
*
|
|
2161
|
+
* @returns List of organizations with metadata
|
|
2162
|
+
*
|
|
2163
|
+
* @example
|
|
2164
|
+
* ```typescript
|
|
2165
|
+
* const result = await sdk.listOrganizations()
|
|
2166
|
+
*
|
|
2167
|
+
* if (result.success) {
|
|
2168
|
+
* result.data.organizations.forEach(org => {
|
|
2169
|
+
* console.log(org.name, org.slug) // Guaranteed fields
|
|
2170
|
+
* })
|
|
2171
|
+
* }
|
|
2172
|
+
* ```
|
|
2173
|
+
*
|
|
2174
|
+
* @see https://docs.socket.dev/reference/getorganizations
|
|
2175
|
+
* @apiEndpoint GET /organizations
|
|
2176
|
+
* @quota 1 unit
|
|
2177
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2178
|
+
*/
|
|
2179
|
+
async listOrganizations() {
|
|
2180
|
+
try {
|
|
2181
|
+
const data = await this.#getCached(
|
|
2182
|
+
"organizations",
|
|
2183
|
+
async () => await getResponseJson(
|
|
2184
|
+
await createGetRequest(
|
|
2185
|
+
this.#baseUrl,
|
|
2186
|
+
"organizations",
|
|
2187
|
+
this.#reqOptions
|
|
2188
|
+
)
|
|
2189
|
+
)
|
|
2190
|
+
);
|
|
2191
|
+
return {
|
|
2192
|
+
cause: void 0,
|
|
2193
|
+
data,
|
|
2194
|
+
error: void 0,
|
|
2195
|
+
status: 200,
|
|
2196
|
+
success: true
|
|
2197
|
+
};
|
|
2198
|
+
} catch (e) {
|
|
2199
|
+
const errorResult = await this.#handleApiError(e);
|
|
2200
|
+
return {
|
|
2201
|
+
cause: errorResult.cause,
|
|
2202
|
+
data: void 0,
|
|
2203
|
+
error: errorResult.error,
|
|
2204
|
+
status: errorResult.status,
|
|
2205
|
+
success: false
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Get complete full scan results buffered in memory.
|
|
2211
|
+
*
|
|
2212
|
+
* Returns entire scan data as JSON for programmatic processing.
|
|
2213
|
+
* For large scans, consider using streamFullScan() instead.
|
|
2214
|
+
*
|
|
2215
|
+
* @param orgSlug - Organization identifier
|
|
2216
|
+
* @param scanId - Full scan identifier
|
|
2217
|
+
* @returns Complete full scan data including all artifacts
|
|
2218
|
+
*
|
|
2219
|
+
* @example
|
|
2220
|
+
* ```typescript
|
|
2221
|
+
* const result = await sdk.getFullScan('my-org', 'scan_123')
|
|
2222
|
+
*
|
|
2223
|
+
* if (result.success) {
|
|
2224
|
+
* console.log('Scan status:', result.data.scan_state)
|
|
2225
|
+
* console.log('Repository:', result.data.repository_slug)
|
|
2226
|
+
* }
|
|
2227
|
+
* ```
|
|
2228
|
+
*
|
|
2229
|
+
* @see https://docs.socket.dev/reference/getorgfullscan
|
|
2230
|
+
* @apiEndpoint GET /orgs/{org_slug}/full-scans/{full_scan_id}
|
|
2231
|
+
* @quota 1 unit
|
|
2232
|
+
* @scopes full-scans:list
|
|
2233
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2234
|
+
*/
|
|
2235
|
+
async getFullScan(orgSlug, scanId) {
|
|
2236
|
+
try {
|
|
2237
|
+
const data = await this.#executeWithRetry(
|
|
2238
|
+
async () => await getResponseJson(
|
|
2239
|
+
await createGetRequest(
|
|
2240
|
+
this.#baseUrl,
|
|
2241
|
+
`orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(scanId)}`,
|
|
2242
|
+
this.#reqOptions
|
|
2243
|
+
)
|
|
2244
|
+
)
|
|
2245
|
+
);
|
|
2246
|
+
return {
|
|
2247
|
+
cause: void 0,
|
|
2248
|
+
data,
|
|
2249
|
+
error: void 0,
|
|
2250
|
+
status: 200,
|
|
2251
|
+
success: true
|
|
2252
|
+
};
|
|
2253
|
+
} catch (e) {
|
|
2254
|
+
const errorResult = await this.#handleApiError(e);
|
|
2255
|
+
return {
|
|
2256
|
+
cause: errorResult.cause,
|
|
2257
|
+
data: void 0,
|
|
2258
|
+
error: errorResult.error,
|
|
2259
|
+
status: errorResult.status,
|
|
2260
|
+
success: false
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* List all full scans for an organization.
|
|
2266
|
+
*
|
|
2267
|
+
* Returns paginated list of full scan metadata with guaranteed required fields
|
|
2268
|
+
* for improved TypeScript autocomplete.
|
|
2269
|
+
*
|
|
2270
|
+
* @param orgSlug - Organization identifier
|
|
2271
|
+
* @param options - Filtering and pagination options
|
|
2272
|
+
* @returns List of full scans with metadata
|
|
2273
|
+
*
|
|
2274
|
+
* @example
|
|
2275
|
+
* ```typescript
|
|
2276
|
+
* const result = await sdk.listFullScans('my-org', {
|
|
2277
|
+
* branch: 'main',
|
|
2278
|
+
* per_page: 50,
|
|
2279
|
+
* use_cursor: true
|
|
2280
|
+
* })
|
|
2281
|
+
*
|
|
2282
|
+
* if (result.success) {
|
|
2283
|
+
* result.data.results.forEach(scan => {
|
|
2284
|
+
* console.log(scan.id, scan.created_at) // Guaranteed fields
|
|
2285
|
+
* })
|
|
2286
|
+
* }
|
|
2287
|
+
* ```
|
|
2288
|
+
*
|
|
2289
|
+
* @see https://docs.socket.dev/reference/getorgfullscanlist
|
|
2290
|
+
* @apiEndpoint GET /orgs/{org_slug}/full-scans
|
|
2291
|
+
* @quota 1 unit
|
|
2292
|
+
* @scopes full-scans:list
|
|
2293
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2294
|
+
*/
|
|
2295
|
+
async listFullScans(orgSlug, options) {
|
|
2296
|
+
try {
|
|
2297
|
+
const data = await this.#executeWithRetry(
|
|
2298
|
+
async () => await getResponseJson(
|
|
2299
|
+
await createGetRequest(
|
|
2300
|
+
this.#baseUrl,
|
|
2301
|
+
`orgs/${encodeURIComponent(orgSlug)}/full-scans?${queryToSearchParams(options)}`,
|
|
2302
|
+
this.#reqOptions
|
|
2303
|
+
)
|
|
2304
|
+
)
|
|
2305
|
+
);
|
|
2306
|
+
return {
|
|
2307
|
+
cause: void 0,
|
|
2308
|
+
data,
|
|
2309
|
+
error: void 0,
|
|
2310
|
+
status: 200,
|
|
2311
|
+
success: true
|
|
2312
|
+
};
|
|
2313
|
+
} catch (e) {
|
|
2314
|
+
const errorResult = await this.#handleApiError(e);
|
|
2315
|
+
return {
|
|
2316
|
+
cause: errorResult.cause,
|
|
2317
|
+
data: void 0,
|
|
2318
|
+
error: errorResult.error,
|
|
2319
|
+
status: errorResult.status,
|
|
2320
|
+
success: false
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Get metadata for a specific full scan.
|
|
2326
|
+
*
|
|
2327
|
+
* Returns scan configuration, status, and summary information without full artifact data.
|
|
2328
|
+
* Useful for checking scan status without downloading complete results.
|
|
2329
|
+
*
|
|
2330
|
+
* @param orgSlug - Organization identifier
|
|
2331
|
+
* @param scanId - Full scan identifier
|
|
2332
|
+
* @returns Scan metadata including status and configuration
|
|
2333
|
+
*
|
|
2334
|
+
* @example
|
|
2335
|
+
* ```typescript
|
|
2336
|
+
* const result = await sdk.getFullScanMetadata('my-org', 'scan_123')
|
|
2337
|
+
*
|
|
2338
|
+
* if (result.success) {
|
|
2339
|
+
* console.log('Scan state:', result.data.scan_state)
|
|
2340
|
+
* console.log('Branch:', result.data.branch)
|
|
2341
|
+
* }
|
|
2342
|
+
* ```
|
|
2343
|
+
*
|
|
2344
|
+
* @see https://docs.socket.dev/reference/getorgfullscanmetadata
|
|
2345
|
+
* @apiEndpoint GET /orgs/{org_slug}/full-scans/{full_scan_id}/metadata
|
|
2346
|
+
* @quota 1 unit
|
|
2347
|
+
* @scopes full-scans:list
|
|
2348
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2349
|
+
*/
|
|
2350
|
+
async getFullScanMetadata(orgSlug, scanId) {
|
|
2351
|
+
try {
|
|
2352
|
+
const data = await this.#executeWithRetry(
|
|
2353
|
+
async () => await getResponseJson(
|
|
2354
|
+
await createGetRequest(
|
|
2355
|
+
this.#baseUrl,
|
|
2356
|
+
`orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(scanId)}/metadata`,
|
|
2357
|
+
this.#reqOptions
|
|
2358
|
+
)
|
|
2359
|
+
)
|
|
2360
|
+
);
|
|
2361
|
+
return {
|
|
2362
|
+
cause: void 0,
|
|
2363
|
+
data,
|
|
2364
|
+
error: void 0,
|
|
2365
|
+
status: 200,
|
|
2366
|
+
success: true
|
|
2367
|
+
};
|
|
2368
|
+
} catch (e) {
|
|
2369
|
+
const errorResult = await this.#handleApiError(e);
|
|
2370
|
+
return {
|
|
2371
|
+
cause: errorResult.cause,
|
|
2372
|
+
data: void 0,
|
|
2373
|
+
error: errorResult.error,
|
|
2374
|
+
status: errorResult.status,
|
|
2375
|
+
success: false
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Get organization's license policy configuration.* Returns allowed, restricted, and monitored license types.
|
|
2381
|
+
*
|
|
2382
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2383
|
+
*/
|
|
2384
|
+
async getOrgLicensePolicy(orgSlug) {
|
|
2385
|
+
try {
|
|
2386
|
+
const data = await this.#executeWithRetry(
|
|
2387
|
+
async () => await getResponseJson(
|
|
2388
|
+
await createGetRequest(
|
|
2389
|
+
this.#baseUrl,
|
|
2390
|
+
`orgs/${encodeURIComponent(orgSlug)}/settings/license-policy`,
|
|
2391
|
+
this.#reqOptions
|
|
2392
|
+
)
|
|
2393
|
+
)
|
|
2394
|
+
);
|
|
2395
|
+
return this.#handleApiSuccess(data);
|
|
2396
|
+
} catch (e) {
|
|
2397
|
+
return await this.#handleApiError(e);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Get details for a specific repository.
|
|
2402
|
+
*
|
|
2403
|
+
* Returns repository configuration, monitoring status, and metadata.
|
|
2404
|
+
*
|
|
2405
|
+
* @param orgSlug - Organization identifier
|
|
2406
|
+
* @param repoSlug - Repository slug/name
|
|
2407
|
+
* @returns Repository details with configuration
|
|
2408
|
+
*
|
|
2409
|
+
* @example
|
|
2410
|
+
* ```typescript
|
|
2411
|
+
* const result = await sdk.getRepository('my-org', 'my-repo')
|
|
2412
|
+
*
|
|
2413
|
+
* if (result.success) {
|
|
2414
|
+
* console.log('Repository:', result.data.name)
|
|
2415
|
+
* console.log('Visibility:', result.data.visibility)
|
|
2416
|
+
* console.log('Default branch:', result.data.default_branch)
|
|
2417
|
+
* }
|
|
2418
|
+
* ```
|
|
2419
|
+
*
|
|
2420
|
+
* @see https://docs.socket.dev/reference/getorgrepo
|
|
2421
|
+
* @apiEndpoint GET /orgs/{org_slug}/repos/{repo_slug}
|
|
2422
|
+
* @quota 1 unit
|
|
2423
|
+
* @scopes repo:read
|
|
2424
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2425
|
+
*/
|
|
2426
|
+
async getRepository(orgSlug, repoSlug) {
|
|
2427
|
+
const orgSlugParam = encodeURIComponent(orgSlug);
|
|
2428
|
+
const repoSlugParam = encodeURIComponent(repoSlug);
|
|
2429
|
+
try {
|
|
2430
|
+
const data = await this.#executeWithRetry(
|
|
2431
|
+
async () => await getResponseJson(
|
|
2432
|
+
await createGetRequest(
|
|
2433
|
+
this.#baseUrl,
|
|
2434
|
+
`orgs/${orgSlugParam}/repos/${repoSlugParam}`,
|
|
2435
|
+
this.#reqOptions
|
|
2436
|
+
)
|
|
2437
|
+
)
|
|
2438
|
+
);
|
|
2439
|
+
return {
|
|
2440
|
+
cause: void 0,
|
|
2441
|
+
data,
|
|
2442
|
+
error: void 0,
|
|
2443
|
+
status: 200,
|
|
2444
|
+
success: true
|
|
2445
|
+
};
|
|
2446
|
+
} catch (e) {
|
|
2447
|
+
const errorResult = await this.#handleApiError(e);
|
|
2448
|
+
return {
|
|
2449
|
+
cause: errorResult.cause,
|
|
2450
|
+
data: void 0,
|
|
2451
|
+
error: errorResult.error,
|
|
2452
|
+
status: errorResult.status,
|
|
2453
|
+
success: false
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Get details for a specific repository label.
|
|
2459
|
+
*
|
|
2460
|
+
* Returns label configuration, associated repositories, and policy settings.
|
|
2461
|
+
*
|
|
2462
|
+
* @param orgSlug - Organization identifier
|
|
2463
|
+
* @param labelId - Label identifier
|
|
2464
|
+
* @returns Label details with guaranteed id and name fields
|
|
2465
|
+
*
|
|
2466
|
+
* @example
|
|
2467
|
+
* ```typescript
|
|
2468
|
+
* const result = await sdk.getRepositoryLabel('my-org', 'label-id-123')
|
|
2469
|
+
*
|
|
2470
|
+
* if (result.success) {
|
|
2471
|
+
* console.log('Label name:', result.data.name)
|
|
2472
|
+
* console.log('Associated repos:', result.data.repository_ids)
|
|
2473
|
+
* console.log('Has security policy:', result.data.has_security_policy)
|
|
2474
|
+
* }
|
|
2475
|
+
* ```
|
|
2476
|
+
*
|
|
2477
|
+
* @see https://docs.socket.dev/reference/getorgrepolabel
|
|
2478
|
+
* @apiEndpoint GET /orgs/{org_slug}/repos/labels/{label_id}
|
|
2479
|
+
* @quota 1 unit
|
|
2480
|
+
* @scopes repo-label:list
|
|
2481
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2482
|
+
*/
|
|
2483
|
+
async getRepositoryLabel(orgSlug, labelId) {
|
|
2484
|
+
try {
|
|
2485
|
+
const data = await this.#executeWithRetry(
|
|
2486
|
+
async () => await getResponseJson(
|
|
2487
|
+
await createGetRequest(
|
|
2488
|
+
this.#baseUrl,
|
|
2489
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos/labels/${encodeURIComponent(labelId)}`,
|
|
2490
|
+
this.#reqOptions
|
|
2491
|
+
)
|
|
2492
|
+
)
|
|
2493
|
+
);
|
|
2494
|
+
return {
|
|
2495
|
+
cause: void 0,
|
|
2496
|
+
data,
|
|
2497
|
+
error: void 0,
|
|
2498
|
+
status: 200,
|
|
2499
|
+
success: true
|
|
2500
|
+
};
|
|
2501
|
+
} catch (e) {
|
|
2502
|
+
const errorResult = await this.#handleApiError(e);
|
|
2503
|
+
return {
|
|
2504
|
+
cause: errorResult.cause,
|
|
2505
|
+
data: void 0,
|
|
2506
|
+
error: errorResult.error,
|
|
2507
|
+
status: errorResult.status,
|
|
2508
|
+
success: false
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* List all repository labels for an organization.
|
|
2514
|
+
*
|
|
2515
|
+
* Returns paginated list of labels configured for repository organization and policy management.
|
|
2516
|
+
*
|
|
2517
|
+
* @param orgSlug - Organization identifier
|
|
2518
|
+
* @param options - Pagination options
|
|
2519
|
+
* @returns List of labels with guaranteed id and name fields
|
|
2520
|
+
*
|
|
2521
|
+
* @example
|
|
2522
|
+
* ```typescript
|
|
2523
|
+
* const result = await sdk.listRepositoryLabels('my-org', { per_page: 50, page: 1 })
|
|
2524
|
+
*
|
|
2525
|
+
* if (result.success) {
|
|
2526
|
+
* result.data.results.forEach(label => {
|
|
2527
|
+
* console.log('Label:', label.name)
|
|
2528
|
+
* console.log('Associated repos:', label.repository_ids?.length || 0)
|
|
2529
|
+
* })
|
|
2530
|
+
* }
|
|
2531
|
+
* ```
|
|
2532
|
+
*
|
|
2533
|
+
* @see https://docs.socket.dev/reference/getorgrepolabellist
|
|
2534
|
+
* @apiEndpoint GET /orgs/{org_slug}/repos/labels
|
|
2535
|
+
* @quota 1 unit
|
|
2536
|
+
* @scopes repo-label:list
|
|
2537
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2538
|
+
*/
|
|
2539
|
+
async listRepositoryLabels(orgSlug, options) {
|
|
2540
|
+
try {
|
|
2541
|
+
const data = await this.#executeWithRetry(
|
|
2542
|
+
async () => await getResponseJson(
|
|
2543
|
+
await createGetRequest(
|
|
2544
|
+
this.#baseUrl,
|
|
2545
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos/labels?${queryToSearchParams(options)}`,
|
|
2546
|
+
this.#reqOptions
|
|
2547
|
+
)
|
|
2548
|
+
)
|
|
2549
|
+
);
|
|
2550
|
+
return {
|
|
2551
|
+
cause: void 0,
|
|
2552
|
+
data,
|
|
2553
|
+
error: void 0,
|
|
2554
|
+
status: 200,
|
|
2555
|
+
success: true
|
|
2556
|
+
};
|
|
2557
|
+
} catch (e) {
|
|
2558
|
+
const errorResult = await this.#handleApiError(e);
|
|
2559
|
+
return {
|
|
2560
|
+
cause: errorResult.cause,
|
|
2561
|
+
data: void 0,
|
|
2562
|
+
error: errorResult.error,
|
|
2563
|
+
status: errorResult.status,
|
|
2564
|
+
success: false
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* List all repositories in an organization.
|
|
2570
|
+
*
|
|
2571
|
+
* Returns paginated list of repository metadata with guaranteed required fields.
|
|
2572
|
+
*
|
|
2573
|
+
* @param orgSlug - Organization identifier
|
|
2574
|
+
* @param options - Pagination and filtering options
|
|
2575
|
+
* @returns List of repositories with metadata
|
|
2576
|
+
*
|
|
2577
|
+
* @example
|
|
2578
|
+
* ```typescript
|
|
2579
|
+
* const result = await sdk.listRepositories('my-org', {
|
|
2580
|
+
* per_page: 50,
|
|
2581
|
+
* sort: 'name',
|
|
2582
|
+
* direction: 'asc'
|
|
2583
|
+
* })
|
|
2584
|
+
*
|
|
2585
|
+
* if (result.success) {
|
|
2586
|
+
* result.data.results.forEach(repo => {
|
|
2587
|
+
* console.log(repo.name, repo.visibility)
|
|
2588
|
+
* })
|
|
2589
|
+
* }
|
|
2590
|
+
* ```
|
|
2591
|
+
*
|
|
2592
|
+
* @see https://docs.socket.dev/reference/getorgrepolist
|
|
2593
|
+
* @apiEndpoint GET /orgs/{org_slug}/repos
|
|
2594
|
+
* @quota 1 unit
|
|
2595
|
+
* @scopes repo:list
|
|
2596
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2597
|
+
*/
|
|
2598
|
+
async listRepositories(orgSlug, options) {
|
|
2599
|
+
try {
|
|
2600
|
+
const data = await this.#executeWithRetry(
|
|
2601
|
+
async () => await getResponseJson(
|
|
2602
|
+
await createGetRequest(
|
|
2603
|
+
this.#baseUrl,
|
|
2604
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos?${queryToSearchParams(options)}`,
|
|
2605
|
+
this.#reqOptions
|
|
2606
|
+
)
|
|
2607
|
+
)
|
|
2608
|
+
);
|
|
2609
|
+
return {
|
|
2610
|
+
cause: void 0,
|
|
2611
|
+
data,
|
|
2612
|
+
error: void 0,
|
|
2613
|
+
status: 200,
|
|
2614
|
+
success: true
|
|
2615
|
+
};
|
|
2616
|
+
} catch (e) {
|
|
2617
|
+
const errorResult = await this.#handleApiError(e);
|
|
2618
|
+
return {
|
|
2619
|
+
cause: errorResult.cause,
|
|
2620
|
+
data: void 0,
|
|
2621
|
+
error: errorResult.error,
|
|
2622
|
+
status: errorResult.status,
|
|
2623
|
+
success: false
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Get organization's security policy configuration.* Returns alert rules, severity thresholds, and enforcement settings.
|
|
2629
|
+
*
|
|
2630
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2631
|
+
*/
|
|
2632
|
+
async getOrgSecurityPolicy(orgSlug) {
|
|
2633
|
+
try {
|
|
2634
|
+
const data = await this.#executeWithRetry(
|
|
2635
|
+
async () => await getResponseJson(
|
|
2636
|
+
await createGetRequest(
|
|
2637
|
+
this.#baseUrl,
|
|
2638
|
+
`orgs/${encodeURIComponent(orgSlug)}/settings/security-policy`,
|
|
2639
|
+
this.#reqOptions
|
|
2640
|
+
)
|
|
2641
|
+
)
|
|
2642
|
+
);
|
|
2643
|
+
return this.#handleApiSuccess(data);
|
|
2644
|
+
} catch (e) {
|
|
2645
|
+
return await this.#handleApiError(e);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Get organization triage settings and status.
|
|
2650
|
+
* Returns alert triage configuration and current state.
|
|
2651
|
+
*
|
|
2652
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2653
|
+
*/
|
|
2654
|
+
async getOrgTriage(orgSlug) {
|
|
2655
|
+
try {
|
|
2656
|
+
const data = await this.#executeWithRetry(
|
|
2657
|
+
async () => await getResponseJson(
|
|
2658
|
+
await createGetRequest(
|
|
2659
|
+
this.#baseUrl,
|
|
2660
|
+
`orgs/${encodeURIComponent(orgSlug)}/triage`,
|
|
2661
|
+
this.#reqOptions
|
|
2662
|
+
)
|
|
2663
|
+
)
|
|
2664
|
+
);
|
|
2665
|
+
return this.#handleApiSuccess(data);
|
|
2666
|
+
} catch (e) {
|
|
2667
|
+
return await this.#handleApiError(e);
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Get current API quota usage and limits.
|
|
2672
|
+
* Returns remaining requests, rate limits, and quota reset times.
|
|
2673
|
+
*
|
|
2674
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2675
|
+
*/
|
|
2676
|
+
async getQuota() {
|
|
2677
|
+
try {
|
|
2678
|
+
const data = await this.#getCached(
|
|
2679
|
+
"quota",
|
|
2680
|
+
async () => await getResponseJson(
|
|
2681
|
+
await createGetRequest(this.#baseUrl, "quota", this.#reqOptions)
|
|
2682
|
+
)
|
|
2683
|
+
);
|
|
2684
|
+
return this.#handleApiSuccess(data);
|
|
2685
|
+
} catch (e) {
|
|
2686
|
+
return await this.#handleApiError(e);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
/**
|
|
2690
|
+
* Get analytics data for a specific repository.
|
|
2691
|
+
* Returns security metrics, dependency trends, and vulnerability statistics.
|
|
2692
|
+
*
|
|
2693
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2694
|
+
*/
|
|
2695
|
+
async getRepoAnalytics(repo, time) {
|
|
2696
|
+
try {
|
|
2697
|
+
const data = await this.#executeWithRetry(
|
|
2698
|
+
async () => await getResponseJson(
|
|
2699
|
+
await createGetRequest(
|
|
2700
|
+
this.#baseUrl,
|
|
2701
|
+
`analytics/repo/${encodeURIComponent(repo)}/${encodeURIComponent(time)}`,
|
|
2702
|
+
this.#reqOptions
|
|
2703
|
+
)
|
|
2704
|
+
)
|
|
2705
|
+
);
|
|
2706
|
+
return this.#handleApiSuccess(data);
|
|
2707
|
+
} catch (e) {
|
|
2708
|
+
return await this.#handleApiError(e);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Get detailed results for a legacy scan report.
|
|
2713
|
+
/**
|
|
2714
|
+
/**
|
|
2715
|
+
* Get security score for a specific npm package and version.
|
|
2716
|
+
* Returns numerical security rating and scoring breakdown.
|
|
2717
|
+
*
|
|
2718
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2719
|
+
*/
|
|
2720
|
+
async getScoreByNpmPackage(pkgName, version) {
|
|
2721
|
+
try {
|
|
2722
|
+
const data = await this.#executeWithRetry(
|
|
2723
|
+
async () => await getResponseJson(
|
|
2724
|
+
await createGetRequest(
|
|
2725
|
+
this.#baseUrl,
|
|
2726
|
+
`npm/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/score`,
|
|
2727
|
+
this.#reqOptions
|
|
2728
|
+
)
|
|
2729
|
+
)
|
|
2730
|
+
);
|
|
2731
|
+
return this.#handleApiSuccess(data);
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
return await this.#handleApiError(e);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Get list of file types and formats supported for scanning.
|
|
2738
|
+
* Returns supported manifest files, lockfiles, and configuration formats.
|
|
2739
|
+
*
|
|
2740
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2741
|
+
*/
|
|
2742
|
+
async getSupportedScanFiles() {
|
|
2743
|
+
try {
|
|
2744
|
+
const data = await this.#executeWithRetry(
|
|
2745
|
+
async () => await getResponseJson(
|
|
2746
|
+
await createGetRequest(
|
|
2747
|
+
this.#baseUrl,
|
|
2748
|
+
"report/supported",
|
|
2749
|
+
this.#reqOptions
|
|
2750
|
+
)
|
|
2751
|
+
)
|
|
2752
|
+
);
|
|
2753
|
+
return this.#handleApiSuccess(data);
|
|
2754
|
+
} catch (e) {
|
|
2755
|
+
return await this.#handleApiError(e);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* List all diff scans for an organization.
|
|
2760
|
+
* Returns paginated list of diff scan metadata and status.
|
|
2761
|
+
*
|
|
2762
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2763
|
+
*/
|
|
2764
|
+
async listOrgDiffScans(orgSlug) {
|
|
2765
|
+
try {
|
|
2766
|
+
const data = await this.#executeWithRetry(
|
|
2767
|
+
async () => await getResponseJson(
|
|
2768
|
+
await createGetRequest(
|
|
2769
|
+
this.#baseUrl,
|
|
2770
|
+
`orgs/${encodeURIComponent(orgSlug)}/diff-scans`,
|
|
2771
|
+
this.#reqOptions
|
|
2772
|
+
)
|
|
2773
|
+
)
|
|
2774
|
+
);
|
|
2775
|
+
return this.#handleApiSuccess(data);
|
|
2776
|
+
} catch (e) {
|
|
2777
|
+
return await this.#handleApiError(e);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Create a new API token for an organization.
|
|
2782
|
+
* Generates API token with specified scopes and metadata.
|
|
2783
|
+
*
|
|
2784
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2785
|
+
*/
|
|
2786
|
+
async postAPIToken(orgSlug, tokenData) {
|
|
2787
|
+
try {
|
|
2788
|
+
const data = await this.#executeWithRetry(
|
|
2789
|
+
async () => await getResponseJson(
|
|
2790
|
+
await createRequestWithJson(
|
|
2791
|
+
"POST",
|
|
2792
|
+
this.#baseUrl,
|
|
2793
|
+
`orgs/${encodeURIComponent(orgSlug)}/tokens`,
|
|
2794
|
+
tokenData,
|
|
2795
|
+
this.#reqOptions
|
|
2796
|
+
)
|
|
2797
|
+
)
|
|
2798
|
+
);
|
|
2799
|
+
return this.#handleApiSuccess(data);
|
|
2800
|
+
} catch (e) {
|
|
2801
|
+
return await this.#handleApiError(e);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Revoke an API token for an organization.
|
|
2806
|
+
* Permanently disables the token and removes access.
|
|
2807
|
+
*
|
|
2808
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2809
|
+
*/
|
|
2810
|
+
async postAPITokensRevoke(orgSlug, tokenId) {
|
|
2811
|
+
try {
|
|
2812
|
+
const data = await this.#executeWithRetry(
|
|
2813
|
+
async () => await getResponseJson(
|
|
2814
|
+
await createRequestWithJson(
|
|
2815
|
+
"POST",
|
|
2816
|
+
this.#baseUrl,
|
|
2817
|
+
`orgs/${encodeURIComponent(orgSlug)}/tokens/${encodeURIComponent(tokenId)}/revoke`,
|
|
2818
|
+
{},
|
|
2819
|
+
this.#reqOptions
|
|
2820
|
+
)
|
|
2821
|
+
)
|
|
2822
|
+
);
|
|
2823
|
+
return this.#handleApiSuccess(data);
|
|
2824
|
+
} catch (e) {
|
|
2825
|
+
return await this.#handleApiError(e);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Rotate an API token for an organization.
|
|
2830
|
+
* Generates new token value while preserving token metadata.
|
|
2831
|
+
*
|
|
2832
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2833
|
+
*/
|
|
2834
|
+
async postAPITokensRotate(orgSlug, tokenId) {
|
|
2835
|
+
try {
|
|
2836
|
+
const data = await this.#executeWithRetry(
|
|
2837
|
+
async () => await getResponseJson(
|
|
2838
|
+
await createRequestWithJson(
|
|
2839
|
+
"POST",
|
|
2840
|
+
this.#baseUrl,
|
|
2841
|
+
`orgs/${encodeURIComponent(orgSlug)}/tokens/${encodeURIComponent(tokenId)}/rotate`,
|
|
2842
|
+
{},
|
|
2843
|
+
this.#reqOptions
|
|
2844
|
+
)
|
|
2845
|
+
)
|
|
2846
|
+
);
|
|
2847
|
+
return this.#handleApiSuccess(data);
|
|
2848
|
+
} catch (e) {
|
|
2849
|
+
return await this.#handleApiError(e);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
/**
|
|
2853
|
+
* Update an existing API token for an organization.
|
|
2854
|
+
* Modifies token metadata, scopes, or other properties.
|
|
2855
|
+
*
|
|
2856
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2857
|
+
*/
|
|
2858
|
+
async postAPITokenUpdate(orgSlug, tokenId, updateData) {
|
|
2859
|
+
try {
|
|
2860
|
+
const data = await this.#executeWithRetry(
|
|
2861
|
+
async () => await getResponseJson(
|
|
2862
|
+
await createRequestWithJson(
|
|
2863
|
+
"POST",
|
|
2864
|
+
this.#baseUrl,
|
|
2865
|
+
`orgs/${encodeURIComponent(orgSlug)}/tokens/${encodeURIComponent(tokenId)}/update`,
|
|
2866
|
+
updateData,
|
|
2867
|
+
this.#reqOptions
|
|
2868
|
+
)
|
|
2869
|
+
)
|
|
2870
|
+
);
|
|
2871
|
+
return this.#handleApiSuccess(data);
|
|
2872
|
+
} catch (e) {
|
|
2873
|
+
return await this.#handleApiError(e);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Update user or organization settings.
|
|
2878
|
+
* Configures preferences, notifications, and security policies.
|
|
2879
|
+
*
|
|
2880
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2881
|
+
*/
|
|
2882
|
+
async postSettings(selectors) {
|
|
2883
|
+
try {
|
|
2884
|
+
const data = await this.#executeWithRetry(
|
|
2885
|
+
async () => await getResponseJson(
|
|
2886
|
+
await createRequestWithJson(
|
|
2887
|
+
"POST",
|
|
2888
|
+
this.#baseUrl,
|
|
2889
|
+
"settings",
|
|
2890
|
+
{ json: selectors },
|
|
2891
|
+
this.#reqOptions
|
|
2892
|
+
)
|
|
2893
|
+
)
|
|
2894
|
+
);
|
|
2895
|
+
return this.#handleApiSuccess(data);
|
|
2896
|
+
} catch (e) {
|
|
2897
|
+
return await this.#handleApiError(e);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Search for dependencies across monitored projects.
|
|
2902
|
+
* Returns matching packages with security information and usage patterns.
|
|
2903
|
+
*
|
|
2904
|
+
* @throws {Error} When server returns 5xx status codes
|
|
2905
|
+
*/
|
|
2906
|
+
async searchDependencies(queryParams) {
|
|
2907
|
+
try {
|
|
2908
|
+
const data = await this.#executeWithRetry(
|
|
2909
|
+
async () => await getResponseJson(
|
|
2910
|
+
await createRequestWithJson(
|
|
2911
|
+
"POST",
|
|
2912
|
+
this.#baseUrl,
|
|
2913
|
+
"dependencies/search",
|
|
2914
|
+
queryParams,
|
|
2915
|
+
this.#reqOptions
|
|
2916
|
+
)
|
|
2917
|
+
)
|
|
2918
|
+
);
|
|
2919
|
+
return this.#handleApiSuccess(data);
|
|
2920
|
+
} catch (e) {
|
|
2921
|
+
return await this.#handleApiError(e);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Send POST or PUT request with JSON body and return parsed JSON response.
|
|
2926
|
+
* Supports both throwing (default) and non-throwing modes.
|
|
2927
|
+
* @param urlPath - API endpoint path (e.g., 'organizations')
|
|
2928
|
+
* @param options - Request options including method, body, and throws behavior
|
|
2929
|
+
* @returns Parsed JSON response or SocketSdkGenericResult based on options
|
|
2930
|
+
*/
|
|
2931
|
+
async sendApi(urlPath, options) {
|
|
2932
|
+
const {
|
|
2933
|
+
body,
|
|
2934
|
+
// Default to POST method for JSON API requests.
|
|
2935
|
+
method = "POST",
|
|
2936
|
+
throws = true
|
|
2937
|
+
} = { __proto__: null, ...options };
|
|
2938
|
+
try {
|
|
2939
|
+
const response = await createRequestWithJson(
|
|
2940
|
+
method,
|
|
2941
|
+
this.#baseUrl,
|
|
2942
|
+
urlPath,
|
|
2943
|
+
body,
|
|
2944
|
+
this.#reqOptions
|
|
2945
|
+
);
|
|
2946
|
+
const data = await getResponseJson(response);
|
|
2947
|
+
if (throws) {
|
|
2948
|
+
return data;
|
|
2949
|
+
}
|
|
2950
|
+
return {
|
|
2951
|
+
cause: void 0,
|
|
2952
|
+
data,
|
|
2953
|
+
error: void 0,
|
|
2954
|
+
/* c8 ignore next - Defensive fallback: response.statusCode is always defined in Node.js http/https */
|
|
2955
|
+
status: response.statusCode ?? 200,
|
|
2956
|
+
success: true
|
|
2957
|
+
};
|
|
2958
|
+
} catch (e) {
|
|
2959
|
+
if (throws) {
|
|
2960
|
+
throw e;
|
|
2961
|
+
}
|
|
2962
|
+
if (e instanceof ResponseError) {
|
|
2963
|
+
const errorResult = await this.#handleApiError(e);
|
|
2964
|
+
return {
|
|
2965
|
+
cause: errorResult.cause,
|
|
2966
|
+
data: void 0,
|
|
2967
|
+
error: errorResult.error,
|
|
2968
|
+
status: errorResult.status,
|
|
2969
|
+
success: false
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
const errStr = e ? String(e).trim() : "";
|
|
2973
|
+
return {
|
|
2974
|
+
cause: errStr || UNKNOWN_ERROR,
|
|
2975
|
+
data: void 0,
|
|
2976
|
+
error: "API request failed",
|
|
2977
|
+
status: 0,
|
|
2978
|
+
success: false
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Stream a full scan's results to file or stdout.
|
|
2984
|
+
*
|
|
2985
|
+
* Provides efficient streaming for large scan datasets without loading
|
|
2986
|
+
* entire response into memory. Useful for processing large SBOMs.
|
|
2987
|
+
*
|
|
2988
|
+
* @param orgSlug - Organization identifier
|
|
2989
|
+
* @param scanId - Full scan identifier
|
|
2990
|
+
* @param options - Streaming options (output file path, stdout, or buffered)
|
|
2991
|
+
* @returns Scan result with streaming response
|
|
2992
|
+
*
|
|
2993
|
+
* @example
|
|
2994
|
+
* ```typescript
|
|
2995
|
+
* // Stream to file
|
|
2996
|
+
* await sdk.streamFullScan('my-org', 'scan_123', {
|
|
2997
|
+
* output: './scan-results.json'
|
|
2998
|
+
* })
|
|
2999
|
+
*
|
|
3000
|
+
* // Stream to stdout
|
|
3001
|
+
* await sdk.streamFullScan('my-org', 'scan_123', {
|
|
3002
|
+
* output: true
|
|
3003
|
+
* })
|
|
3004
|
+
*
|
|
3005
|
+
* // Get buffered response
|
|
3006
|
+
* const result = await sdk.streamFullScan('my-org', 'scan_123')
|
|
3007
|
+
* ```
|
|
3008
|
+
*
|
|
3009
|
+
* @see https://docs.socket.dev/reference/getorgfullscan
|
|
3010
|
+
* @apiEndpoint GET /orgs/{org_slug}/full-scans/{full_scan_id}
|
|
3011
|
+
* @quota 1 unit
|
|
3012
|
+
* @scopes full-scans:list
|
|
3013
|
+
* @throws {Error} When server returns 5xx status codes
|
|
3014
|
+
*/
|
|
3015
|
+
async streamFullScan(orgSlug, scanId, options) {
|
|
3016
|
+
const { output } = {
|
|
3017
|
+
__proto__: null,
|
|
3018
|
+
...options
|
|
3019
|
+
};
|
|
3020
|
+
try {
|
|
3021
|
+
const req = getHttpModule(this.#baseUrl).request(
|
|
3022
|
+
`${this.#baseUrl}orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(scanId)}`,
|
|
3023
|
+
{
|
|
3024
|
+
method: "GET",
|
|
3025
|
+
...this.#reqOptions
|
|
3026
|
+
}
|
|
3027
|
+
).end();
|
|
3028
|
+
const res = await getResponse(req);
|
|
3029
|
+
if (!isResponseOk(res)) {
|
|
3030
|
+
throw new ResponseError(res);
|
|
3031
|
+
}
|
|
3032
|
+
if (typeof output === "string") {
|
|
3033
|
+
const writeStream = createWriteStream(output);
|
|
3034
|
+
let bytesWritten = 0;
|
|
3035
|
+
res.on("data", (chunk) => {
|
|
3036
|
+
bytesWritten += chunk.length;
|
|
3037
|
+
if (bytesWritten > MAX_STREAM_SIZE) {
|
|
3038
|
+
res.destroy();
|
|
3039
|
+
writeStream.destroy();
|
|
3040
|
+
throw new Error(
|
|
3041
|
+
`Response exceeds maximum stream size of ${MAX_STREAM_SIZE} bytes`
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
res.pipe(writeStream);
|
|
3046
|
+
writeStream.on("error", (error) => {
|
|
3047
|
+
throw new Error(`Failed to write to file: ${output}`, {
|
|
3048
|
+
cause: error
|
|
3049
|
+
});
|
|
3050
|
+
});
|
|
3051
|
+
} else if (output === true) {
|
|
3052
|
+
let bytesWritten = 0;
|
|
3053
|
+
res.on("data", (chunk) => {
|
|
3054
|
+
bytesWritten += chunk.length;
|
|
3055
|
+
if (bytesWritten > MAX_STREAM_SIZE) {
|
|
3056
|
+
res.destroy();
|
|
3057
|
+
throw new Error(
|
|
3058
|
+
`Response exceeds maximum stream size of ${MAX_STREAM_SIZE} bytes`
|
|
3059
|
+
);
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
res.pipe(process.stdout);
|
|
3063
|
+
process.stdout.on("error", (error) => {
|
|
3064
|
+
throw new Error("Failed to write to stdout", { cause: error });
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
return this.#handleApiSuccess(res);
|
|
3068
|
+
} catch (e) {
|
|
3069
|
+
return await this.#handleApiError(e);
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
/**
|
|
3073
|
+
* Stream patches for artifacts in a scan report.
|
|
3074
|
+
*
|
|
3075
|
+
* This method streams all available patches for artifacts in a scan.
|
|
3076
|
+
* Free tier users will only receive free patches.
|
|
3077
|
+
*
|
|
3078
|
+
* Note: This method returns a ReadableStream for processing large datasets.
|
|
3079
|
+
*/
|
|
3080
|
+
async streamPatchesFromScan(orgSlug, scanId) {
|
|
3081
|
+
const response = await this.#executeWithRetry(
|
|
3082
|
+
async () => await createGetRequest(
|
|
3083
|
+
this.#baseUrl,
|
|
3084
|
+
`orgs/${encodeURIComponent(orgSlug)}/patches/scan?scan_id=${encodeURIComponent(scanId)}`,
|
|
3085
|
+
this.#reqOptions
|
|
3086
|
+
)
|
|
3087
|
+
);
|
|
3088
|
+
if (!isResponseOk(response)) {
|
|
3089
|
+
throw new ResponseError(response, "GET Request failed");
|
|
3090
|
+
}
|
|
3091
|
+
const rli = readline.createInterface({
|
|
3092
|
+
input: response,
|
|
3093
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
3094
|
+
});
|
|
3095
|
+
return new ReadableStream({
|
|
3096
|
+
async start(controller) {
|
|
3097
|
+
try {
|
|
3098
|
+
for await (const line of rli) {
|
|
3099
|
+
const trimmed = line.trim();
|
|
3100
|
+
if (!trimmed) {
|
|
3101
|
+
continue;
|
|
3102
|
+
}
|
|
3103
|
+
try {
|
|
3104
|
+
const data = JSON.parse(trimmed);
|
|
3105
|
+
controller.enqueue(data);
|
|
3106
|
+
} catch (e) {
|
|
3107
|
+
debugLog2("streamPatchesFromScan", `Failed to parse line: ${e}`);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
} catch (error) {
|
|
3111
|
+
controller.error(error);
|
|
3112
|
+
} finally {
|
|
3113
|
+
controller.close();
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
/**
|
|
3119
|
+
* Update alert triage status for an organization.
|
|
3120
|
+
* Modifies alert resolution status and triage decisions.
|
|
3121
|
+
*
|
|
3122
|
+
* @throws {Error} When server returns 5xx status codes
|
|
3123
|
+
*/
|
|
3124
|
+
async updateOrgAlertTriage(orgSlug, alertId, triageData) {
|
|
3125
|
+
try {
|
|
3126
|
+
const data = await this.#executeWithRetry(
|
|
3127
|
+
async () => await getResponseJson(
|
|
3128
|
+
await createRequestWithJson(
|
|
3129
|
+
"PUT",
|
|
3130
|
+
this.#baseUrl,
|
|
3131
|
+
`orgs/${encodeURIComponent(orgSlug)}/triage/${encodeURIComponent(alertId)}`,
|
|
3132
|
+
triageData,
|
|
3133
|
+
this.#reqOptions
|
|
3134
|
+
)
|
|
3135
|
+
)
|
|
3136
|
+
);
|
|
3137
|
+
return this.#handleApiSuccess(data);
|
|
3138
|
+
} catch (e) {
|
|
3139
|
+
return await this.#handleApiError(e);
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Update organization's license policy configuration.* Modifies allowed, restricted, and monitored license types.
|
|
3144
|
+
*
|
|
3145
|
+
* @throws {Error} When server returns 5xx status codes
|
|
3146
|
+
*/
|
|
3147
|
+
async updateOrgLicensePolicy(orgSlug, policyData, queryParams) {
|
|
3148
|
+
try {
|
|
3149
|
+
const data = await this.#executeWithRetry(
|
|
3150
|
+
async () => await getResponseJson(
|
|
3151
|
+
await createRequestWithJson(
|
|
3152
|
+
"POST",
|
|
3153
|
+
this.#baseUrl,
|
|
3154
|
+
`orgs/${encodeURIComponent(orgSlug)}/settings/license-policy?${queryToSearchParams(queryParams)}`,
|
|
3155
|
+
policyData,
|
|
3156
|
+
this.#reqOptions
|
|
3157
|
+
)
|
|
3158
|
+
)
|
|
3159
|
+
);
|
|
3160
|
+
return this.#handleApiSuccess(data);
|
|
3161
|
+
} catch (e) {
|
|
3162
|
+
return await this.#handleApiError(e);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
/**
|
|
3166
|
+
* Update configuration for a repository.
|
|
3167
|
+
*
|
|
3168
|
+
* Modifies monitoring settings, branch configuration, and scan preferences.
|
|
3169
|
+
*
|
|
3170
|
+
* @param orgSlug - Organization identifier
|
|
3171
|
+
* @param repoSlug - Repository slug/name
|
|
3172
|
+
* @param params - Configuration updates (description, homepage, default_branch, etc.)
|
|
3173
|
+
* @returns Updated repository details
|
|
3174
|
+
*
|
|
3175
|
+
* @example
|
|
3176
|
+
* ```typescript
|
|
3177
|
+
* const result = await sdk.updateRepository('my-org', 'my-repo', {
|
|
3178
|
+
* description: 'Updated description',
|
|
3179
|
+
* default_branch: 'develop'
|
|
3180
|
+
* })
|
|
3181
|
+
*
|
|
3182
|
+
* if (result.success) {
|
|
3183
|
+
* console.log('Repository updated:', result.data.name)
|
|
3184
|
+
* }
|
|
3185
|
+
* ```
|
|
3186
|
+
*
|
|
3187
|
+
* @see https://docs.socket.dev/reference/updateorgrepo
|
|
3188
|
+
* @apiEndpoint POST /orgs/{org_slug}/repos/{repo_slug}
|
|
3189
|
+
* @quota 1 unit
|
|
3190
|
+
* @scopes repo:write
|
|
3191
|
+
* @throws {Error} When server returns 5xx status codes
|
|
3192
|
+
*/
|
|
3193
|
+
async updateRepository(orgSlug, repoSlug, params) {
|
|
3194
|
+
try {
|
|
3195
|
+
const data = await this.#executeWithRetry(
|
|
3196
|
+
async () => await getResponseJson(
|
|
3197
|
+
await createRequestWithJson(
|
|
3198
|
+
"POST",
|
|
3199
|
+
this.#baseUrl,
|
|
3200
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos/${encodeURIComponent(repoSlug)}`,
|
|
3201
|
+
params,
|
|
3202
|
+
this.#reqOptions
|
|
3203
|
+
)
|
|
3204
|
+
)
|
|
3205
|
+
);
|
|
3206
|
+
return {
|
|
3207
|
+
cause: void 0,
|
|
3208
|
+
data,
|
|
3209
|
+
error: void 0,
|
|
3210
|
+
status: 200,
|
|
3211
|
+
success: true
|
|
3212
|
+
};
|
|
3213
|
+
} catch (e) {
|
|
3214
|
+
const errorResult = await this.#handleApiError(e);
|
|
3215
|
+
return {
|
|
3216
|
+
cause: errorResult.cause,
|
|
3217
|
+
data: void 0,
|
|
3218
|
+
error: errorResult.error,
|
|
3219
|
+
status: errorResult.status,
|
|
3220
|
+
success: false
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
/**
|
|
3225
|
+
* Update a repository label for an organization.
|
|
3226
|
+
*
|
|
3227
|
+
* Modifies label properties like name. Label names must be non-empty and less than 1000 characters.
|
|
3228
|
+
*
|
|
3229
|
+
* @param orgSlug - Organization identifier
|
|
3230
|
+
* @param labelId - Label identifier
|
|
3231
|
+
* @param labelData - Label updates (typically name property)
|
|
3232
|
+
* @returns Updated label with guaranteed id and name fields
|
|
3233
|
+
*
|
|
3234
|
+
* @example
|
|
3235
|
+
* ```typescript
|
|
3236
|
+
* const result = await sdk.updateRepositoryLabel('my-org', 'label-id-123', { name: 'staging' })
|
|
3237
|
+
*
|
|
3238
|
+
* if (result.success) {
|
|
3239
|
+
* console.log('Label updated:', result.data.name)
|
|
3240
|
+
* console.log('Label ID:', result.data.id)
|
|
3241
|
+
* }
|
|
3242
|
+
* ```
|
|
3243
|
+
*
|
|
3244
|
+
* @see https://docs.socket.dev/reference/updateorgrepolabel
|
|
3245
|
+
* @apiEndpoint PUT /orgs/{org_slug}/repos/labels/{label_id}
|
|
3246
|
+
* @quota 1 unit
|
|
3247
|
+
* @scopes repo-label:update
|
|
3248
|
+
* @throws {Error} When server returns 5xx status codes
|
|
3249
|
+
*/
|
|
3250
|
+
async updateRepositoryLabel(orgSlug, labelId, labelData) {
|
|
3251
|
+
try {
|
|
3252
|
+
const data = await this.#executeWithRetry(
|
|
3253
|
+
async () => await getResponseJson(
|
|
3254
|
+
await createRequestWithJson(
|
|
3255
|
+
"PUT",
|
|
3256
|
+
this.#baseUrl,
|
|
3257
|
+
`orgs/${encodeURIComponent(orgSlug)}/repos/labels/${encodeURIComponent(labelId)}`,
|
|
3258
|
+
labelData,
|
|
3259
|
+
this.#reqOptions
|
|
3260
|
+
)
|
|
3261
|
+
)
|
|
3262
|
+
);
|
|
3263
|
+
return {
|
|
3264
|
+
cause: void 0,
|
|
3265
|
+
data,
|
|
3266
|
+
error: void 0,
|
|
3267
|
+
status: 200,
|
|
3268
|
+
success: true
|
|
3269
|
+
};
|
|
3270
|
+
} catch (e) {
|
|
3271
|
+
const errorResult = await this.#handleApiError(e);
|
|
3272
|
+
return {
|
|
3273
|
+
cause: errorResult.cause,
|
|
3274
|
+
data: void 0,
|
|
3275
|
+
error: errorResult.error,
|
|
3276
|
+
status: errorResult.status,
|
|
3277
|
+
success: false
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Update organization's security policy configuration.* Modifies alert rules, severity thresholds, and enforcement settings.
|
|
3283
|
+
*
|
|
3284
|
+
* @throws {Error} When server returns 5xx status codes
|
|
3285
|
+
*/
|
|
3286
|
+
async updateOrgSecurityPolicy(orgSlug, policyData) {
|
|
3287
|
+
try {
|
|
3288
|
+
const data = await this.#executeWithRetry(
|
|
3289
|
+
async () => await getResponseJson(
|
|
3290
|
+
await createRequestWithJson(
|
|
3291
|
+
"POST",
|
|
3292
|
+
this.#baseUrl,
|
|
3293
|
+
`orgs/${encodeURIComponent(orgSlug)}/settings/security-policy`,
|
|
3294
|
+
policyData,
|
|
3295
|
+
this.#reqOptions
|
|
3296
|
+
)
|
|
3297
|
+
)
|
|
3298
|
+
);
|
|
3299
|
+
return this.#handleApiSuccess(data);
|
|
3300
|
+
} catch (e) {
|
|
3301
|
+
return await this.#handleApiError(e);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Upload manifest files for dependency analysis.
|
|
3306
|
+
* Processes package files to create dependency snapshots and security analysis.
|
|
3307
|
+
*
|
|
3308
|
+
* @throws {Error} When server returns 5xx status codes
|
|
3309
|
+
*/
|
|
3310
|
+
async uploadManifestFiles(orgSlug, filepaths, options) {
|
|
3311
|
+
const { pathsRelativeTo = "." } = {
|
|
3312
|
+
__proto__: null,
|
|
3313
|
+
...options
|
|
3314
|
+
};
|
|
3315
|
+
const basePath = resolveBasePath(pathsRelativeTo);
|
|
3316
|
+
const absFilepaths = resolveAbsPaths(filepaths, basePath);
|
|
3317
|
+
const { invalidPaths, validPaths } = validateFiles(absFilepaths);
|
|
3318
|
+
if (this.#onFileValidation && invalidPaths.length > 0) {
|
|
3319
|
+
const result = await this.#onFileValidation(validPaths, invalidPaths, {
|
|
3320
|
+
operation: "uploadManifestFiles",
|
|
3321
|
+
orgSlug
|
|
3322
|
+
});
|
|
3323
|
+
if (!result.shouldContinue) {
|
|
3324
|
+
return {
|
|
3325
|
+
error: result.errorMessage ?? "File validation failed",
|
|
3326
|
+
status: 400,
|
|
3327
|
+
success: false,
|
|
3328
|
+
...result.errorCause ? { cause: result.errorCause } : {}
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
if (!this.#onFileValidation && invalidPaths.length > 0) {
|
|
3333
|
+
const samplePaths = invalidPaths.slice(0, 3).join("\n - ");
|
|
3334
|
+
const remaining = invalidPaths.length > 3 ? `
|
|
3335
|
+
... and ${invalidPaths.length - 3} more` : "";
|
|
3336
|
+
console.warn(
|
|
3337
|
+
`Warning: ${invalidPaths.length} files skipped (unreadable):
|
|
3338
|
+
- ${samplePaths}${remaining}
|
|
56
3339
|
\u2192 This may occur with Yarn Berry PnP or pnpm symlinks.
|
|
57
|
-
\u2192 Try: Run installation command to ensure files are accessible.`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
3340
|
+
\u2192 Try: Run installation command to ensure files are accessible.`
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
if (validPaths.length === 0) {
|
|
3344
|
+
const samplePaths = invalidPaths.slice(0, 5).join("\n - ");
|
|
3345
|
+
const remaining = invalidPaths.length > 5 ? `
|
|
3346
|
+
... and ${invalidPaths.length - 5} more` : "";
|
|
3347
|
+
return {
|
|
3348
|
+
cause: [
|
|
3349
|
+
`All ${invalidPaths.length} files failed validation:`,
|
|
3350
|
+
` - ${samplePaths}${remaining}`,
|
|
3351
|
+
"",
|
|
3352
|
+
"\u2192 Common causes:",
|
|
3353
|
+
" \u2022 Yarn Berry PnP virtual filesystem (files are not on disk)",
|
|
3354
|
+
" \u2022 pnpm symlinks pointing to inaccessible locations",
|
|
3355
|
+
" \u2022 Incorrect file permissions",
|
|
3356
|
+
" \u2022 Files were deleted after discovery",
|
|
3357
|
+
"",
|
|
3358
|
+
"\u2192 Solutions:",
|
|
3359
|
+
" \u2022 Yarn Berry: Use `nodeLinker: node-modules` in .yarnrc.yml",
|
|
3360
|
+
" \u2022 pnpm: Use `node-linker=hoisted` in .npmrc",
|
|
3361
|
+
" \u2022 Check file permissions with: ls -la <file>",
|
|
3362
|
+
" \u2022 Run package manager install command"
|
|
3363
|
+
].join("\n"),
|
|
3364
|
+
error: "No readable manifest files found",
|
|
3365
|
+
status: 400,
|
|
3366
|
+
success: false
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
try {
|
|
3370
|
+
const data = await this.#executeWithRetry(
|
|
3371
|
+
async () => await getResponseJson(
|
|
3372
|
+
await createUploadRequest(
|
|
3373
|
+
this.#baseUrl,
|
|
3374
|
+
`orgs/${encodeURIComponent(orgSlug)}/upload-manifest-files`,
|
|
3375
|
+
createRequestBodyForFilepaths(validPaths, basePath),
|
|
3376
|
+
this.#reqOptions
|
|
3377
|
+
)
|
|
3378
|
+
)
|
|
3379
|
+
);
|
|
3380
|
+
return this.#handleApiSuccess(
|
|
3381
|
+
data
|
|
3382
|
+
);
|
|
3383
|
+
} catch (e) {
|
|
3384
|
+
return await this.#handleApiError(
|
|
3385
|
+
e
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* View detailed information about a specific patch by its UUID.
|
|
3391
|
+
*
|
|
3392
|
+
* This method retrieves comprehensive patch details including files,
|
|
3393
|
+
* vulnerabilities, description, license, and tier information.
|
|
3394
|
+
*/
|
|
3395
|
+
async viewPatch(orgSlug, uuid) {
|
|
3396
|
+
const data = await getResponseJson(
|
|
3397
|
+
await createGetRequest(
|
|
3398
|
+
this.#baseUrl,
|
|
3399
|
+
`orgs/${encodeURIComponent(orgSlug)}/patches/view/${encodeURIComponent(uuid)}`,
|
|
3400
|
+
this.#reqOptions
|
|
3401
|
+
)
|
|
3402
|
+
);
|
|
3403
|
+
return data;
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* Download patch file content by hash.
|
|
3407
|
+
*
|
|
3408
|
+
* Downloads the actual patched file content from the public Socket blob store.
|
|
3409
|
+
* This is used after calling viewPatch() to get the patch metadata.
|
|
3410
|
+
* No authentication is required as patch blobs are publicly accessible.
|
|
3411
|
+
*
|
|
3412
|
+
* @param hash - The blob hash in SSRI (sha256-base64) or hex format
|
|
3413
|
+
* @param options - Optional configuration
|
|
3414
|
+
* @param options.baseUrl - Override blob store URL (for testing)
|
|
3415
|
+
* @returns Promise<string> - The patch file content as UTF-8 string
|
|
3416
|
+
* @throws Error if blob not found (404) or download fails
|
|
3417
|
+
*
|
|
3418
|
+
* @example
|
|
3419
|
+
* ```typescript
|
|
3420
|
+
* const sdk = new SocketSdk('your-api-token')
|
|
3421
|
+
* // First get patch metadata
|
|
3422
|
+
* const patch = await sdk.viewPatch('my-org', 'patch-uuid')
|
|
3423
|
+
* // Then download the actual patched file
|
|
3424
|
+
* const fileContent = await sdk.downloadPatch(patch.files['index.js'].socketBlob)
|
|
3425
|
+
* ```
|
|
3426
|
+
*/
|
|
3427
|
+
async downloadPatch(hash, options) {
|
|
3428
|
+
const https2 = await import("node:https");
|
|
3429
|
+
const http2 = await import("node:http");
|
|
3430
|
+
const blobPath = `/blob/${encodeURIComponent(hash)}`;
|
|
3431
|
+
const blobBaseUrl = options?.baseUrl || SOCKET_PUBLIC_BLOB_STORE_URL;
|
|
3432
|
+
const url = `${blobBaseUrl}${blobPath}`;
|
|
3433
|
+
const isHttps = url.startsWith("https:");
|
|
3434
|
+
return await new Promise((resolve, reject) => {
|
|
3435
|
+
const client = isHttps ? https2 : http2;
|
|
3436
|
+
client.get(url, (res) => {
|
|
3437
|
+
if (res.statusCode === 404) {
|
|
3438
|
+
const message = [
|
|
3439
|
+
`Blob not found: ${hash}`,
|
|
3440
|
+
`\u2192 URL: ${url}`,
|
|
3441
|
+
"\u2192 The patch file may have expired or the hash is incorrect.",
|
|
3442
|
+
"\u2192 Verify: The blob hash is correct.",
|
|
3443
|
+
"\u2192 Note: Blob URLs may expire after a certain time period."
|
|
3444
|
+
].join("\n");
|
|
3445
|
+
reject(new Error(message));
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
if (res.statusCode !== 200) {
|
|
3449
|
+
const message = [
|
|
3450
|
+
`Failed to download blob: ${res.statusCode} ${res.statusMessage}`,
|
|
3451
|
+
`\u2192 Hash: ${hash}`,
|
|
3452
|
+
`\u2192 URL: ${url}`,
|
|
3453
|
+
"\u2192 The blob storage service may be temporarily unavailable.",
|
|
3454
|
+
res.statusCode && res.statusCode >= 500 ? "\u2192 Try: Retry the download after a short delay." : "\u2192 Verify: The blob hash and URL are correct."
|
|
3455
|
+
].join("\n");
|
|
3456
|
+
reject(new Error(message));
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
let data = "";
|
|
3460
|
+
res.on("data", (chunk) => {
|
|
3461
|
+
data += chunk;
|
|
3462
|
+
});
|
|
3463
|
+
res.on("end", () => {
|
|
3464
|
+
resolve(data);
|
|
3465
|
+
});
|
|
3466
|
+
res.on("error", (err) => {
|
|
3467
|
+
reject(err);
|
|
3468
|
+
});
|
|
3469
|
+
}).on("error", (err) => {
|
|
3470
|
+
const nodeErr = err;
|
|
3471
|
+
const message = [
|
|
3472
|
+
`Error downloading blob: ${hash}`,
|
|
3473
|
+
`\u2192 URL: ${url}`,
|
|
3474
|
+
`\u2192 Network error: ${nodeErr.message}`
|
|
3475
|
+
];
|
|
3476
|
+
if (nodeErr.code === "ENOTFOUND") {
|
|
3477
|
+
message.push(
|
|
3478
|
+
"\u2192 DNS lookup failed. Cannot resolve blob storage hostname.",
|
|
3479
|
+
"\u2192 Check: Internet connection and DNS settings."
|
|
3480
|
+
);
|
|
3481
|
+
} else if (nodeErr.code === "ECONNREFUSED") {
|
|
3482
|
+
message.push(
|
|
3483
|
+
"\u2192 Connection refused. Blob storage service is unreachable.",
|
|
3484
|
+
"\u2192 Check: Network connectivity and firewall settings."
|
|
3485
|
+
);
|
|
3486
|
+
} else if (nodeErr.code === "ETIMEDOUT") {
|
|
3487
|
+
message.push(
|
|
3488
|
+
"\u2192 Connection timed out.",
|
|
3489
|
+
"\u2192 Try: Check network connectivity and retry."
|
|
3490
|
+
);
|
|
3491
|
+
} else if (nodeErr.code) {
|
|
3492
|
+
message.push(`\u2192 Error code: ${nodeErr.code}`);
|
|
3493
|
+
}
|
|
3494
|
+
reject(new Error(message.join("\n"), { cause: err }));
|
|
3495
|
+
});
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
};
|
|
3499
|
+
if (isDebugNs("heap")) {
|
|
3500
|
+
const used = process.memoryUsage();
|
|
3501
|
+
debugLog2("heap", `heap used: ${Math.round(used.heapUsed / 1024 / 1024)}MB`);
|
|
3502
|
+
}
|
|
3503
|
+
export {
|
|
3504
|
+
DEFAULT_USER_AGENT,
|
|
3505
|
+
ResponseError,
|
|
3506
|
+
SocketSdk,
|
|
3507
|
+
calculateTotalQuotaCost,
|
|
3508
|
+
createDeleteRequest,
|
|
3509
|
+
createGetRequest,
|
|
3510
|
+
createRequestBodyForFilepaths,
|
|
3511
|
+
createRequestBodyForJson,
|
|
3512
|
+
createRequestWithJson,
|
|
3513
|
+
createUploadRequest,
|
|
3514
|
+
createUserAgentFromPkgJson,
|
|
3515
|
+
getAllMethodRequirements,
|
|
3516
|
+
getErrorResponseBody,
|
|
3517
|
+
getHttpModule,
|
|
3518
|
+
getMethodRequirements,
|
|
3519
|
+
getMethodsByPermissions,
|
|
3520
|
+
getMethodsByQuotaCost,
|
|
3521
|
+
getQuotaCost,
|
|
3522
|
+
getQuotaUsageSummary,
|
|
3523
|
+
getRequiredPermissions,
|
|
3524
|
+
getResponse,
|
|
3525
|
+
getResponseJson,
|
|
3526
|
+
hasQuotaForMethods,
|
|
3527
|
+
httpAgentNames,
|
|
3528
|
+
isResponseOk,
|
|
3529
|
+
normalizeBaseUrl,
|
|
3530
|
+
promiseWithResolvers,
|
|
3531
|
+
publicPolicy,
|
|
3532
|
+
queryToSearchParams,
|
|
3533
|
+
reshapeArtifactForPublicPolicy,
|
|
3534
|
+
resolveAbsPaths,
|
|
3535
|
+
resolveBasePath
|
|
3536
|
+
};
|