@uploadcare/upload-client 6.0.1-alpha.0 → 6.0.1-alpha.10

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.
@@ -1,7 +1,7 @@
1
1
  import http from 'http';
2
2
  import https from 'https';
3
- import { parse } from 'url';
4
3
  import { Transform, Readable } from 'stream';
4
+ import { parse } from 'url';
5
5
  import NodeFormData from 'form-data';
6
6
  import WebSocket from 'ws';
7
7
 
@@ -164,6 +164,26 @@ const poll = ({ check, interval = DEFAULT_INTERVAL, timeout, signal }) => new Pr
164
164
  tickTimeoutId = setTimeout(tick, 0);
165
165
  });
166
166
 
167
+ /*
168
+ Settings for future support:
169
+ parallelDirectUploads: 10,
170
+ */
171
+ const defaultSettings = {
172
+ baseCDN: 'https://ucarecdn.com',
173
+ baseURL: 'https://upload.uploadcare.com',
174
+ maxContentLength: 50 * 1024 * 1024,
175
+ retryThrottledRequestMaxTimes: 1,
176
+ retryNetworkErrorMaxTimes: 3,
177
+ multipartMinFileSize: 25 * 1024 * 1024,
178
+ multipartChunkSize: 5 * 1024 * 1024,
179
+ multipartMinLastPartSize: 1024 * 1024,
180
+ maxConcurrentRequests: 4,
181
+ pollingTimeoutMilliseconds: 10000,
182
+ pusherKey: '79ae88bd931ea68464d9'
183
+ };
184
+ const defaultContentType = 'application/octet-stream';
185
+ const defaultFilename = 'original';
186
+
167
187
  // ProgressEmitter is a simple PassThrough-style transform stream which keeps
168
188
  // track of the number of bytes which have been piped through it and will
169
189
  // invoke the `onprogress` function whenever new number are available.
