@socketsecurity/sdk 1.11.0 → 1.11.2

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/dist/constants.js CHANGED
@@ -1,124 +1,30 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.publicPolicy = exports.httpAgentNames = exports.DEFAULT_USER_AGENT = void 0;
1
+ 'use strict';
2
+
3
+ var _package = require('./package.json.js');
4
+ var userAgent = require('./user-agent.js');
5
+
7
6
  /**
8
7
  * @fileoverview Configuration constants and enums for the Socket SDK.
9
8
  * Provides default values, HTTP agents, and public policy configurations for API interactions.
10
9
  */
11
- const package_json_1 = __importDefault(require("../package.json"));
12
- const user_agent_1 = require("./user-agent");
13
- exports.DEFAULT_USER_AGENT = (0, user_agent_1.createUserAgentFromPkgJson)(package_json_1.default);
10
+
11
+ const DEFAULT_USER_AGENT = userAgent.createUserAgentFromPkgJson(_package.default);
12
+
14
13
  // https://github.com/sindresorhus/got/blob/v14.4.6/documentation/2-options.md#agent
15
14
  // Valid HTTP agent names for Got-style agent configuration compatibility.
16
- exports.httpAgentNames = new Set(['http', 'https', 'http2']);
15
+ const httpAgentNames = new Set(['http', 'https', 'http2']);
16
+
17
17
  // Public security policy.
