@uploadcare/upload-client 6.0.1-alpha.7 → 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,45 +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
- return uri.startsWith('file:') || uri.startsWith('content:');
280
+ const isBlob = (data) => {
281
+ return typeof Blob !== 'undefined' && data instanceof Blob;
266
282
  };
267
- const isReactNativeAsset = (asset) => {
268
- return (typeof asset === 'object' &&
269
- !!asset &&
270
- 'uri' in asset &&
271
- typeof asset.uri === 'string' &&
272
- isReactNativeUri(asset.uri));
283
+ const isFile = (data) => {
284
+ return typeof File !== 'undefined' && data instanceof File;
273
285
  };
274
-
275
- /**
276
- * FileData type guard.
277
- */
278
- const isFileData = (data) => {
279
- return (data !== undefined &&
280
- ((typeof Blob !== 'undefined' && data instanceof Blob) ||
281
- (typeof File !== 'undefined' && data instanceof File) ||
282
- (typeof Buffer !== 'undefined' && data instanceof Buffer) ||
283
- (typeof data === 'string' && isReactNativeUri(data)) ||
284
- (typeof data === 'object' && isReactNativeAsset(data))));
286
+ const isBuffer = (data) => {
287
+ return typeof Buffer !== 'undefined' && data instanceof Buffer;
285
288
  };
286
- /**
287
- * Uuid type guard.
288
- */
289
- const isUuid = (data) => {
290
- const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
291
- const regExp = new RegExp(UUID_REGEX);
292
- 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');
293
295
  };
294
- /**
295
- * Url type guard.
296
- *
297
- * @param {NodeFile | BrowserFile | Url | Uuid} data
298
- */
299
- const isUrl = (data) => {
300
- const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
301
- const regExp = new RegExp(URL_REGEX);
302
- return !isFileData(data) && regExp.test(data);
296
+ const isFileData = (data) => {
297
+ return (isBlob(data) || isFile(data) || isBuffer(data) || isReactNativeAsset(data));
303
298
  };
304
299
 
305
300
  const isSimpleValue = (value) => {
@@ -340,11 +335,9 @@ function getFormDataParams(options) {
340
335
  return params;
341
336
  }
342
337
  function buildFormData(options) {
343
- console.log('buildFormData', options);
344
338
  const formData = getFormData();
345
339
  const paramsList = getFormDataParams(options);
346
340
  for (const params of paramsList) {
347
- console.log('params', params);
348
341
  const [key, value, ...rest] = params;
349
342
  // node form-data has another signature for append
350
343
  formData.append(key, value, ...rest);
@@ -352,6 +345,19 @@ function buildFormData(options) {
352
345
  return formData;
353
346
  }
354
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
+
355
361
  const buildSearchParams = (query) => {
356
362
  const searchParams = new URLSearchParams();
357
363
  for (const [key, value] of Object.entries(query)) {
@@ -383,26 +389,6 @@ const getUrl = (base, path, query) => {
383
389
  return url.toString();
384
390
  };
385
391
 
386
- /*
387
- Settings for future support:
388
- parallelDirectUploads: 10,
389
- */
390
- const defaultSettings = {
391
- baseCDN: 'https://ucarecdn.com',
392
- baseURL: 'https://upload.uploadcare.com',
393
- maxContentLength: 50 * 1024 * 1024,
394
- retryThrottledRequestMaxTimes: 1,
395
- retryNetworkErrorMaxTimes: 3,
396
- multipartMinFileSize: 25 * 1024 * 1024,
397
- multipartChunkSize: 5 * 1024 * 1024,
398
- multipartMinLastPartSize: 1024 * 1024,
399
- maxConcurrentRequests: 4,
400
- pollingTimeoutMilliseconds: 10000,
401
- pusherKey: '79ae88bd931ea68464d9'
402
- };
403
- const defaultContentType = 'application/octet-stream';
404
- const defaultFilename = 'original';
405
-
406
392
  var version = '6.0.0';
407
393
 
408
394
  const LIBRARY_NAME = 'UploadcareUploadClient';
@@ -415,19 +401,6 @@ function getUserAgent(options) {
415
401
  });
416
402
  }
417
403
 
418
- class UploadClientError extends Error {
419
- constructor(message, code, request, response, headers) {
420
- super();
421
- this.name = 'UploadClientError';
422
- this.message = message;
423
- this.code = code;
424
- this.request = request;
425
- this.response = response;
426
- this.headers = headers;
427
- Object.setPrototypeOf(this, UploadClientError.prototype);
428
- }
429
- }
430
-
431
404
  const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
432
405
  const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
433
406
  const DEFAULT_NETWORK_ERROR_TIMEOUT = 1000;
@@ -458,6 +431,31 @@ function retryIfFailed(fn, options) {
458
431
  }));
459
432
  }
460
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
+
461
459
  function getStoreValue(store) {
462
460
  return typeof store === 'undefined' ? 'auto' : store ? '1' : '0';
463
461
  }
@@ -473,13 +471,13 @@ function base(file, { publicKey, fileName, contentType, baseURL = defaultSetting
473
471
  jsonerrors: 1
474
472
  }),
475
473
  headers: {
476
- 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent }),
474
+ 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
477
475
  },
478
476
  data: buildFormData({
479
477
  file: {
480
478
  data: file,
481
- name: fileName ?? file?.name ?? defaultFilename,
482
- contentType
479
+ name: fileName || getFilename(file),
480
+ contentType: contentType || getContentType(file)
483
481
  },
484
482
  UPLOADCARE_PUB_KEY: publicKey,
485
483
  UPLOADCARE_STORE: getStoreValue(store),
@@ -677,9 +675,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
677
675
  'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
678
676
  },
679
677
  data: buildFormData({
680
- filename: fileName ?? defaultFilename,
678
+ filename: fileName || defaultFilename,
681
679
  size: size,
682
- content_type: contentType ?? defaultContentType,
680
+ content_type: contentType || defaultContentType,
683
681
  part_size: multipartChunkSize,
684
682
  UPLOADCARE_STORE: getStoreValue(store),
685
683
  UPLOADCARE_PUB_KEY: publicKey,
@@ -756,6 +754,28 @@ function multipartComplete(uuid, { publicKey, baseURL = defaultSettings.baseURL,
756
754
  }), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
757
755
  }
758
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
+
759
779
  class UploadcareFile {
760
780
  constructor(fileInfo, { baseCDN, fileName }) {
761
781
  this.name = null;
@@ -793,28 +813,6 @@ class UploadcareFile {
793
813
  }
794
814
  }
795
815
 
796
- function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
797
- return poll({
798
- check: (signal) => info(file, {
799
- publicKey,
800
- baseURL,
801
- signal,
802
- source,
803
- integration,
804
- userAgent,
805
- retryThrottledRequestMaxTimes,
806
- retryNetworkErrorMaxTimes
807
- }).then((response) => {
808
- if (response.isReady) {
809
- return response;
810
- }
811
- onProgress && onProgress({ isComputable: true, value: 1 });
812
- return false;
813
- }),
814
- signal
815
- });
816
- }
817
-
818
816
  const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, secureExpire, store, contentType, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN, metadata }) => {
819
817
  return base(file, {
820
818
  publicKey,
@@ -850,6 +848,29 @@ const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, sec
850
848
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
851
849
  };
852
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
+
853
874
  const race = (fns, { signal } = {}) => {
854
875
  let lastError = null;
855
876
  let winnerIndex = null;
@@ -1189,35 +1210,30 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
1189
1210
  }))
1190
1211
  .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
1191
1212
 
1192
- const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
1193
- return info(uuid, {
1194
- publicKey,
1195
- baseURL,
1196
- signal,
1197
- source,
1198
- integration,
1199
- userAgent,
1200
- retryThrottledRequestMaxTimes,
1201
- retryNetworkErrorMaxTimes
1202
- })
1203
- .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
1204
- .then((result) => {
1205
- // hack for node ¯\_(ツ)_/¯
1206
- if (onProgress)
1207
- onProgress({
1208
- isComputable: true,
1209
- value: 1
1210
- });
1211
- return result;
1212
- });
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;
1213
1221
  };
