@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/CHANGELOG.md +18 -0
- package/README.md +153 -312
- package/dist/constants.js +23 -117
- package/dist/file-upload.js +118 -127
- package/dist/http-client.d.ts +3 -0
- package/dist/http-client.js +269 -211
- package/dist/index.js +43 -48
- package/dist/package.json.js +207 -0
- package/dist/quota-utils.d.ts +13 -6
- package/dist/quota-utils.js +128 -105
- package/dist/socket-sdk-class.js +1488 -1430
- package/dist/testing.d.ts +453 -0
- package/dist/testing.js +387 -0
- package/dist/user-agent.js +11 -7
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +68 -60
- package/package.json +47 -31
- package/requirements.json +2 -2
- package/types/api-helpers.d.ts +5 -0
- package/dist/promise-queue.js +0 -91
- package/dist/types.js +0 -3
package/dist/http-client.js
CHANGED
|
@@ -1,53 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
exports.createRequestWithJson = createRequestWithJson;
|
|
10
|
-
exports.getErrorResponseBody = getErrorResponseBody;
|
|
11
|
-
exports.getHttpModule = getHttpModule;
|
|
12
|
-
exports.getResponse = getResponse;
|
|
13
|
-
exports.getResponseJson = getResponseJson;
|
|
14
|
-
exports.isResponseOk = isResponseOk;
|
|
15
|
-
exports.reshapeArtifactForPublicPolicy = reshapeArtifactForPublicPolicy;
|
|
16
|
-
exports.withRetry = withRetry;
|
|
17
|
-
exports.createGetRequestWithRetry = createGetRequestWithRetry;
|
|
18
|
-
exports.createDeleteRequestWithRetry = createDeleteRequestWithRetry;
|
|
19
|
-
exports.createRequestWithJsonAndRetry = createRequestWithJsonAndRetry;
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var http = require('node:http');
|
|
4
|
+
var https = require('node:https');
|
|
5
|
+
var debug = require('@socketsecurity/registry/lib/debug');
|
|
6
|
+
var json = require('@socketsecurity/registry/lib/json');
|
|
7
|
+
var performance = require('@socketsecurity/registry/lib/performance');
|
|
8
|
+
|
|
20
9
|
/**
|
|
21
10
|
* @fileoverview HTTP client utilities for Socket API communication.
|
|
22
11
|
* Provides low-level HTTP request handling with proper error management and response parsing.
|
|
23
12
|
*/
|
|
24
|
-
|
|
25
|
-
const node_https_1 = __importDefault(require("node:https"));
|
|
26
|
-
const debug_1 = require("@socketsecurity/registry/lib/debug");
|
|
27
|
-
const json_1 = require("@socketsecurity/registry/lib/json");
|
|
13
|
+
|
|
28
14
|
/**
|
|
29
15
|
* HTTP response error for Socket API requests.
|
|
30
16
|
* Extends Error with response details for debugging failed API calls.
|
|
31
17
|
*/
|
|
32
18
|
class ResponseError extends Error {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Error.captureStackTrace(this, ResponseError);
|
|
48
|
-
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a new ResponseError from an HTTP response.
|
|
21
|
+
* Automatically formats error message with status code and message.
|
|
22
|
+
*/
|
|
23
|
+
constructor(response, message = '') {
|
|
24
|
+
/* c8 ignore next 2 - statusCode and statusMessage may be undefined in edge cases */
|
|
25
|
+
const statusCode = response.statusCode ?? 'unknown';
|
|
26
|
+
const statusMessage = response.statusMessage ?? 'No status message';
|
|
27
|
+
super(/* c8 ignore next - fallback empty message if not provided */
|
|
28
|
+
`Socket API ${message || 'Request failed'} (${statusCode}): ${statusMessage}`);
|
|
29
|
+
this.name = 'ResponseError';
|
|
30
|
+
this.response = response;
|
|
31
|
+
Error.captureStackTrace(this, ResponseError);
|
|
32
|
+
}
|
|
49
33
|
}
|
|
50
|
-
|
|
34
|
+
|
|
51
35
|
/**
|
|
52
36
|
* Create and execute an HTTP DELETE request.
|
|
53
37
|
* Returns the response stream for further processing.
|
|
@@ -55,50 +39,79 @@ exports.ResponseError = ResponseError;
|
|
|
55
39
|
* @throws {Error} When network or timeout errors occur
|
|
56
40
|
*/
|
|
57
41
|
async function createDeleteRequest(baseUrl, urlPath, options) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.end();
|
|
64
|
-
return await getResponse(req);
|
|
42
|
+
const req = getHttpModule(baseUrl).request(`${baseUrl}${urlPath}`, {
|
|
43
|
+
method: 'DELETE',
|
|
44
|
+
...options
|
|
45
|
+
}).end();
|
|
46
|
+
return await getResponse(req);
|
|
65
47
|
}
|
|
48
|
+
|
|
66
49
|
/**
|
|
67
50
|
* Create and execute an HTTP GET request.
|
|
68
51
|
* Returns the response stream for further processing.
|
|
52
|
+
* Performance tracking enabled with DEBUG=perf.
|
|
69
53
|
*
|
|
70
54
|
* @throws {Error} When network or timeout errors occur
|
|
71
55
|
*/
|
|
72
56
|
async function createGetRequest(baseUrl, urlPath, options) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
57
|
+
const stopTimer = performance.perfTimer('http:get', {
|
|
58
|
+
urlPath
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
const req = getHttpModule(baseUrl).request(`${baseUrl}${urlPath}`, {
|
|
62
|
+
method: 'GET',
|
|
63
|
+
...options
|
|
64
|
+
}).end();
|
|
65
|
+
const response = await getResponse(req);
|
|
66
|
+
stopTimer({
|
|
67
|
+
statusCode: response.statusCode
|
|
68
|
+
});
|
|
69
|
+
return response;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
stopTimer({
|
|
72
|
+
error: true
|
|
73
|
+
});
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
80
76
|
}
|
|
77
|
+
|
|
81
78
|
/**
|
|
82
79
|
* Create and execute an HTTP request with JSON payload.
|
|
83
80
|
* Automatically sets appropriate content headers and serializes the body.
|
|
81
|
+
* Performance tracking enabled with DEBUG=perf.
|
|
84
82
|
*
|
|
85
83
|
* @throws {Error} When network or timeout errors occur
|
|
86
84
|
*/
|
|
87
85
|
async function createRequestWithJson(method, baseUrl, urlPath, json, options) {
|
|
86
|
+
const stopTimer = performance.perfTimer(`http:${method.toLowerCase()}`, {
|
|
87
|
+
urlPath
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
88
90
|
const body = JSON.stringify(json);
|
|
89
91
|
const req = getHttpModule(baseUrl).request(`${baseUrl}${urlPath}`, {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
method,
|
|
93
|
+
...options,
|
|
94
|
+
headers: {
|
|
95
|
+
...options.headers,
|
|
96
|
+
'Content-Length': Buffer.byteLength(body, 'utf8'),
|
|
97
|
+
'Content-Type': 'application/json'
|
|
98
|
+
}
|
|
97
99
|
});
|
|
98
100
|
req.write(body);
|
|
99
101
|
req.end();
|
|
100
|
-
|
|
102
|
+
const response = await getResponse(req);
|
|
103
|
+
stopTimer({
|
|
104
|
+
statusCode: response.statusCode
|
|
105
|
+
});
|
|
106
|
+
return response;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
stopTimer({
|
|
109
|
+
error: true
|
|
110
|
+
});
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
101
113
|
}
|
|
114
|
+
|
|
102
115
|
/**
|
|
103
116
|
* Read the response body from an HTTP error response.
|
|
104
117
|
* Accumulates all chunks into a complete string for error handling.
|
|
@@ -106,22 +119,24 @@ async function createRequestWithJson(method, baseUrl, urlPath, json, options) {
|
|
|
106
119
|
* @throws {Error} When stream errors occur during reading
|
|
107
120
|
*/
|
|
108
121
|
async function getErrorResponseBody(response) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
return await new Promise((resolve, reject) => {
|
|
123
|
+
let body = '';
|
|
124
|
+
response.setEncoding('utf8');
|
|
125
|
+
response.on('data', chunk => body += chunk);
|
|
126
|
+
response.on('end', () => resolve(body));
|
|
127
|
+
/* c8 ignore next - Extremely rare network or stream error during error response reading. */
|
|
128
|
+
response.on('error', e => reject(e));
|
|
129
|
+
});
|
|
117
130
|
}
|
|
131
|
+
|
|
118
132
|
/**
|
|
119
133
|
* Get the appropriate HTTP module based on URL protocol.
|
|
120
134
|
* Returns http module for http: URLs, https module for https: URLs.
|
|
121
135
|
*/
|
|
122
136
|
function getHttpModule(url) {
|
|
123
|
-
|
|
137
|
+
return url.startsWith('https:') ? https : http;
|
|
124
138
|
}
|
|
139
|
+
|
|
125
140
|
/**
|
|
126
141
|
* Wait for and return the HTTP response from a request.
|
|
127
142
|
* Handles timeout and error conditions during request processing.
|
|
@@ -129,145 +144,166 @@ function getHttpModule(url) {
|
|
|
129
144
|
* @throws {Error} When request times out or network errors occur
|
|
130
145
|
*/
|
|
131
146
|
async function getResponse(req) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
152
|
-
/* c8 ignore stop */
|
|
147
|
+
return await new Promise((resolve, reject) => {
|
|
148
|
+
let timedOut = false;
|
|
149
|
+
req.on('response', response => {
|
|
150
|
+
/* c8 ignore next 3 - Race condition where response arrives after timeout. */
|
|
151
|
+
if (timedOut) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
resolve(response);
|
|
155
|
+
});
|
|
156
|
+
req.on('timeout', () => {
|
|
157
|
+
timedOut = true;
|
|
158
|
+
req.destroy();
|
|
159
|
+
reject(new Error('Request timed out'));
|
|
160
|
+
});
|
|
161
|
+
/* c8 ignore start - Network error handling during request, difficult to test reliably. */
|
|
162
|
+
req.on('error', e => {
|
|
163
|
+
if (!timedOut) {
|
|
164
|
+
reject(e);
|
|
165
|
+
}
|
|
153
166
|
});
|
|
167
|
+
/* c8 ignore stop */
|
|
168
|
+
});
|
|
154
169
|
}
|
|
170
|
+
|
|
155
171
|
/**
|
|
156
172
|
* Parse HTTP response body as JSON.
|
|
157
173
|
* Validates response status and handles empty responses gracefully.
|
|
174
|
+
* Performance tracking enabled with DEBUG=perf.
|
|
158
175
|
*
|
|
159
176
|
* @throws {ResponseError} When response has non-2xx status code
|
|
160
177
|
* @throws {SyntaxError} When response body contains invalid JSON
|
|
161
178
|
*/
|
|
162
179
|
async function getResponseJson(response, method) {
|
|
180
|
+
const stopTimer = performance.perfTimer('http:parse-json');
|
|
181
|
+
try {
|
|
163
182
|
if (!isResponseOk(response)) {
|
|
164
|
-
|
|
183
|
+
throw new ResponseError(response, method ? `${method} Request failed` : undefined);
|
|
165
184
|
}
|
|
166
185
|
const responseBody = await getErrorResponseBody(response);
|
|
186
|
+
|
|
167
187
|
// Handle truly empty responses (not whitespace) as valid empty objects.
|
|
168
188
|
if (responseBody === '') {
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
debug.debugLog('API response: empty response treated as {}');
|
|
190
|
+
stopTimer({
|
|
191
|
+
success: true
|
|
192
|
+
});
|
|
193
|
+
return {};
|
|
171
194
|
}
|
|
172
195
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (e instanceof Error) {
|
|
188
|
-
throw e;
|
|
189
|
-
}
|
|
190
|
-
// Handle non-Error objects thrown by JSON parsing.
|
|
191
|
-
const unknownError = new Error('Unknown JSON parsing error', {
|
|
192
|
-
cause: e,
|
|
196
|
+
const responseJson = json.jsonParse(responseBody);
|
|
197
|
+
debug.debugLog('API response:', responseJson);
|
|
198
|
+
stopTimer({
|
|
199
|
+
success: true
|
|
200
|
+
});
|
|
201
|
+
return responseJson;
|
|
202
|
+
} catch (e) {
|
|
203
|
+
stopTimer({
|
|
204
|
+
error: true
|
|
205
|
+
});
|
|
206
|
+
if (e instanceof SyntaxError) {
|
|
207
|
+
// Attach the original response text for better error reporting.
|
|
208
|
+
const enhancedError = new Error(`Socket API - Invalid JSON response:\n${responseBody}\n→ ${e.message}`, {
|
|
209
|
+
cause: e
|
|
193
210
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Object.setPrototypeOf(
|
|
197
|
-
throw
|
|
198
|
-
|
|
211
|
+
enhancedError.name = 'SyntaxError';
|
|
212
|
+
enhancedError.originalResponse = responseBody;
|
|
213
|
+
Object.setPrototypeOf(enhancedError, SyntaxError.prototype);
|
|
214
|
+
throw enhancedError;
|
|
215
|
+
}
|
|
216
|
+
/* c8 ignore start - Error instanceof check and unknown error handling for JSON parsing edge cases. */
|
|
217
|
+
if (e instanceof Error) {
|
|
218
|
+
throw e;
|
|
219
|
+
}
|
|
220
|
+
// Handle non-Error objects thrown by JSON parsing.
|
|
221
|
+
const unknownError = new Error('Unknown JSON parsing error', {
|
|
222
|
+
cause: e
|
|
223
|
+
});
|
|
224
|
+
unknownError.name = 'SyntaxError';
|
|
225
|
+
unknownError.originalResponse = responseBody;
|
|
226
|
+
Object.setPrototypeOf(unknownError, SyntaxError.prototype);
|
|
227
|
+
throw unknownError;
|
|
228
|
+
/* c8 ignore stop */
|
|
199
229
|
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
stopTimer({
|
|
232
|
+
error: true
|
|
233
|
+
});
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
200
236
|
}
|
|
237
|
+
|
|
201
238
|
/**
|
|
202
239
|
* Check if HTTP response has a successful status code (2xx range).
|
|
203
240
|
* Returns true for status codes between 200-299, false otherwise.
|
|
204
241
|
*/
|
|
205
242
|
function isResponseOk(response) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
243
|
+
const {
|
|
244
|
+
statusCode
|
|
245
|
+
} = response;
|
|
246
|
+
/* c8 ignore next - Defensive fallback for edge cases where statusCode might be undefined. */
|
|
247
|
+
return statusCode ? statusCode >= 200 && statusCode < 300 : false;
|
|
209
248
|
}
|
|
249
|
+
|
|
210
250
|
/**
|
|
211
251
|
* Transform artifact data based on authentication status.
|
|
212
252
|
* Filters and compacts response data for public/free-tier users.
|
|
213
253
|
*/
|
|
214
254
|
function reshapeArtifactForPublicPolicy(data, isAuthenticated, actions) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
// Filter by actions if specified.
|
|
239
|
-
if (allowedActions &&
|
|
240
|
-
alert.action &&
|
|
241
|
-
!allowedActions.includes(alert.action)) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
return true;
|
|
245
|
-
})
|
|
246
|
-
.map((alert) => ({
|
|
247
|
-
type: alert.type,
|
|
248
|
-
severity: alert.severity,
|
|
249
|
-
key: alert.key,
|
|
250
|
-
})),
|
|
251
|
-
});
|
|
252
|
-
// Handle both single artifacts and objects with artifacts arrays.
|
|
253
|
-
if (data['artifacts']) {
|
|
254
|
-
// Object with artifacts array.
|
|
255
|
-
const artifacts = data['artifacts'];
|
|
256
|
-
return {
|
|
257
|
-
...data,
|
|
258
|
-
artifacts: Array.isArray(artifacts)
|
|
259
|
-
? artifacts.map(reshapeArtifact)
|
|
260
|
-
: artifacts,
|
|
261
|
-
};
|
|
255
|
+
/* c8 ignore start - Public policy artifact reshaping for unauthenticated users, difficult to test edge cases. */
|
|
256
|
+
// If user is not authenticated, provide a different response structure
|
|
257
|
+
// optimized for the public free-tier experience.
|
|
258
|
+
if (!isAuthenticated) {
|
|
259
|
+
// Parse actions parameter for alert filtering.
|
|
260
|
+
const allowedActions = actions ? actions.split(',') : undefined;
|
|
261
|
+
const reshapeArtifact = artifact => ({
|
|
262
|
+
name: artifact.name,
|
|
263
|
+
version: artifact.version,
|
|
264
|
+
size: artifact.size,
|
|
265
|
+
author: artifact.author,
|
|
266
|
+
type: artifact.type,
|
|
267
|
+
supplyChainRisk: artifact.supplyChainRisk,
|
|
268
|
+
scorecards: artifact.scorecards,
|
|
269
|
+
topLevelAncestors: artifact.topLevelAncestors,
|
|
270
|
+
// Compact the alerts array to reduce response size for non-authenticated
|
|
271
|
+
// requests.
|
|
272
|
+
alerts: artifact.alerts?.filter(alert => {
|
|
273
|
+
// Filter by severity (remove low severity alerts).
|
|
274
|
+
if (alert.severity === 'low') {
|
|
275
|
+
return false;
|
|
262
276
|
}
|
|
263
|
-
if
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
// Filter by actions if specified.
|
|
278
|
+
if (allowedActions && alert.action && !allowedActions.includes(alert.action)) {
|
|
279
|
+
return false;
|
|
266
280
|
}
|
|
281
|
+
return true;
|
|
282
|
+
}).map(alert => ({
|
|
283
|
+
type: alert.type,
|
|
284
|
+
severity: alert.severity,
|
|
285
|
+
key: alert.key
|
|
286
|
+
}))
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Handle both single artifacts and objects with artifacts arrays.
|
|
290
|
+
if (data['artifacts']) {
|
|
291
|
+
// Object with artifacts array.
|
|
292
|
+
const artifacts = data['artifacts'];
|
|
293
|
+
return {
|
|
294
|
+
...data,
|
|
295
|
+
artifacts: Array.isArray(artifacts) ? artifacts.map(reshapeArtifact) : artifacts
|
|
296
|
+
};
|
|
267
297
|
}
|
|
268
|
-
|
|
269
|
-
|
|
298
|
+
if (data['alerts']) {
|
|
299
|
+
// Single artifact with alerts.
|
|
300
|
+
return reshapeArtifact(data);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return data;
|
|
304
|
+
/* c8 ignore stop */
|
|
270
305
|
}
|
|
306
|
+
|
|
271
307
|
/**
|
|
272
308
|
* Retry helper for HTTP requests with exponential backoff.
|
|
273
309
|
* Wraps any async HTTP function and retries on failure.
|
|
@@ -279,42 +315,47 @@ function reshapeArtifactForPublicPolicy(data, isAuthenticated, actions) {
|
|
|
279
315
|
* @throws {Error} Last error if all retries exhausted
|
|
280
316
|
*/
|
|
281
317
|
async function withRetry(fn, retries = 0, retryDelay = 100) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
else {
|
|
305
|
-
(0, debug_1.debugLog)('withRetry', `Retrying after network error (attempt ${attempt + 1}/${retries + 1})`);
|
|
306
|
-
}
|
|
307
|
-
// Exponential backoff.
|
|
308
|
-
const delayMs = retryDelay * 2 ** attempt;
|
|
309
|
-
(0, debug_1.debugLog)('withRetry', `Waiting ${delayMs}ms before retry`);
|
|
310
|
-
// eslint-disable-next-line no-await-in-loop
|
|
311
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
318
|
+
let lastError;
|
|
319
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
320
|
+
try {
|
|
321
|
+
// eslint-disable-next-line no-await-in-loop
|
|
322
|
+
return await fn();
|
|
323
|
+
} catch (error) {
|
|
324
|
+
lastError = error;
|
|
325
|
+
|
|
326
|
+
// Last attempt - throw error with retry context.
|
|
327
|
+
if (attempt === retries) {
|
|
328
|
+
const enhancedError = new Error(`Request failed after ${retries + 1} attempts`, {
|
|
329
|
+
cause: lastError
|
|
330
|
+
});
|
|
331
|
+
throw enhancedError;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Check if error is retryable (network errors, 5xx responses).
|
|
335
|
+
if (error instanceof ResponseError) {
|
|
336
|
+
const status = error.response.statusCode;
|
|
337
|
+
// Don't retry client errors (4xx).
|
|
338
|
+
if (status && status >= 400 && status < 500) {
|
|
339
|
+
throw error;
|
|
312
340
|
}
|
|
341
|
+
debug.debugLog('withRetry', `Retrying after ${status} error (attempt ${attempt + 1}/${retries + 1})`);
|
|
342
|
+
} else {
|
|
343
|
+
debug.debugLog('withRetry', `Retrying after network error (attempt ${attempt + 1}/${retries + 1})`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Exponential backoff.
|
|
347
|
+
const delayMs = retryDelay * 2 ** attempt;
|
|
348
|
+
debug.debugLog('withRetry', `Waiting ${delayMs}ms before retry`);
|
|
349
|
+
// eslint-disable-next-line no-await-in-loop
|
|
350
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
313
351
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Fallback error if lastError is somehow undefined.
|
|
355
|
+
/* c8 ignore next - Defensive fallback for undefined lastError */
|
|
356
|
+
throw lastError || new Error('Request failed after retries');
|
|
317
357
|
}
|
|
358
|
+
|
|
318
359
|
/**
|
|
319
360
|
* Create GET request with automatic retry logic.
|
|
320
361
|
* Retries on network errors and 5xx responses.
|
|
@@ -323,8 +364,9 @@ async function withRetry(fn, retries = 0, retryDelay = 100) {
|
|
|
323
364
|
* @param retryDelay - Initial delay in ms (default: 100)
|
|
324
365
|
*/
|
|
325
366
|
async function createGetRequestWithRetry(baseUrl, urlPath, options, retries = 0, retryDelay = 100) {
|
|
326
|
-
|
|
367
|
+
return await withRetry(() => createGetRequest(baseUrl, urlPath, options), retries, retryDelay);
|
|
327
368
|
}
|
|
369
|
+
|
|
328
370
|
/**
|
|
329
371
|
* Create DELETE request with automatic retry logic.
|
|
330
372
|
* Retries on network errors and 5xx responses.
|
|
@@ -333,8 +375,9 @@ async function createGetRequestWithRetry(baseUrl, urlPath, options, retries = 0,
|
|
|
333
375
|
* @param retryDelay - Initial delay in ms (default: 100)
|
|
334
376
|
*/
|
|
335
377
|
async function createDeleteRequestWithRetry(baseUrl, urlPath, options, retries = 0, retryDelay = 100) {
|
|
336
|
-
|
|
378
|
+
return await withRetry(() => createDeleteRequest(baseUrl, urlPath, options), retries, retryDelay);
|
|
337
379
|
}
|
|
380
|
+
|
|
338
381
|
/**
|
|
339
382
|
* Create request with JSON payload and automatic retry logic.
|
|
340
383
|
* Retries on network errors and 5xx responses.
|
|
@@ -343,5 +386,20 @@ async function createDeleteRequestWithRetry(baseUrl, urlPath, options, retries =
|
|
|
343
386
|
* @param retryDelay - Initial delay in ms (default: 100)
|
|
344
387
|
*/
|
|
345
388
|
async function createRequestWithJsonAndRetry(method, baseUrl, urlPath, json, options, retries = 0, retryDelay = 100) {
|
|
346
|
-
|
|
389
|
+
return await withRetry(() => createRequestWithJson(method, baseUrl, urlPath, json, options), retries, retryDelay);
|
|
347
390
|
}
|
|
391
|
+
|
|
392
|
+
exports.ResponseError = ResponseError;
|
|
393
|
+
exports.createDeleteRequest = createDeleteRequest;
|
|
394
|
+
exports.createDeleteRequestWithRetry = createDeleteRequestWithRetry;
|
|
395
|
+
exports.createGetRequest = createGetRequest;
|
|
396
|
+
exports.createGetRequestWithRetry = createGetRequestWithRetry;
|
|
397
|
+
exports.createRequestWithJson = createRequestWithJson;
|
|
398
|
+
exports.createRequestWithJsonAndRetry = createRequestWithJsonAndRetry;
|
|
399
|
+
exports.getErrorResponseBody = getErrorResponseBody;
|
|
400
|
+
exports.getHttpModule = getHttpModule;
|
|
401
|
+
exports.getResponse = getResponse;
|
|
402
|
+
exports.getResponseJson = getResponseJson;
|
|
403
|
+
exports.isResponseOk = isResponseOk;
|
|
404
|
+
exports.reshapeArtifactForPublicPolicy = reshapeArtifactForPublicPolicy;
|
|
405
|
+
exports.withRetry = withRetry;
|