@jupiterone/integration-sdk-http-client 12.1.0 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ import { Response } from 'node-fetch';
2
+ import { IntegrationLogger } from '@jupiterone/integration-sdk-core';
3
+ import { AttemptContext } from '@lifeomic/attempt';
4
+ import { ClientConfig, OptionalPromise, RetryOptions, RequestOptions, RateLimitThrottlingOptions, IterateCallbackResult } from './types';
5
+ import { HierarchicalTokenBucket } from '@jupiterone/hierarchical-token-bucket';
6
+ export declare const defaultErrorHandler: (err: any, context: AttemptContext, logger: IntegrationLogger) => Promise<void>;
7
+ export declare abstract class BaseAPIClient {
8
+ protected baseUrl: string;
9
+ protected logger: IntegrationLogger;
10
+ protected retryOptions: RetryOptions;
11
+ protected logErrorBody: boolean;
12
+ protected rateLimitThrottling: RateLimitThrottlingOptions | undefined;
13
+ protected baseTokenBucket?: HierarchicalTokenBucket;
14
+ protected endpointTokenBuckets: Record<string, HierarchicalTokenBucket>;
15
+ /**
16
+ * The authorization headers for the API requests
17
+ */
18
+ protected authorizationHeaders: Record<string, string>;
19
+ /**
20
+ * Create a new API client
21
+ *
22
+ * @param {ClientConfig} config - The configuration for the client
23
+ * @param {string} config.baseUrl - The base URL for the API
24
+ * @param {IntegrationLogger} config.logger - The logger to use
25
+ * @param {Partial<RetryOptions>} [config.retryOptions] - The retry options for the client,
26
+ * if not provided, the default retry options will be used
27
+ * @param {boolean} [config.logErrorBody] - Whether to log the error body,
28
+ * if true, the error body will be logged when a request fails
29
+ * @param {RateLimitThrottlingOptions} [config.rateLimitThrottling] - The rate limit throttling options,
30
+ * if not provided, rate limit throttling will not be enabled
31
+ * @param {number} [config.rateLimitThrottling.threshold] - The threshold at which to throttle requests
32
+ * @param {RateLimitHeaders} [config.rateLimitThrottling.rateLimitHeaders] - The headers to use for rate limiting
33
+ * @param {string} [config.rateLimitThrottling.rateLimitHeaders.limit='x-rate-limit-limit'] - The header for the rate limit limit
34
+ * @param {string} [config.rateLimitThrottling.rateLimitHeaders.remaining='x-rate-limit-remaining'] - The header for the rate limit remaining
35
+ * @param {string} [config.rateLimitThrottling.rateLimitHeaders.reset='x-rate-limit-reset'] - The header for the rate limit reset
36
+ * @param {TokenBucketOptions} [config.tokenBucket] - The token bucket options,
37
+ * if not provided, token bucket will not be enabled
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const client = new APIClient({
42
+ * baseUrl: 'https://api.example.com',
43
+ * logger: context.logger,
44
+ * rateLimitThrottling: {
45
+ * threshold: 0.3,
46
+ * rateLimitHeaders: {
47
+ * limit: 'x-ratelimit-limit',
48
+ * remaining: 'x-ratelimit-remaining',
49
+ * reset: 'x-ratelimit-reset',
50
+ * },
51
+ * },
52
+ * })
53
+ * ```
54
+ */
55
+ constructor(config: ClientConfig);
56
+ protected withBaseUrl(endpoint: string): string;
57
+ /**
58
+ * Get the authorization headers for the API requests.
59
+ *
60
+ * @return {Promise<Record<string, string>>} - The authorization headers
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * async getAuthorizationHeaders(): Record<string, string> {
65
+ * return {
66
+ * Authorization: `Bearer ${this.config.apiKey}`,
67
+ * };
68
+ * }
69
+ * ```
70
+ * @example
71
+ * ```typescript
72
+ * async getAuthorizationHeaders(): Promise<Record<string, string>> {
73
+ * const response = this.request('/token', {
74
+ * method: 'POST',
75
+ * body: {
76
+ * email: this.config.email,
77
+ * password: this.config.password,
78
+ * },
79
+ * authorize: false, // don't try to do authorization on this request, it will go into an infinite loop.
80
+ * });
81
+ * const data = await response.json();
82
+ * return {
83
+ * Authorization: `Bearer ${data.token}`,
84
+ * };
85
+ * }
86
+ */
87
+ abstract getAuthorizationHeaders(): OptionalPromise<Record<string, string>>;
88
+ /**
89
+ * Perform a request to the API.
90
+ *
91
+ * @param {string} endpoint - The endpoint to request
92
+ * @param {RequestOptions} options - The options for the request
93
+ * @param {string} [options.method=GET] - The HTTP method to use
94
+ * @param {Record<string, unknown>} [options.body] - The body of the request
95
+ * @param {Record<string, string>} [options.headers] - The headers for the request
96
+ * @param {boolean} [options.authorize=true] - Whether to include authorization headers
97
+ * @return {Promise<Response>} - The response from the API
98
+ */
99
+ protected request(endpoint: string, options?: RequestOptions): Promise<Response>;
100
+ /**
101
+ * Perform a request to the API with retries and error handling.
102
+ *
103
+ * @param {string} endpoint - The endpoint path to request
104
+ * @param {RequestOptions} options - The options for the request
105
+ * @param {string} [options.method=GET] - The HTTP method to use
106
+ * @param {Record<string, unknown>} [options.body] - The body of the request
107
+ * @param {Record<string, string>} [options.headers] - The headers for the request
108
+ * @param {boolean} [options.authorize=true] - Whether to include authorization headers
109
+ * @return {Promise<Response>} - The response from the API
110
+ */
111
+ protected retryableRequest(endpoint: string, options?: RequestOptions): Promise<Response>;
112
+ /**
113
+ * Iteratively performs API requests based on the initial request and subsequent requests defined by a callback function.
114
+ * This method is designed to facilitate paginated API requests where each request's response determines the parameters of the next request.
115
+ *
116
+ * @param {object} initialRequest - The initial request parameters
117
+ * @param {string} initialRequest.endpoint - The endpoint path to request
118
+ * @param {RequestOptions} [initialRequest.options] - The options for the request
119
+ * @param {string|string[]|undefined} dataPath - The path to the data in the response to iterate over
120
+ * @param {function} nextPageCallback - The callback function to determine the parameters of the next request
121
+ *
122
+ * @return {AsyncGenerator<T, void, unknown>} - An async generator that yields the items from the paginated requests
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * async iterateUsers(iteratee: (user: User) => Promise<void>): Promise<void>{
127
+ * const baseEndpoint = '/users?limit=100';
128
+ * const iterator = await paginate({
129
+ * endpoint: baseEndpoint,
130
+ * }, 'users', this.getNextPageCallback(baseEndpoint));
131
+ *
132
+ * for await (const user of iterator) {
133
+ * await iteratee(user);
134
+ * }
135
+ * };
136
+ *
137
+ * async getNextPageCallback(baseEndpoint: string) {
138
+ * return async (response: Response): Promise<IterateCallbackResult | undefined> => {
139
+ * const data = await response.json();
140
+ * const nextCursor = data.metadata?.cursor;
141
+ * if (!nextCursor) {
142
+ * return;
143
+ * }
144
+ * const nextUrl = `${baseEndpoint}&cursor=${nextCursor}`;
145
+ * return {
146
+ * nextUrl,
147
+ * };
148
+ * }
149
+ * };
150
+ * ```
151
+ */
152
+ protected paginate<T>(initialRequest: {
153
+ endpoint: string;
154
+ options?: RequestOptions;
155
+ }, dataPath: string | string[] | undefined, nextPageCallback: (response: Response) => OptionalPromise<IterateCallbackResult | undefined>): AsyncGenerator<T, void, unknown>;
156
+ /**
157
+ * Get the token bucket for the given endpoint
158
+ *
159
+ * @param {string} endpoint - The endpoint to get the token bucket for
160
+ * @param {number} [tokens] - The number of tokens to use for the token bucket
161
+ */
162
+ private getTokenBucket;
163
+ /**
164
+ * Wait until the rate limit reset time before sending the next request
165
+ * if the rate limit threshold is exceeded.
166
+ *
167
+ * @param {function} fn - The function to rate limit
168
+ * @return {Promise<Response>}
169
+ */
170
+ protected withRateLimiting(fn: () => Promise<Response>): Promise<Response>;
171
+ private parseRateLimitHeaders;
172
+ /**
173
+ * Determine if the next request should be throttled based on the rate limit headers
174
+ *
175
+ * @param {object} params - The parameters for the function
176
+ * @param {number|undefined} params.rateLimitLimit - The rate limit limit
177
+ * @param {number|undefined} params.rateLimitRemaining - The rate limit remaining
178
+ * @param {number} params.threshold - The threshold at which to throttle requests
179
+ * @return {boolean} - Whether the next request should be throttled
180
+ */
181
+ private shouldThrottleNextRequest;
182
+ /**
183
+ * Determine wait time by getting the delta X-Rate-Limit-Reset and the Date header
184
+ * Add 1 second to account for sub second differences between the clocks that create these headers
185
+ */
186
+ private getRetryDelayMs;
187
+ private getRateLimitHeaderValue;
188
+ }
@@ -0,0 +1,358 @@
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.BaseAPIClient = exports.defaultErrorHandler = void 0;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ const attempt_1 = require("@lifeomic/attempt");
9
+ const errors_1 = require("./errors");
10
+ const get_1 = __importDefault(require("lodash/get"));
11
+ const hierarchical_token_bucket_1 = require("@jupiterone/hierarchical-token-bucket");
12
+ const defaultErrorHandler = async (err, context, logger) => {
13
+ if (err.code === 'ECONNRESET' || err.message.includes('ECONNRESET')) {
14
+ return;
15
+ }
16
+ if (!err.retryable) {
17
+ // can't retry this? just abort
18
+ context.abort();
19
+ return;
20
+ }
21
+ if (err.status === 429) {
22
+ const retryAfter = err.retryAfter ? err.retryAfter * 1000 : 5000;
23
+ logger.warn({ retryAfter }, 'Received a rate limit error. Waiting before retrying.');
24
+ await (0, attempt_1.sleep)(retryAfter);
25
+ }
26
+ };
27
+ exports.defaultErrorHandler = defaultErrorHandler;
28
+ const DEFAULT_RETRY_OPTIONS = {
29
+ maxAttempts: 3,
30
+ delay: 30000,
31
+ timeout: 180000,
32
+ factor: 2,
33
+ handleError: exports.defaultErrorHandler,
34
+ };
35
+ const DEFAULT_RATE_LIMIT_HEADERS = {
36
+ limit: 'x-rate-limit-limit',
37
+ remaining: 'x-rate-limit-remaining',
38
+ reset: 'x-rate-limit-reset',
39
+ };
40
+ class BaseAPIClient {
41
+ baseUrl;
42
+ logger;
43
+ retryOptions;
44
+ logErrorBody;
45
+ rateLimitThrottling;
46
+ baseTokenBucket;
47
+ endpointTokenBuckets = {};
48
+ /**
49
+ * The authorization headers for the API requests
50
+ */
51
+ authorizationHeaders;
52
+ /**
53
+ * Create a new API client
54
+ *
55
+ * @param {ClientConfig} config - The configuration for the client
56
+ * @param {string} config.baseUrl - The base URL for the API
57
+ * @param {IntegrationLogger} config.logger - The logger to use
58
+ * @param {Partial<RetryOptions>} [config.retryOptions] - The retry options for the client,
59
+ * if not provided, the default retry options will be used
60
+ * @param {boolean} [config.logErrorBody] - Whether to log the error body,
61
+ * if true, the error body will be logged when a request fails
62
+ * @param {RateLimitThrottlingOptions} [config.rateLimitThrottling] - The rate limit throttling options,
63
+ * if not provided, rate limit throttling will not be enabled
64
+ * @param {number} [config.rateLimitThrottling.threshold] - The threshold at which to throttle requests
65
+ * @param {RateLimitHeaders} [config.rateLimitThrottling.rateLimitHeaders] - The headers to use for rate limiting
66
+ * @param {string} [config.rateLimitThrottling.rateLimitHeaders.limit='x-rate-limit-limit'] - The header for the rate limit limit
67
+ * @param {string} [config.rateLimitThrottling.rateLimitHeaders.remaining='x-rate-limit-remaining'] - The header for the rate limit remaining
68
+ * @param {string} [config.rateLimitThrottling.rateLimitHeaders.reset='x-rate-limit-reset'] - The header for the rate limit reset
69
+ * @param {TokenBucketOptions} [config.tokenBucket] - The token bucket options,
70
+ * if not provided, token bucket will not be enabled
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const client = new APIClient({
75
+ * baseUrl: 'https://api.example.com',
76
+ * logger: context.logger,
77
+ * rateLimitThrottling: {
78
+ * threshold: 0.3,
79
+ * rateLimitHeaders: {
80
+ * limit: 'x-ratelimit-limit',
81
+ * remaining: 'x-ratelimit-remaining',
82
+ * reset: 'x-ratelimit-reset',
83
+ * },
84
+ * },
85
+ * })
86
+ * ```
87
+ */
88
+ constructor(config) {
89
+ this.baseUrl = config.baseUrl;
90
+ this.logger = config.logger;
91
+ this.retryOptions = {
92
+ ...DEFAULT_RETRY_OPTIONS,
93
+ ...config.retryOptions,
94
+ };
95
+ this.logErrorBody = config.logErrorBody ?? false;
96
+ this.rateLimitThrottling = config.rateLimitThrottling;
97
+ if (config.tokenBucket) {
98
+ this.baseTokenBucket = new hierarchical_token_bucket_1.HierarchicalTokenBucket({
99
+ maximumCapacity: config.tokenBucket.maximumCapacity,
100
+ refillRate: config.tokenBucket.refillRate,
101
+ });
102
+ }
103
+ }
104
+ withBaseUrl(endpoint) {
105
+ return new URL(endpoint, this.baseUrl).toString();
106
+ }
107
+ /**
108
+ * Perform a request to the API.
109
+ *
110
+ * @param {string} endpoint - The endpoint to request
111
+ * @param {RequestOptions} options - The options for the request
112
+ * @param {string} [options.method=GET] - The HTTP method to use
113
+ * @param {Record<string, unknown>} [options.body] - The body of the request
114
+ * @param {Record<string, string>} [options.headers] - The headers for the request
115
+ * @param {boolean} [options.authorize=true] - Whether to include authorization headers
116
+ * @return {Promise<Response>} - The response from the API
117
+ */
118
+ async request(endpoint, options) {
119
+ const tokenBucket = this.getTokenBucket(endpoint, options?.bucketTokens);
120
+ if (tokenBucket) {
121
+ const timeToWaitInMs = tokenBucket.take();
122
+ await (0, attempt_1.sleep)(timeToWaitInMs);
123
+ }
124
+ const { method = 'GET', body, headers, authorize = true } = options ?? {};
125
+ if (authorize && !this.authorizationHeaders) {
126
+ this.authorizationHeaders = await this.getAuthorizationHeaders();
127
+ }
128
+ let url;
129
+ try {
130
+ url = new URL(endpoint).toString();
131
+ }
132
+ catch (e) {
133
+ // If the path is not a valid URL, assume it's a path and prepend the base URL
134
+ url = this.withBaseUrl(endpoint);
135
+ }
136
+ const response = await (0, node_fetch_1.default)(url, {
137
+ method,
138
+ headers: {
139
+ 'Content-Type': 'application/json',
140
+ Accept: 'application/json',
141
+ ...(authorize && this.authorizationHeaders),
142
+ ...headers,
143
+ },
144
+ body: body ? JSON.stringify(body) : undefined,
145
+ });
146
+ return response;
147
+ }
148
+ /**
149
+ * Perform a request to the API with retries and error handling.
150
+ *
151
+ * @param {string} endpoint - The endpoint path to request
152
+ * @param {RequestOptions} options - The options for the request
153
+ * @param {string} [options.method=GET] - The HTTP method to use
154
+ * @param {Record<string, unknown>} [options.body] - The body of the request
155
+ * @param {Record<string, string>} [options.headers] - The headers for the request
156
+ * @param {boolean} [options.authorize=true] - Whether to include authorization headers
157
+ * @return {Promise<Response>} - The response from the API
158
+ */
159
+ async retryableRequest(endpoint, options) {
160
+ const { method = 'GET', body, headers, authorize = true } = options ?? {};
161
+ return (0, attempt_1.retry)(async () => {
162
+ return this.withRateLimiting(async () => {
163
+ let response;
164
+ try {
165
+ response = await this.request(endpoint, {
166
+ method,
167
+ body,
168
+ headers,
169
+ authorize,
170
+ });
171
+ }
172
+ catch (err) {
173
+ this.logger.error({ code: err.code, err, endpoint }, 'Error sending request');
174
+ throw err;
175
+ }
176
+ if (response.ok) {
177
+ return response;
178
+ }
179
+ let error;
180
+ const requestErrorParams = {
181
+ endpoint,
182
+ response,
183
+ logger: this.logger,
184
+ logErrorBody: this.logErrorBody,
185
+ };
186
+ if ((0, errors_1.isRetryableRequest)(response)) {
187
+ error = await (0, errors_1.retryableRequestError)(requestErrorParams);
188
+ }
189
+ else {
190
+ error = await (0, errors_1.fatalRequestError)(requestErrorParams);
191
+ }
192
+ throw error;
193
+ });
194
+ }, {
195
+ maxAttempts: this.retryOptions.maxAttempts,
196
+ delay: this.retryOptions.delay,
197
+ timeout: this.retryOptions.timeout,
198
+ factor: this.retryOptions.factor,
199
+ handleError: async (err, context) => {
200
+ await this.retryOptions.handleError(err, context, this.logger);
201
+ },
202
+ });
203
+ }
204
+ /**
205
+ * Iteratively performs API requests based on the initial request and subsequent requests defined by a callback function.
206
+ * This method is designed to facilitate paginated API requests where each request's response determines the parameters of the next request.
207
+ *
208
+ * @param {object} initialRequest - The initial request parameters
209
+ * @param {string} initialRequest.endpoint - The endpoint path to request
210
+ * @param {RequestOptions} [initialRequest.options] - The options for the request
211
+ * @param {string|string[]|undefined} dataPath - The path to the data in the response to iterate over
212
+ * @param {function} nextPageCallback - The callback function to determine the parameters of the next request
213
+ *
214
+ * @return {AsyncGenerator<T, void, unknown>} - An async generator that yields the items from the paginated requests
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * async iterateUsers(iteratee: (user: User) => Promise<void>): Promise<void>{
219
+ * const baseEndpoint = '/users?limit=100';
220
+ * const iterator = await paginate({
221
+ * endpoint: baseEndpoint,
222
+ * }, 'users', this.getNextPageCallback(baseEndpoint));
223
+ *
224
+ * for await (const user of iterator) {
225
+ * await iteratee(user);
226
+ * }
227
+ * };
228
+ *
229
+ * async getNextPageCallback(baseEndpoint: string) {
230
+ * return async (response: Response): Promise<IterateCallbackResult | undefined> => {
231
+ * const data = await response.json();
232
+ * const nextCursor = data.metadata?.cursor;
233
+ * if (!nextCursor) {
234
+ * return;
235
+ * }
236
+ * const nextUrl = `${baseEndpoint}&cursor=${nextCursor}`;
237
+ * return {
238
+ * nextUrl,
239
+ * };
240
+ * }
241
+ * };
242
+ * ```
243
+ */
244
+ async *paginate(initialRequest, dataPath, nextPageCallback) {
245
+ let nextUrl;
246
+ let nextRequestOptions;
247
+ do {
248
+ const response = await this.retryableRequest(nextUrl ?? initialRequest.endpoint, nextRequestOptions ?? initialRequest.options);
249
+ const responseClone = response.clone(); // Clone the response to allow for multiple reads, we need it in the nextPageCallback
250
+ const data = await response.json();
251
+ let items = dataPath ? (0, get_1.default)(data, dataPath) : data;
252
+ items = Array.isArray(items) ? items : [];
253
+ for (const item of items) {
254
+ yield item;
255
+ }
256
+ const cbOptions = await nextPageCallback(responseClone);
257
+ nextUrl = cbOptions?.nextUrl;
258
+ if (nextUrl) {
259
+ nextRequestOptions = cbOptions?.nextRequestOptions;
260
+ }
261
+ } while (nextUrl);
262
+ }
263
+ /**
264
+ * Get the token bucket for the given endpoint
265
+ *
266
+ * @param {string} endpoint - The endpoint to get the token bucket for
267
+ * @param {number} [tokens] - The number of tokens to use for the token bucket
268
+ */
269
+ getTokenBucket(endpoint, tokens) {
270
+ if (!this.baseTokenBucket) {
271
+ return;
272
+ }
273
+ if (this.endpointTokenBuckets[endpoint]) {
274
+ return this.endpointTokenBuckets[endpoint];
275
+ }
276
+ this.endpointTokenBuckets[endpoint] = new hierarchical_token_bucket_1.HierarchicalTokenBucket({
277
+ parent: this.baseTokenBucket,
278
+ maximumCapacity: tokens ?? this.baseTokenBucket.metadata.options.maximumCapacity,
279
+ refillRate: tokens ?? this.baseTokenBucket.metadata.options.refillRate,
280
+ });
281
+ return this.endpointTokenBuckets[endpoint];
282
+ }
283
+ /**
284
+ * Wait until the rate limit reset time before sending the next request
285
+ * if the rate limit threshold is exceeded.
286
+ *
287
+ * @param {function} fn - The function to rate limit
288
+ * @return {Promise<Response>}
289
+ */
290
+ async withRateLimiting(fn) {
291
+ const response = await fn();
292
+ if (!this.rateLimitThrottling) {
293
+ return response;
294
+ }
295
+ const { rateLimitLimit, rateLimitRemaining } = this.parseRateLimitHeaders(response.headers);
296
+ if (this.shouldThrottleNextRequest({
297
+ rateLimitLimit,
298
+ rateLimitRemaining,
299
+ threshold: this.rateLimitThrottling.threshold,
300
+ })) {
301
+ const timeToSleepInMs = this.getRetryDelayMs(response.headers);
302
+ const thresholdPercentage = this.rateLimitThrottling.threshold * 100;
303
+ const resetHeaderName = this.rateLimitThrottling.rateLimitHeaders?.reset ??
304
+ 'x-rate-limit-reset';
305
+ this.logger.warn({ rateLimitLimit, rateLimitRemaining, timeToSleepInMs }, `Exceeded ${thresholdPercentage}% of rate limit. Sleeping until ${resetHeaderName}`);
306
+ await (0, attempt_1.sleep)(timeToSleepInMs);
307
+ }
308
+ return response;
309
+ }
310
+ parseRateLimitHeaders(headers) {
311
+ const strRateLimitLimit = this.getRateLimitHeaderValue(headers, 'limit');
312
+ const strRateLimitRemaining = this.getRateLimitHeaderValue(headers, 'remaining');
313
+ return {
314
+ rateLimitLimit: strRateLimitLimit
315
+ ? parseInt(strRateLimitLimit, 10)
316
+ : undefined,
317
+ rateLimitRemaining: strRateLimitRemaining
318
+ ? parseInt(strRateLimitRemaining, 10)
319
+ : undefined,
320
+ };
321
+ }
322
+ /**
323
+ * Determine if the next request should be throttled based on the rate limit headers
324
+ *
325
+ * @param {object} params - The parameters for the function
326
+ * @param {number|undefined} params.rateLimitLimit - The rate limit limit
327
+ * @param {number|undefined} params.rateLimitRemaining - The rate limit remaining
328
+ * @param {number} params.threshold - The threshold at which to throttle requests
329
+ * @return {boolean} - Whether the next request should be throttled
330
+ */
331
+ shouldThrottleNextRequest(params) {
332
+ const { rateLimitLimit, rateLimitRemaining } = params;
333
+ if (rateLimitLimit === undefined || rateLimitRemaining === undefined)
334
+ return false;
335
+ const rateLimitConsumed = rateLimitLimit - rateLimitRemaining;
336
+ return rateLimitConsumed / rateLimitLimit > params.threshold;
337
+ }
338
+ /**
339
+ * Determine wait time by getting the delta X-Rate-Limit-Reset and the Date header
340
+ * Add 1 second to account for sub second differences between the clocks that create these headers
341
+ */
342
+ getRetryDelayMs(headers) {
343
+ const resetValue = this.getRateLimitHeaderValue(headers, 'reset');
344
+ if (!resetValue) {
345
+ // If the header is not present, we can't determine the wait time, so we'll just wait 60 seconds
346
+ return 60000;
347
+ }
348
+ const nowDate = new Date(headers.get('date') ?? Date.now());
349
+ const retryDate = new Date(parseInt(resetValue, 10) * 1000);
350
+ return retryDate.getTime() - nowDate.getTime() + 1000;
351
+ }
352
+ getRateLimitHeaderValue(headers, header) {
353
+ return headers.get(this.rateLimitThrottling?.rateLimitHeaders?.[header] ??
354
+ DEFAULT_RATE_LIMIT_HEADERS[header]);
355
+ }
356
+ }
357
+ exports.BaseAPIClient = BaseAPIClient;
358
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,30 @@
1
- export interface APIErrorParams {
2
- message: string;
3
- code: number;
1
+ import { IntegrationLogger, IntegrationProviderAPIError } from '@jupiterone/integration-sdk-core';
2
+ import { Response } from 'node-fetch';
3
+ type RateLimitErrorParams = ConstructorParameters<typeof IntegrationProviderAPIError>[0] & {
4
+ retryAfter: number;
5
+ };
6
+ interface RequestErrorParams {
7
+ endpoint: string;
8
+ response: Response;
9
+ logger: IntegrationLogger;
10
+ logErrorBody: boolean;
4
11
  }
5
- export declare class APIError extends Error {
6
- /**
7
- * Code associated with the error.
8
- */
9
- readonly code: number;
10
- constructor(config: APIErrorParams);
12
+ export declare class RetryableIntegrationProviderApiError extends IntegrationProviderAPIError {
13
+ retryable: boolean;
11
14
  }
15
+ export declare class RateLimitError extends RetryableIntegrationProviderApiError {
16
+ constructor(options: RateLimitErrorParams);
17
+ retryAfter: number;
18
+ }
19
+ export declare class HTTPResponseError extends Error {
20
+ response: Response;
21
+ constructor(response: Response);
22
+ }
23
+ export declare function retryableRequestError({ endpoint, response, logger, logErrorBody, }: RequestErrorParams): Promise<RetryableIntegrationProviderApiError>;
24
+ export declare function fatalRequestError({ endpoint, response, logger, logErrorBody, }: RequestErrorParams): Promise<IntegrationProviderAPIError>;
25
+ /**
26
+ * Function for determining if a request is retryable
27
+ * based on the returned status.
28
+ */
29
+ export declare function isRetryableRequest({ status }: Response): boolean;
30
+ export {};