@uploadcare/upload-client 3.1.1 → 4.0.1

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,1546 @@
1
+ class UploadClientError extends Error {
2
+ constructor(message, code, request, response, headers) {
3
+ super();
4
+ this.name = 'UploadClientError';
5
+ this.message = message;
6
+ this.code = code;
7
+ this.request = request;
8
+ this.response = response;
9
+ this.headers = headers;
10
+ Object.setPrototypeOf(this, UploadClientError.prototype);
11
+ }
12
+ }
13
+ const cancelError = (message = 'Request canceled') => {
14
+ const error = new UploadClientError(message);
15
+ error.isCancel = true;
16
+ return error;
17
+ };
18
+
19
+ const onCancel = (signal, callback) => {
20
+ if (signal) {
21
+ if (signal.aborted) {
22
+ Promise.resolve().then(callback);
23
+ }
24
+ else {
25
+ signal.addEventListener('abort', () => callback(), { once: true });
26
+ }
27
+ }
28
+ };
29
+
30
+ const request = ({ method, url, data, headers = {}, signal, onProgress }) => new Promise((resolve, reject) => {
31
+ const xhr = new XMLHttpRequest();
32
+ const requestMethod = (method === null || method === void 0 ? void 0 : method.toUpperCase()) || 'GET';
33
+ let aborted = false;
34
+ xhr.open(requestMethod, url);
35
+ if (headers) {
36
+ Object.entries(headers).forEach((entry) => {
37
+ const [key, value] = entry;
38
+ typeof value !== 'undefined' &&
39
+ !Array.isArray(value) &&
40
+ xhr.setRequestHeader(key, value);
41
+ });
42
+ }
43
+ xhr.responseType = 'text';
44
+ onCancel(signal, () => {
45
+ aborted = true;
46
+ xhr.abort();
47
+ reject(cancelError());
48
+ });
49
+ xhr.onload = () => {
50
+ if (xhr.status != 200) {
51
+ // analyze HTTP status of the response
52
+ reject(new Error(`Error ${xhr.status}: ${xhr.statusText}`)); // e.g. 404: Not Found
53
+ }
54
+ else {
55
+ const request = {
56
+ method: requestMethod,
57
+ url,
58
+ data,
59
+ headers: headers || undefined,
60
+ signal,
61
+ onProgress
62
+ };
63
+ // Convert the header string into an array
64
+ // of individual headers
65
+ const headersArray = xhr
66
+ .getAllResponseHeaders()
67
+ .trim()
68
+ .split(/[\r\n]+/);
69
+ // Create a map of header names to values
70
+ const responseHeaders = {};
71
+ headersArray.forEach(function (line) {
72
+ const parts = line.split(': ');
73
+ const header = parts.shift();
74
+ const value = parts.join(': ');
75
+ if (header && typeof header !== 'undefined') {
76
+ responseHeaders[header] = value;
77
+ }
78
+ });
79
+ const responseData = xhr.response;
80
+ const responseStatus = xhr.status;
81
+ resolve({
82
+ request,
83
+ data: responseData,
84
+ headers: responseHeaders,
85
+ status: responseStatus
86
+ });
87
+ }
88
+ };
89
+ xhr.onerror = () => {
90
+ if (aborted)
91
+ return;
92
+ // only triggers if the request couldn't be made at all
93
+ reject(new Error('Network error'));
94
+ };
95
+ if (onProgress && typeof onProgress === 'function') {
96
+ xhr.upload.onprogress = (event) => {
97
+ if (event.lengthComputable) {
98
+ onProgress({
99
+ isComputable: true,
100
+ value: event.loaded / event.total
101
+ });
102
+ }
103
+ else {
104
+ onProgress({ isComputable: false });
105
+ }
106
+ };
107
+ }
108
+ if (data) {
109
+ xhr.send(data);
110
+ }
111
+ else {
112
+ xhr.send();
113
+ }
114
+ });
115
+
116
+ const getFileOptions = ({ name }) => name ? [name] : [];
117
+ const transformFile = (file, name) => {
118
+ if (!file) {
119
+ return file;
120
+ }
121
+ const uri = URL.createObjectURL(file);
122
+ const type = file.type;
123
+ return { uri, name, type };
124
+ };
125
+ var getFormData = () => new FormData();
126
+
127
+ /**
128
+ * FileData type guard.
129
+ */
130
+ const isFileData = (data) => {
131
+ return (data !== undefined &&
132
+ ((typeof Blob !== 'undefined' && data instanceof Blob) ||
133
+ (typeof File !== 'undefined' && data instanceof File) ||
134
+ (typeof Buffer !== 'undefined' && data instanceof Buffer)));
135
+ };
136
+ /**
137
+ * Uuid type guard.
138
+ */
139
+ const isUuid = (data) => {
140
+ const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
141
+ const regExp = new RegExp(UUID_REGEX);
142
+ return !isFileData(data) && regExp.test(data);
143
+ };
144
+ /**
145
+ * Url type guard.
146
+ *
147
+ * @param {NodeFile | BrowserFile | Url | Uuid} data
148
+ */
149
+ const isUrl = (data) => {
150
+ const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
151
+ const regExp = new RegExp(URL_REGEX);
152
+ return !isFileData(data) && regExp.test(data);
153
+ };
154
+
155
+ const isSimpleValue = (value) => {
156
+ return (typeof value === 'string' ||
157
+ typeof value === 'number' ||
158
+ typeof value === 'undefined');
159
+ };
160
+ const isObjectValue = (value) => {
161
+ return !!value && typeof value === 'object' && !Array.isArray(value);
162
+ };
163
+ const isFileValue = (value) => !!value &&
164
+ typeof value === 'object' &&
165
+ 'data' in value &&
166
+ isFileData(value.data);
167
+ function collectParams(params, inputKey, inputValue) {
168
+ if (isFileValue(inputValue)) {
169
+ const { name, contentType } = inputValue;
170
+ const file = transformFile(inputValue.data, name); // lgtm [js/superfluous-trailing-arguments]
171
+ const options = getFileOptions({ name, contentType });
172
+ params.push([inputKey, file, ...options]);
173
+ }
174
+ else if (isObjectValue(inputValue)) {
175
+ for (const [key, value] of Object.entries(inputValue)) {
176
+ if (typeof value !== 'undefined') {
177
+ params.push([`${inputKey}[${key}]`, String(value)]);
178
+ }
179
+ }
180
+ }
181
+ else if (isSimpleValue(inputValue) && inputValue) {
182
+ params.push([inputKey, inputValue.toString()]);
183
+ }
184
+ }
185
+ function getFormDataParams(options) {
186
+ const params = [];
187
+ for (const [key, value] of Object.entries(options)) {
188
+ collectParams(params, key, value);
189
+ }
190
+ return params;
191
+ }
192
+ function buildFormData(options) {
193
+ const formData = getFormData();
194
+ const paramsList = getFormDataParams(options);
195
+ for (const params of paramsList) {
196
+ const [key, value, ...options] = params;
197
+ // node form-data has another signature for append
198
+ formData.append(key, value, ...options);
199
+ }
200
+ return formData;
201
+ }
202
+
203
+ const serializePair = (key, value) => typeof value !== 'undefined' ? `${key}=${encodeURIComponent(value)}` : null;
204
+ // TODO: generalize value transforming logic and use it here and inside `buildFormData`
205
+ const createQuery = (query) => Object.entries(query)
206
+ .reduce((params, [key, value]) => {
207
+ let param;
208
+ if (typeof value === 'object' && !Array.isArray(value)) {
209
+ param = Object.entries(value)
210
+ .filter((entry) => typeof entry[1] !== 'undefined')
211
+ .map((entry) => serializePair(`${key}[${entry[0]}]`, String(entry[1])));
212
+ }
213
+ else if (Array.isArray(value)) {
214
+ param = value.map((val) => serializePair(`${key}[]`, val));
215
+ }
216
+ else {
217
+ param = serializePair(key, value);
218
+ }
219
+ return params.concat(param);
220
+ }, [])
221
+ .filter((x) => !!x)
222
+ .join('&');
223
+ const getUrl = (base, path, query) => [
224
+ base,
225
+ path,
226
+ query && Object.keys(query).length > 0 ? '?' : '',
227
+ query && createQuery(query)
228
+ ]
229
+ .filter(Boolean)
230
+ .join('');
231
+
232
+ /*
233
+ Settings for future support:
234
+ parallelDirectUploads: 10,
235
+ */
236
+ const defaultSettings = {
237
+ baseCDN: 'https://ucarecdn.com',
238
+ baseURL: 'https://upload.uploadcare.com',
239
+ maxContentLength: 50 * 1024 * 1024,
240
+ retryThrottledRequestMaxTimes: 1,
241
+ multipartMinFileSize: 25 * 1024 * 1024,
242
+ multipartChunkSize: 5 * 1024 * 1024,
243
+ multipartMinLastPartSize: 1024 * 1024,
244
+ maxConcurrentRequests: 4,
245
+ multipartMaxAttempts: 3,
246
+ pollingTimeoutMilliseconds: 10000,
247
+ pusherKey: '79ae88bd931ea68464d9'
248
+ };
249
+ const defaultContentType = 'application/octet-stream';
250
+ const defaultFilename = 'original';
251
+
252
+ var version = '4.0.1';
253
+
254
+ /**
255
+ * Returns User Agent based on version and settings.
256
+ */
257
+ function getUserAgent({ userAgent, publicKey = '', integration = '' } = {}) {
258
+ const libraryName = 'UploadcareUploadClient';
259
+ const libraryVersion = version;
260
+ const languageName = 'JavaScript';
261
+ if (typeof userAgent === 'string') {
262
+ return userAgent;
263
+ }
264
+ if (typeof userAgent === 'function') {
265
+ return userAgent({
266
+ publicKey,
267
+ libraryName,
268
+ libraryVersion,
269
+ languageName,
270
+ integration
271
+ });
272
+ }
273
+ const mainInfo = [libraryName, libraryVersion, publicKey]
274
+ .filter(Boolean)
275
+ .join('/');
276
+ const additionInfo = [languageName, integration].filter(Boolean).join('; ');
277
+ return `${mainInfo} (${additionInfo})`;
278
+ }
279
+
280
+ const SEPARATOR = /\W|_/g;
281
+ /**
282
+ * Transforms a string to camelCased.
283
+ */
284
+ function camelize(text) {
285
+ return text
286
+ .split(SEPARATOR)
287
+ .map((word, index) => word.charAt(0)[index > 0 ? 'toUpperCase' : 'toLowerCase']() +
288
+ word.slice(1))
289
+ .join('');
290
+ }
291
+ /**
292
+ * Transforms keys of an object to camelCased recursively.
293
+ */
294
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
295
+ function camelizeKeys(source) {
296
+ if (!source || typeof source !== 'object') {
297
+ return source;
298
+ }
299
+ return Object.keys(source).reduce((accumulator, key) => {
300
+ accumulator[camelize(key)] =
301
+ typeof source[key] === 'object' ? camelizeKeys(source[key]) : source[key];
302
+ return accumulator;
303
+ }, {});
304
+ }
305
+
306
+ /**
307
+ * setTimeout as Promise.
308
+ *
309
+ * @param {number} ms Timeout in milliseconds.
310
+ */
311
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
312
+
313
+ const defaultOptions = {
314
+ factor: 2,
315
+ time: 100
316
+ };
317
+ function retrier(fn, options = defaultOptions) {
318
+ let attempts = 0;
319
+ function runAttempt(fn) {
320
+ const defaultDelayTime = Math.round(options.time * Math.pow(options.factor, attempts));
321
+ const retry = (ms) => delay(ms !== null && ms !== void 0 ? ms : defaultDelayTime).then(() => {
322
+ attempts += 1;
323
+ return runAttempt(fn);
324
+ });
325
+ return fn({
326
+ attempt: attempts,
327
+ retry
328
+ });
329
+ }
330
+ return runAttempt(fn);
331
+ }
332
+
333
+ const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
334
+ const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
335
+ function getTimeoutFromThrottledRequest(error) {
336
+ const { headers } = error || {};
337
+ return ((headers &&
338
+ Number.parseInt(headers['x-throttle-wait-seconds']) * 1000) ||
339
+ DEFAULT_RETRY_AFTER_TIMEOUT);
340
+ }
341
+ function retryIfThrottled(fn, retryThrottledMaxTimes) {
342
+ return retrier(({ attempt, retry }) => fn().catch((error) => {
343
+ if ('response' in error &&
344
+ (error === null || error === void 0 ? void 0 : error.code) === REQUEST_WAS_THROTTLED_CODE &&
345
+ attempt < retryThrottledMaxTimes) {
346
+ return retry(getTimeoutFromThrottledRequest(error));
347
+ }
348
+ throw error;
349
+ }));
350
+ }
351
+
352
+ function getStoreValue(store) {
353
+ return typeof store === 'undefined' ? 'auto' : store ? '1' : '0';
354
+ }
355
+
356
+ /**
357
+ * Performs file uploading request to Uploadcare Upload API.
358
+ * Can be canceled and has progress.
359
+ */
360
+ function base(file, { publicKey, fileName, contentType, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, metadata }) {
361
+ return retryIfThrottled(() => {
362
+ var _a;
363
+ return request({
364
+ method: 'POST',
365
+ url: getUrl(baseURL, '/base/', {
366
+ jsonerrors: 1
367
+ }),
368
+ headers: {
369
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
370
+ },
371
+ data: buildFormData({
372
+ file: {
373
+ data: file,
374
+ name: (_a = fileName !== null && fileName !== void 0 ? fileName : file.name) !== null && _a !== void 0 ? _a : defaultFilename,
375
+ contentType
376
+ },
377
+ UPLOADCARE_PUB_KEY: publicKey,
378
+ UPLOADCARE_STORE: getStoreValue(store),
379
+ signature: secureSignature,
380
+ expire: secureExpire,
381
+ source: source,
382
+ metadata
383
+ }),
384
+ signal,
385
+ onProgress
386
+ }).then(({ data, headers, request }) => {
387
+ const response = camelizeKeys(JSON.parse(data));
388
+ if ('error' in response) {
389
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
390
+ }
391
+ else {
392
+ return response;
393
+ }
394
+ });
395
+ }, retryThrottledRequestMaxTimes);
396
+ }
397
+
398
+ var TypeEnum;
399
+ (function (TypeEnum) {
400
+ TypeEnum["Token"] = "token";
401
+ TypeEnum["FileInfo"] = "file_info";
402
+ })(TypeEnum || (TypeEnum = {}));
403
+ /**
404
+ * Uploading files from URL.
405
+ */
406
+ function fromUrl(sourceUrl, { publicKey, baseURL = defaultSettings.baseURL, store, fileName, checkForUrlDuplicates, saveUrlForRecurrentUploads, secureSignature, secureExpire, source = 'url', signal, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, metadata }) {
407
+ return retryIfThrottled(() => request({
408
+ method: 'POST',
409
+ headers: {
410
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
411
+ },
412
+ url: getUrl(baseURL, '/from_url/', {
413
+ jsonerrors: 1,
414
+ pub_key: publicKey,
415
+ source_url: sourceUrl,
416
+ store: getStoreValue(store),
417
+ filename: fileName,
418
+ check_URL_duplicates: checkForUrlDuplicates ? 1 : undefined,
419
+ save_URL_duplicates: saveUrlForRecurrentUploads ? 1 : undefined,
420
+ signature: secureSignature,
421
+ expire: secureExpire,
422
+ source: source,
423
+ metadata
424
+ }),
425
+ signal
426
+ }).then(({ data, headers, request }) => {
427
+ const response = camelizeKeys(JSON.parse(data));
428
+ if ('error' in response) {
429
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
430
+ }
431
+ else {
432
+ return response;
433
+ }
434
+ }), retryThrottledRequestMaxTimes);
435
+ }
436
+
437
+ var Status;
438
+ (function (Status) {
439
+ Status["Unknown"] = "unknown";
440
+ Status["Waiting"] = "waiting";
441
+ Status["Progress"] = "progress";
442
+ Status["Error"] = "error";
443
+ Status["Success"] = "success";
444
+ })(Status || (Status = {}));
445
+ const isErrorResponse = (response) => {
446
+ return 'status' in response && response.status === Status.Error;
447
+ };
448
+ /**
449
+ * Checking upload status and working with file tokens.
450
+ */
451
+ function fromUrlStatus(token, { publicKey, baseURL = defaultSettings.baseURL, signal, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes } = {}) {
452
+ return retryIfThrottled(() => request({
453
+ method: 'GET',
454
+ headers: publicKey
455
+ ? {
456
+ 'X-UC-User-Agent': getUserAgent({
457
+ publicKey,
458
+ integration,
459
+ userAgent
460
+ })
461
+ }
462
+ : undefined,
463
+ url: getUrl(baseURL, '/from_url/status/', {
464
+ jsonerrors: 1,
465
+ token
466
+ }),
467
+ signal
468
+ }).then(({ data, headers, request }) => {
469
+ const response = camelizeKeys(JSON.parse(data));
470
+ if ('error' in response && !isErrorResponse(response)) {
471
+ throw new UploadClientError(response.error.content, undefined, request, response, headers);
472
+ }
473
+ else {
474
+ return response;
475
+ }
476
+ }), retryThrottledRequestMaxTimes);
477
+ }
478
+
479
+ /**
480
+ * Create files group.
481
+ */
482
+ function group(uuids, { publicKey, baseURL = defaultSettings.baseURL, jsonpCallback, secureSignature, secureExpire, signal, source, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes }) {
483
+ return retryIfThrottled(() => request({
484
+ method: 'POST',
485
+ headers: {
486
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
487
+ },
488
+ url: getUrl(baseURL, '/group/', {
489
+ jsonerrors: 1,
490
+ pub_key: publicKey,
491
+ files: uuids,
492
+ callback: jsonpCallback,
493
+ signature: secureSignature,
494
+ expire: secureExpire,
495
+ source
496
+ }),
497
+ signal
498
+ }).then(({ data, headers, request }) => {
499
+ const response = camelizeKeys(JSON.parse(data));
500
+ if ('error' in response) {
501
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
502
+ }
503
+ else {
504
+ return response;
505
+ }
506
+ }), retryThrottledRequestMaxTimes);
507
+ }
508
+
509
+ /**
510
+ * Get info about group.
511
+ */
512
+ function groupInfo(id, { publicKey, baseURL = defaultSettings.baseURL, signal, source, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes }) {
513
+ return retryIfThrottled(() => request({
514
+ method: 'GET',
515
+ headers: {
516
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
517
+ },
518
+ url: getUrl(baseURL, '/group/info/', {
519
+ jsonerrors: 1,
520
+ pub_key: publicKey,
521
+ group_id: id,
522
+ source
523
+ }),
524
+ signal
525
+ }).then(({ data, headers, request }) => {
526
+ const response = camelizeKeys(JSON.parse(data));
527
+ if ('error' in response) {
528
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
529
+ }
530
+ else {
531
+ return response;
532
+ }
533
+ }), retryThrottledRequestMaxTimes);
534
+ }
535
+
536
+ /**
537
+ * Returns a JSON dictionary holding file info.
538
+ */
539
+ function info(uuid, { publicKey, baseURL = defaultSettings.baseURL, signal, source, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes }) {
540
+ return retryIfThrottled(() => request({
541
+ method: 'GET',
542
+ headers: {
543
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
544
+ },
545
+ url: getUrl(baseURL, '/info/', {
546
+ jsonerrors: 1,
547
+ pub_key: publicKey,
548
+ file_id: uuid,
549
+ source
550
+ }),
551
+ signal
552
+ }).then(({ data, headers, request }) => {
553
+ const response = camelizeKeys(JSON.parse(data));
554
+ if ('error' in response) {
555
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
556
+ }
557
+ else {
558
+ return response;
559
+ }
560
+ }), retryThrottledRequestMaxTimes);
561
+ }
562
+
563
+ /**
564
+ * Start multipart uploading.
565
+ */
566
+ function multipartStart(size, { publicKey, contentType, fileName, multipartChunkSize = defaultSettings.multipartChunkSize, baseURL = '', secureSignature, secureExpire, store, signal, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, metadata }) {
567
+ return retryIfThrottled(() => request({
568
+ method: 'POST',
569
+ url: getUrl(baseURL, '/multipart/start/', { jsonerrors: 1 }),
570
+ headers: {
571
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
572
+ },
573
+ data: buildFormData({
574
+ filename: fileName !== null && fileName !== void 0 ? fileName : defaultFilename,
575
+ size: size,
576
+ content_type: contentType !== null && contentType !== void 0 ? contentType : defaultContentType,
577
+ part_size: multipartChunkSize,
578
+ UPLOADCARE_STORE: getStoreValue(store),
579
+ UPLOADCARE_PUB_KEY: publicKey,
580
+ signature: secureSignature,
581
+ expire: secureExpire,
582
+ source: source,
583
+ metadata
584
+ }),
585
+ signal
586
+ }).then(({ data, headers, request }) => {
587
+ const response = camelizeKeys(JSON.parse(data));
588
+ if ('error' in response) {
589
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
590
+ }
591
+ else {
592
+ // convert to array
593
+ response.parts = Object.keys(response.parts).map((key) => response.parts[key]);
594
+ return response;
595
+ }
596
+ }), retryThrottledRequestMaxTimes);
597
+ }
598
+
599
+ /**
600
+ * Complete multipart uploading.
601
+ */
602
+ function multipartUpload(part, url, { signal, onProgress }) {
603
+ return request({
604
+ method: 'PUT',
605
+ url,
606
+ data: part,
607
+ // Upload request can't be non-computable because we always know exact size
608
+ onProgress: onProgress,
609
+ signal
610
+ })
611
+ .then((result) => {
612
+ // hack for node ¯\_(ツ)_/¯
613
+ if (onProgress)
614
+ onProgress({
615
+ isComputable: true,
616
+ value: 1
617
+ });
618
+ return result;
619
+ })
620
+ .then(({ status }) => ({ code: status }));
621
+ }
622
+
623
+ /**
624
+ * Complete multipart uploading.
625
+ */
626
+ function multipartComplete(uuid, { publicKey, baseURL = defaultSettings.baseURL, source = 'local', signal, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes }) {
627
+ return retryIfThrottled(() => request({
628
+ method: 'POST',
629
+ url: getUrl(baseURL, '/multipart/complete/', { jsonerrors: 1 }),
630
+ headers: {
631
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
632
+ },
633
+ data: buildFormData({
634
+ uuid: uuid,
635
+ UPLOADCARE_PUB_KEY: publicKey,
636
+ source: source
637
+ }),
638
+ signal
639
+ }).then(({ data, headers, request }) => {
640
+ const response = camelizeKeys(JSON.parse(data));
641
+ if ('error' in response) {
642
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
643
+ }
644
+ else {
645
+ return response;
646
+ }
647
+ }), retryThrottledRequestMaxTimes);
648
+ }
649
+
650
+ class UploadcareFile {
651
+ constructor(fileInfo, { baseCDN, fileName }) {
652
+ this.name = null;
653
+ this.size = null;
654
+ this.isStored = null;
655
+ this.isImage = null;
656
+ this.mimeType = null;
657
+ this.cdnUrl = null;
658
+ this.s3Url = null;
659
+ this.originalFilename = null;
660
+ this.imageInfo = null;
661
+ this.videoInfo = null;
662
+ this.contentInfo = null;
663
+ this.metadata = null;
664
+ this.s3Bucket = null;
665
+ const { uuid, s3Bucket } = fileInfo;
666
+ const cdnUrl = `${baseCDN}/${uuid}/`;
667
+ const s3Url = s3Bucket
668
+ ? `https://${s3Bucket}.s3.amazonaws.com/${uuid}/${fileInfo.filename}`
669
+ : null;
670
+ this.uuid = uuid;
671
+ this.name = fileName || fileInfo.filename;
672
+ this.size = fileInfo.size;
673
+ this.isStored = fileInfo.isStored;
674
+ this.isImage = fileInfo.isImage;
675
+ this.mimeType = fileInfo.mimeType;
676
+ this.cdnUrl = cdnUrl;
677
+ this.originalFilename = fileInfo.originalFilename;
678
+ this.imageInfo = camelizeKeys(fileInfo.imageInfo);
679
+ this.videoInfo = camelizeKeys(fileInfo.videoInfo);
680
+ this.contentInfo = camelizeKeys(fileInfo.contentInfo);
681
+ this.metadata = fileInfo.metadata || null;
682
+ this.s3Bucket = s3Bucket || null;
683
+ this.s3Url = s3Url;
684
+ }
685
+ }
686
+
687
+ const DEFAULT_INTERVAL = 500;
688
+ const poll = ({ check, interval = DEFAULT_INTERVAL, signal }) => new Promise((resolve, reject) => {
689
+ let timeoutId;
690
+ onCancel(signal, () => {
691
+ timeoutId && clearTimeout(timeoutId);
692
+ reject(cancelError('Poll cancelled'));
693
+ });
694
+ const tick = () => {
695
+ try {
696
+ Promise.resolve(check(signal))
697
+ .then((result) => {
698
+ if (result) {
699
+ resolve(result);
700
+ }
701
+ else {
702
+ timeoutId = setTimeout(tick, interval);
703
+ }
704
+ })
705
+ .catch((error) => reject(error));
706
+ }
707
+ catch (error) {
708
+ reject(error);
709
+ }
710
+ };
711
+ timeoutId = setTimeout(tick, 0);
712
+ });
713
+
714
+ function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, signal, onProgress }) {
715
+ return poll({
716
+ check: (signal) => info(file, {
717
+ publicKey,
718
+ baseURL,
719
+ signal,
720
+ source,
721
+ integration,
722
+ userAgent,
723
+ retryThrottledRequestMaxTimes
724
+ }).then((response) => {
725
+ if (response.isReady) {
726
+ return response;
727
+ }
728
+ onProgress && onProgress({ isComputable: true, value: 1 });
729
+ return false;
730
+ }),
731
+ signal
732
+ });
733
+ }
734
+
735
+ const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, secureExpire, store, contentType, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, baseCDN, metadata }) => {
736
+ return base(file, {
737
+ publicKey,
738
+ fileName,
739
+ contentType,
740
+ baseURL,
741
+ secureSignature,
742
+ secureExpire,
743
+ store,
744
+ signal,
745
+ onProgress,
746
+ source,
747
+ integration,
748
+ userAgent,
749
+ retryThrottledRequestMaxTimes,
750
+ metadata
751
+ })
752
+ .then(({ file }) => {
753
+ return isReadyPoll({
754
+ file,
755
+ publicKey,
756
+ baseURL,
757
+ source,
758
+ integration,
759
+ userAgent,
760
+ retryThrottledRequestMaxTimes,
761
+ onProgress,
762
+ signal
763
+ });
764
+ })
765
+ .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
766
+ };
767
+
768
+ const race = (fns, { signal } = {}) => {
769
+ let lastError = null;
770
+ let winnerIndex = null;
771
+ const controllers = fns.map(() => new AbortController());
772
+ const createStopRaceCallback = (i) => () => {
773
+ winnerIndex = i;
774
+ controllers.forEach((controller, index) => index !== i && controller.abort());
775
+ };
776
+ onCancel(signal, () => {
777
+ controllers.forEach((controller) => controller.abort());
778
+ });
779
+ return Promise.all(fns.map((fn, i) => {
780
+ const stopRace = createStopRaceCallback(i);
781
+ return Promise.resolve()
782
+ .then(() => fn({ stopRace, signal: controllers[i].signal }))
783
+ .then((result) => {
784
+ stopRace();
785
+ return result;
786
+ })
787
+ .catch((error) => {
788
+ lastError = error;
789
+ return null;
790
+ });
791
+ })).then((results) => {
792
+ if (winnerIndex === null) {
793
+ throw lastError;
794
+ }
795
+ else {
796
+ return results[winnerIndex];
797
+ }
798
+ });
799
+ };
800
+
801
+ var WebSocket = window.WebSocket;
802
+
803
+ class Events {
804
+ constructor() {
805
+ this.events = Object.create({});
806
+ }
807
+ emit(event, data) {
808
+ var _a;
809
+ (_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.forEach((fn) => fn(data));
810
+ }
811
+ on(event, callback) {
812
+ this.events[event] = this.events[event] || [];
813
+ this.events[event].push(callback);
814
+ }
815
+ off(event, callback) {
816
+ if (callback) {
817
+ this.events[event] = this.events[event].filter((fn) => fn !== callback);
818
+ }
819
+ else {
820
+ this.events[event] = [];
821
+ }
822
+ }
823
+ }
824
+
825
+ const response = (type, data) => {
826
+ if (type === 'success') {
827
+ return Object.assign({ status: Status.Success }, data);
828
+ }
829
+ if (type === 'progress') {
830
+ return Object.assign({ status: Status.Progress }, data);
831
+ }
832
+ return Object.assign({ status: Status.Error }, data);
833
+ };
834
+ class Pusher {
835
+ constructor(pusherKey, disconnectTime = 30000) {
836
+ this.ws = undefined;
837
+ this.queue = [];
838
+ this.isConnected = false;
839
+ this.subscribers = 0;
840
+ this.emmitter = new Events();
841
+ this.disconnectTimeoutId = null;
842
+ this.key = pusherKey;
843
+ this.disconnectTime = disconnectTime;
844
+ }
845
+ connect() {
846
+ this.disconnectTimeoutId && clearTimeout(this.disconnectTimeoutId);
847
+ if (!this.isConnected && !this.ws) {
848
+ const pusherUrl = `wss://ws.pusherapp.com/app/${this.key}?protocol=5&client=js&version=1.12.2`;
849
+ this.ws = new WebSocket(pusherUrl);
850
+ this.ws.addEventListener('error', (error) => {
851
+ this.emmitter.emit('error', new Error(error.message));
852
+ });
853
+ this.emmitter.on('connected', () => {
854
+ this.isConnected = true;
855
+ this.queue.forEach((message) => this.send(message.event, message.data));
856
+ this.queue = [];
857
+ });
858
+ this.ws.addEventListener('message', (e) => {
859
+ const data = JSON.parse(e.data.toString());
860
+ switch (data.event) {
861
+ case 'pusher:connection_established': {
862
+ this.emmitter.emit('connected', undefined);
863
+ break;
864
+ }
865
+ case 'pusher:ping': {
866
+ this.send('pusher:pong', {});
867
+ break;
868
+ }
869
+ case 'progress':
870
+ case 'success':
871
+ case 'fail': {
872
+ this.emmitter.emit(data.channel, response(data.event, JSON.parse(data.data)));
873
+ }
874
+ }
875
+ });
876
+ }
877
+ }
878
+ disconnect() {
879
+ const actualDisconect = () => {
880
+ var _a;
881
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
882
+ this.ws = undefined;
883
+ this.isConnected = false;
884
+ };
885
+ if (this.disconnectTime) {
886
+ this.disconnectTimeoutId = setTimeout(() => {
887
+ actualDisconect();
888
+ }, this.disconnectTime);
889
+ }
890
+ else {
891
+ actualDisconect();
892
+ }
893
+ }
894
+ send(event, data) {
895
+ var _a;
896
+ const str = JSON.stringify({ event, data });
897
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(str);
898
+ }
899
+ subscribe(token, handler) {
900
+ this.subscribers += 1;
901
+ this.connect();
902
+ const channel = `task-status-${token}`;
903
+ const message = {
904
+ event: 'pusher:subscribe',
905
+ data: { channel }
906
+ };
907
+ this.emmitter.on(channel, handler);
908
+ if (this.isConnected) {
909
+ this.send(message.event, message.data);
910
+ }
911
+ else {
912
+ this.queue.push(message);
913
+ }
914
+ }
915
+ unsubscribe(token) {
916
+ this.subscribers -= 1;
917
+ const channel = `task-status-${token}`;
918
+ const message = {
919
+ event: 'pusher:unsubscribe',
920
+ data: { channel }
921
+ };
922
+ this.emmitter.off(channel);
923
+ if (this.isConnected) {
924
+ this.send(message.event, message.data);
925
+ }
926
+ else {
927
+ this.queue = this.queue.filter((msg) => msg.data.channel !== channel);
928
+ }
929
+ if (this.subscribers === 0) {
930
+ this.disconnect();
931
+ }
932
+ }
933
+ onError(callback) {
934
+ this.emmitter.on('error', callback);
935
+ return () => this.emmitter.off('error', callback);
936
+ }
937
+ }
938
+ let pusher = null;
939
+ const getPusher = (key) => {
940
+ if (!pusher) {
941
+ // no timeout for nodeJS and 30000 ms for browser
942
+ const disconectTimeout = typeof window === 'undefined' ? 0 : 30000;
943
+ pusher = new Pusher(key, disconectTimeout);
944
+ }
945
+ return pusher;
946
+ };
947
+ const preconnect = (key) => {
948
+ getPusher(key).connect();
949
+ };
950
+
951
+ function pollStrategy({ token, publicKey, baseURL, integration, userAgent, retryThrottledRequestMaxTimes, onProgress, signal }) {
952
+ return poll({
953
+ check: (signal) => fromUrlStatus(token, {
954
+ publicKey,
955
+ baseURL,
956
+ integration,
957
+ userAgent,
958
+ retryThrottledRequestMaxTimes,
959
+ signal
960
+ }).then((response) => {
961
+ switch (response.status) {
962
+ case Status.Error: {
963
+ return new UploadClientError(response.error, response.errorCode);
964
+ }
965
+ case Status.Waiting: {
966
+ return false;
967
+ }
968
+ case Status.Unknown: {
969
+ return new UploadClientError(`Token "${token}" was not found.`);
970
+ }
971
+ case Status.Progress: {
972
+ if (onProgress) {
973
+ if (response.total === 'unknown') {
974
+ onProgress({ isComputable: false });
975
+ }
976
+ else {
977
+ onProgress({
978
+ isComputable: true,
979
+ value: response.done / response.total
980
+ });
981
+ }
982
+ }
983
+ return false;
984
+ }
985
+ case Status.Success: {
986
+ if (onProgress)
987
+ onProgress({
988
+ isComputable: true,
989
+ value: response.done / response.total
990
+ });
991
+ return response;
992
+ }
993
+ default: {
994
+ throw new Error('Unknown status');
995
+ }
996
+ }
997
+ }),
998
+ signal
999
+ });
1000
+ }
1001
+ const pushStrategy = ({ token, pusherKey, signal, onProgress }) => new Promise((resolve, reject) => {
1002
+ const pusher = getPusher(pusherKey);
1003
+ const unsubErrorHandler = pusher.onError(reject);
1004
+ const destroy = () => {
1005
+ unsubErrorHandler();
1006
+ pusher.unsubscribe(token);
1007
+ };
1008
+ onCancel(signal, () => {
1009
+ destroy();
1010
+ reject(cancelError('pusher cancelled'));
1011
+ });
1012
+ pusher.subscribe(token, (result) => {
1013
+ switch (result.status) {
1014
+ case Status.Progress: {
1015
+ if (onProgress) {
1016
+ if (result.total === 'unknown') {
1017
+ onProgress({ isComputable: false });
1018
+ }
1019
+ else {
1020
+ onProgress({
1021
+ isComputable: true,
1022
+ value: result.done / result.total
1023
+ });
1024
+ }
1025
+ }
1026
+ break;
1027
+ }
1028
+ case Status.Success: {
1029
+ destroy();
1030
+ if (onProgress)
1031
+ onProgress({
1032
+ isComputable: true,
1033
+ value: result.done / result.total
1034
+ });
1035
+ resolve(result);
1036
+ break;
1037
+ }
1038
+ case Status.Error: {
1039
+ destroy();
1040
+ reject(new UploadClientError(result.msg, result.error_code));
1041
+ }
1042
+ }
1043
+ });
1044
+ });
1045
+ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, checkForUrlDuplicates, saveUrlForRecurrentUploads, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, pusherKey = defaultSettings.pusherKey, metadata }) => Promise.resolve(preconnect(pusherKey))
1046
+ .then(() => fromUrl(sourceUrl, {
1047
+ publicKey,
1048
+ fileName,
1049
+ baseURL,
1050
+ checkForUrlDuplicates,
1051
+ saveUrlForRecurrentUploads,
1052
+ secureSignature,
1053
+ secureExpire,
1054
+ store,
1055
+ signal,
1056
+ source,
1057
+ integration,
1058
+ userAgent,
1059
+ retryThrottledRequestMaxTimes,
1060
+ metadata
1061
+ }))
1062
+ .catch((error) => {
1063
+ const pusher = getPusher(pusherKey);
1064
+ pusher === null || pusher === void 0 ? void 0 : pusher.disconnect();
1065
+ return Promise.reject(error);
1066
+ })
1067
+ .then((urlResponse) => {
1068
+ if (urlResponse.type === TypeEnum.FileInfo) {
1069
+ return urlResponse;
1070
+ }
1071
+ else {
1072
+ return race([
1073
+ ({ signal }) => pollStrategy({
1074
+ token: urlResponse.token,
1075
+ publicKey,
1076
+ baseURL,
1077
+ integration,
1078
+ userAgent,
1079
+ retryThrottledRequestMaxTimes,
1080
+ onProgress,
1081
+ signal
1082
+ }),
1083
+ ({ signal }) => pushStrategy({
1084
+ token: urlResponse.token,
1085
+ pusherKey,
1086
+ signal,
1087
+ onProgress
1088
+ })
1089
+ ], { signal });
1090
+ }
1091
+ })
1092
+ .then((result) => {
1093
+ if (result instanceof UploadClientError)
1094
+ throw result;
1095
+ return result;
1096
+ })
1097
+ .then((result) => isReadyPoll({
1098
+ file: result.uuid,
1099
+ publicKey,
1100
+ baseURL,
1101
+ integration,
1102
+ userAgent,
1103
+ retryThrottledRequestMaxTimes,
1104
+ onProgress,
1105
+ signal
1106
+ }))
1107
+ .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
1108
+
1109
+ const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, baseCDN }) => {
1110
+ return info(uuid, {
1111
+ publicKey,
1112
+ baseURL,
1113
+ signal,
1114
+ source,
1115
+ integration,
1116
+ userAgent,
1117
+ retryThrottledRequestMaxTimes
1118
+ })
1119
+ .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
1120
+ .then((result) => {
1121
+ // hack for node ¯\_(ツ)_/¯
1122
+ if (onProgress)
1123
+ onProgress({
1124
+ isComputable: true,
1125
+ value: 1
1126
+ });
1127
+ return result;
1128
+ });
1129
+ };
1130
+
1131
+ /**
1132
+ * Get file size.
1133
+ */
1134
+ const getFileSize = (file) => {
1135
+ return file.length || file.size;
1136
+ };
1137
+ /**
1138
+ * Check if FileData is multipart data.
1139
+ */
1140
+ const isMultipart = (fileSize, multipartMinFileSize = defaultSettings.multipartMinFileSize) => {
1141
+ return fileSize >= multipartMinFileSize;
1142
+ };
1143
+
1144
+ const sliceChunk = (file, index, fileSize, chunkSize) => {
1145
+ const start = chunkSize * index;
1146
+ const end = Math.min(start + chunkSize, fileSize);
1147
+ return file.slice(start, end);
1148
+ };
1149
+
1150
+ /**
1151
+ * React-native hack for blob slicing
1152
+ *
1153
+ * We need to store references to sliced blobs to prevent source blob from
1154
+ * being deallocated until uploading complete. Access to deallocated blob
1155
+ * causes app crash.
1156
+ *
1157
+ * See https://github.com/uploadcare/uploadcare-upload-client/issues/306
1158
+ * and https://github.com/facebook/react-native/issues/27543
1159
+ */
1160
+ function prepareChunks(file, fileSize, chunkSize) {
1161
+ return (index) => {
1162
+ const chunk = sliceChunk(file, index, fileSize, chunkSize);
1163
+ return chunk;
1164
+ };
1165
+ }
1166
+
1167
+ const runWithConcurrency = (concurrency, tasks) => {
1168
+ return new Promise((resolve, reject) => {
1169
+ const results = [];
1170
+ let rejected = false;
1171
+ let settled = tasks.length;
1172
+ const forRun = [...tasks];
1173
+ const run = () => {
1174
+ const index = tasks.length - forRun.length;
1175
+ const next = forRun.shift();
1176
+ if (next) {
1177
+ next()
1178
+ .then((result) => {
1179
+ if (rejected)
1180
+ return;
1181
+ results[index] = result;
1182
+ settled -= 1;
1183
+ if (settled) {
1184
+ run();
1185
+ }
1186
+ else {
1187
+ resolve(results);
1188
+ }
1189
+ })
1190
+ .catch((error) => {
1191
+ rejected = true;
1192
+ reject(error);
1193
+ });
1194
+ }
1195
+ };
1196
+ for (let i = 0; i < concurrency; i++) {
1197
+ run();
1198
+ }
1199
+ });
1200
+ };
1201
+
1202
+ const uploadPartWithRetry = (chunk, url, { publicKey, onProgress, signal, integration, multipartMaxAttempts }) => retrier(({ attempt, retry }) => multipartUpload(chunk, url, {
1203
+ publicKey,
1204
+ onProgress,
1205
+ signal,
1206
+ integration
1207
+ }).catch((error) => {
1208
+ if (attempt < multipartMaxAttempts) {
1209
+ return retry();
1210
+ }
1211
+ throw error;
1212
+ }));
1213
+ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, contentType, multipartChunkSize = defaultSettings.multipartChunkSize, maxConcurrentRequests = defaultSettings.maxConcurrentRequests, multipartMaxAttempts = defaultSettings.multipartMaxAttempts, baseCDN, metadata }) => {
1214
+ const size = fileSize || getFileSize(file);
1215
+ let progressValues;
1216
+ const createProgressHandler = (totalChunks, chunkIdx) => {
1217
+ if (!onProgress)
1218
+ return;
1219
+ if (!progressValues) {
1220
+ progressValues = Array(totalChunks).fill(0);
1221
+ }
1222
+ const sum = (values) => values.reduce((sum, next) => sum + next, 0);
1223
+ return (info) => {
1224
+ if (!info.isComputable) {
1225
+ return;
1226
+ }
1227
+ progressValues[chunkIdx] = info.value;
1228
+ onProgress({
1229
+ isComputable: true,
1230
+ value: sum(progressValues) / totalChunks
1231
+ });
1232
+ };
1233
+ };
1234
+ return multipartStart(size, {
1235
+ publicKey,
1236
+ contentType,
1237
+ fileName: fileName !== null && fileName !== void 0 ? fileName : file.name,
1238
+ baseURL,
1239
+ secureSignature,
1240
+ secureExpire,
1241
+ store,
1242
+ signal,
1243
+ source,
1244
+ integration,
1245
+ userAgent,
1246
+ retryThrottledRequestMaxTimes,
1247
+ metadata
1248
+ })
1249
+ .then(({ uuid, parts }) => {
1250
+ const getChunk = prepareChunks(file, size, multipartChunkSize);
1251
+ return Promise.all([
1252
+ uuid,
1253
+ runWithConcurrency(maxConcurrentRequests, parts.map((url, index) => () => uploadPartWithRetry(getChunk(index), url, {
1254
+ publicKey,
1255
+ onProgress: createProgressHandler(parts.length, index),
1256
+ signal,
1257
+ integration,
1258
+ multipartMaxAttempts
1259
+ })))
1260
+ ]);
1261
+ })
1262
+ .then(([uuid]) => multipartComplete(uuid, {
1263
+ publicKey,
1264
+ baseURL,
1265
+ source,
1266
+ integration,
1267
+ userAgent,
1268
+ retryThrottledRequestMaxTimes
1269
+ }))
1270
+ .then((fileInfo) => {
1271
+ if (fileInfo.isReady) {
1272
+ return fileInfo;
1273
+ }
1274
+ else {
1275
+ return isReadyPoll({
1276
+ file: fileInfo.uuid,
1277
+ publicKey,
1278
+ baseURL,
1279
+ source,
1280
+ integration,
1281
+ userAgent,
1282
+ retryThrottledRequestMaxTimes,
1283
+ onProgress,
1284
+ signal
1285
+ });
1286
+ }
1287
+ })
1288
+ .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
1289
+ };
1290
+
1291
+ /**
1292
+ * Uploads file from provided data.
1293
+ */
1294
+ function uploadFile(data, { publicKey, fileName, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, contentType, multipartMinFileSize, multipartChunkSize, multipartMaxAttempts, maxConcurrentRequests, baseCDN = defaultSettings.baseCDN, checkForUrlDuplicates, saveUrlForRecurrentUploads, pusherKey, metadata }) {
1295
+ if (isFileData(data)) {
1296
+ const fileSize = getFileSize(data);
1297
+ if (isMultipart(fileSize, multipartMinFileSize)) {
1298
+ return uploadMultipart(data, {
1299
+ publicKey,
1300
+ contentType,
1301
+ multipartChunkSize,
1302
+ multipartMaxAttempts,
1303
+ fileName,
1304
+ baseURL,
1305
+ secureSignature,
1306
+ secureExpire,
1307
+ store,
1308
+ signal,
1309
+ onProgress,
1310
+ source,
1311
+ integration,
1312
+ userAgent,
1313
+ maxConcurrentRequests,
1314
+ retryThrottledRequestMaxTimes,
1315
+ baseCDN,
1316
+ metadata
1317
+ });
1318
+ }
1319
+ return uploadDirect(data, {
1320
+ publicKey,
1321
+ fileName,
1322
+ contentType,
1323
+ baseURL,
1324
+ secureSignature,
1325
+ secureExpire,
1326
+ store,
1327
+ signal,
1328
+ onProgress,
1329
+ source,
1330
+ integration,
1331
+ userAgent,
1332
+ retryThrottledRequestMaxTimes,
1333
+ baseCDN,
1334
+ metadata
1335
+ });
1336
+ }
1337
+ if (isUrl(data)) {
1338
+ return uploadFromUrl(data, {
1339
+ publicKey,
1340
+ fileName,
1341
+ baseURL,
1342
+ baseCDN,
1343
+ checkForUrlDuplicates,
1344
+ saveUrlForRecurrentUploads,
1345
+ secureSignature,
1346
+ secureExpire,
1347
+ store,
1348
+ signal,
1349
+ onProgress,
1350
+ source,
1351
+ integration,
1352
+ userAgent,
1353
+ retryThrottledRequestMaxTimes,
1354
+ pusherKey,
1355
+ metadata
1356
+ });
1357
+ }
1358
+ if (isUuid(data)) {
1359
+ return uploadFromUploaded(data, {
1360
+ publicKey,
1361
+ fileName,
1362
+ baseURL,
1363
+ signal,
1364
+ onProgress,
1365
+ source,
1366
+ integration,
1367
+ userAgent,
1368
+ retryThrottledRequestMaxTimes,
1369
+ baseCDN
1370
+ });
1371
+ }
1372
+ throw new TypeError(`File uploading from "${data}" is not supported`);
1373
+ }
1374
+
1375
+ class UploadcareGroup {
1376
+ constructor(groupInfo, files) {
1377
+ this.storedAt = null;
1378
+ this.uuid = groupInfo.id;
1379
+ this.filesCount = groupInfo.filesCount;
1380
+ this.totalSize = Object.values(groupInfo.files).reduce((acc, file) => acc + file.size, 0);
1381
+ this.isStored = !!groupInfo.datetimeStored;
1382
+ this.isImage = !!Object.values(groupInfo.files).filter((file) => file.isImage).length;
1383
+ this.cdnUrl = groupInfo.cdnUrl;
1384
+ this.files = files;
1385
+ this.createdAt = groupInfo.datetimeCreated;
1386
+ this.storedAt = groupInfo.datetimeStored;
1387
+ }
1388
+ }
1389
+
1390
+ /**
1391
+ * FileData type guard.
1392
+ */
1393
+ const isFileDataArray = (data) => {
1394
+ for (const item of data) {
1395
+ if (!isFileData(item)) {
1396
+ return false;
1397
+ }
1398
+ }
1399
+ return true;
1400
+ };
1401
+ /**
1402
+ * Uuid type guard.
1403
+ */
1404
+ const isUuidArray = (data) => {
1405
+ for (const item of data) {
1406
+ if (!isUuid(item)) {
1407
+ return false;
1408
+ }
1409
+ }
1410
+ return true;
1411
+ };
1412
+ /**
1413
+ * Url type guard.
1414
+ */
1415
+ const isUrlArray = (data) => {
1416
+ for (const item of data) {
1417
+ if (!isUrl(item)) {
1418
+ return false;
1419
+ }
1420
+ }
1421
+ return true;
1422
+ };
1423
+
1424
+ function uploadFileGroup(data, { publicKey, fileName, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, contentType, multipartChunkSize = defaultSettings.multipartChunkSize, baseCDN = defaultSettings.baseCDN, jsonpCallback }) {
1425
+ if (!isFileDataArray(data) && !isUrlArray(data) && !isUuidArray(data)) {
1426
+ throw new TypeError(`Group uploading from "${data}" is not supported`);
1427
+ }
1428
+ let progressValues;
1429
+ let isStillComputable = true;
1430
+ const filesCount = data.length;
1431
+ const createProgressHandler = (size, index) => {
1432
+ if (!onProgress)
1433
+ return;
1434
+ if (!progressValues) {
1435
+ progressValues = Array(size).fill(0);
1436
+ }
1437
+ const normalize = (values) => values.reduce((sum, next) => sum + next) / size;
1438
+ return (info) => {
1439
+ if (!info.isComputable || !isStillComputable) {
1440
+ isStillComputable = false;
1441
+ onProgress({ isComputable: false });
1442
+ return;
1443
+ }
1444
+ progressValues[index] = info.value;
1445
+ onProgress({ isComputable: true, value: normalize(progressValues) });
1446
+ };
1447
+ };
1448
+ return Promise.all(data.map((file, index) => uploadFile(file, {
1449
+ publicKey,
1450
+ fileName,
1451
+ baseURL,
1452
+ secureSignature,
1453
+ secureExpire,
1454
+ store,
1455
+ signal,
1456
+ onProgress: createProgressHandler(filesCount, index),
1457
+ source,
1458
+ integration,
1459
+ userAgent,
1460
+ retryThrottledRequestMaxTimes,
1461
+ contentType,
1462
+ multipartChunkSize,
1463
+ baseCDN
1464
+ }))).then((files) => {
1465
+ const uuids = files.map((file) => file.uuid);
1466
+ return group(uuids, {
1467
+ publicKey,
1468
+ baseURL,
1469
+ jsonpCallback,
1470
+ secureSignature,
1471
+ secureExpire,
1472
+ signal,
1473
+ source,
1474
+ integration,
1475
+ userAgent,
1476
+ retryThrottledRequestMaxTimes
1477
+ })
1478
+ .then((groupInfo) => new UploadcareGroup(groupInfo, files))
1479
+ .then((group) => {
1480
+ onProgress && onProgress({ isComputable: true, value: 1 });
1481
+ return group;
1482
+ });
1483
+ });
1484
+ }
1485
+
1486
+ /**
1487
+ * Populate options with settings.
1488
+ */
1489
+ const populateOptionsWithSettings = (options, settings) => (Object.assign(Object.assign({}, settings), options));
1490
+ class UploadClient {
1491
+ constructor(settings) {
1492
+ this.settings = Object.assign({}, defaultSettings, settings);
1493
+ }
1494
+ updateSettings(newSettings) {
1495
+ this.settings = Object.assign(this.settings, newSettings);
1496
+ }
1497
+ getSettings() {
1498
+ return this.settings;
1499
+ }
1500
+ base(file, options = {}) {
1501
+ const settings = this.getSettings();
1502
+ return base(file, populateOptionsWithSettings(options, settings));
1503
+ }
1504
+ info(uuid, options = {}) {
1505
+ const settings = this.getSettings();
1506
+ return info(uuid, populateOptionsWithSettings(options, settings));
1507
+ }
1508
+ fromUrl(sourceUrl, options = {}) {
1509
+ const settings = this.getSettings();
1510
+ return fromUrl(sourceUrl, populateOptionsWithSettings(options, settings));
1511
+ }
1512
+ fromUrlStatus(token, options = {}) {
1513
+ const settings = this.getSettings();
1514
+ return fromUrlStatus(token, populateOptionsWithSettings(options, settings));
1515
+ }
1516
+ group(uuids, options = {}) {
1517
+ const settings = this.getSettings();
1518
+ return group(uuids, populateOptionsWithSettings(options, settings));
1519
+ }
1520
+ groupInfo(id, options = {}) {
1521
+ const settings = this.getSettings();
1522
+ return groupInfo(id, populateOptionsWithSettings(options, settings));
1523
+ }
1524
+ multipartStart(size, options = {}) {
1525
+ const settings = this.getSettings();
1526
+ return multipartStart(size, populateOptionsWithSettings(options, settings));
1527
+ }
1528
+ multipartUpload(part, url, options = {}) {
1529
+ const settings = this.getSettings();
1530
+ return multipartUpload(part, url, populateOptionsWithSettings(options, settings));
1531
+ }
1532
+ multipartComplete(uuid, options = {}) {
1533
+ const settings = this.getSettings();
1534
+ return multipartComplete(uuid, populateOptionsWithSettings(options, settings));
1535
+ }
1536
+ uploadFile(data, options = {}) {
1537
+ const settings = this.getSettings();
1538
+ return uploadFile(data, populateOptionsWithSettings(options, settings));
1539
+ }
1540
+ uploadFileGroup(data, options = {}) {
1541
+ const settings = this.getSettings();
1542
+ return uploadFileGroup(data, populateOptionsWithSettings(options, settings));
1543
+ }
1544
+ }
1545
+
1546
+ export { UploadClient, UploadClientError, UploadcareFile, UploadcareGroup, base, fromUrl, fromUrlStatus, group, groupInfo, info, multipartComplete, multipartStart, multipartUpload, uploadDirect, uploadFile, uploadFileGroup, uploadFromUploaded, uploadFromUrl, uploadMultipart };