18
- exports.publicPolicy = new Map([
19
- // error (1):
20
- ['malware', 'error'],
21
- // warn (7):
22
- ['criticalCVE', 'warn'],
23
- ['didYouMean', 'warn'],
24
- ['gitDependency', 'warn'],
25
- ['httpDependency', 'warn'],
26
- ['licenseSpdxDisj', 'warn'],
27
- ['obfuscatedFile', 'warn'],
28
- ['troll', 'warn'],
29
- // monitor (7):
30
- ['deprecated', 'monitor'],
31
- ['mediumCVE', 'monitor'],
32
- ['mildCVE', 'monitor'],
33
- ['shrinkwrap', 'monitor'],
34
- ['telemetry', 'monitor'],
35
- ['unpopularPackage', 'monitor'],
36
- ['unstableOwnership', 'monitor'],
37
- // ignore (85):
38
- ['ambiguousClassifier', 'ignore'],
39
- ['badEncoding', 'ignore'],
40
- ['badSemver', 'ignore'],
41
- ['badSemverDependency', 'ignore'],
42
- ['bidi', 'ignore'],
43
- ['binScriptConfusion', 'ignore'],
44
- ['chromeContentScript', 'ignore'],
45
- ['chromeHostPermission', 'ignore'],
46
- ['chromePermission', 'ignore'],
47
- ['chromeWildcardHostPermission', 'ignore'],
48
- ['chronoAnomaly', 'ignore'],
49
- ['compromisedSSHKey', 'ignore'],
50
- ['copyleftLicense', 'ignore'],
51
- ['cve', 'ignore'],
52
- ['debugAccess', 'ignore'],
53
- ['deprecatedLicense', 'ignore'],
54
- ['deprecatedException', 'ignore'],
55
- ['dynamicRequire', 'ignore'],
56
- ['emptyPackage', 'ignore'],
57
- ['envVars', 'ignore'],
58
- ['explicitlyUnlicensedItem', 'ignore'],
59
- ['extraneousDependency', 'ignore'],
60
- ['fileDependency', 'ignore'],
61
- ['filesystemAccess', 'ignore'],
62
- ['floatingDependency', 'ignore'],
63
- ['gitHubDependency', 'ignore'],
64
- ['gptAnomaly', 'ignore'],
65
- ['gptDidYouMean', 'ignore'],
66
- ['gptMalware', 'ignore'],
67
- ['gptSecurity', 'ignore'],
68
- ['hasNativeCode', 'ignore'],
69
- ['highEntropyStrings', 'ignore'],
70
- ['homoglyphs', 'ignore'],
71
- ['installScripts', 'ignore'],
72
- ['invalidPackageJSON', 'ignore'],
73
- ['invisibleChars', 'ignore'],
74
- ['licenseChange', 'ignore'],
75
- ['licenseException', 'ignore'],
76
- ['longStrings', 'ignore'],
77
- ['majorRefactor', 'ignore'],
78
- ['manifestConfusion', 'ignore'],
79
- ['minifiedFile', 'ignore'],
80
- ['miscLicenseIssues', 'ignore'],
81
- ['missingAuthor', 'ignore'],
82
- ['missingDependency', 'ignore'],
83
- ['missingLicense', 'ignore'],
84
- ['missingTarball', 'ignore'],
85
- ['mixedLicense', 'ignore'],
86
- ['modifiedException', 'ignore'],
87
- ['modifiedLicense', 'ignore'],
88
- ['networkAccess', 'ignore'],
89
- ['newAuthor', 'ignore'],
90
- ['noAuthorData', 'ignore'],
91
- ['noBugTracker', 'ignore'],
92
- ['noLicenseFound', 'ignore'],
93
- ['noREADME', 'ignore'],
94
- ['noRepository', 'ignore'],
95
- ['noTests', 'ignore'],
96
- ['noV1', 'ignore'],
97
- ['noWebsite', 'ignore'],
98
- ['nonOSILicense', 'ignore'],
99
- ['nonSPDXLicense', 'ignore'],
100
- ['nonpermissiveLicense', 'ignore'],
101
- ['notice', 'ignore'],
102
- ['obfuscatedRequire', 'ignore'],
103
- ['peerDependency', 'ignore'],
104
- ['potentialVulnerability', 'ignore'],
105
- ['semverAnomaly', 'ignore'],
106
- ['shellAccess', 'ignore'],
107
- ['shellScriptOverride', 'ignore'],
108
- ['socketUpgradeAvailable', 'ignore'],
109
- ['suspiciousStarActivity', 'ignore'],
110
- ['suspiciousString', 'ignore'],
111
- ['trivialPackage', 'ignore'],
112
- ['typeModuleCompatibility', 'ignore'],
113
- ['uncaughtOptionalDependency', 'ignore'],
114
- ['unclearLicense', 'ignore'],
115
- ['unidentifiedLicense', 'ignore'],
116
- ['unmaintained', 'ignore'],
117
- ['unpublished', 'ignore'],
118
- ['unresolvedRequire', 'ignore'],
119
- ['unsafeCopyright', 'ignore'],
120
- ['unusedDependency', 'ignore'],
121
- ['urlStrings', 'ignore'],
122
- ['usesEval', 'ignore'],
123
- ['zeroWidth', 'ignore'],
124
- ]);
18
+ const publicPolicy = new Map([
19
+ // error (1):
20
+ ['malware', 'error'],
21
+ // warn (7):
22
+ ['criticalCVE', 'warn'], ['didYouMean', 'warn'], ['gitDependency', 'warn'], ['httpDependency', 'warn'], ['licenseSpdxDisj', 'warn'], ['obfuscatedFile', 'warn'], ['troll', 'warn'],
23
+ // monitor (7):
24
+ ['deprecated', 'monitor'], ['mediumCVE', 'monitor'], ['mildCVE', 'monitor'], ['shrinkwrap', 'monitor'], ['telemetry', 'monitor'], ['unpopularPackage', 'monitor'], ['unstableOwnership', 'monitor'],
25
+ // ignore (85):
26
+ ['ambiguousClassifier', 'ignore'], ['badEncoding', 'ignore'], ['badSemver', 'ignore'], ['badSemverDependency', 'ignore'], ['bidi', 'ignore'], ['binScriptConfusion', 'ignore'], ['chromeContentScript', 'ignore'], ['chromeHostPermission', 'ignore'], ['chromePermission', 'ignore'], ['chromeWildcardHostPermission', 'ignore'], ['chronoAnomaly', 'ignore'], ['compromisedSSHKey', 'ignore'], ['copyleftLicense', 'ignore'], ['cve', 'ignore'], ['debugAccess', 'ignore'], ['deprecatedLicense', 'ignore'], ['deprecatedException', 'ignore'], ['dynamicRequire', 'ignore'], ['emptyPackage', 'ignore'], ['envVars', 'ignore'], ['explicitlyUnlicensedItem', 'ignore'], ['extraneousDependency', 'ignore'], ['fileDependency', 'ignore'], ['filesystemAccess', 'ignore'], ['floatingDependency', 'ignore'], ['gitHubDependency', 'ignore'], ['gptAnomaly', 'ignore'], ['gptDidYouMean', 'ignore'], ['gptMalware', 'ignore'], ['gptSecurity', 'ignore'], ['hasNativeCode', 'ignore'], ['highEntropyStrings', 'ignore'], ['homoglyphs', 'ignore'], ['installScripts', 'ignore'], ['invalidPackageJSON', 'ignore'], ['invisibleChars', 'ignore'], ['licenseChange', 'ignore'], ['licenseException', 'ignore'], ['longStrings', 'ignore'], ['majorRefactor', 'ignore'], ['manifestConfusion', 'ignore'], ['minifiedFile', 'ignore'], ['miscLicenseIssues', 'ignore'], ['missingAuthor', 'ignore'], ['missingDependency', 'ignore'], ['missingLicense', 'ignore'], ['missingTarball', 'ignore'], ['mixedLicense', 'ignore'], ['modifiedException', 'ignore'], ['modifiedLicense', 'ignore'], ['networkAccess', 'ignore'], ['newAuthor', 'ignore'], ['noAuthorData', 'ignore'], ['noBugTracker', 'ignore'], ['noLicenseFound', 'ignore'], ['noREADME', 'ignore'], ['noRepository', 'ignore'], ['noTests', 'ignore'], ['noV1', 'ignore'], ['noWebsite', 'ignore'], ['nonOSILicense', 'ignore'], ['nonSPDXLicense', 'ignore'], ['nonpermissiveLicense', 'ignore'], ['notice', 'ignore'], ['obfuscatedRequire', 'ignore'], ['peerDependency', 'ignore'], ['potentialVulnerability', 'ignore'], ['semverAnomaly', 'ignore'], ['shellAccess', 'ignore'], ['shellScriptOverride', 'ignore'], ['socketUpgradeAvailable', 'ignore'], ['suspiciousStarActivity', 'ignore'], ['suspiciousString', 'ignore'], ['trivialPackage', 'ignore'], ['typeModuleCompatibility', 'ignore'], ['uncaughtOptionalDependency', 'ignore'], ['unclearLicense', 'ignore'], ['unidentifiedLicense', 'ignore'], ['unmaintained', 'ignore'], ['unpublished', 'ignore'], ['unresolvedRequire', 'ignore'], ['unsafeCopyright', 'ignore'], ['unusedDependency', 'ignore'], ['urlStrings', 'ignore'], ['usesEval', 'ignore'], ['zeroWidth', 'ignore']]);
27
+
28
+ exports.DEFAULT_USER_AGENT = DEFAULT_USER_AGENT;
29
+ exports.httpAgentNames = httpAgentNames;
30
+ exports.publicPolicy = publicPolicy;
@@ -1,49 +1,41 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createRequestBodyForFilepaths = createRequestBodyForFilepaths;
7
- exports.createRequestBodyForJson = createRequestBodyForJson;
8
- exports.createUploadRequest = createUploadRequest;
1
+ 'use strict';
2
+
3
+ var events = require('node:events');
4
+ var node_fs = require('node:fs');
5
+ var path$1 = require('node:path');
6
+ var node_stream = require('node:stream');
7
+ var path = require('@socketsecurity/registry/lib/path');
8
+ var httpClient = require('./http-client.js');
9
+
9
10
  /** @fileoverview File upload utilities for Socket API with multipart form data support. */