1214
1222
 
1215
- /**
1216
- * Get file size.
1217
- */
1218
- const getFileSize = (file) => {
1219
- 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}`);
1220
1235
  };
1236
+
1221
1237
  /**
1222
1238
  * Check if FileData is multipart data.
1223
1239
  */
@@ -1225,15 +1241,24 @@ const isMultipart = (fileSize, multipartMinFileSize = defaultSettings.multipartM
1225
1241
  return fileSize >= multipartMinFileSize;
1226
1242
  };
1227
1243
 
1228
- const sliceChunk = (file, index, fileSize, chunkSize) => {
1229
- const start = chunkSize * index;
1230
- const end = Math.min(start + chunkSize, fileSize);
1231
- 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);
1232
1261
  };
1233
-
1234
- function prepareChunks(file, fileSize, chunkSize) {
1235
- return (index) => sliceChunk(file, index, fileSize, chunkSize);
1236
- }
1237
1262
 
1238
1263
  const runWithConcurrency = (concurrency, tasks) => {
1239
1264
  return new Promise((resolve, reject) => {
@@ -1270,6 +1295,16 @@ const runWithConcurrency = (concurrency, tasks) => {
1270
1295
  });
1271
1296
  };
1272
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
+
1273
1308
  const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes }) => multipartUpload(chunk, url, {
1274
1309
  publicKey,
1275
1310
  onProgress,
@@ -1278,8 +1313,8 @@ const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, re
1278
1313
  retryThrottledRequestMaxTimes,
1279
1314
  retryNetworkErrorMaxTimes
1280
1315
  });
1281
- 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 }) => {
1282
- 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);
1283
1318
  let progressValues;
1284
1319
  const createProgressHandler = (totalChunks, chunkIdx) => {
1285
1320
  if (!onProgress)
@@ -1301,8 +1336,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1301
1336
  };
1302
1337
  return multipartStart(size, {
1303
1338
  publicKey,
1304
- contentType,
1305
- fileName: fileName ?? file.name,
1339
+ contentType: contentType || getContentType(file),
1340
+ fileName: fileName || getFilename(file),
1306
1341
  baseURL,
1307
1342
  secureSignature,
1308
1343
  secureExpire,
@@ -1315,8 +1350,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1315
1350
  retryNetworkErrorMaxTimes,
1316
1351
  metadata
1317
1352
  })
1318
- .then(({ uuid, parts }) => {
1319
- const getChunk = prepareChunks(file, size, multipartChunkSize);
1353
+ .then(async ({ uuid, parts }) => {
1354
+ const getChunk = await prepareChunks(file, size, multipartChunkSize);
1320
1355
  return Promise.all([
1321
1356
  uuid,
1322
1357
  runWithConcurrency(maxConcurrentRequests, parts.map((url, index) => () => uploadPart(getChunk(index), url, {
@@ -1363,14 +1398,15 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
1363
1398
  /**
1364
1399
  * Uploads file from provided data.
1365
1400
  */
1366
- 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 }) {
1367
1402
  if (isFileData(data)) {
1368
- const fileSize = getFileSize(data);
1403
+ const fileSize = await getFileSize(data);
1369
1404
  if (isMultipart(fileSize, multipartMinFileSize)) {
1370
1405
  return uploadMultipart(data, {
1371
1406
  publicKey,
1372
1407
  contentType,
1373
1408
  multipartChunkSize,
1409
+ fileSize,
1374
1410
  fileName,
1375
1411
  baseURL,
1376
1412
  secureSignature,