@uploadcare/upload-client 6.0.1-alpha.8 → 6.0.1-alpha.9

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.
@@ -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 {
@@ -261,49 +277,24 @@ const getFileOptions = ({ name }) => name ? [name] : [];
261
277
  const transformFile = identity;
262
278
  var getFormData = () => new FormData();
263
279
 
264
- const isReactNativeUri = (uri) => {
265
- if (typeof uri !== 'string') {
266
- return false;
267
- }
268
- return uri.startsWith('file:') || uri.startsWith('content:');
280
+ const isBlob = (data) => {
281
+ return typeof Blob !== 'undefined' && data instanceof Blob;
269
282
  };
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));
283
+ const isFile = (data) => {
284
+ return typeof File !== 'undefined' && data instanceof File;
277
285
  };
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))));
286
+ const isBuffer = (data) => {
287
+ return typeof Buffer !== 'undefined' && data instanceof Buffer;
289
288
  };
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);
289
+ const isReactNativeAsset = (data) => {
290
+ return (!!data &&
291
+ typeof data === 'object' &&
292
+ !Array.isArray(data) &&
293
+ 'uri' in data &&
294
+ typeof data.uri === 'string');
297
295
  };
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);
296
+ const isFileData = (data) => {
297
+ return (isBlob(data) || isFile(data) || isBuffer(data) || isReactNativeAsset(data));
307
298
  };
308
299
 
309
300
  const isSimpleValue = (value) => {
@@ -344,11 +335,9 @@ function getFormDataParams(options) {
344
335
  return params;
345
336
  }
346
337
  function buildFormData(options) {
347
- console.log('buildFormData', options);
348
338
  const formData = getFormData();
349
339
  const paramsList = getFormDataParams(options);
350
340
  for (const params of paramsList) {
351
- console.log('params', params);
352
341
  const [key, value, ...rest] = params;
353
342
  // node form-data has another signature for append
354
343
  formData.append(key, value, ...rest);
@@ -356,6 +345,19 @@ function buildFormData(options) {
356
345
  return formData;
357
346
  }
358
347
 
348
+ class UploadClientError extends Error {
349
+ constructor(message, code, request, response, headers) {
350
+ super();
351
+ this.name = 'UploadClientError';
352
+ this.message = message;
353
+ this.code = code;
354
+ this.request = request;
355
+ this.response = response;
356
+ this.headers = headers;
357
+ Object.setPrototypeOf(this, UploadClientError.prototype);
358
+ }
359
+ }
360
+
359
361
  const buildSearchParams = (query) => {
360
362
  const searchParams = new URLSearchParams();
361
363
  for (const [key, value] of Object.entries(query)) {
@@ -387,26 +389,6 @@ const getUrl = (base, path, query) => {
387
389
  return url.toString();
388
390
  };
389
391
 
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
392
  var version = '6.0.0';
411
393
 
412
394
  const LIBRARY_NAME = 'UploadcareUploadClient';
@@ -419,19 +401,6 @@ function getUserAgent(options) {
419
401
  });
420
402
  }
421
403
 
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
404
  const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
436
405
  const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
437
406
  const DEFAULT_NETWORK_ERROR_TIMEOUT = 1000;
@@ -462,6 +431,31 @@ function retryIfFailed(fn, options) {
462
431
  }));
463
432
  }
464
433
 
434
+ const getContentType = (file) => {
435
+ if (isBlob(file) || isFile(file) || isReactNativeAsset(file)) {
436
+ return file.type || defaultContentType;
437
+ }
438
+ if (isBuffer(file)) {
439
+ return defaultFilename;
440
+ }
441
+ console.warn(`Unknown filename: ${file}. Using default filename: ${defaultFilename}`);
442
+ return defaultFilename;
443
+ };
444
+
445
+ const getFilename = (file) => {
446
+ if (isBlob(file) || isBuffer(file)) {
447
+ return defaultFilename;
448
+ }
449
+ if (isFile(file)) {
450
+ return file.name || defaultFilename;
451
+ }
452
+ if (isReactNativeAsset(file)) {
453
+ return file.name || defaultFilename;
454
+ }
455
+ console.warn(`Unknown file type: ${file}. Using default filename: ${defaultFilename}`);
456
+ return defaultFilename;
457
+ };
458
+
465
459
  function getStoreValue(store) {
466
460
  return typeof store === 'undefined' ? 'auto' : store ? '1' : '0';
467
461
  }
