@uploadcare/upload-client 4.0.1 → 4.2.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Uploadcare Upload Client
2
2
 
3
- <a href="https://uploadcare.com/?utm_source=github&utm_campaign=uploadcare-upload-client">
3
+ <a href="https://uploadcare.com/?utm_source=github&utm_campaign=uploadcare-js-api-clients">
4
4
  <img align="right" width="64" height="64"
5
5
  src="https://ucarecdn.com/edfdf045-34c0-4087-bbdd-e3834921f890/userpiccircletransparent.svg"
6
6
  alt="">
@@ -9,6 +9,8 @@
9
9
  This is an Uploadcare [Upload API][uc-docs-upload-api] wrapper to work with
10
10
  Node.js and browser.
11
11
 
12
+ [API Reference](https://uploadcare.github.io/uploadcare-js-api-clients/upload-client/)
13
+
12
14
  [![Build Status][badge-build]][build-url]
13
15
  [![NPM version][npm-img]][npm-url]
14
16
  [![GitHub release][badge-release-img]][badge-release-url]&nbsp;
@@ -227,11 +229,11 @@ fromUrlStatus(
227
229
  ```
228
230
 
229
231
  ```typescript
230
- group(uuids: Uuid[], options: GroupOptions): Promise<GroupInfo>
232
+ group(uuids: Uuid[], options: GroupOptions): Promise<GroupInfo>
231
233
  ```
232
234
 
233
235
  ```typescript
234
- groupInfo(id: GroupId, options: GroupInfoOptions): Promise<GroupInfo>
236
+ groupInfo(id: GroupId, options: GroupInfoOptions): Promise<GroupInfo>
235
237
  ```
236
238
 
237
239
  ```typescript
@@ -461,16 +463,16 @@ request at [hello@uploadcare.com][uc-email-hello].
461
463
 
462
464
  [uc-email-bounty]: mailto:bugbounty@uploadcare.com
463
465
  [uc-email-hello]: mailto:hello@uploadcare.com
464
- [github-releases]: https://github.com/uploadcare/uploadcare-upload-client/releases
465
- [github-branch-release]: https://github.com/uploadcare/uploadcare-upload-client/tree/release
466
- [github-contributors]: https://github.com/uploadcare/uploadcare-upload-client/graphs/contributors
466
+ [github-releases]: https://github.com/uploadcare/uploadcare-js-api-clients/releases
467
+ [github-branch-release]: https://github.com/uploadcare/uploadcare-js-api-clients/tree/release
468
+ [github-contributors]: https://github.com/uploadcare/uploadcare-js-api-clients/graphs/contributors
467
469
  [badge-stack-img]: https://img.shields.io/badge/tech-stack-0690fa.svg?style=flat
468
470
  [badge-stack-url]: https://stackshare.io/uploadcare/stacks/
469
- [badge-release-img]: https://img.shields.io/github/release/uploadcare/uploadcare-upload-client.svg
470
- [badge-release-url]: https://github.com/uploadcare/uploadcare-upload-client/releases
471
+ [badge-release-img]: https://img.shields.io/github/release/uploadcare/uploadcare-js-api-clients.svg
472
+ [badge-release-url]: https://github.com/uploadcare/uploadcare-js-api-clients/releases
471
473
  [npm-img]: http://img.shields.io/npm/v/@uploadcare/upload-client.svg
472
474
  [npm-url]: https://www.npmjs.org/package/@uploadcare/upload-client
473
- [badge-build]: https://github.com/uploadcare/uploadcare-upload-client/actions/workflows/checks.yml/badge.svg
474
- [build-url]: https://github.com/uploadcare/uploadcare-upload-client/actions/workflows/checks.yml
475
- [uc-docs-upload-api]: https://uploadcare.com/docs/api_reference/upload/?utm_source=github&utm_campaign=uploadcare-upload-client
475
+ [badge-build]: https://github.com/uploadcare/uploadcare-js-api-clients/actions/workflows/checks.yml/badge.svg
476
+ [build-url]: https://github.com/uploadcare/uploadcare-js-api-clients/actions/workflows/checks.yml
477
+ [uc-docs-upload-api]: https://uploadcare.com/docs/api_reference/upload/?utm_source=github&utm_campaign=uploadcare-js-api-clients
476
478
  [uc-docs-metadata]: https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-Metadata
@@ -29,7 +29,7 @@ const onCancel = (signal, callback) => {
29
29
 
30
30
  const request = ({ method, url, data, headers = {}, signal, onProgress }) => new Promise((resolve, reject) => {
31
31
  const xhr = new XMLHttpRequest();
32
- const requestMethod = (method === null || method === void 0 ? void 0 : method.toUpperCase()) || 'GET';
32
+ const requestMethod = method?.toUpperCase() || 'GET';
33
33
  let aborted = false;
34
34
  xhr.open(requestMethod, url);
35
35
  if (headers) {
@@ -197,34 +197,36 @@ function buildFormData(options) {
197
197
  return formData;
198
198
  }
199
199
 
200
- const serializePair = (key, value) => typeof value !== 'undefined' ? `${key}=${encodeURIComponent(value)}` : null;
201
- // TODO: generalize value transforming logic and use it here and inside `buildFormData`
202
- const createQuery = (query) => Object.entries(query)
203
- .reduce((params, [key, value]) => {
204
- let param;
205
- if (typeof value === 'object' && !Array.isArray(value)) {
206
- param = Object.entries(value)
207
- .filter((entry) => typeof entry[1] !== 'undefined')
208
- .map((entry) => serializePair(`${key}[${entry[0]}]`, String(entry[1])));
209
- }
210
- else if (Array.isArray(value)) {
211
- param = value.map((val) => serializePair(`${key}[]`, val));
200
+ const buildSearchParams = (query) => {
201
+ const searchParams = new URLSearchParams();
202
+ for (const [key, value] of Object.entries(query)) {
203
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
204
+ Object.entries(value)
205
+ .filter((entry) => entry[1] ?? false)
206
+ .forEach((entry) => searchParams.set(`${key}[${entry[0]}]`, String(entry[1])));
207
+ }
208
+ else if (Array.isArray(value)) {
209
+ value.forEach((val) => {
210
+ searchParams.append(`${key}[]`, val);
211
+ });
212
+ }
213
+ else if (typeof value === 'string' && value) {
214
+ searchParams.set(key, value);
215
+ }
216
+ else if (typeof value === 'number') {
217
+ searchParams.set(key, value.toString());
218
+ }
212
219
  }
213
- else {
214
- param = serializePair(key, value);
220
+ return searchParams.toString();
221
+ };
222
+ const getUrl = (base, path, query) => {
223
+ const url = new URL(base);
224
+ url.pathname = path;
225
+ if (query) {
226
+ url.search = buildSearchParams(query);
215
227
  }
216
- return params.concat(param);
217
- }, [])
218
- .filter((x) => !!x)
219
- .join('&');
220
- const getUrl = (base, path, query) => [
221
- base,
222
- path,
223
- query && Object.keys(query).length > 0 ? '?' : '',
224
- query && createQuery(query)
225
- ]
226
- .filter(Boolean)
227
- .join('');
228
+ return url.toString();
229
+ };
228
230
 
229
231
  /*
230
232
  Settings for future support:
@@ -246,14 +248,59 @@ const defaultSettings = {
246
248
  const defaultContentType = 'application/octet-stream';
247
249
  const defaultFilename = 'original';
248
250
 
249
- var version = '4.0.1';
251
+ var version = '4.2.1';
252
+
253
+ function isObject(o) {
254
+ return Object.prototype.toString.call(o) === '[object Object]';
255
+ }
256
+
257
+ const SEPARATOR = /\W|_/g;
258
+ function camelizeString(text) {
259
+ return text
260
+ .split(SEPARATOR)
261
+ .map((word, index) => word.charAt(0)[index > 0 ? 'toUpperCase' : 'toLowerCase']() +
262
+ word.slice(1))
263
+ .join('');
264
+ }
265
+ function camelizeArrayItems(array, { ignoreKeys } = { ignoreKeys: [] }) {
266
+ if (!Array.isArray(array)) {
267
+ return array;
268
+ }
269
+ return array.map((item) => camelizeKeys(item, { ignoreKeys }));
270
+ }
271
+ function camelizeKeys(source, { ignoreKeys } = { ignoreKeys: [] }) {
272
+ if (Array.isArray(source)) {
273
+ return camelizeArrayItems(source, { ignoreKeys });
274
+ }
275
+ if (!isObject(source)) {
276
+ return source;
277
+ }
278
+ const result = {};
279
+ for (const key of Object.keys(source)) {
280
+ let value = source[key];
281
+ if (ignoreKeys.includes(key)) {
282
+ result[key] = value;
283
+ continue;
284
+ }
285
+ if (isObject(value)) {
286
+ value = camelizeKeys(value, { ignoreKeys });
287
+ }
288
+ else if (Array.isArray(value)) {
289
+ value = camelizeArrayItems(value, { ignoreKeys });
290
+ }
291
+ result[camelizeString(key)] = value;
292
+ }
293
+ return result;
294
+ }
250
295
 
251
296
  /**
252
- * Returns User Agent based on version and settings.
297
+ * setTimeout as Promise.
298
+ *
299
+ * @param {number} ms Timeout in milliseconds.
253
300
  */
254
- function getUserAgent({ userAgent, publicKey = '', integration = '' } = {}) {
255
- const libraryName = 'UploadcareUploadClient';
256
- const libraryVersion = version;
301
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
302
+
303
+ function getUserAgent$1({ libraryName, libraryVersion, userAgent, publicKey = '', integration = '' }) {
257
304
  const languageName = 'JavaScript';
258
305
  if (typeof userAgent === 'string') {
259
306
  return userAgent;
@@ -274,39 +321,6 @@ function getUserAgent({ userAgent, publicKey = '', integration = '' } = {}) {
274
321
  return `${mainInfo} (${additionInfo})`;
275
322
  }
276
323
 
277
- const SEPARATOR = /\W|_/g;
278
- /**
279
- * Transforms a string to camelCased.
280
- */
281
- function camelize(text) {
282
- return text
283
- .split(SEPARATOR)
284
- .map((word, index) => word.charAt(0)[index > 0 ? 'toUpperCase' : 'toLowerCase']() +
285
- word.slice(1))
286
- .join('');
287
- }
288
- /**
289
- * Transforms keys of an object to camelCased recursively.
290
- */
291
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
- function camelizeKeys(source) {
293
- if (!source || typeof source !== 'object') {
294
- return source;
295
- }
296
- return Object.keys(source).reduce((accumulator, key) => {
297
- accumulator[camelize(key)] =
298
- typeof source[key] === 'object' ? camelizeKeys(source[key]) : source[key];
299
- return accumulator;
300
- }, {});
301
- }
302
-
303
- /**
304
- * setTimeout as Promise.
305
- *
306
- * @param {number} ms Timeout in milliseconds.
307
- */
308
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
309
-
310
324
  const defaultOptions = {
311
325
  factor: 2,
312
326
  time: 100
@@ -314,8 +328,8 @@ const defaultOptions = {
314
328
  function retrier(fn, options = defaultOptions) {
315
329
  let attempts = 0;
316
330
  function runAttempt(fn) {
317
- const defaultDelayTime = Math.round(options.time * Math.pow(options.factor, attempts));
318
- const retry = (ms) => delay(ms !== null && ms !== void 0 ? ms : defaultDelayTime).then(() => {
331
+ const defaultDelayTime = Math.round(options.time * options.factor ** attempts);
332
+ const retry = (ms) => delay(ms ?? defaultDelayTime).then(() => {
319
333
  attempts += 1;
320
334
  return runAttempt(fn);
321
335
  });
@@ -327,6 +341,16 @@ function retrier(fn, options = defaultOptions) {
327
341
  return runAttempt(fn);
328
342
  }
329
343
 
344
+ const LIBRARY_NAME = 'UploadcareUploadClient';
345
+ const LIBRARY_VERSION = version;
346
+ function getUserAgent(options) {
347
+ return getUserAgent$1({
348
+ libraryName: LIBRARY_NAME,
349
+ libraryVersion: LIBRARY_VERSION,
350
+ ...options
351
+ });
352
+ }
353
+
330
354
  const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
331
355
  const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
332
356
  function getTimeoutFromThrottledRequest(error) {
@@ -338,7 +362,7 @@ function getTimeoutFromThrottledRequest(error) {
338
362
  function retryIfThrottled(fn, retryThrottledMaxTimes) {
339
363
  return retrier(({ attempt, retry }) => fn().catch((error) => {
340
364
  if ('response' in error &&
341
- (error === null || error === void 0 ? void 0 : error.code) === REQUEST_WAS_THROTTLED_CODE &&
365
+ error?.code === REQUEST_WAS_THROTTLED_CODE &&
342
366
  attempt < retryThrottledMaxTimes) {
343
367
  return retry(getTimeoutFromThrottledRequest(error));
344
368
  }
@@ -355,41 +379,38 @@ function getStoreValue(store) {
355
379
  * Can be canceled and has progress.
356
380
  */
357
381
  function base(file, { publicKey, fileName, contentType, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, metadata }) {
358
- return retryIfThrottled(() => {
359
- var _a;
360
- return request({
361
- method: 'POST',
362
- url: getUrl(baseURL, '/base/', {
363
- jsonerrors: 1
364
- }),
365
- headers: {
366
- 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
382
+ return retryIfThrottled(() => request({
383
+ method: 'POST',
384
+ url: getUrl(baseURL, '/base/', {
385
+ jsonerrors: 1
386
+ }),
387
+ headers: {
388
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
389
+ },
390
+ data: buildFormData({
391
+ file: {
392
+ data: file,
393
+ name: fileName ?? file.name ?? defaultFilename,
394
+ contentType
367
395
  },
368
- data: buildFormData({
369
- file: {
370
- data: file,
371
- name: (_a = fileName !== null && fileName !== void 0 ? fileName : file.name) !== null && _a !== void 0 ? _a : defaultFilename,
372
- contentType
373
- },
374
- UPLOADCARE_PUB_KEY: publicKey,
375
- UPLOADCARE_STORE: getStoreValue(store),
376
- signature: secureSignature,
377
- expire: secureExpire,
378
- source: source,
379
- metadata
380
- }),
381
- signal,
382
- onProgress
383
- }).then(({ data, headers, request }) => {
384
- const response = camelizeKeys(JSON.parse(data));
385
- if ('error' in response) {
386
- throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
387
- }
388
- else {
389
- return response;
390
- }
391
- });
392
- }, retryThrottledRequestMaxTimes);
396
+ UPLOADCARE_PUB_KEY: publicKey,
397
+ UPLOADCARE_STORE: getStoreValue(store),
398
+ signature: secureSignature,
399
+ expire: secureExpire,
400
+ source: source,
401
+ metadata
402
+ }),
403
+ signal,
404
+ onProgress
405
+ }).then(({ data, headers, request }) => {
406
+ const response = camelizeKeys(JSON.parse(data));
407
+ if ('error' in response) {
408
+ throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
409
+ }
410
+ else {
411
+ return response;
412
+ }
413
+ }), retryThrottledRequestMaxTimes);
393
414
  }
394
415
 
395
416
  var TypeEnum;
@@ -568,9 +589,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
568
589
  'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
569
590
  },
570
591
  data: buildFormData({
571
- filename: fileName !== null && fileName !== void 0 ? fileName : defaultFilename,
592
+ filename: fileName ?? defaultFilename,
572
593
  size: size,
573
- content_type: contentType !== null && contentType !== void 0 ? contentType : defaultContentType,
594
+ content_type: contentType ?? defaultContentType,
574
595
  part_size: multipartChunkSize,
575
596
  UPLOADCARE_STORE: getStoreValue(store),
576
597
  UPLOADCARE_PUB_KEY: publicKey,
@@ -672,9 +693,9 @@ class UploadcareFile {
672
693
  this.mimeType = fileInfo.mimeType;
673
694
  this.cdnUrl = cdnUrl;
674
695
  this.originalFilename = fileInfo.originalFilename;
675
- this.imageInfo = camelizeKeys(fileInfo.imageInfo);
676
- this.videoInfo = camelizeKeys(fileInfo.videoInfo);
677
- this.contentInfo = camelizeKeys(fileInfo.contentInfo);
696
+ this.imageInfo = fileInfo.imageInfo;
697
+ this.videoInfo = fileInfo.videoInfo;
698
+ this.contentInfo = fileInfo.contentInfo;
678
699
  this.metadata = fileInfo.metadata || null;
679
700
  this.s3Bucket = s3Bucket || null;
680
701
  this.s3Url = s3Url;
@@ -802,8 +823,7 @@ class Events {
802
823
  this.events = Object.create({});
803
824
  }
804
825
  emit(event, data) {
805
- var _a;
806
- (_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.forEach((fn) => fn(data));
826
+ this.events[event]?.forEach((fn) => fn(data));
807
827
  }
808
828
  on(event, callback) {
809
829
  this.events[event] = this.events[event] || [];
@@ -821,12 +841,12 @@ class Events {
821
841
 
822
842
  const response = (type, data) => {
823
843
  if (type === 'success') {
824
- return Object.assign({ status: Status.Success }, data);
844
+ return { status: Status.Success, ...data };
825
845
  }
826
846
  if (type === 'progress') {
827
- return Object.assign({ status: Status.Progress }, data);
847
+ return { status: Status.Progress, ...data };
828
848
  }
829
- return Object.assign({ status: Status.Error }, data);
849
+ return { status: Status.Error, ...data };
830
850
  };
831
851
  class Pusher {
832
852
  constructor(pusherKey, disconnectTime = 30000) {
@@ -874,8 +894,7 @@ class Pusher {
874
894
  }
875
895
  disconnect() {
876
896
  const actualDisconect = () => {
877
- var _a;
878
- (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
897
+ this.ws?.close();
879
898
  this.ws = undefined;
880
899
  this.isConnected = false;
881
900
  };
@@ -889,9 +908,8 @@ class Pusher {
889
908
  }
890
909
  }
891
910
  send(event, data) {
892
- var _a;
893
911
  const str = JSON.stringify({ event, data });
894
- (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(str);
912
+ this.ws?.send(str);
895
913
  }
896
914
  subscribe(token, handler) {
897
915
  this.subscribers += 1;
@@ -1058,7 +1076,7 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
1058
1076
  }))
1059
1077
  .catch((error) => {
1060
1078
  const pusher = getPusher(pusherKey);
1061
- pusher === null || pusher === void 0 ? void 0 : pusher.disconnect();
1079
+ pusher?.disconnect();
1062
1080
  return Promise.reject(error);
1063
1081
  })
1064
1082
  .then((urlResponse) => {
@@ -1218,7 +1236,7 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1218
1236
  return multipartStart(size, {
1219
1237
  publicKey,
1220
1238
  contentType,
1221
- fileName: fileName !== null && fileName !== void 0 ? fileName : file.name,
1239
+ fileName: fileName ?? file.name,
1222
1240
  baseURL,
1223
1241
  secureSignature,
1224
1242
  secureExpire,
@@ -1470,7 +1488,10 @@ function uploadFileGroup(data, { publicKey, fileName, baseURL = defaultSettings.
1470
1488
  /**
1471
1489
  * Populate options with settings.
1472
1490
  */
1473
- const populateOptionsWithSettings = (options, settings) => (Object.assign(Object.assign({}, settings), options));
1491
+ const populateOptionsWithSettings = (options, settings) => ({
1492
+ ...settings,
1493
+ ...options
1494
+ });
1474
1495
  class UploadClient {
1475
1496
  constructor(settings) {
1476
1497
  this.settings = Object.assign({}, defaultSettings, settings);