10
- const node_events_1 = __importDefault(require("node:events"));
11
- const node_fs_1 = require("node:fs");
12
- const node_path_1 = __importDefault(require("node:path"));
13
- const node_stream_1 = require("node:stream");
14
- const path_1 = require("@socketsecurity/registry/lib/path");
15
- const http_client_1 = require("./http-client");
16
11
  /**
17
12
  * Create multipart form-data body parts for file uploads.
18
13
  * Converts file paths to readable streams with proper multipart headers.
19
14
  */
20
15
  function createRequestBodyForFilepaths(filepaths, basePath) {
21
- const requestBody = [];
22
- for (const absPath of filepaths) {
23
- const relPath = (0, path_1.normalizePath)(node_path_1.default.relative(basePath, absPath));
24
- const filename = node_path_1.default.basename(absPath);
25
- requestBody.push([
26
- `Content-Disposition: form-data; name="${relPath}"; filename="${filename}"\r\n`,
27
- 'Content-Type: application/octet-stream\r\n\r\n',
28
- (0, node_fs_1.createReadStream)(absPath, { highWaterMark: 1024 * 1024 }),
29
- ]);
30
- }
31
- return requestBody;
16
+ const requestBody = [];
17
+ for (const absPath of filepaths) {
18
+ const relPath = path.normalizePath(path$1.relative(basePath, absPath));
19
+ const filename = path$1.basename(absPath);
20
+ requestBody.push([`Content-Disposition: form-data; name="${relPath}"; filename="${filename}"\r\n`, 'Content-Type: application/octet-stream\r\n\r\n', node_fs.createReadStream(absPath, {
21
+ highWaterMark: 1024 * 1024
22
+ })]);
23
+ }
24
+ return requestBody;
32
25
  }