@@ -477,13 +471,13 @@ function base(file, { publicKey, fileName, contentType, baseURL = defaultSetting
477
471
  jsonerrors: 1
478
472
  }),
479
473
  headers: {
480
- 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent }),
474
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
481
475
  },
482
476
  data: buildFormData({
483
477
  file: {
484
478
  data: file,
485
- name: fileName ?? file?.name ?? defaultFilename,
486
- contentType: contentType ?? file?.type ?? defaultContentType
479
+ name: fileName || getFilename(file),
480
+ contentType: contentType || getContentType(file)
487
481
  },
488
482
  UPLOADCARE_PUB_KEY: publicKey,
489
483
  UPLOADCARE_STORE: getStoreValue(store),
@@ -681,9 +675,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
681
675
  'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
682
676
  },
683
677
  data: buildFormData({
684
- filename: fileName ?? defaultFilename,
678
+ filename: fileName || defaultFilename,
685
679
  size: size,
686
- content_type: contentType ?? defaultContentType,
680
+ content_type: contentType || defaultContentType,
687
681
  part_size: multipartChunkSize,
688
682
  UPLOADCARE_STORE: getStoreValue(store),
689
683
  UPLOADCARE_PUB_KEY: publicKey,
@@ -760,6 +754,28 @@ function multipartComplete(uuid, { publicKey, baseURL = defaultSettings.baseURL,
760
754
  }), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
761
755
  }
762
756
 
757
+ function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
758
+ return poll({
759
+ check: (signal) => info(file, {
760
+ publicKey,
761
+ baseURL,
762
+ signal,
763
+ source,
764
+ integration,
765
+ userAgent,
766
+ retryThrottledRequestMaxTimes,
767
+ retryNetworkErrorMaxTimes
768
+ }).then((response) => {
769
+ if (response.isReady) {
770
+ return response;
771
+ }
772
+ onProgress && onProgress({ isComputable: true, value: 1 });
773
+ return false;
774
+ }),
775
+ signal
776
+ });
777
+ }
778
+
763
779
  class UploadcareFile {
764
780
  constructor(fileInfo, { baseCDN, fileName }) {
765
781
  this.name = null;
@@ -797,28 +813,6 @@ class UploadcareFile {
797
813
  }
798
814
  }
799
815
 
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
816
  const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, secureExpire, store, contentType, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN, metadata }) => {
823
817
  return base(file, {
824
818
  publicKey,
@@ -854,6 +848,29 @@ const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, sec
854
848
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
855
849
  };
856
850
 
851
+ const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
852
+ return info(uuid, {
853
+ publicKey,
854
+ baseURL,
855
+ signal,
856
+ source,
857
+ integration,
858
+ userAgent,
859
+ retryThrottledRequestMaxTimes,
860
+ retryNetworkErrorMaxTimes
861
+ })
862
+ .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
863
+ .then((result) => {
864
+ // hack for node ¯\_(ツ)_/¯
865
+ if (onProgress)
866
+ onProgress({
867
+ isComputable: true,
868
+ value: 1
869
+ });
870
+ return result;
871
+ });
872
+ };
873
+
857
874
  const race = (fns, { signal } = {}) => {
858
875
  let lastError = null;
859
876
  let winnerIndex = null;
@@ -1193,35 +1210,30 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
1193
1210
  }))
1194
1211
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
1195
1212
 
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
- });
1213
+ const memo = new WeakMap();
1214
+ const getReactNativeBlob = async (asset) => {
1215
+ if (memo.has(asset)) {
1216
+ return memo.get(asset);
1217
+ }
1218
+ const blob = await fetch(asset.uri).then((res) => res.blob());
1219
+ memo.set(asset, blob);
1220
+ return blob;
1217
1221
  };
1218
1222
 
