@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 +42 -6
- package/dist/index.browser.js +187 -149
- package/dist/index.d.ts +90 -85
- package/dist/index.node.js +188 -146
- package/dist/index.react-native.js +212 -168
- package/package.json +3 -3
|
@@ -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,26 +269,28 @@ const request = ({ method, url, data, headers = {}, signal, onProgress }) => new
|
|
|
253
269
|
}
|
|
254
270
|
});
|
|
255
271
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
return uri.startsWith('file:') || uri.startsWith('content:');
|
|
272
|
+
const isBlob = (data) => {
|
|
273
|
+
return typeof Blob !== 'undefined' && data instanceof Blob;
|
|
261
274
|
};
|
|
262
|
-
const
|
|
263
|
-
return
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
275
|
+
const isFile = (data) => {
|
|
276
|
+
return typeof File !== 'undefined' && data instanceof File;
|
|
277
|
+
};
|
|
278
|
+
const isBuffer = (data) => {
|
|
279
|
+
return typeof Buffer !== 'undefined' && data instanceof Buffer;
|
|
280
|
+
};
|
|
281
|
+
const isReactNativeAsset = (data) => {
|
|
282
|
+
return (!!data &&
|
|
283
|
+
typeof data === 'object' &&
|
|
284
|
+
!Array.isArray(data) &&
|
|
285
|
+
'uri' in data &&
|
|
286
|
+
typeof data.uri === 'string');
|
|
287
|
+
};
|
|
288
|
+
const isFileData = (data) => {
|
|
289
|
+
return (isBlob(data) || isFile(data) || isBuffer(data) || isReactNativeAsset(data));
|
|
269
290
|
};
|
|
270
291
|
|
|
271
|
-
const getFileOptions = (
|
|
292
|
+
const getFileOptions = () => [];
|
|
272
293
|
const transformFile = (file, name, contentType) => {
|
|
273
|
-
if (isReactNativeUri(file)) {
|
|
274
|
-
return { uri: file, name, type: contentType };
|
|
275
|
-
}
|
|
276
294
|
if (isReactNativeAsset(file)) {
|
|
277
295
|
return {
|
|
278
296
|
uri: file.uri,
|
|
@@ -280,41 +298,14 @@ const transformFile = (file, name, contentType) => {
|
|
|
280
298
|
type: file.type || contentType
|
|
281
299
|
};
|
|
282
300
|
}
|
|
283
|
-
|
|
284
|
-
|
|
301
|
+
if (isBlob(file)) {
|
|
302
|
+
const uri = URL.createObjectURL(file);
|
|
303
|
+
return { uri, name: name, type: file.type || contentType };
|
|
304
|
+
}
|
|
305
|
+
throw new Error(`Unsupported file type.`);
|
|
285
306
|
};
|
|
286
307
|
var getFormData = () => new FormData();
|
|
287
308
|
|
|
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' && isReactNativeUri(data)) ||
|
|
297
|
-
(typeof data === 'object' && isReactNativeAsset(data))));
|
|
298
|
-
};
|
|
299
|
-
/**
|
|
300
|
-
* Uuid type guard.
|
|
301
|
-
*/
|
|
302
|
-
const isUuid = (data) => {
|
|
303
|
-
const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
|
|
304
|
-
const regExp = new RegExp(UUID_REGEX);
|
|
305
|
-
return !isFileData(data) && regExp.test(data);
|
|
306
|
-
};
|
|
307
|
-
/**
|
|
308
|
-
* Url type guard.
|
|
309
|
-
*
|
|
310
|
-
* @param {NodeFile | BrowserFile | Url | Uuid} data
|
|
311
|
-
*/
|
|
312
|
-
const isUrl = (data) => {
|
|
313
|
-
const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
|
|
314
|
-
const regExp = new RegExp(URL_REGEX);
|
|
315
|
-
return !isFileData(data) && regExp.test(data);
|
|
316
|
-
};
|
|
317
|
-
|
|
318
309
|
const isSimpleValue = (value) => {
|
|
319
310
|
return (typeof value === 'string' ||
|
|
320
311
|
typeof value === 'number' ||
|
|
@@ -330,8 +321,8 @@ const isFileValue = (value) => !!value &&
|
|
|
330
321
|
function collectParams(params, inputKey, inputValue) {
|
|
331
322
|
if (isFileValue(inputValue)) {
|
|
332
323
|
const { name, contentType } = inputValue;
|
|
333
|
-
const file = transformFile(inputValue.data, name, contentType);
|
|
334
|
-
const options = getFileOptions(
|
|
324
|
+
const file = transformFile(inputValue.data, name, contentType);
|
|
325
|
+
const options = getFileOptions();
|
|
335
326
|
params.push([inputKey, file, ...options]);
|
|
336
327
|
}
|
|
337
328
|
else if (isObjectValue(inputValue)) {
|
|
@@ -353,11 +344,9 @@ function getFormDataParams(options) {
|
|
|
353
344
|
return params;
|
|
354
345
|
}
|
|
355
346
|
function buildFormData(options) {
|
|
356
|
-
console.log('buildFormData', options);
|
|
357
347
|
const formData = getFormData();
|
|
358
348
|
const paramsList = getFormDataParams(options);
|
|
359
349
|
for (const params of paramsList) {
|
|
360
|
-
console.log('params', params);
|
|
361
350
|
const [key, value, ...rest] = params;
|
|
362
351
|
// node form-data has another signature for append
|
|
363
352
|
formData.append(key, value, ...rest);
|
|
@@ -365,6 +354,19 @@ function buildFormData(options) {
|
|
|
365
354
|
return formData;
|
|
366
355
|
}
|
|
367
356
|
|
|
357
|
+
class UploadClientError extends Error {
|
|
358
|
+
constructor(message, code, request, response, headers) {
|
|
359
|
+
super();
|
|
360
|
+
this.name = 'UploadClientError';
|
|
361
|
+
this.message = message;
|
|
362
|
+
this.code = code;
|
|
363
|
+
this.request = request;
|
|
364
|
+
this.response = response;
|
|
365
|
+
this.headers = headers;
|
|
366
|
+
Object.setPrototypeOf(this, UploadClientError.prototype);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
368
370
|
const buildSearchParams = (query) => {
|
|
369
371
|
const searchParams = new URLSearchParams();
|
|
370
372
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -396,26 +398,6 @@ const getUrl = (base, path, query) => {
|
|
|
396
398
|
return url.toString();
|
|
397
399
|
};
|
|
398
400
|
|
|
399
|
-
/*
|
|
400
|
-
Settings for future support:
|
|
401
|
-
parallelDirectUploads: 10,
|
|
402
|
-
*/
|
|
403
|
-
const defaultSettings = {
|
|
404
|
-
baseCDN: 'https://ucarecdn.com',
|
|
405
|
-
baseURL: 'https://upload.uploadcare.com',
|
|
406
|
-
maxContentLength: 50 * 1024 * 1024,
|
|
407
|
-
retryThrottledRequestMaxTimes: 1,
|
|
408
|
-
retryNetworkErrorMaxTimes: 3,
|
|
409
|
-
multipartMinFileSize: 25 * 1024 * 1024,
|
|
410
|
-
multipartChunkSize: 5 * 1024 * 1024,
|
|
411
|
-
multipartMinLastPartSize: 1024 * 1024,
|
|
412
|
-
maxConcurrentRequests: 4,
|
|
413
|
-
pollingTimeoutMilliseconds: 10000,
|
|
414
|
-
pusherKey: '79ae88bd931ea68464d9'
|
|
415
|
-
};
|
|
416
|
-
const defaultContentType = 'application/octet-stream';
|
|
417
|
-
const defaultFilename = 'original';
|
|
418
|
-
|
|
419
401
|
var version = '6.0.0';
|
|
420
402
|
|
|
421
403
|
const LIBRARY_NAME = 'UploadcareUploadClient';
|
|
@@ -428,19 +410,6 @@ function getUserAgent(options) {
|
|
|
428
410
|
});
|
|
429
411
|
}
|
|
430
412
|
|
|
431
|
-
class UploadClientError extends Error {
|
|
432
|
-
constructor(message, code, request, response, headers) {
|
|
433
|
-
super();
|
|
434
|
-
this.name = 'UploadClientError';
|
|
435
|
-
this.message = message;
|
|
436
|
-
this.code = code;
|
|
437
|
-
this.request = request;
|
|
438
|
-
this.response = response;
|
|
439
|
-
this.headers = headers;
|
|
440
|
-
Object.setPrototypeOf(this, UploadClientError.prototype);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
413
|
const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
|
|
445
414
|
const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
|
|
446
415
|
const DEFAULT_NETWORK_ERROR_TIMEOUT = 1000;
|
|
@@ -471,6 +440,36 @@ function retryIfFailed(fn, options) {
|
|
|
471
440
|
}));
|
|
472
441
|
}
|
|
473
442
|
|
|
443
|
+
const getContentType = (file) => {
|
|
444
|
+
let contentType = '';
|
|
445
|
+
if (isBlob(file) || isFile(file) || isReactNativeAsset(file)) {
|
|
446
|
+
contentType = file.type;
|
|
447
|
+
}
|
|
448
|
+
if (contentType) {
|
|
449
|
+
return contentType;
|
|
450
|
+
}
|
|
451
|
+
console.warn(`Cannot determine content type. Using default content type: ${defaultContentType}`, file);
|
|
452
|
+
return defaultContentType;
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const getFileName = (file) => {
|
|
456
|
+
let filename = '';
|
|
457
|
+
if (isFile(file) && file.name) {
|
|
458
|
+
filename = file.name;
|
|
459
|
+
}
|
|
460
|
+
else if (isBlob(file) || isBuffer(file)) {
|
|
461
|
+
filename = '';
|
|
462
|
+
}
|
|
463
|
+
else if (isReactNativeAsset(file) && file.name) {
|
|
464
|
+
filename = file.name;
|
|
465
|
+
}
|
|
466
|
+
if (filename) {
|
|
467
|
+
return filename;
|
|
468
|
+
}
|
|
469
|
+
console.warn(`Cannot determine filename. Using default filename: ${defaultFilename}`, file);
|
|
470
|
+
return defaultFilename;
|
|
471
|
+
};
|
|
472
|
+
|
|
474
473
|
function getStoreValue(store) {
|
|
475
474
|
return typeof store === 'undefined' ? 'auto' : store ? '1' : '0';
|
|
476
475
|
}
|
|
@@ -486,13 +485,13 @@ function base(file, { publicKey, fileName, contentType, baseURL = defaultSetting
|
|
|
486
485
|
jsonerrors: 1
|
|
487
486
|
}),
|
|
488
487
|
headers: {
|
|
489
|
-
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
488
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
490
489
|
},
|
|
491
490
|
data: buildFormData({
|
|
492
491
|
file: {
|
|
493
492
|
data: file,
|
|
494
|
-
name: fileName
|
|
495
|
-
contentType: contentType
|
|
493
|
+
name: fileName || getFileName(file),
|
|
494
|
+
contentType: contentType || getContentType(file)
|
|
496
495
|
},
|
|
497
496
|
UPLOADCARE_PUB_KEY: publicKey,
|
|
498
497
|
UPLOADCARE_STORE: getStoreValue(store),
|
|
@@ -690,9 +689,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
|
|
|
690
689
|
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
691
690
|
},
|
|
692
691
|
data: buildFormData({
|
|
693
|
-
filename: fileName
|
|
692
|
+
filename: fileName || defaultFilename,
|
|
694
693
|
size: size,
|
|
695
|
-
content_type: contentType
|
|
694
|
+
content_type: contentType || defaultContentType,
|
|
696
695
|
part_size: multipartChunkSize,
|
|
697
696
|
UPLOADCARE_STORE: getStoreValue(store),
|
|
698
697
|
UPLOADCARE_PUB_KEY: publicKey,
|
|
@@ -769,6 +768,28 @@ function multipartComplete(uuid, { publicKey, baseURL = defaultSettings.baseURL,
|
|
|
769
768
|
}), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
|
|
770
769
|
}
|
|
771
770
|
|
|
771
|
+
function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
|
|
772
|
+
return poll({
|
|
773
|
+
check: (signal) => info(file, {
|
|
774
|
+
publicKey,
|
|
775
|
+
baseURL,
|
|
776
|
+
signal,
|
|
777
|
+
source,
|
|
778
|
+
integration,
|
|
779
|
+
userAgent,
|
|
780
|
+
retryThrottledRequestMaxTimes,
|
|
781
|
+
retryNetworkErrorMaxTimes
|
|
782
|
+
}).then((response) => {
|
|
783
|
+
if (response.isReady) {
|
|
784
|
+
return response;
|
|
785
|
+
}
|
|
786
|
+
onProgress && onProgress({ isComputable: true, value: 1 });
|
|
787
|
+
return false;
|
|
788
|
+
}),
|
|
789
|
+
signal
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
772
793
|
class UploadcareFile {
|
|
773
794
|
constructor(fileInfo, { baseCDN, fileName }) {
|
|
774
795
|
this.name = null;
|
|
@@ -806,28 +827,6 @@ class UploadcareFile {
|
|
|
806
827
|
}
|
|
807
828
|
}
|
|
808
829
|
|
|
809
|
-
function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
|
|
810
|
-
return poll({
|
|
811
|
-
check: (signal) => info(file, {
|
|
812
|
-
publicKey,
|
|
813
|
-
baseURL,
|
|
814
|
-
signal,
|
|
815
|
-
source,
|
|
816
|
-
integration,
|
|
817
|
-
userAgent,
|
|
818
|
-
retryThrottledRequestMaxTimes,
|
|
819
|
-
retryNetworkErrorMaxTimes
|
|
820
|
-
}).then((response) => {
|
|
821
|
-
if (response.isReady) {
|
|
822
|
-
return response;
|
|
823
|
-
}
|
|
824
|
-
onProgress && onProgress({ isComputable: true, value: 1 });
|
|
825
|
-
return false;
|
|
826
|
-
}),
|
|
827
|
-
signal
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
|
|
831
830
|
const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, secureExpire, store, contentType, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN, metadata }) => {
|
|
832
831
|
return base(file, {
|
|
833
832
|
publicKey,
|
|
@@ -863,6 +862,29 @@ const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, sec
|
|
|
863
862
|
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
|
|
864
863
|
};
|
|
865
864
|
|
|
865
|
+
const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
|
|
866
|
+
return info(uuid, {
|
|
867
|
+
publicKey,
|
|
868
|
+
baseURL,
|
|
869
|
+
signal,
|
|
870
|
+
source,
|
|
871
|
+
integration,
|
|
872
|
+
userAgent,
|
|
873
|
+
retryThrottledRequestMaxTimes,
|
|
874
|
+
retryNetworkErrorMaxTimes
|
|
875
|
+
})
|
|
876
|
+
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
|
|
877
|
+
.then((result) => {
|
|
878
|
+
// hack for node ¯\_(ツ)_/¯
|
|
879
|
+
if (onProgress)
|
|
880
|
+
onProgress({
|
|
881
|
+
isComputable: true,
|
|
882
|
+
value: 1
|
|
883
|
+
});
|
|
884
|
+
return result;
|
|
885
|
+
});
|
|
886
|
+
};
|
|
887
|
+
|
|
866
888
|
const race = (fns, { signal } = {}) => {
|
|
867
889
|
let lastError = null;
|
|
868
890
|
let winnerIndex = null;
|
|
@@ -1202,35 +1224,30 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
|
|
|
1202
1224
|
}))
|
|
1203
1225
|
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
|
|
1204
1226
|
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
retryThrottledRequestMaxTimes,
|
|
1214
|
-
retryNetworkErrorMaxTimes
|
|
1215
|
-
})
|
|
1216
|
-
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
|
|
1217
|
-
.then((result) => {
|
|
1218
|
-
// hack for node ¯\_(ツ)_/¯
|
|
1219
|
-
if (onProgress)
|
|
1220
|
-
onProgress({
|
|
1221
|
-
isComputable: true,
|
|
1222
|
-
value: 1
|
|
1223
|
-
});
|
|
1224
|
-
return result;
|
|
1225
|
-
});
|
|
1227
|
+
const memo = new WeakMap();
|
|
1228
|
+
const getBlobFromReactNativeAsset = async (asset) => {
|
|
1229
|
+
if (memo.has(asset)) {
|
|
1230
|
+
return memo.get(asset);
|
|
1231
|
+
}
|
|
1232
|
+
const blob = await fetch(asset.uri).then((res) => res.blob());
|
|
1233
|
+
memo.set(asset, blob);
|
|
1234
|
+
return blob;
|
|
1226
1235
|
};
|
|
1227
1236
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1237
|
+
const getFileSize = async (file) => {
|
|
1238
|
+
if (isBuffer(file)) {
|
|
1239
|
+
return file.length;
|
|
1240
|
+
}
|
|
1241
|
+
if (isFile(file) || isBlob(file)) {
|
|
1242
|
+
return file.size;
|
|
1243
|
+
}
|
|
1244
|
+
if (isReactNativeAsset(file)) {
|
|
1245
|
+
const blob = await getBlobFromReactNativeAsset(file);
|
|
1246
|
+
return blob.size;
|
|
1247
|
+
}
|
|
1248
|
+
throw new Error(`Unknown file type. Cannot determine file size.`);
|
|
1233
1249
|
};
|
|
1250
|
+
|
|
1234
1251
|
/**
|
|
1235
1252
|
* Check if FileData is multipart data.
|
|
1236
1253
|
*/
|
|
@@ -1238,28 +1255,24 @@ const isMultipart = (fileSize, multipartMinFileSize = defaultSettings.multipartM
|
|
|
1238
1255
|
return fileSize >= multipartMinFileSize;
|
|
1239
1256
|
};
|
|
1240
1257
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1258
|
+
/**
|
|
1259
|
+
* Uuid type guard.
|
|
1260
|
+
*/
|
|
1261
|
+
const isUuid = (data) => {
|
|
1262
|
+
const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
|
|
1263
|
+
const regExp = new RegExp(UUID_REGEX);
|
|
1264
|
+
return !isFileData(data) && regExp.test(data);
|
|
1245
1265
|
};
|
|
1246
|
-
|
|
1247
1266
|
/**
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
1250
|
-
* We need to store references to sliced blobs to prevent source blob from
|
|
1251
|
-
* being deallocated until uploading complete. Access to deallocated blob
|
|
1252
|
-
* causes app crash.
|
|
1267
|
+
* Url type guard.
|
|
1253
1268
|
*
|
|
1254
|
-
*
|
|
1255
|
-
* and https://github.com/facebook/react-native/issues/27543
|
|
1269
|
+
* @param {SupportedFileInput | Url | Uuid} data
|
|
1256
1270
|
*/
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1271
|
+
const isUrl = (data) => {
|
|
1272
|
+
const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
|
|
1273
|
+
const regExp = new RegExp(URL_REGEX);
|
|
1274
|
+
return !isFileData(data) && regExp.test(data);
|
|
1275
|
+
};
|
|
1263
1276
|
|
|
1264
1277
|
const runWithConcurrency = (concurrency, tasks) => {
|
|
1265
1278
|
return new Promise((resolve, reject) => {
|
|
@@ -1296,6 +1309,36 @@ const runWithConcurrency = (concurrency, tasks) => {
|
|
|
1296
1309
|
});
|
|
1297
1310
|
};
|
|
1298
1311
|
|
|
1312
|
+
const sliceChunk = (file, index, fileSize, chunkSize) => {
|
|
1313
|
+
const start = chunkSize * index;
|
|
1314
|
+
const end = Math.min(start + chunkSize, fileSize);
|
|
1315
|
+
return file.slice(start, end);
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* React-native hack for blob slicing
|
|
1320
|
+
*
|
|
1321
|
+
* We need to store references to sliced blobs to prevent source blob from
|
|
1322
|
+
* being deallocated until uploading complete. Access to deallocated blob
|
|
1323
|
+
* causes app crash.
|
|
1324
|
+
*
|
|
1325
|
+
* See https://github.com/uploadcare/uploadcare-js-api-clients/issues/306
|
|
1326
|
+
* and https://github.com/facebook/react-native/issues/27543
|
|
1327
|
+
*/
|
|
1328
|
+
const prepareChunks = async (file, fileSize, chunkSize) => {
|
|
1329
|
+
let blob;
|
|
1330
|
+
if (isReactNativeAsset(file)) {
|
|
1331
|
+
blob = await getBlobFromReactNativeAsset(file);
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
blob = file;
|
|
1335
|
+
}
|
|
1336
|
+
return (index) => {
|
|
1337
|
+
const chunk = sliceChunk(blob, index, fileSize, chunkSize);
|
|
1338
|
+
return chunk;
|
|
1339
|
+
};
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1299
1342
|
const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes }) => multipartUpload(chunk, url, {
|
|
1300
1343
|
publicKey,
|
|
1301
1344
|
onProgress,
|
|
@@ -1304,8 +1347,8 @@ const uploadPart = (chunk, url, { publicKey, onProgress, signal, integration, re
|
|
|
1304
1347
|
retryThrottledRequestMaxTimes,
|
|
1305
1348
|
retryNetworkErrorMaxTimes
|
|
1306
1349
|
});
|
|
1307
|
-
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 }) => {
|
|
1308
|
-
const size = fileSize
|
|
1350
|
+
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 }) => {
|
|
1351
|
+
const size = fileSize ?? (await getFileSize(file));
|
|
1309
1352
|
let progressValues;
|
|
1310
1353
|
const createProgressHandler = (totalChunks, chunkIdx) => {
|
|
1311
1354
|
if (!onProgress)
|
|
@@ -1327,8 +1370,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
|
|
|
1327
1370
|
};
|
|
1328
1371
|
return multipartStart(size, {
|
|
1329
1372
|
publicKey,
|
|
1330
|
-
contentType,
|
|
1331
|
-
fileName: fileName
|
|
1373
|
+
contentType: contentType || getContentType(file),
|
|
1374
|
+
fileName: fileName || getFileName(file),
|
|
1332
1375
|
baseURL,
|
|
1333
1376
|
secureSignature,
|
|
1334
1377
|
secureExpire,
|
|
@@ -1341,8 +1384,8 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
|
|
|
1341
1384
|
retryNetworkErrorMaxTimes,
|
|
1342
1385
|
metadata
|
|
1343
1386
|
})
|
|
1344
|
-
.then(({ uuid, parts }) => {
|
|
1345
|
-
const getChunk = prepareChunks(file, size, multipartChunkSize);
|
|
1387
|
+
.then(async ({ uuid, parts }) => {
|
|
1388
|
+
const getChunk = await prepareChunks(file, size, multipartChunkSize);
|
|
1346
1389
|
return Promise.all([
|
|
1347
1390
|
uuid,
|
|
1348
1391
|
runWithConcurrency(maxConcurrentRequests, parts.map((url, index) => () => uploadPart(getChunk(index), url, {
|
|
@@ -1389,14 +1432,15 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
|
|
|
1389
1432
|
/**
|
|
1390
1433
|
* Uploads file from provided data.
|
|
1391
1434
|
*/
|
|
1392
|
-
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 }) {
|
|
1435
|
+
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 }) {
|
|
1393
1436
|
if (isFileData(data)) {
|
|
1394
|
-
const fileSize = getFileSize(data);
|
|
1437
|
+
const fileSize = await getFileSize(data);
|
|
1395
1438
|
if (isMultipart(fileSize, multipartMinFileSize)) {
|
|
1396
1439
|
return uploadMultipart(data, {
|
|
1397
1440
|
publicKey,
|
|
1398
1441
|
contentType,
|
|
1399
1442
|
multipartChunkSize,
|
|
1443
|
+
fileSize,
|
|
1400
1444
|
fileName,
|
|
1401
1445
|
baseURL,
|
|
1402
1446
|
secureSignature,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadcare/upload-client",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.1.0-alpha.0",
|
|
4
4
|
"description": "Library for work with Uploadcare Upload API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.node.js",
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
"@types/ws": "8.5.3",
|
|
72
72
|
"data-uri-to-buffer": "3.0.1",
|
|
73
73
|
"dataurl-to-blob": "0.0.1",
|
|
74
|
-
"jest-environment-jsdom": "
|
|
75
|
-
"jest-websocket-mock": "2.
|
|
74
|
+
"jest-environment-jsdom": "29.3.1",
|
|
75
|
+
"jest-websocket-mock": "2.4.0",
|
|
76
76
|
"koa": "2.13.4",
|
|
77
77
|
"koa-add-trailing-slashes": "2.0.1",
|
|
78
78
|
"koa-body": "5.0.0",
|