26
+
33
27
  /**
34
28
  * Create multipart form-data body part for JSON data.
35
29
  * Converts JSON object to readable stream with appropriate headers.
36
30
  */
37
31
  function createRequestBodyForJson(jsonData, basename = 'data.json') {
38
- const ext = node_path_1.default.extname(basename);
39
- const name = node_path_1.default.basename(basename, ext);
40
- return [
41
- `Content-Disposition: form-data; name="${name}"; filename="${basename}"\r\n` +
42
- 'Content-Type: application/json\r\n\r\n',
43
- node_stream_1.Readable.from(JSON.stringify(jsonData), { highWaterMark: 1024 * 1024 }),
44
- '\r\n',
45
- ];
32
+ const ext = path$1.extname(basename);
33
+ const name = path$1.basename(basename, ext);
34
+ return [`Content-Disposition: form-data; name="${name}"; filename="${basename}"\r\n` + 'Content-Type: application/json\r\n\r\n', node_stream.Readable.from(JSON.stringify(jsonData), {
35
+ highWaterMark: 1024 * 1024
36
+ }), '\r\n'];
46
37
  }
38
+
47
39
  /**
48
40
  * Create and execute a multipart/form-data upload request.
49
41
  * Streams large files efficiently with backpressure handling and early server validation.
@@ -51,101 +43,100 @@ function createRequestBodyForJson(jsonData, basename = 'data.json') {
51
43
  * @throws {Error} When network errors occur or stream processing fails
52
44
  */