1219
- /**
1220
- * Get file size.
1221
- */
1222
- const getFileSize = (file) => {
1223
- return file.length || file.size;
1223
+ const getFileSize = async (file) => {
1224
+ if (isBuffer(file)) {
1225
+ return file.length;
1226
+ }
1227
+ if (isFile(file) || isBlob(file)) {
1228
+ return file.size;
1229
+ }
1230
+ if (isReactNativeAsset(file)) {
1231
+ const blob = await getReactNativeBlob(file);
1232
+ return blob.size;
1233
+ }
1234
+ throw new Error(`Failed to get file size for file: ${file}`);
1224
1235
  };
1236
+
1225
1237
  /**
1226
1238
  * Check if FileData is multipart data.
1227
1239
  */
@@ -1229,15 +1241,24 @@ const isMultipart = (fileSize, multipartMinFileSize = defaultSettings.multipartM
1229
1241
  return fileSize >= multipartMinFileSize;
1230
1242
  };
1231
1243
 
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);
1244
+ /**
1245
+ * Uuid type guard.
1246
+ */
1247
+ const isUuid = (data) => {
1248
+ const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
1249
+ const regExp = new RegExp(UUID_REGEX);
1250
+ return !isFileData(data) && regExp.test(data);
1251
+ };
1252
+ /**
1253
+ * Url type guard.
1254
+ *
1255
+ * @param {AnyFile | Url | Uuid} data
1256
+ */
1257
+ const isUrl = (data) => {
1258
+ const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
1259
+ const regExp = new RegExp(URL_REGEX);
1260
+ return !isFileData(data) && regExp.test(data);
1236
1261
  };
1237
-
1238
- function prepareChunks(file, fileSize, chunkSize) {
1239
- return (index) => sliceChunk(file, index, fileSize, chunkSize);
1240
- }
1241
1262
 
1242
1263
  const runWithConcurrency = (concurrency, tasks) => {
1243
1264
  return new Promise((resolve, reject) => {
@@ -1274,6 +1295,16 @@ const runWithConcurrency = (concurrency, tasks) => {
1274
1295
  });
1275
1296
  };
1276
1297
 
1298
+ const sliceChunk = (file, index, fileSize, chunkSize) => {
1299
+ const start = chunkSize * index;
1300
+ const end = Math.min(start + chunkSize, fileSize);
1301
+ return file.slice(start, end);
1302
+ };
1303
+
1304
+ const prepareChunks = async (file, fileSize, chunkSize) => {
1305
+ return (index) => sliceChunk(file, index, fileSize, chunkSize);
1306
+ };
1307
+
1277
1308
  const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes }) => multipartUpload(chunk, url, {
1278
1309
  publicKey,
1279
1310
  onProgress,
@@ -1282,8 +1313,8 @@ const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, re
1282
1313
  retryThrottledRequestMaxTimes,
1283
1314
  retryNetworkErrorMaxTimes
1284
1315
  });
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);
1316
+ 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 }) => {
1317
+ const size = fileSize || await getFileSize(file);
1287
1318
  let progressValues;
1288
1319
  const createProgressHandler = (totalChunks, chunkIdx) => {
1289
1320
  if (!onProgress)
@@ -1305,8 +1336,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1305
1336
  };
1306
1337
  return multipartStart(size, {
1307
1338
  publicKey,
1308
- contentType,
1309
- fileName: fileName ?? file.name,
1339
+ contentType: contentType || getContentType(file),
1340
+ fileName: fileName || getFilename(file),
1310
1341
  baseURL,
1311
1342
  secureSignature,
1312
1343
  secureExpire,
@@ -1319,8 +1350,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1319
1350
  retryNetworkErrorMaxTimes,
1320
1351
  metadata
1321
1352
  })
1322
- .then(({ uuid, parts }) => {
1323
- const getChunk = prepareChunks(file, size, multipartChunkSize);
1353
+ .then(async ({ uuid, parts }) => {
1354
+ const getChunk = await prepareChunks(file, size, multipartChunkSize);
1324
1355
  return Promise.all([
1325
1356
  uuid,
1326
1357
  runWithConcurrency(maxConcurrentRequests, parts.map((url, index) => () => uploadPart(getChunk(index), url, {
@@ -1367,14 +1398,15 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1367
1398
  /**
1368
1399
  * Uploads file from provided data.
1369
1400
  */
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 }) {
1401
+ 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
1402
  if (isFileData(data)) {
1372
- const fileSize = getFileSize(data);
1403
+ const fileSize = await getFileSize(data);
1373
1404
  if (isMultipart(fileSize, multipartMinFileSize)) {
1374
1405
  return uploadMultipart(data, {
1375
1406
  publicKey,
1376
1407
  contentType,
1377
1408
  multipartChunkSize,
1409
+ fileSize,
1378
1410
  fileName,
1379
1411
  baseURL,
1380
1412
  secureSignature,