@uploadcare/upload-client 6.0.1-alpha.8 → 6.1.0-alpha.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.
package/README.md CHANGED
@@ -23,6 +23,7 @@ Node.js and browser.
23
23
  - [High-Level API](#high-level-api)
24
24
  - [Low-Level API](#low-level-api)
25
25
  - [Settings](#settings)
26
+ - [React Native](#react-native)
26
27
  - [Testing](#testing)
27
28
  - [Security issues](#security-issues)
28
29
  - [Feedback](#feedback)
@@ -124,7 +125,7 @@ interface UploadClient {
124
125
  getSettings(): Settings
125
126
 
126
127
  base(
127
- file: NodeFile | BrowserFile,
128
+ file: Blob | File | Buffer | ReactNativeAsset,
128
129
  options: BaseOptions
129
130
  ): Promise<BaseResponse>
130
131
 
@@ -158,12 +159,12 @@ interface UploadClient {
158
159
  ): Promise<FileInfo>
159
160
 
160
161
  uploadFile(
161
- data: NodeFile | BrowserFile | Url | Uuid,
162
+ data: Blob | File | Buffer | ReactNativeAsset | Url | Uuid,
162
163
  options: FileFromOptions
163
164
  ): Promise<UploadcareFile>
164
165
 
165
166
  uploadFileGroup(
166
- data: (NodeFile | BrowserFile)[] | Url[] | Uuid[],
167
+ data: (Blob | File | Buffer | ReactNativeAsset)[] | Url[] | Uuid[],
167
168
  options: FileFromOptions & GroupFromOptions
168
169
  ): Promise<UploadcareGroup>
169
170
  }
@@ -208,7 +209,7 @@ List of all available API methods:
208
209
 
209
210
  ```typescript
210
211
  base(
211
- file: NodeFile | BrowserFile,
212
+ file: Blob | File | Buffer | ReactNativeAsset,
212
213
  options: BaseOptions
213
214
  ): Promise<BaseResponse>
214
215
  ```
@@ -245,7 +246,7 @@ multipartStart(
245
246
 
246
247
  ```typescript
247
248
  multipartUpload(
248
- part: Buffer | Blob,
249
+ part: Buffer | Blob | File,
249
250
  url: MultipartPart,
250
251
  options: MultipartUploadOptions
251
252
  ): Promise<MultipartUploadResponse>
@@ -288,6 +289,7 @@ Defaults to `https://upload.uploadcare.com`
288
289
  #### `fileName: string`
289
290
 
290
291
  You can specify an original filename.
292
+ It could useful when file input does not contain filename.
291
293
 
292
294
  Defaults to `original`.
293
295
 
@@ -408,7 +410,7 @@ Defaults to `4`.
408
410
 
409
411
  ### `contentType: string`
410
412
 
411
- This setting is needed for correct multipart uploads.
413
+ This option is useful when file input does not contain content type.
412
414
 
413
415
  Defaults to `application/octet-stream`.
414
416
 
@@ -426,6 +428,37 @@ Non-string values will be converted to `string`. `undefined` values will be igno
426
428
 
427
429
  See [docs][uc-file-metadata] and [REST API][uc-docs-metadata] for details.
428
430
 
431
+ ## React Native
432
+
433
+ To be able to use `@uploadcare/upload-client` with React Native, you need to
434
+ install [react-native-url-polyfill][react-native-url-polyfill].
435
+
436
+ To prevent [`Error: Cannot create URL for blob`][react-native-url-polyfill-issue]
437
+ errors you need to configure your Android app schema to accept blobs -
438
+ have a look at this pull request for an example: [5985d7e][react-native-url-polyfill-example].
439
+
440
+ You can use `ReactNativeAsset` as an input to the `@uploadcare/upload-client` like this:
441
+
442
+ ```ts
443
+ type ReactNativeAsset = {
444
+ uri: string
445
+ type: string
446
+ name?: string
447
+ }
448
+ ```
449
+
450
+ ```ts
451
+ const asset = { uri: 'URI_TO_FILE', name: 'file.txt', type: 'text/plain' }
452
+ uploadFile(asset, { publicKey: 'YOUR_PUBLIC_KEY' })
453
+ ```
454
+
455
+ Or `Blob` like this:
456
+
457
+ ```ts
458
+ const uri = 'URI_TO_FILE'
459
+ const blob = await fetch(uri).then((res) => res.blob())
460
+ uploadFile(blob, { publicKey: 'YOUR_PUBLIC_KEY' })
461
+ ```
429
462
 
430
463
  ## Testing
431
464
 
@@ -490,3 +523,6 @@ request at [hello@uploadcare.com][uc-email-hello].
490
523
  [uc-docs-upload-api]: https://uploadcare.com/docs/api_reference/upload/?utm_source=github&utm_campaign=uploadcare-js-api-clients
491
524
  [uc-docs-metadata]: https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-Metadata
492
525
  [uc-file-metadata]: https://uploadcare.com/docs/file-metadata/
526
+ [react-native-url-polyfill]: https://github.com/charpeni/react-native-url-polyfill
527
+ [react-native-url-polyfill-issue]: https://github.com/charpeni/react-native-url-polyfill/issues/284
528
+ [react-native-url-polyfill-example]: https://github.com/charpeni/react-native-url-polyfill/commit/5985d7efc07b496b829883540d09c6f0be384387
@@ -157,6 +157,26 @@ const poll = ({ check, interval = DEFAULT_INTERVAL, timeout, signal }) => new Pr
157
157
  tickTimeoutId = setTimeout(tick, 0);
158
158
  });
159
159
 
160
+ /*
161
+ Settings for future support:
162
+ parallelDirectUploads: 10,
163
+ */
164
+ const defaultSettings = {
165
+ baseCDN: 'https://ucarecdn.com',
166
+ baseURL: 'https://upload.uploadcare.com',
167
+ maxContentLength: 50 * 1024 * 1024,
168
+ retryThrottledRequestMaxTimes: 1,
169
+ retryNetworkErrorMaxTimes: 3,
170
+ multipartMinFileSize: 25 * 1024 * 1024,
171
+ multipartChunkSize: 5 * 1024 * 1024,
172
+ multipartMinLastPartSize: 1024 * 1024,
173
+ maxConcurrentRequests: 4,
174
+ pollingTimeoutMilliseconds: 10000,
175
+ pusherKey: '79ae88bd931ea68464d9'
176
+ };
177
+ const defaultContentType = 'application/octet-stream';
178
+ const defaultFilename = 'original';
179
+
160
180
  const request = ({ method, url, data, headers = {}, signal, onProgress }) => new Promise((resolve, reject) => {
161
181
  const xhr = new XMLHttpRequest();
162
182
  const requestMethod = method?.toUpperCase() || 'GET';
@@ -168,14 +188,12 @@ const request = ({ method, url, data, headers = {}, signal, onProgress }) => new
168
188
  * and https://bugs.chromium.org/p/chromium/issues/detail?id=1346628
169
189
  */
170
190
  xhr.open(requestMethod, url, true);
171
- console.log(`xhr.open(${requestMethod}, ${url}, true)`);
172
191
  if (headers) {
173
192
  Object.entries(headers).forEach((entry) => {
174
193
  const [key, value] = entry;
175
194
  typeof value !== 'undefined' &&
176
195
  !Array.isArray(value) &&
177
196
  xhr.setRequestHeader(key, value);
178
- console.log(`xhr.setRequestHeader(${key}, ${value})`);
179
197
  });
180
198
  }
181
199
  xhr.responseType = 'text';
@@ -227,7 +245,6 @@ const request = ({ method, url, data, headers = {}, signal, onProgress }) => new
227
245
  xhr.onerror = (progressEvent) => {
228
246
  if (aborted)
229
247
  return;
230
- console.log('aboirt', xhr, progressEvent);
231
248
  // only triggers if the request couldn't be made at all
232
249
  reject(new UploadcareNetworkError(progressEvent));
233
250
  };
@@ -245,7 +262,6 @@ const request = ({ method, url, data, headers = {}, signal, onProgress }) => new
245
262
  };
246
263
  }
247
264
  if (data) {
248
- console.log(`xhr.send(${data})`);
249
265
  xhr.send(data);
250
266
  }
251
267
  else {
@@ -253,7 +269,8 @@ const request = ({ method, url, data, headers = {}, signal, onProgress }) => new
253
269
  }
254
270
  });
255
271
 
256
- function identity(obj) {
272
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
273
+ function identity(obj, ..._args) {
257
274
  return obj;
258
275
  }
259
276
 
@@ -261,49 +278,24 @@ const getFileOptions = ({ name }) => name ? [name] : [];
261
278
  const transformFile = identity;
262
279
  var getFormData = () => new FormData();
263
280
 
264
- const isReactNativeUri = (uri) => {
265
- if (typeof uri !== 'string') {
266
- return false;
267
- }
268
- return uri.startsWith('file:') || uri.startsWith('content:');
281
+ const isBlob = (data) => {
282
+ return typeof Blob !== 'undefined' && data instanceof Blob;
269
283
  };
270
- const isReactNativeAsset = (asset) => {
271
- return (!!asset &&
272
- typeof asset === 'object' &&
273
- !Array.isArray(asset) &&
274
- 'uri' in asset &&
275
- typeof asset.uri === 'string' &&
276
- isReactNativeUri(asset.uri));
284
+ const isFile = (data) => {
285
+ return typeof File !== 'undefined' && data instanceof File;
277
286
  };
278
-
279
- /**
280
- * FileData type guard.
281
- */
282
- const isFileData = (data) => {
283
- return (data !== undefined &&
284
- ((typeof Blob !== 'undefined' && data instanceof Blob) ||
285
- (typeof File !== 'undefined' && data instanceof File) ||
286
- (typeof Buffer !== 'undefined' && data instanceof Buffer) ||
287
- (typeof data === 'string' && isReactNativeUri(data)) ||
288
- (typeof data === 'object' && isReactNativeAsset(data))));
287
+ const isBuffer = (data) => {
288
+ return typeof Buffer !== 'undefined' && data instanceof Buffer;
289
289
  };
290
- /**
291
- * Uuid type guard.
292
- */
293
- const isUuid = (data) => {
294
- const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
295
- const regExp = new RegExp(UUID_REGEX);
296
- return !isFileData(data) && regExp.test(data);
290
+ const isReactNativeAsset = (data) => {
291
+ return (!!data &&
292
+ typeof data === 'object' &&
293
+ !Array.isArray(data) &&
294
+ 'uri' in data &&
295
+ typeof data.uri === 'string');
297
296
  };
298
- /**
299
- * Url type guard.
300
- *
301
- * @param {NodeFile | BrowserFile | Url | Uuid} data
302
- */
303
- const isUrl = (data) => {
304
- const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
305
- const regExp = new RegExp(URL_REGEX);
306
- return !isFileData(data) && regExp.test(data);
297
+ const isFileData = (data) => {
298
+ return (isBlob(data) || isFile(data) || isBuffer(data) || isReactNativeAsset(data));
307
299
  };
308
300
 
309
301
  const isSimpleValue = (value) => {
@@ -321,7 +313,7 @@ const isFileValue = (value) => !!value &&
321
313
  function collectParams(params, inputKey, inputValue) {
322
314
  if (isFileValue(inputValue)) {
323
315
  const { name, contentType } = inputValue;
324
- const file = transformFile(inputValue.data); // lgtm [js/superfluous-trailing-arguments]
316
+ const file = transformFile(inputValue.data, name, contentType);
325
317
  const options = getFileOptions({ name, contentType });
326
318
  params.push([inputKey, file, ...options]);
327
319
  }
@@ -344,11 +336,9 @@ function getFormDataParams(options) {
344
336
  return params;
345
337
  }
346
338
  function buildFormData(options) {
347
- console.log('buildFormData', options);
348
339
  const formData = getFormData();
349
340
  const paramsList = getFormDataParams(options);
350
341
  for (const params of paramsList) {
351
- console.log('params', params);
352
342
  const [key, value, ...rest] = params;
353
343
  // node form-data has another signature for append
354
344
  formData.append(key, value, ...rest);
@@ -356,6 +346,19 @@ function buildFormData(options) {
356
346
  return formData;
357
347
  }
358
348
 
349
+ class UploadClientError extends Error {
350
+ constructor(message, code, request, response, headers) {
351
+ super();
352
+ this.name = 'UploadClientError';
353
+ this.message = message;
354
+ this.code = code;
355
+ this.request = request;
356
+ this.response = response;
357
+ this.headers = headers;
358
+ Object.setPrototypeOf(this, UploadClientError.prototype);
359
+ }
360
+ }
361
+
359
362
  const buildSearchParams = (query) => {
360
363
  const searchParams = new URLSearchParams();
361
364
  for (const [key, value] of Object.entries(query)) {
@@ -387,26 +390,6 @@ const getUrl = (base, path, query) => {
387
390
  return url.toString();
388
391
  };
389
392
 
390
- /*
391
- Settings for future support:
392
- parallelDirectUploads: 10,
393
- */
394
- const defaultSettings = {
395
- baseCDN: 'https://ucarecdn.com',
396
- baseURL: 'https://upload.uploadcare.com',
397
- maxContentLength: 50 * 1024 * 1024,
398
- retryThrottledRequestMaxTimes: 1,
399
- retryNetworkErrorMaxTimes: 3,
400
- multipartMinFileSize: 25 * 1024 * 1024,
401
- multipartChunkSize: 5 * 1024 * 1024,
402
- multipartMinLastPartSize: 1024 * 1024,
403
- maxConcurrentRequests: 4,
404
- pollingTimeoutMilliseconds: 10000,
405
- pusherKey: '79ae88bd931ea68464d9'
406
- };
407
- const defaultContentType = 'application/octet-stream';
408
- const defaultFilename = 'original';
409
-
410
393
  var version = '6.0.0';
411
394
 
412
395
  const LIBRARY_NAME = 'UploadcareUploadClient';
@@ -419,19 +402,6 @@ function getUserAgent(options) {
419
402
  });
420
403
  }
421
404
 
422
- class UploadClientError extends Error {
423
- constructor(message, code, request, response, headers) {
424
- super();
425
- this.name = 'UploadClientError';
426
- this.message = message;
427
- this.code = code;
428
- this.request = request;
429
- this.response = response;
430
- this.headers = headers;
431
- Object.setPrototypeOf(this, UploadClientError.prototype);
432
- }
433
- }
434
-
435
405
  const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
436
406
  const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
437
407
  const DEFAULT_NETWORK_ERROR_TIMEOUT = 1000;
@@ -462,6 +432,36 @@ function retryIfFailed(fn, options) {
462
432
  }));
463
433
  }
464
434
 
435
+ const getContentType = (file) => {
436
+ let contentType = '';
437
+ if (isBlob(file) || isFile(file) || isReactNativeAsset(file)) {
438
+ contentType = file.type;
439
+ }
440
+ if (contentType) {
441
+ return contentType;
442
+ }
443
+ console.warn(`Cannot determine content type. Using default content type: ${defaultContentType}`, file);
444
+ return defaultContentType;
445
+ };
446
+
447
+ const getFileName = (file) => {
448
+ let filename = '';
449
+ if (isFile(file) && file.name) {
450
+ filename = file.name;
451
+ }
452
+ else if (isBlob(file) || isBuffer(file)) {
453
+ filename = '';
454
+ }
455
+ else if (isReactNativeAsset(file) && file.name) {
456
+ filename = file.name;
457
+ }
458
+ if (filename) {
459
+ return filename;
460
+ }
461
+ console.warn(`Cannot determine filename. Using default filename: ${defaultFilename}`, file);
462
+ return defaultFilename;
463
+ };
464
+
465
465
  function getStoreValue(store) {
466
466
  return typeof store === 'undefined' ? 'auto' : store ? '1' : '0';
467
467
  }
@@ -477,13 +477,13 @@ function base(file, { publicKey, fileName, contentType, baseURL = defaultSetting
477
477
  jsonerrors: 1
478
478
  }),
479
479
  headers: {
480
- 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent }),
480
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
481
481
  },
482
482
  data: buildFormData({
483
483
  file: {
484
484
  data: file,
485
- name: fileName ?? file?.name ?? defaultFilename,
486
- contentType: contentType ?? file?.type ?? defaultContentType
485
+ name: fileName || getFileName(file),
486
+ contentType: contentType || getContentType(file)
487
487
  },
488
488
  UPLOADCARE_PUB_KEY: publicKey,
489
489
  UPLOADCARE_STORE: getStoreValue(store),
@@ -681,9 +681,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
681
681
  'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
682
682
  },
683
683
  data: buildFormData({
684
- filename: fileName ?? defaultFilename,
684
+ filename: fileName || defaultFilename,
685
685
  size: size,
686
- content_type: contentType ?? defaultContentType,
686
+ content_type: contentType || defaultContentType,
687
687
  part_size: multipartChunkSize,
688
688
  UPLOADCARE_STORE: getStoreValue(store),
689
689
  UPLOADCARE_PUB_KEY: publicKey,
@@ -760,6 +760,28 @@ function multipartComplete(uuid, { publicKey, baseURL = defaultSettings.baseURL,
760
760
  }), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
761
761
  }
762
762
 
763
+ function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
764
+ return poll({
765
+ check: (signal) => info(file, {
766
+ publicKey,
767
+ baseURL,
768
+ signal,
769
+ source,
770
+ integration,
771
+ userAgent,
772
+ retryThrottledRequestMaxTimes,
773
+ retryNetworkErrorMaxTimes
774
+ }).then((response) => {
775
+ if (response.isReady) {
776
+ return response;
777
+ }
778
+ onProgress && onProgress({ isComputable: true, value: 1 });
779
+ return false;
780
+ }),
781
+ signal
782
+ });
783
+ }
784
+
763
785
  class UploadcareFile {
764
786
  constructor(fileInfo, { baseCDN, fileName }) {
765
787
  this.name = null;
@@ -797,28 +819,6 @@ class UploadcareFile {
797
819
  }
798
820
  }
799
821
 
800
- function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
801
- return poll({
802
- check: (signal) => info(file, {
803
- publicKey,
804
- baseURL,
805
- signal,
806
- source,
807
- integration,
808
- userAgent,
809
- retryThrottledRequestMaxTimes,
810
- retryNetworkErrorMaxTimes
811
- }).then((response) => {
812
- if (response.isReady) {
813
- return response;
814
- }
815
- onProgress && onProgress({ isComputable: true, value: 1 });
816
- return false;
817
- }),
818
- signal
819
- });
820
- }
821
-
822
822
  const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, secureExpire, store, contentType, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN, metadata }) => {
823
823
  return base(file, {
824
824
  publicKey,
@@ -854,6 +854,29 @@ const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, sec
854
854
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
855
855
  };
856
856
 
857
+ const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
858
+ return info(uuid, {
859
+ publicKey,
860
+ baseURL,
861
+ signal,
862
+ source,
863
+ integration,
864
+ userAgent,
865
+ retryThrottledRequestMaxTimes,
866
+ retryNetworkErrorMaxTimes
867
+ })
868
+ .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
869
+ .then((result) => {
870
+ // hack for node ¯\_(ツ)_/¯
871
+ if (onProgress)
872
+ onProgress({
873
+ isComputable: true,
874
+ value: 1
875
+ });
876
+ return result;
877
+ });
878
+ };
879
+
857
880
  const race = (fns, { signal } = {}) => {
858
881
  let lastError = null;
859
882
  let winnerIndex = null;
@@ -1193,35 +1216,30 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
1193
1216
  }))
1194
1217
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
1195
1218
 
1196
- const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
1197
- return info(uuid, {
1198
- publicKey,
1199
- baseURL,
1200
- signal,
1201
- source,
1202
- integration,
1203
- userAgent,
1204
- retryThrottledRequestMaxTimes,
1205
- retryNetworkErrorMaxTimes
1206
- })
1207
- .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
1208
- .then((result) => {
1209
- // hack for node ¯\_(ツ)_/¯
1210
- if (onProgress)
1211
- onProgress({
1212
- isComputable: true,
1213
- value: 1
1214
- });
1215
- return result;
1216
- });
1219
+ const memo = new WeakMap();
1220
+ const getBlobFromReactNativeAsset = async (asset) => {
1221
+ if (memo.has(asset)) {
1222
+ return memo.get(asset);
1223
+ }
1224
+ const blob = await fetch(asset.uri).then((res) => res.blob());
1225
+ memo.set(asset, blob);
1226
+ return blob;
1217
1227
  };
1218
1228
 
1219
- /**
1220
- * Get file size.
1221
- */
1222
- const getFileSize = (file) => {
1223
- return file.length || file.size;
1229
+ const getFileSize = async (file) => {
1230
+ if (isBuffer(file)) {
1231
+ return file.length;
1232
+ }
1233
+ if (isFile(file) || isBlob(file)) {
1234
+ return file.size;
1235
+ }
1236
+ if (isReactNativeAsset(file)) {
1237
+ const blob = await getBlobFromReactNativeAsset(file);
1238
+ return blob.size;
1239
+ }
1240
+ throw new Error(`Unknown file type. Cannot determine file size.`);
1224
1241
  };
1242
+
1225
1243
  /**
1226
1244
  * Check if FileData is multipart data.
1227
1245
  */
@@ -1229,15 +1247,24 @@ const isMultipart = (fileSize, multipartMinFileSize = defaultSettings.multipartM
1229
1247
  return fileSize >= multipartMinFileSize;
1230
1248
  };
1231
1249
 
1232
- const sliceChunk = (file, index, fileSize, chunkSize) => {
1233
- const start = chunkSize * index;
1234
- const end = Math.min(start + chunkSize, fileSize);
1235
- return file.slice(start, end);
1250
+ /**
1251
+ * Uuid type guard.
1252
+ */
1253
+ const isUuid = (data) => {
1254
+ const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
1255
+ const regExp = new RegExp(UUID_REGEX);
1256
+ return !isFileData(data) && regExp.test(data);
1257
+ };
1258
+ /**
1259
+ * Url type guard.
1260
+ *
1261
+ * @param {SupportedFileInput | Url | Uuid} data
1262
+ */
1263
+ const isUrl = (data) => {
1264
+ const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
1265
+ const regExp = new RegExp(URL_REGEX);
1266
+ return !isFileData(data) && regExp.test(data);
1236
1267
  };
1237
-
1238
- function prepareChunks(file, fileSize, chunkSize) {
1239
- return (index) => sliceChunk(file, index, fileSize, chunkSize);
1240
- }
1241
1268
 
1242
1269
  const runWithConcurrency = (concurrency, tasks) => {
1243
1270
  return new Promise((resolve, reject) => {
@@ -1274,6 +1301,16 @@ const runWithConcurrency = (concurrency, tasks) => {
1274
1301
  });
1275
1302
  };
1276
1303
 
1304
+ const sliceChunk = (file, index, fileSize, chunkSize) => {
1305
+ const start = chunkSize * index;
1306
+ const end = Math.min(start + chunkSize, fileSize);
1307
+ return file.slice(start, end);
1308
+ };
1309
+
1310
+ const prepareChunks = async (file, fileSize, chunkSize) => {
1311
+ return (index) => sliceChunk(file, index, fileSize, chunkSize);
1312
+ };
1313
+
1277
1314
  const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes }) => multipartUpload(chunk, url, {
1278
1315
  publicKey,
1279
1316
  onProgress,
@@ -1282,8 +1319,8 @@ const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, re
1282
1319
  retryThrottledRequestMaxTimes,
1283
1320
  retryNetworkErrorMaxTimes
1284
1321
  });
1285
- const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, contentType, multipartChunkSize = defaultSettings.multipartChunkSize, maxConcurrentRequests = defaultSettings.maxConcurrentRequests, baseCDN, metadata }) => {
1286
- const size = fileSize || getFileSize(file);
1322
+ const uploadMultipart = async (file, { publicKey, fileName, fileSize, baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, contentType, multipartChunkSize = defaultSettings.multipartChunkSize, maxConcurrentRequests = defaultSettings.maxConcurrentRequests, baseCDN, metadata }) => {
1323
+ const size = fileSize ?? (await getFileSize(file));
1287
1324
  let progressValues;
1288
1325
  const createProgressHandler = (totalChunks, chunkIdx) => {
1289
1326
  if (!onProgress)
@@ -1305,8 +1342,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1305
1342
  };
1306
1343
  return multipartStart(size, {
1307
1344
  publicKey,
1308
- contentType,
1309
- fileName: fileName ?? file.name,
1345
+ contentType: contentType || getContentType(file),
1346
+ fileName: fileName || getFileName(file),
1310
1347
  baseURL,
1311
1348
  secureSignature,
1312
1349
  secureExpire,
@@ -1319,8 +1356,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1319
1356
  retryNetworkErrorMaxTimes,
1320
1357
  metadata
1321
1358
  })
1322
- .then(({ uuid, parts }) => {
1323
- const getChunk = prepareChunks(file, size, multipartChunkSize);
1359
+ .then(async ({ uuid, parts }) => {
1360
+ const getChunk = await prepareChunks(file, size, multipartChunkSize);
1324
1361
  return Promise.all([
1325
1362
  uuid,
1326
1363
  runWithConcurrency(maxConcurrentRequests, parts.map((url, index) => () => uploadPart(getChunk(index), url, {
@@ -1367,14 +1404,15 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1367
1404
  /**
1368
1405
  * Uploads file from provided data.
1369
1406
  */
1370
- function uploadFile(data, { publicKey, fileName, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, contentType, multipartMinFileSize, multipartChunkSize, maxConcurrentRequests, baseCDN = defaultSettings.baseCDN, checkForUrlDuplicates, saveUrlForRecurrentUploads, pusherKey, metadata }) {
1407
+ async function uploadFile(data, { publicKey, fileName, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, contentType, multipartMinFileSize, multipartChunkSize, maxConcurrentRequests, baseCDN = defaultSettings.baseCDN, checkForUrlDuplicates, saveUrlForRecurrentUploads, pusherKey, metadata }) {
1371
1408
  if (isFileData(data)) {
1372
- const fileSize = getFileSize(data);
1409
+ const fileSize = await getFileSize(data);
1373
1410
  if (isMultipart(fileSize, multipartMinFileSize)) {
1374
1411
  return uploadMultipart(data, {
1375
1412
  publicKey,
1376
1413
  contentType,
1377
1414
  multipartChunkSize,
1415
+ fileSize,
1378
1416
  fileName,
1379
1417
  baseURL,
1380
1418
  secureSignature,