@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
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:
|
|
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:
|
|
162
|
+
data: Blob | File | Buffer | ReactNativeAsset | Url | Uuid,
|
|
162
163
|
options: FileFromOptions
|
|
163
164
|
): Promise<UploadcareFile>
|
|
164
165
|
|
|
165
166
|
uploadFileGroup(
|
|
166
|
-
data: (
|
|
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:
|
|
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
|
|
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
|
package/dist/index.browser.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
265
|
-
|
|
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
|
|
271
|
-
return
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
|
486
|
-
contentType: contentType
|
|
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
|
|
684
|
+
filename: fileName || defaultFilename,
|
|
685
685
|
size: size,
|
|
686
|
-
content_type: contentType
|
|
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
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
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
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
|
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
|
|
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,
|