53
45
  async function createUploadRequest(baseUrl, urlPath, requestBodyNoBoundaries, options) {
54
- // This function constructs and sends a multipart/form-data HTTP POST request
55
- // where each part is streamed to the server. It supports string payloads
56
- // and readable streams (e.g., large file uploads).
57
- // The body is streamed manually with proper backpressure support to avoid
58
- // overwhelming Node.js memory (i.e., avoiding out-of-memory crashes for large inputs).
59
- // We call `flushHeaders()` early to ensure headers are sent before body transmission
60
- // begins. If the server rejects the request (e.g., bad org or auth), it will likely
61
- // respond immediately. We listen for that response while still streaming the body.
62
- //
63
- // This protects against cases where the server closes the connection (EPIPE/ECONNRESET)
64
- // mid-stream, which would otherwise cause hard-to-diagnose failures during file upload.
65
- //
66
- // Example failure this mitigates: `socket scan create --org badorg`
67
- // eslint-disable-next-line no-async-promise-executor
68
- return await new Promise(async (pass, fail) => {
69
- const boundary = `NodeMultipartBoundary${Date.now()}`;
70
- const boundarySep = `--${boundary}\r\n`;
71
- const finalBoundary = `--${boundary}--\r\n`;
72
- const requestBody = [
73
- ...requestBodyNoBoundaries.flatMap(part => [
74
- boundarySep,
75
- /* c8 ignore next - Array.isArray branch for part is defensive coding for edge cases. */
76
- ...(Array.isArray(part) ? part : [part]),
77
- ]),
78
- finalBoundary,
79
- ];
80
- const url = new URL(urlPath, baseUrl);
81
- const req = (0, http_client_1.getHttpModule)(baseUrl).request(url, {
82
- method: 'POST',
83
- ...options,
84
- headers: {
85
- ...options?.headers,
86
- 'Content-Type': `multipart/form-data; boundary=${boundary}`,
87
- },
88
- });
89
- // Send headers early to prompt server validation (auth, URL, quota, etc.).
90
- req.flushHeaders();
91
- // Concurrently wait for response while we stream body.
92
- (0, http_client_1.getResponse)(req).then(pass, fail);
93
- let aborted = false;
94
- req.on('error', () => (aborted = true));
95
- req.on('close', () => (aborted = true));
96
- try {
97
- for (const part of requestBody) {
98
- /* c8 ignore next 3 - aborted state is difficult to test reliably */
99
- if (aborted) {
100
- break;
101
- }
102
- if (typeof part === 'string') {
103
- /* c8 ignore next 5 - backpressure handling requires specific stream conditions */
104
- if (!req.write(part)) {
105
- // Wait for 'drain' if backpressure is signaled.
106
- // eslint-disable-next-line no-await-in-loop
107
- await node_events_1.default.once(req, 'drain');
108
- }
109
- }
110
- else if (typeof part?.pipe === 'function') {
111
- // Stream data chunk-by-chunk with backpressure support.
112
- const stream = part;
113
- // eslint-disable-next-line no-await-in-loop
114
- for await (const chunk of stream) {
115
- /* c8 ignore next 3 - aborted state during streaming is difficult to test reliably */
116
- if (aborted) {
117
- break;
118
- }
119
- /* c8 ignore next 3 - backpressure handling requires specific stream conditions */
120
- if (!req.write(chunk)) {
121
- await node_events_1.default.once(req, 'drain');
122
- }
123
- }
124
- // Ensure trailing CRLF after file part.
125
- /* c8 ignore next 4 - trailing CRLF backpressure handling is edge case */
126
- if (!aborted && !req.write('\r\n')) {
127
- // eslint-disable-next-line no-await-in-loop
128
- await node_events_1.default.once(req, 'drain');
129
- }
130
- // Cleanup stream to free memory buffers.
131
- if (typeof part.destroy === 'function') {
132
- part.destroy();
133
- }
134
- /* c8 ignore next 3 - defensive check for non-string/stream types */
135
- }
136
- else {
137
- throw new TypeError('Expected "string" or "stream" type');
138
- }
139
- }
140
- }
141
- catch (e) {
142
- req.destroy(e);
143
- fail(e);
46
+ // This function constructs and sends a multipart/form-data HTTP POST request
47
+ // where each part is streamed to the server. It supports string payloads
48
+ // and readable streams (e.g., large file uploads).
49
+
50
+ // The body is streamed manually with proper backpressure support to avoid
51
+ // overwhelming Node.js memory (i.e., avoiding out-of-memory crashes for large inputs).
52
+
53
+ // We call `flushHeaders()` early to ensure headers are sent before body transmission
54
+ // begins. If the server rejects the request (e.g., bad org or auth), it will likely
55
+ // respond immediately. We listen for that response while still streaming the body.
56
+ //
57
+ // This protects against cases where the server closes the connection (EPIPE/ECONNRESET)
58
+ // mid-stream, which would otherwise cause hard-to-diagnose failures during file upload.
59
+ //
60
+ // Example failure this mitigates: `socket scan create --org badorg`
61
+
62
+ // eslint-disable-next-line no-async-promise-executor
63
+ return await new Promise(async (pass, fail) => {
64
+ const boundary = `NodeMultipartBoundary${Date.now()}`;
65
+ const boundarySep = `--${boundary}\r\n`;
66
+ const finalBoundary = `--${boundary}--\r\n`;
67
+ const requestBody = [...requestBodyNoBoundaries.flatMap(part => [boundarySep, /* c8 ignore next - Array.isArray branch for part is defensive coding for edge cases. */
68
+ ...(Array.isArray(part) ? part : [part])]), finalBoundary];
69
+ const url = new URL(urlPath, baseUrl);
70
+ const req = httpClient.getHttpModule(baseUrl).request(url, {
71
+ method: 'POST',
72
+ ...options,
73
+ headers: {
74
+ ...options?.headers,
75
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`
76
+ }
77
+ });
78
+
79
+ // Send headers early to prompt server validation (auth, URL, quota, etc.).
80
+ req.flushHeaders();
81
+
82
+ // Concurrently wait for response while we stream body.
83
+ httpClient.getResponse(req).then(pass, fail);
84
+ let aborted = false;
85
+ req.on('error', () => aborted = true);
86
+ req.on('close', () => aborted = true);
87
+ try {
88
+ for (const part of requestBody) {
89
+ /* c8 ignore next 3 - aborted state is difficult to test reliably */
90
+ if (aborted) {
91
+ break;
144
92
  }
145
- finally {
146
- if (!aborted) {
147
- req.end();
93
+ if (typeof part === 'string') {
94
+ /* c8 ignore next 5 - backpressure handling requires specific stream conditions */
95
+ if (!req.write(part)) {
96
+ // Wait for 'drain' if backpressure is signaled.
97
+ // eslint-disable-next-line no-await-in-loop
98
+ await events.once(req, 'drain');
99
+ }
100
+ } else if (typeof part?.pipe === 'function') {
101
+ // Stream data chunk-by-chunk with backpressure support.
102
+ const stream = part;
103
+ // eslint-disable-next-line no-await-in-loop
104
+ for await (const chunk of stream) {
105
+ /* c8 ignore next 3 - aborted state during streaming is difficult to test reliably */
106
+ if (aborted) {
107
+ break;
148
108
  }
109
+ /* c8 ignore next 3 - backpressure handling requires specific stream conditions */
110
+ if (!req.write(chunk)) {
111
+ await events.once(req, 'drain');
112
+ }
113
+ }
114
+ // Ensure trailing CRLF after file part.
115
+ /* c8 ignore next 4 - trailing CRLF backpressure handling is edge case */
116
+ if (!aborted && !req.write('\r\n')) {
117
+ // eslint-disable-next-line no-await-in-loop
118
+ await events.once(req, 'drain');
119
+ }
120
+ // Cleanup stream to free memory buffers.
121
+ if (typeof part.destroy === 'function') {
122
+ part.destroy();
123
+ }
124
+ /* c8 ignore next 3 - defensive check for non-string/stream types */
125
+ } else {
126
+ throw new TypeError('Expected "string" or "stream" type');
149
127
  }
150
- });
128
+ }
129
+ } catch (e) {
130
+ req.destroy(e);
131
+ fail(e);
132
+ } finally {
133
+ if (!aborted) {
134
+ req.end();
135
+ }
136
+ }
137
+ });
151
138
  }
139
+
140
+ exports.createRequestBodyForFilepaths = createRequestBodyForFilepaths;
141
+ exports.createRequestBodyForJson = createRequestBodyForJson;
142
+ exports.createUploadRequest = createUploadRequest;
@@ -28,6 +28,7 @@ export declare function createDeleteRequest(baseUrl: string, urlPath: string, op
28
28
  /**
29
29
  * Create and execute an HTTP GET request.
30
30
  * Returns the response stream for further processing.
31
+ * Performance tracking enabled with DEBUG=perf.
31
32
  *
32
33
  * @throws {Error} When network or timeout errors occur
33
34
  */
@@ -35,6 +36,7 @@ export declare function createGetRequest(baseUrl: string, urlPath: string, optio
35
36
  /**
36
37
  * Create and execute an HTTP request with JSON payload.
37
38
  * Automatically sets appropriate content headers and serializes the body.
39
+ * Performance tracking enabled with DEBUG=perf.
38
40
  *
39
41
  * @throws {Error} When network or timeout errors occur
40
42
  */
@@ -61,6 +63,7 @@ export declare function getResponse(req: ClientRequest): Promise<IncomingMessage
61
63
  /**
62
64
  * Parse HTTP response body as JSON.
63
65
  * Validates response status and handles empty responses gracefully.
66
+ * Performance tracking enabled with DEBUG=perf.
64
67
  *
65
68
  * @throws {ResponseError} When response has non-2xx status code
66
69
  * @throws {SyntaxError} When response body contains invalid JSON