@unito/integration-sdk 2.3.13 → 2.3.15
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/src/index.cjs +126 -1
- package/dist/src/resources/provider.d.ts +12 -1
- package/dist/src/resources/provider.js +126 -1
- package/dist/test/resources/provider.test.js +365 -31
- package/package.json +2 -1
- package/src/resources/provider.ts +152 -3
- package/test/resources/provider.test.ts +431 -31
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import https from 'https';
|
|
2
2
|
import FormData from 'form-data';
|
|
3
|
+
import * as stream from 'stream';
|
|
4
|
+
import { IncomingHttpHeaders } from 'http';
|
|
3
5
|
|
|
4
6
|
import { buildHttpError } from '../errors.js';
|
|
5
7
|
import * as HttpErrors from '../httpErrors.js';
|
|
@@ -56,7 +58,7 @@ export type RequestOptions = {
|
|
|
56
58
|
export type Response<T> = {
|
|
57
59
|
body: T;
|
|
58
60
|
status: number;
|
|
59
|
-
headers: Headers;
|
|
61
|
+
headers: Headers | IncomingHttpHeaders;
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
export type PreparedRequest = {
|
|
@@ -230,7 +232,7 @@ export class Provider {
|
|
|
230
232
|
if (body.error) {
|
|
231
233
|
reject(this.handleError(400, body.error.message, options));
|
|
232
234
|
}
|
|
233
|
-
resolve({ status: 201, headers: response.headers
|
|
235
|
+
resolve({ status: 201, headers: response.headers, body });
|
|
234
236
|
} catch (error) {
|
|
235
237
|
reject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
|
|
236
238
|
}
|
|
@@ -241,6 +243,25 @@ export class Provider {
|
|
|
241
243
|
reject(this.handleError(400, `Error while calling the provider: "${error}"`, options));
|
|
242
244
|
});
|
|
243
245
|
|
|
246
|
+
if (options.signal) {
|
|
247
|
+
const abortHandler = () => {
|
|
248
|
+
request.destroy();
|
|
249
|
+
reject(this.handleError(408, 'Timeout', options));
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (options.signal.aborted) {
|
|
253
|
+
abortHandler();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
options.signal.addEventListener('abort', abortHandler);
|
|
257
|
+
|
|
258
|
+
request.on('close', () => {
|
|
259
|
+
if (options.signal) {
|
|
260
|
+
options.signal.removeEventListener('abort', abortHandler);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
244
265
|
form.pipe(request);
|
|
245
266
|
} catch (error) {
|
|
246
267
|
reject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`, options));
|
|
@@ -251,6 +272,134 @@ export class Provider {
|
|
|
251
272
|
return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
|
|
252
273
|
}
|
|
253
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Performs a POST request to the provider streaming a Readable directly without loading it into memory.
|
|
277
|
+
*
|
|
278
|
+
* @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
|
|
279
|
+
* @param stream The Readable stream containing the binary data to be sent.
|
|
280
|
+
* @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
|
|
281
|
+
* @returns The {@link Response} extracted from the provider.
|
|
282
|
+
*/
|
|
283
|
+
public async postStream<T>(endpoint: string, stream: stream.Readable, options: RequestOptions): Promise<Response<T>> {
|
|
284
|
+
const { url: providerUrl, headers: providerHeaders } = await this.prepareRequest(options);
|
|
285
|
+
const absoluteUrl = this.generateAbsoluteUrl(providerUrl, endpoint, options.queryParams);
|
|
286
|
+
const headers = {
|
|
287
|
+
'Content-Type': 'application/octet-stream',
|
|
288
|
+
Accept: 'application/json',
|
|
289
|
+
...providerHeaders,
|
|
290
|
+
...options.additionnalheaders,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const callToProvider = async (): Promise<Response<T>> => {
|
|
294
|
+
return new Promise((resolve, reject) => {
|
|
295
|
+
let isSettled = false; // Prevent double rejection
|
|
296
|
+
|
|
297
|
+
const cleanup = () => {
|
|
298
|
+
if (!stream.destroyed) {
|
|
299
|
+
stream.destroy();
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const safeReject = (error: HttpErrors.HttpError) => {
|
|
304
|
+
if (!isSettled) {
|
|
305
|
+
isSettled = true;
|
|
306
|
+
cleanup();
|
|
307
|
+
reject(error);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const safeResolve = (response: Response<T>) => {
|
|
312
|
+
if (!isSettled) {
|
|
313
|
+
isSettled = true;
|
|
314
|
+
resolve(response);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const urlObj = new URL(absoluteUrl);
|
|
320
|
+
const requestOptions: https.RequestOptions = {
|
|
321
|
+
hostname: urlObj.hostname,
|
|
322
|
+
path: urlObj.pathname + urlObj.search,
|
|
323
|
+
method: 'POST',
|
|
324
|
+
headers,
|
|
325
|
+
timeout: 0,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const request = https.request(requestOptions, response => {
|
|
329
|
+
response.setEncoding('utf8');
|
|
330
|
+
let responseBody = '';
|
|
331
|
+
|
|
332
|
+
response.on('data', chunk => {
|
|
333
|
+
responseBody += chunk;
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
response.on('error', error => {
|
|
337
|
+
safeReject(this.handleError(500, `Response stream error: "${error}"`, options));
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
response.on('end', () => {
|
|
341
|
+
try {
|
|
342
|
+
if (response.statusCode && response.statusCode >= 400) {
|
|
343
|
+
safeReject(this.handleError(response.statusCode, responseBody, options));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const body = responseBody ? JSON.parse(responseBody) : undefined;
|
|
348
|
+
safeResolve({
|
|
349
|
+
status: response.statusCode || 200,
|
|
350
|
+
headers: response.headers,
|
|
351
|
+
body,
|
|
352
|
+
});
|
|
353
|
+
} catch (error) {
|
|
354
|
+
safeReject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
request.on('timeout', () => {
|
|
360
|
+
request.destroy();
|
|
361
|
+
safeReject(this.handleError(408, 'Request timeout', options));
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
request.on('error', error => {
|
|
365
|
+
safeReject(this.handleError(500, `Error while calling the provider: "${error}"`, options));
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
stream.on('error', error => {
|
|
369
|
+
request.destroy();
|
|
370
|
+
safeReject(this.handleError(500, `Stream error: "${error}"`, options));
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (options.signal) {
|
|
374
|
+
const abortHandler = () => {
|
|
375
|
+
request.destroy();
|
|
376
|
+
safeReject(this.handleError(408, 'Timeout', options));
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
if (options.signal.aborted) {
|
|
380
|
+
abortHandler();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
options.signal.addEventListener('abort', abortHandler);
|
|
384
|
+
|
|
385
|
+
request.on('close', () => {
|
|
386
|
+
if (options.signal) {
|
|
387
|
+
options.signal.removeEventListener('abort', abortHandler);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Stream the data directly without buffering
|
|
393
|
+
stream.pipe(request);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
safeReject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`, options));
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
|
|
401
|
+
}
|
|
402
|
+
|
|
254
403
|
/**
|
|
255
404
|
* Performs a PUT request to the provider.
|
|
256
405
|
*
|
|
@@ -409,7 +558,7 @@ export class Provider {
|
|
|
409
558
|
response = await fetch(absoluteUrl, {
|
|
410
559
|
method: options.method,
|
|
411
560
|
headers,
|
|
412
|
-
body: fetchBody,
|
|
561
|
+
body: fetchBody as BodyInit | null,
|
|
413
562
|
...(options.signal ? { signal: options.signal } : {}),
|
|
414
563
|
});
|
|
415
564
|
} catch (error) {
|