@@ -285,33 +305,24 @@ const getFileOptions = ({ name, contentType }) => [
285
305
  const transformFile = identity;
286
306
  var getFormData = () => new NodeFormData();
287
307
 
288
- /**
289
- * FileData type guard.
290
- */
291
- const isFileData = (data) => {
292
- return (data !== undefined &&
293
- ((typeof Blob !== 'undefined' && data instanceof Blob) ||
294
- (typeof File !== 'undefined' && data instanceof File) ||
295
- (typeof Buffer !== 'undefined' && data instanceof Buffer) ||
296
- (typeof data === 'string' && data.startsWith('file:'))));
308
+ const isBlob = (data) => {
309
+ return typeof Blob !== 'undefined' && data instanceof Blob;
297
310
  };
298
- /**
299
- * Uuid type guard.
300
- */
301
- const isUuid = (data) => {
302
- const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
303
- const regExp = new RegExp(UUID_REGEX);
304
- return !isFileData(data) && regExp.test(data);
311
+ const isFile = (data) => {
312
+ return typeof File !== 'undefined' && data instanceof File;
305
313
  };
306
- /**
307
- * Url type guard.
308
- *
309
- * @param {NodeFile | BrowserFile | Url | Uuid} data
310
- */
311
- const isUrl = (data) => {
312
- const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
313
- const regExp = new RegExp(URL_REGEX);
314
- return !isFileData(data) && regExp.test(data);
314
+ const isBuffer = (data) => {
315
+ return typeof Buffer !== 'undefined' && data instanceof Buffer;
316
+ };
317
+ const isReactNativeAsset = (data) => {
318
+ return (!!data &&
319
+ typeof data === 'object' &&
320
+ !Array.isArray(data) &&
321
+ 'uri' in data &&
322
+ typeof data.uri === 'string');
323
+ };
324
+ const isFileData = (data) => {
325
+ return (isBlob(data) || isFile(data) || isBuffer(data) || isReactNativeAsset(data));
315
326
  };
316
327
 
317
328
  const isSimpleValue = (value) => {
@@ -362,6 +373,19 @@ function buildFormData(options) {
362
373
  return formData;
363
374
  }
364
375
 
376
+ class UploadClientError extends Error {
377
+ constructor(message, code, request, response, headers) {
378
+ super();
379
+ this.name = 'UploadClientError';
380
+ this.message = message;
381
+ this.code = code;
382
+ this.request = request;
383
+ this.response = response;
384
+ this.headers = headers;
385
+ Object.setPrototypeOf(this, UploadClientError.prototype);
386
+ }
387
+ }
388
+
365
389
  const buildSearchParams = (query) => {
366
390
  const searchParams = new URLSearchParams();
367
391
  for (const [key, value] of Object.entries(query)) {
@@ -393,26 +417,6 @@ const getUrl = (base, path, query) => {
393
417
  return url.toString();
394
418
  };
395
419
 
396
- /*
397
- Settings for future support:
398
- parallelDirectUploads: 10,
399
- */
400
- const defaultSettings = {
401
- baseCDN: 'https://ucarecdn.com',
402
- baseURL: 'https://upload.uploadcare.com',
403
- maxContentLength: 50 * 1024 * 1024,
404
- retryThrottledRequestMaxTimes: 1,
405
- retryNetworkErrorMaxTimes: 3,
406
- multipartMinFileSize: 25 * 1024 * 1024,
407
- multipartChunkSize: 5 * 1024 * 1024,
408
- multipartMinLastPartSize: 1024 * 1024,
409
- maxConcurrentRequests: 4,
410
- pollingTimeoutMilliseconds: 10000,
411
- pusherKey: '79ae88bd931ea68464d9'
412
- };
413
- const defaultContentType = 'application/octet-stream';
414
- const defaultFilename = 'original';
415
-
416
420
  var version = '6.0.0';
417
421
 
418
422
  const LIBRARY_NAME = 'UploadcareUploadClient';
@@ -425,19 +429,6 @@ function getUserAgent(options) {
425
429
  });
426
430
  }
427
431
 
428
- class UploadClientError extends Error {
429
- constructor(message, code, request, response, headers) {
430
- super();
431
- this.name = 'UploadClientError';
432
- this.message = message;
433
- this.code = code;
434
- this.request = request;
435
- this.response = response;
436
- this.headers = headers;
437
- Object.setPrototypeOf(this, UploadClientError.prototype);
438
- }
439
- }
440
-
441
432
  const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
442
433
  const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
443
434
  const DEFAULT_NETWORK_ERROR_TIMEOUT = 1000;
@@ -468,6 +459,36 @@ function retryIfFailed(fn, options) {
468
459
  }));
469
460
  }
470
461
 
462
+ const getContentType = (file) => {
463
+ let contentType = '';
464
+ if (isBlob(file) || isFile(file) || isReactNativeAsset(file)) {
465
+ contentType = file.type;
466
+ }
467
+ if (contentType) {
468
+ return contentType;
469
+ }
470
+ console.warn(`Cannot determine content type for ${file}. Using default content type: ${defaultContentType}`);
471
+ return defaultContentType;
472
+ };
473
+
474
+ const getFilename = (file) => {
475
+ let filename = '';
476
+ if (isBlob(file) || isBuffer(file)) {
477
+ filename = '';
478
+ }
479
+ else if (isFile(file) && file.name) {
480
+ filename = file.name;
481
+ }
482
+ else if (isReactNativeAsset(file) && file.name) {
483
+ filename = file.name;
484
+ }
485
+ if (filename) {
486
+ return filename;
487
+ }
488
+ console.warn(`Cannot determine filename for ${file}. Using default filename: ${defaultFilename}`);
489
+ return defaultFilename;
490
+ };
491
+
471
492
  function getStoreValue(store) {
472
493
  return typeof store === 'undefined' ? 'auto' : store ? '1' : '0';
473
494
  }
@@ -488,8 +509,8 @@ function base(file, { publicKey, fileName, contentType, baseURL = defaultSetting
488
509
  data: buildFormData({
489
510
  file: {
490
511
  data: file,
491
- name: fileName ?? file.name ?? defaultFilename,
492
- contentType
512
+ name: fileName || getFilename(file),
513
+ contentType: contentType || getContentType(file)
493
514
  },
494
515
  UPLOADCARE_PUB_KEY: publicKey,
495
516
  UPLOADCARE_STORE: getStoreValue(store),
@@ -687,9 +708,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
687
708
  'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
688
709
  },
689
710
  data: buildFormData({
690
- filename: fileName ?? defaultFilename,
711
+ filename: fileName || defaultFilename,
691
712
  size: size,
692
- content_type: contentType ?? defaultContentType,
713
+ content_type: contentType || defaultContentType,
693
714
  part_size: multipartChunkSize,
694
715
  UPLOADCARE_STORE: getStoreValue(store),
695
716
  UPLOADCARE_PUB_KEY: publicKey,
@@ -766,6 +787,28 @@ function multipartComplete(uuid, { publicKey, baseURL = defaultSettings.baseURL,
766
787
  }), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
767
788
  }
768
789
 
790
+ function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
791
+ return poll({
792
+ check: (signal) => info(file, {
793
+ publicKey,
794
+ baseURL,
795
+ signal,
796
+ source,
797
+ integration,
798
+ userAgent,
799
+ retryThrottledRequestMaxTimes,
800
+ retryNetworkErrorMaxTimes
801
+ }).then((response) => {
802
+ if (response.isReady) {
803
+ return response;
804
+ }
805
+ onProgress && onProgress({ isComputable: true, value: 1 });
806
+ return false;
807
+ }),
808
+ signal
809
+ });
810
+ }
811
+
769
812
  class UploadcareFile {
770
813
  constructor(fileInfo, { baseCDN, fileName }) {
771
814
  this.name = null;
@@ -803,28 +846,6 @@ class UploadcareFile {
803
846
  }
804
847
  }
805
848
 
806
- function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
807
- return poll({
808
- check: (signal) => info(file, {
809
- publicKey,
810
- baseURL,
811
- signal,
812
- source,
813
- integration,
814
- userAgent,
815
- retryThrottledRequestMaxTimes,
816
- retryNetworkErrorMaxTimes
817
- }).then((response) => {
818
- if (response.isReady) {
819
- return response;
820
- }
821
- onProgress && onProgress({ isComputable: true, value: 1 });
822
- return false;
823
- }),
824
- signal
825
- });
826
- }
827
-
828
849
  const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, secureExpire, store, contentType, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN, metadata }) => {
829
850
  return base(file, {
830
851
  publicKey,
@@ -860,6 +881,29 @@ const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, sec
860
881
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
861
882
  };
862
883
 
884
+ const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
885
+ return info(uuid, {
886
+ publicKey,
887
+ baseURL,
888
+ signal,
889
+ source,
890
+ integration,
891
+ userAgent,
892
+ retryThrottledRequestMaxTimes,
893
+ retryNetworkErrorMaxTimes
894
+ })
895
+ .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
896
+ .then((result) => {
897
+ // hack for node ¯\_(ツ)_/¯
898
+ if (onProgress)
899
+ onProgress({
900
+ isComputable: true,
901
+ value: 1
902
+ });
903
+ return result;
904
+ });
905
+ };
906
+
863
907
  const race = (fns, { signal } = {}) => {
864
908
  let lastError = null;
865
909
  let winnerIndex = null;
@@ -1197,35 +1241,30 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
1197
1241
  }))
1198
1242
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
1199
1243
 
1200
- const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
1201
- return info(uuid, {
1202
- publicKey,
1203
- baseURL,
1204
- signal,
1205
- source,
1206
- integration,
1207
- userAgent,
1208
- retryThrottledRequestMaxTimes,
1209
- retryNetworkErrorMaxTimes
1210
- })
1211
- .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
1212
- .then((result) => {
1213
- // hack for node ¯\_(ツ)_/¯
1214
- if (onProgress)
1215
- onProgress({
1216
- isComputable: true,
1217
- value: 1
1218
- });
1219
- return result;
1220
- });
1244
+ const memo = new WeakMap();
1245
+ const getReactNativeBlob = async (asset) => {
1246
+ if (memo.has(asset)) {
1247
+ return memo.get(asset);
1248
+ }
1249
+ const blob = await fetch(asset.uri).then((res) => res.blob());
1250
+ memo.set(asset, blob);
1251
+ return blob;
1221
1252
  };
1222
1253
 
1223
- /**
1224
- * Get file size.
1225
- */
1226
- const getFileSize = (file) => {
1227
- return file.length || file.size;
1254
+ const getFileSize = async (file) => {
1255
+ if (isBuffer(file)) {
1256
+ return file.length;
1257
+ }
1258
+ if (isFile(file) || isBlob(file)) {
1259
+ return file.size;
1260
+ }
1261
+ if (isReactNativeAsset(file)) {
1262
+ const blob = await getReactNativeBlob(file);
1263
+ return blob.size;
1264
+ }
1265
+ throw new Error(`Failed to get file size for file: ${file}`);
1228
1266
  };
1267
+
1229
1268
  /**
1230
1269
  * Check if FileData is multipart data.
1231
1270
  */
@@ -1233,15 +1272,24 @@ const isMultipart = (fileSize, multipartMinFileSize = defaultSettings.multipartM
1233
1272
  return fileSize >= multipartMinFileSize;
1234
1273
  };
1235
1274
 
1236
- const sliceChunk = (file, index, fileSize, chunkSize) => {
1237
- const start = chunkSize * index;
1238
- const end = Math.min(start + chunkSize, fileSize);
1239
- return file.slice(start, end);
1275
+ /**
1276
+ * Uuid type guard.
1277
+ */
1278
+ const isUuid = (data) => {
1279
+ const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
1280
+ const regExp = new RegExp(UUID_REGEX);
1281
+ return !isFileData(data) && regExp.test(data);
1282
+ };
1283
+ /**
1284
+ * Url type guard.
1285
+ *
1286
+ * @param {AnyFile | Url | Uuid} data
1287
+ */
1288
+ const isUrl = (data) => {
1289
+ const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
1290
+ const regExp = new RegExp(URL_REGEX);
1291
+ return !isFileData(data) && regExp.test(data);
1240
1292
  };
1241
-
1242
- function prepareChunks(file, fileSize, chunkSize) {
1243
- return (index) => sliceChunk(file, index, fileSize, chunkSize);
1244
- }
1245
1293
 
1246
1294
  const runWithConcurrency = (concurrency, tasks) => {
1247
1295
  return new Promise((resolve, reject) => {
@@ -1278,6 +1326,16 @@ const runWithConcurrency = (concurrency, tasks) => {
1278
1326
  });
1279
1327
  };
1280
1328
 
1329
+ const sliceChunk = (file, index, fileSize, chunkSize) => {
1330
+ const start = chunkSize * index;
1331
+ const end = Math.min(start + chunkSize, fileSize);
1332
+ return file.slice(start, end);
1333
+ };
1334
+
1335
+ const prepareChunks = async (file, fileSize, chunkSize) => {
1336
+ return (index) => sliceChunk(file, index, fileSize, chunkSize);
1337
+ };
1338
+
1281
1339
  const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes }) => multipartUpload(chunk, url, {
1282
1340
  publicKey,
1283
1341
  onProgress,
@@ -1286,8 +1344,8 @@ const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, re
1286
1344
  retryThrottledRequestMaxTimes,
1287
1345
  retryNetworkErrorMaxTimes
1288
1346
  });
1289
- 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 }) => {
1290
- const size = fileSize || getFileSize(file);
1347
+ 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 }) => {
1348
+ const size = fileSize || await getFileSize(file);
1291
1349
  let progressValues;
1292
1350
  const createProgressHandler = (totalChunks, chunkIdx) => {
1293
1351
  if (!onProgress)
@@ -1309,8 +1367,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1309
1367
  };
1310
1368
  return multipartStart(size, {
1311
1369
  publicKey,
1312
- contentType,
1313
- fileName: fileName ?? file.name,
1370
+ contentType: contentType || getContentType(file),
1371
+ fileName: fileName || getFilename(file),
1314
1372
  baseURL,
1315
1373
  secureSignature,
1316
1374
  secureExpire,
@@ -1323,8 +1381,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1323
1381
  retryNetworkErrorMaxTimes,
1324
1382
  metadata
1325
1383
  })
1326
- .then(({ uuid, parts }) => {
1327
- const getChunk = prepareChunks(file, size, multipartChunkSize);
1384
+ .then(async ({ uuid, parts }) => {
1385
+ const getChunk = await prepareChunks(file, size, multipartChunkSize);
1328
1386
  return Promise.all([
1329
1387
  uuid,
1330
1388
  runWithConcurrency(maxConcurrentRequests, parts.map((url, index) => () => uploadPart(getChunk(index), url, {
@@ -1371,14 +1429,15 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1371
1429
  /**
1372
1430
  * Uploads file from provided data.
1373
1431
  */
1374
- 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 }) {
1432
+ 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 }) {
1375
1433
  if (isFileData(data)) {
1376
- const fileSize = getFileSize(data);
1434
+ const fileSize = await getFileSize(data);
1377
1435
  if (isMultipart(fileSize, multipartMinFileSize)) {
1378
1436
  return uploadMultipart(data, {
1379
1437
  publicKey,
1380
1438
  contentType,
1381
1439
  multipartChunkSize,
1440
+ fileSize,
1382
1441
  fileName,
1383
1442
  baseURL,
1384
1443
  secureSignature,