@uploadcare/upload-client 6.4.1 → 6.5.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/dist/cjs/index.browser.cjs +1679 -0
- package/dist/cjs/index.browser.d.cts +501 -0
- package/dist/cjs/index.node.cjs +1708 -0
- package/dist/cjs/index.node.d.cts +501 -0
- package/dist/cjs/index.react-native.cjs +1707 -0
- package/dist/cjs/index.react-native.d.cts +501 -0
- package/dist/esm/index.browser.d.mts +501 -0
- package/dist/{index.browser.js → esm/index.browser.mjs} +43 -27
- package/dist/esm/index.node.d.mts +501 -0
- package/dist/{index.node.js → esm/index.node.mjs} +50 -31
- package/dist/esm/index.react-native.d.mts +501 -0
- package/dist/{index.react-native.js → esm/index.react-native.mjs} +43 -27
- package/dist/index.d.ts +58 -58
- package/package.json +26 -14
|
@@ -0,0 +1,1707 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function isObject(o) {
|
|
4
|
+
return Object.prototype.toString.call(o) === '[object Object]';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const SEPARATOR = /\W|_/g;
|
|
8
|
+
function camelizeString(text) {
|
|
9
|
+
return text
|
|
10
|
+
.split(SEPARATOR)
|
|
11
|
+
.map((word, index) => word.charAt(0)[index > 0 ? 'toUpperCase' : 'toLowerCase']() +
|
|
12
|
+
word.slice(1))
|
|
13
|
+
.join('');
|
|
14
|
+
}
|
|
15
|
+
function camelizeArrayItems(array, { ignoreKeys } = { ignoreKeys: [] }) {
|
|
16
|
+
if (!Array.isArray(array)) {
|
|
17
|
+
return array;
|
|
18
|
+
}
|
|
19
|
+
return array.map((item) => camelizeKeys(item, { ignoreKeys }));
|
|
20
|
+
}
|
|
21
|
+
function camelizeKeys(source, { ignoreKeys } = { ignoreKeys: [] }) {
|
|
22
|
+
if (Array.isArray(source)) {
|
|
23
|
+
return camelizeArrayItems(source, { ignoreKeys });
|
|
24
|
+
}
|
|
25
|
+
if (!isObject(source)) {
|
|
26
|
+
return source;
|
|
27
|
+
}
|
|
28
|
+
const result = {};
|
|
29
|
+
for (const key of Object.keys(source)) {
|
|
30
|
+
let value = source[key];
|
|
31
|
+
if (ignoreKeys.includes(key)) {
|
|
32
|
+
result[key] = value;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (isObject(value)) {
|
|
36
|
+
value = camelizeKeys(value, { ignoreKeys });
|
|
37
|
+
}
|
|
38
|
+
else if (Array.isArray(value)) {
|
|
39
|
+
value = camelizeArrayItems(value, { ignoreKeys });
|
|
40
|
+
}
|
|
41
|
+
result[camelizeString(key)] = value;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* SetTimeout as Promise.
|
|
48
|
+
*
|
|
49
|
+
* @param {number} ms Timeout in milliseconds.
|
|
50
|
+
*/
|
|
51
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
|
+
|
|
53
|
+
function getUserAgent$1({ libraryName, libraryVersion, userAgent, publicKey = '', integration = '' }) {
|
|
54
|
+
const languageName = 'JavaScript';
|
|
55
|
+
if (typeof userAgent === 'string') {
|
|
56
|
+
return userAgent;
|
|
57
|
+
}
|
|
58
|
+
if (typeof userAgent === 'function') {
|
|
59
|
+
return userAgent({
|
|
60
|
+
publicKey,
|
|
61
|
+
libraryName,
|
|
62
|
+
libraryVersion,
|
|
63
|
+
languageName,
|
|
64
|
+
integration
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const mainInfo = [libraryName, libraryVersion, publicKey]
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.join('/');
|
|
70
|
+
const additionInfo = [languageName, integration].filter(Boolean).join('; ');
|
|
71
|
+
return `${mainInfo} (${additionInfo})`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const defaultOptions = {
|
|
75
|
+
factor: 2,
|
|
76
|
+
time: 100
|
|
77
|
+
};
|
|
78
|
+
function retrier(fn, options = defaultOptions) {
|
|
79
|
+
let attempts = 0;
|
|
80
|
+
function runAttempt(fn) {
|
|
81
|
+
const defaultDelayTime = Math.round(options.time * options.factor ** attempts);
|
|
82
|
+
const retry = (ms) => delay(ms ?? defaultDelayTime).then(() => {
|
|
83
|
+
attempts += 1;
|
|
84
|
+
return runAttempt(fn);
|
|
85
|
+
});
|
|
86
|
+
return fn({
|
|
87
|
+
attempt: attempts,
|
|
88
|
+
retry
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return runAttempt(fn);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class UploadcareNetworkError extends Error {
|
|
95
|
+
originalProgressEvent;
|
|
96
|
+
constructor(progressEvent) {
|
|
97
|
+
super();
|
|
98
|
+
this.name = 'UploadcareNetworkError';
|
|
99
|
+
this.message = 'Network error';
|
|
100
|
+
Object.setPrototypeOf(this, UploadcareNetworkError.prototype);
|
|
101
|
+
this.originalProgressEvent = progressEvent;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const onCancel = (signal, callback) => {
|
|
106
|
+
if (signal) {
|
|
107
|
+
if (signal.aborted) {
|
|
108
|
+
Promise.resolve().then(callback);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
signal.addEventListener('abort', () => callback(), { once: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
class CancelError extends Error {
|
|
117
|
+
isCancel = true;
|
|
118
|
+
constructor(message = 'Request canceled') {
|
|
119
|
+
super(message);
|
|
120
|
+
Object.setPrototypeOf(this, CancelError.prototype);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const DEFAULT_INTERVAL = 500;
|
|
125
|
+
const poll = ({ check, interval = DEFAULT_INTERVAL, timeout, signal }) => new Promise((resolve, reject) => {
|
|
126
|
+
let tickTimeoutId;
|
|
127
|
+
let timeoutId;
|
|
128
|
+
onCancel(signal, () => {
|
|
129
|
+
tickTimeoutId && clearTimeout(tickTimeoutId);
|
|
130
|
+
reject(new CancelError('Poll cancelled'));
|
|
131
|
+
});
|
|
132
|
+
if (timeout) {
|
|
133
|
+
timeoutId = setTimeout(() => {
|
|
134
|
+
tickTimeoutId && clearTimeout(tickTimeoutId);
|
|
135
|
+
reject(new CancelError('Timed out'));
|
|
136
|
+
}, timeout);
|
|
137
|
+
}
|
|
138
|
+
const tick = () => {
|
|
139
|
+
try {
|
|
140
|
+
Promise.resolve(check(signal))
|
|
141
|
+
.then((result) => {
|
|
142
|
+
if (result) {
|
|
143
|
+
timeoutId && clearTimeout(timeoutId);
|
|
144
|
+
resolve(result);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
tickTimeoutId = setTimeout(tick, interval);
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.catch((error) => {
|
|
151
|
+
timeoutId && clearTimeout(timeoutId);
|
|
152
|
+
reject(error);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
timeoutId && clearTimeout(timeoutId);
|
|
157
|
+
reject(error);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
tickTimeoutId = setTimeout(tick, 0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
Settings for future support:
|
|
165
|
+
parallelDirectUploads: 10,
|
|
166
|
+
*/
|
|
167
|
+
const defaultSettings = {
|
|
168
|
+
baseCDN: 'https://ucarecdn.com',
|
|
169
|
+
baseURL: 'https://upload.uploadcare.com',
|
|
170
|
+
maxContentLength: 50 * 1024 * 1024,
|
|
171
|
+
retryThrottledRequestMaxTimes: 1,
|
|
172
|
+
retryNetworkErrorMaxTimes: 3,
|
|
173
|
+
multipartMinFileSize: 25 * 1024 * 1024,
|
|
174
|
+
multipartChunkSize: 5 * 1024 * 1024,
|
|
175
|
+
multipartMinLastPartSize: 1024 * 1024,
|
|
176
|
+
maxConcurrentRequests: 4,
|
|
177
|
+
pollingTimeoutMilliseconds: 10000,
|
|
178
|
+
pusherKey: '79ae88bd931ea68464d9'
|
|
179
|
+
};
|
|
180
|
+
const defaultContentType = 'application/octet-stream';
|
|
181
|
+
const defaultFilename = 'original';
|
|
182
|
+
|
|
183
|
+
const request = ({ method, url, data, headers = {}, signal, onProgress }) => new Promise((resolve, reject) => {
|
|
184
|
+
const xhr = new XMLHttpRequest();
|
|
185
|
+
const requestMethod = method?.toUpperCase() || 'GET';
|
|
186
|
+
let aborted = false;
|
|
187
|
+
/**
|
|
188
|
+
* Force synchronous flag to be off Some chrome versions gets
|
|
189
|
+
* `InvalidAccessError` error when we set `responseType` See
|
|
190
|
+
* https://xhr.spec.whatwg.org/#synchronous-flag and
|
|
191
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1346628
|
|
192
|
+
*/
|
|
193
|
+
xhr.open(requestMethod, url, true);
|
|
194
|
+
if (headers) {
|
|
195
|
+
Object.entries(headers).forEach((entry) => {
|
|
196
|
+
const [key, value] = entry;
|
|
197
|
+
typeof value !== 'undefined' &&
|
|
198
|
+
!Array.isArray(value) &&
|
|
199
|
+
xhr.setRequestHeader(key, value);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
xhr.responseType = 'text';
|
|
203
|
+
onCancel(signal, () => {
|
|
204
|
+
aborted = true;
|
|
205
|
+
xhr.abort();
|
|
206
|
+
reject(new CancelError());
|
|
207
|
+
});
|
|
208
|
+
xhr.onload = () => {
|
|
209
|
+
if (xhr.status != 200) {
|
|
210
|
+
// analyze HTTP status of the response
|
|
211
|
+
reject(new Error(`Error ${xhr.status}: ${xhr.statusText}`)); // e.g. 404: Not Found
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const request = {
|
|
215
|
+
method: requestMethod,
|
|
216
|
+
url,
|
|
217
|
+
data,
|
|
218
|
+
headers: headers || undefined,
|
|
219
|
+
signal,
|
|
220
|
+
onProgress
|
|
221
|
+
};
|
|
222
|
+
// Convert the header string into an array
|
|
223
|
+
// of individual headers
|
|
224
|
+
const headersArray = xhr
|
|
225
|
+
.getAllResponseHeaders()
|
|
226
|
+
.trim()
|
|
227
|
+
.split(/[\r\n]+/);
|
|
228
|
+
// Create a map of header names to values
|
|
229
|
+
const responseHeaders = {};
|
|
230
|
+
headersArray.forEach(function (line) {
|
|
231
|
+
const parts = line.split(': ');
|
|
232
|
+
const header = parts.shift();
|
|
233
|
+
const value = parts.join(': ');
|
|
234
|
+
if (header && typeof header !== 'undefined') {
|
|
235
|
+
responseHeaders[header] = value;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
const responseData = xhr.response;
|
|
239
|
+
const responseStatus = xhr.status;
|
|
240
|
+
resolve({
|
|
241
|
+
request,
|
|
242
|
+
data: responseData,
|
|
243
|
+
headers: responseHeaders,
|
|
244
|
+
status: responseStatus
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
xhr.onerror = (progressEvent) => {
|
|
249
|
+
if (aborted)
|
|
250
|
+
return;
|
|
251
|
+
// only triggers if the request couldn't be made at all
|
|
252
|
+
reject(new UploadcareNetworkError(progressEvent));
|
|
253
|
+
};
|
|
254
|
+
if (onProgress && typeof onProgress === 'function') {
|
|
255
|
+
xhr.upload.onprogress = (event) => {
|
|
256
|
+
if (event.lengthComputable) {
|
|
257
|
+
onProgress({
|
|
258
|
+
isComputable: true,
|
|
259
|
+
value: event.loaded / event.total
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
onProgress({ isComputable: false });
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (data) {
|
|
268
|
+
xhr.send(data);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
xhr.send();
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const isBlob = (data) => {
|
|
276
|
+
return typeof Blob !== 'undefined' && data instanceof Blob;
|
|
277
|
+
};
|
|
278
|
+
const isFile = (data) => {
|
|
279
|
+
return typeof File !== 'undefined' && data instanceof File;
|
|
280
|
+
};
|
|
281
|
+
const isBuffer = (data) => {
|
|
282
|
+
return typeof Buffer !== 'undefined' && data instanceof Buffer;
|
|
283
|
+
};
|
|
284
|
+
const isReactNativeAsset = (data) => {
|
|
285
|
+
return (!!data &&
|
|
286
|
+
typeof data === 'object' &&
|
|
287
|
+
!Array.isArray(data) &&
|
|
288
|
+
'uri' in data &&
|
|
289
|
+
typeof data.uri === 'string');
|
|
290
|
+
};
|
|
291
|
+
const isFileData = (data) => {
|
|
292
|
+
return (isBlob(data) || isFile(data) || isBuffer(data) || isReactNativeAsset(data));
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const getFileOptions = () => [];
|
|
296
|
+
const transformFile = (file, name, contentType) => {
|
|
297
|
+
if (isReactNativeAsset(file)) {
|
|
298
|
+
return {
|
|
299
|
+
uri: file.uri,
|
|
300
|
+
name: file.name || name,
|
|
301
|
+
type: file.type || contentType
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (isBlob(file)) {
|
|
305
|
+
const uri = URL.createObjectURL(file);
|
|
306
|
+
return { uri, name: name, type: file.type || contentType };
|
|
307
|
+
}
|
|
308
|
+
throw new Error(`Unsupported file type.`);
|
|
309
|
+
};
|
|
310
|
+
var getFormData = () => new FormData();
|
|
311
|
+
|
|
312
|
+
const isSimpleValue = (value) => {
|
|
313
|
+
return (typeof value === 'string' ||
|
|
314
|
+
typeof value === 'number' ||
|
|
315
|
+
typeof value === 'undefined');
|
|
316
|
+
};
|
|
317
|
+
const isObjectValue = (value) => {
|
|
318
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
319
|
+
};
|
|
320
|
+
const isFileValue = (value) => !!value &&
|
|
321
|
+
typeof value === 'object' &&
|
|
322
|
+
'data' in value &&
|
|
323
|
+
isFileData(value.data);
|
|
324
|
+
function collectParams(params, inputKey, inputValue) {
|
|
325
|
+
if (isFileValue(inputValue)) {
|
|
326
|
+
const { name, contentType } = inputValue;
|
|
327
|
+
const file = transformFile(inputValue.data, name, contentType ?? defaultContentType);
|
|
328
|
+
const options = getFileOptions();
|
|
329
|
+
params.push([inputKey, file, ...options]);
|
|
330
|
+
}
|
|
331
|
+
else if (isObjectValue(inputValue)) {
|
|
332
|
+
for (const [key, value] of Object.entries(inputValue)) {
|
|
333
|
+
if (typeof value !== 'undefined') {
|
|
334
|
+
params.push([`${inputKey}[${key}]`, String(value)]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else if (isSimpleValue(inputValue) && inputValue) {
|
|
339
|
+
params.push([inputKey, inputValue.toString()]);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function getFormDataParams(options) {
|
|
343
|
+
const params = [];
|
|
344
|
+
for (const [key, value] of Object.entries(options)) {
|
|
345
|
+
collectParams(params, key, value);
|
|
346
|
+
}
|
|
347
|
+
return params;
|
|
348
|
+
}
|
|
349
|
+
function buildFormData(options) {
|
|
350
|
+
const formData = getFormData();
|
|
351
|
+
const paramsList = getFormDataParams(options);
|
|
352
|
+
for (const params of paramsList) {
|
|
353
|
+
const [key, value, ...rest] = params;
|
|
354
|
+
// node form-data has another signature for append
|
|
355
|
+
formData.append(key, value, ...rest);
|
|
356
|
+
}
|
|
357
|
+
return formData;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
class UploadClientError extends Error {
|
|
361
|
+
isCancel;
|
|
362
|
+
code;
|
|
363
|
+
request;
|
|
364
|
+
response;
|
|
365
|
+
headers;
|
|
366
|
+
constructor(message, code, request, response, headers) {
|
|
367
|
+
super();
|
|
368
|
+
this.name = 'UploadClientError';
|
|
369
|
+
this.message = message;
|
|
370
|
+
this.code = code;
|
|
371
|
+
this.request = request;
|
|
372
|
+
this.response = response;
|
|
373
|
+
this.headers = headers;
|
|
374
|
+
Object.setPrototypeOf(this, UploadClientError.prototype);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const buildSearchParams = (query) => {
|
|
379
|
+
const searchParams = new URLSearchParams();
|
|
380
|
+
for (const [key, value] of Object.entries(query)) {
|
|
381
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
382
|
+
Object.entries(value)
|
|
383
|
+
.filter((entry) => entry[1] ?? false)
|
|
384
|
+
.forEach((entry) => searchParams.set(`${key}[${entry[0]}]`, String(entry[1])));
|
|
385
|
+
}
|
|
386
|
+
else if (Array.isArray(value)) {
|
|
387
|
+
value.forEach((val) => {
|
|
388
|
+
searchParams.append(`${key}[]`, val);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
else if (typeof value === 'string' && value) {
|
|
392
|
+
searchParams.set(key, value);
|
|
393
|
+
}
|
|
394
|
+
else if (typeof value === 'number') {
|
|
395
|
+
searchParams.set(key, value.toString());
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return searchParams.toString();
|
|
399
|
+
};
|
|
400
|
+
const getUrl = (base, path, query) => {
|
|
401
|
+
const url = new URL(base);
|
|
402
|
+
url.pathname = (url.pathname + path).replace('//', '/');
|
|
403
|
+
if (query) {
|
|
404
|
+
url.search = buildSearchParams(query);
|
|
405
|
+
}
|
|
406
|
+
return url.toString();
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
var version = '6.5.0';
|
|
410
|
+
|
|
411
|
+
const LIBRARY_NAME = 'UploadcareUploadClient';
|
|
412
|
+
const LIBRARY_VERSION = version;
|
|
413
|
+
function getUserAgent(options) {
|
|
414
|
+
return getUserAgent$1({
|
|
415
|
+
libraryName: LIBRARY_NAME,
|
|
416
|
+
libraryVersion: LIBRARY_VERSION,
|
|
417
|
+
...options
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const REQUEST_WAS_THROTTLED_CODE = 'RequestThrottledError';
|
|
422
|
+
const DEFAULT_RETRY_AFTER_TIMEOUT = 15000;
|
|
423
|
+
const DEFAULT_NETWORK_ERROR_TIMEOUT = 1000;
|
|
424
|
+
function getTimeoutFromThrottledRequest(error) {
|
|
425
|
+
const { headers } = error || {};
|
|
426
|
+
if (!headers || typeof headers['retry-after'] !== 'string') {
|
|
427
|
+
return DEFAULT_RETRY_AFTER_TIMEOUT;
|
|
428
|
+
}
|
|
429
|
+
const seconds = parseInt(headers['retry-after'], 10);
|
|
430
|
+
if (!Number.isFinite(seconds)) {
|
|
431
|
+
return DEFAULT_RETRY_AFTER_TIMEOUT;
|
|
432
|
+
}
|
|
433
|
+
return seconds * 1000;
|
|
434
|
+
}
|
|
435
|
+
function retryIfFailed(fn, options) {
|
|
436
|
+
const { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes } = options;
|
|
437
|
+
return retrier(({ attempt, retry }) => fn().catch((error) => {
|
|
438
|
+
if ('response' in error &&
|
|
439
|
+
error?.code === REQUEST_WAS_THROTTLED_CODE &&
|
|
440
|
+
attempt < retryThrottledRequestMaxTimes) {
|
|
441
|
+
return retry(getTimeoutFromThrottledRequest(error));
|
|
442
|
+
}
|
|
443
|
+
if (error instanceof UploadcareNetworkError &&
|
|
444
|
+
attempt < retryNetworkErrorMaxTimes) {
|
|
445
|
+
return retry((attempt + 1) * DEFAULT_NETWORK_ERROR_TIMEOUT);
|
|
446
|
+
}
|
|
447
|
+
throw error;
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const getContentType = (file) => {
|
|
452
|
+
let contentType = '';
|
|
453
|
+
if (isBlob(file) || isFile(file) || isReactNativeAsset(file)) {
|
|
454
|
+
contentType = file.type;
|
|
455
|
+
}
|
|
456
|
+
return contentType || defaultContentType;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const getFileName = (file) => {
|
|
460
|
+
let filename = '';
|
|
461
|
+
if (isFile(file) && file.name) {
|
|
462
|
+
filename = file.name;
|
|
463
|
+
}
|
|
464
|
+
else if (isBlob(file) || isBuffer(file)) {
|
|
465
|
+
filename = '';
|
|
466
|
+
}
|
|
467
|
+
else if (isReactNativeAsset(file) && file.name) {
|
|
468
|
+
filename = file.name;
|
|
469
|
+
}
|
|
470
|
+
return filename || defaultFilename;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
function getStoreValue(store) {
|
|
474
|
+
if (typeof store === 'undefined' || store === 'auto') {
|
|
475
|
+
return 'auto';
|
|
476
|
+
}
|
|
477
|
+
return store ? '1' : '0';
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Performs file uploading request to Uploadcare Upload API. Can be canceled and
|
|
482
|
+
* has progress.
|
|
483
|
+
*/
|
|
484
|
+
function base(file, { publicKey, fileName, contentType, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes, metadata }) {
|
|
485
|
+
return retryIfFailed(() => request({
|
|
486
|
+
method: 'POST',
|
|
487
|
+
url: getUrl(baseURL, '/base/', {
|
|
488
|
+
jsonerrors: 1
|
|
489
|
+
}),
|
|
490
|
+
headers: {
|
|
491
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
492
|
+
},
|
|
493
|
+
data: buildFormData({
|
|
494
|
+
file: {
|
|
495
|
+
data: file,
|
|
496
|
+
name: fileName || getFileName(file),
|
|
497
|
+
contentType: contentType || getContentType(file)
|
|
498
|
+
},
|
|
499
|
+
UPLOADCARE_PUB_KEY: publicKey,
|
|
500
|
+
UPLOADCARE_STORE: getStoreValue(store),
|
|
501
|
+
signature: secureSignature,
|
|
502
|
+
expire: secureExpire,
|
|
503
|
+
source: source,
|
|
504
|
+
metadata
|
|
505
|
+
}),
|
|
506
|
+
signal,
|
|
507
|
+
onProgress
|
|
508
|
+
}).then(({ data, headers, request }) => {
|
|
509
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
510
|
+
if ('error' in response) {
|
|
511
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
return response;
|
|
515
|
+
}
|
|
516
|
+
}), { retryNetworkErrorMaxTimes, retryThrottledRequestMaxTimes });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
var TypeEnum;
|
|
520
|
+
(function (TypeEnum) {
|
|
521
|
+
TypeEnum["Token"] = "token";
|
|
522
|
+
TypeEnum["FileInfo"] = "file_info";
|
|
523
|
+
})(TypeEnum || (TypeEnum = {}));
|
|
524
|
+
/** Uploading files from URL. */
|
|
525
|
+
function fromUrl(sourceUrl, { publicKey, baseURL = defaultSettings.baseURL, store, fileName, checkForUrlDuplicates, saveUrlForRecurrentUploads, secureSignature, secureExpire, source = 'url', signal, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes, metadata }) {
|
|
526
|
+
return retryIfFailed(() => request({
|
|
527
|
+
method: 'POST',
|
|
528
|
+
headers: {
|
|
529
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
530
|
+
},
|
|
531
|
+
url: getUrl(baseURL, '/from_url/', {
|
|
532
|
+
jsonerrors: 1,
|
|
533
|
+
pub_key: publicKey,
|
|
534
|
+
source_url: sourceUrl,
|
|
535
|
+
store: getStoreValue(store),
|
|
536
|
+
filename: fileName,
|
|
537
|
+
check_URL_duplicates: checkForUrlDuplicates ? 1 : undefined,
|
|
538
|
+
save_URL_duplicates: saveUrlForRecurrentUploads ? 1 : undefined,
|
|
539
|
+
signature: secureSignature,
|
|
540
|
+
expire: secureExpire,
|
|
541
|
+
source: source,
|
|
542
|
+
metadata
|
|
543
|
+
}),
|
|
544
|
+
signal
|
|
545
|
+
}).then(({ data, headers, request }) => {
|
|
546
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
547
|
+
if ('error' in response) {
|
|
548
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
return response;
|
|
552
|
+
}
|
|
553
|
+
}), { retryNetworkErrorMaxTimes, retryThrottledRequestMaxTimes });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
var Status;
|
|
557
|
+
(function (Status) {
|
|
558
|
+
Status["Unknown"] = "unknown";
|
|
559
|
+
Status["Waiting"] = "waiting";
|
|
560
|
+
Status["Progress"] = "progress";
|
|
561
|
+
Status["Error"] = "error";
|
|
562
|
+
Status["Success"] = "success";
|
|
563
|
+
})(Status || (Status = {}));
|
|
564
|
+
const isErrorResponse = (response) => {
|
|
565
|
+
return 'status' in response && response.status === Status.Error;
|
|
566
|
+
};
|
|
567
|
+
/** Checking upload status and working with file tokens. */
|
|
568
|
+
function fromUrlStatus(token, { publicKey, baseURL = defaultSettings.baseURL, signal, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes } = {}) {
|
|
569
|
+
return retryIfFailed(() => request({
|
|
570
|
+
method: 'GET',
|
|
571
|
+
headers: publicKey
|
|
572
|
+
? {
|
|
573
|
+
'X-UC-User-Agent': getUserAgent({
|
|
574
|
+
publicKey,
|
|
575
|
+
integration,
|
|
576
|
+
userAgent
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
: undefined,
|
|
580
|
+
url: getUrl(baseURL, '/from_url/status/', {
|
|
581
|
+
jsonerrors: 1,
|
|
582
|
+
token
|
|
583
|
+
}),
|
|
584
|
+
signal
|
|
585
|
+
}).then(({ data, headers, request }) => {
|
|
586
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
587
|
+
if ('error' in response && !isErrorResponse(response)) {
|
|
588
|
+
throw new UploadClientError(response.error.content, undefined, request, response, headers);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
return response;
|
|
592
|
+
}
|
|
593
|
+
}), { retryNetworkErrorMaxTimes, retryThrottledRequestMaxTimes });
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Create files group. */
|
|
597
|
+
function group(uuids, { publicKey, baseURL = defaultSettings.baseURL, jsonpCallback, secureSignature, secureExpire, signal, source, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes }) {
|
|
598
|
+
return retryIfFailed(() => request({
|
|
599
|
+
method: 'POST',
|
|
600
|
+
headers: {
|
|
601
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
602
|
+
},
|
|
603
|
+
url: getUrl(baseURL, '/group/', {
|
|
604
|
+
jsonerrors: 1,
|
|
605
|
+
pub_key: publicKey,
|
|
606
|
+
files: uuids,
|
|
607
|
+
callback: jsonpCallback,
|
|
608
|
+
signature: secureSignature,
|
|
609
|
+
expire: secureExpire,
|
|
610
|
+
source
|
|
611
|
+
}),
|
|
612
|
+
signal
|
|
613
|
+
}).then(({ data, headers, request }) => {
|
|
614
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
615
|
+
if ('error' in response) {
|
|
616
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
return response;
|
|
620
|
+
}
|
|
621
|
+
}), { retryNetworkErrorMaxTimes, retryThrottledRequestMaxTimes });
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Get info about group. */
|
|
625
|
+
function groupInfo(id, { publicKey, baseURL = defaultSettings.baseURL, signal, source, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes }) {
|
|
626
|
+
return retryIfFailed(() => request({
|
|
627
|
+
method: 'GET',
|
|
628
|
+
headers: {
|
|
629
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
630
|
+
},
|
|
631
|
+
url: getUrl(baseURL, '/group/info/', {
|
|
632
|
+
jsonerrors: 1,
|
|
633
|
+
pub_key: publicKey,
|
|
634
|
+
group_id: id,
|
|
635
|
+
source
|
|
636
|
+
}),
|
|
637
|
+
signal
|
|
638
|
+
}).then(({ data, headers, request }) => {
|
|
639
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
640
|
+
if ('error' in response) {
|
|
641
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
return response;
|
|
645
|
+
}
|
|
646
|
+
}), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** Returns a JSON dictionary holding file info. */
|
|
650
|
+
function info(uuid, { publicKey, baseURL = defaultSettings.baseURL, signal, source, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes }) {
|
|
651
|
+
return retryIfFailed(() => request({
|
|
652
|
+
method: 'GET',
|
|
653
|
+
headers: {
|
|
654
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
655
|
+
},
|
|
656
|
+
url: getUrl(baseURL, '/info/', {
|
|
657
|
+
jsonerrors: 1,
|
|
658
|
+
pub_key: publicKey,
|
|
659
|
+
file_id: uuid,
|
|
660
|
+
source
|
|
661
|
+
}),
|
|
662
|
+
signal
|
|
663
|
+
}).then(({ data, headers, request }) => {
|
|
664
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
665
|
+
if ('error' in response) {
|
|
666
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
return response;
|
|
670
|
+
}
|
|
671
|
+
}), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Start multipart uploading. */
|
|
675
|
+
function multipartStart(size, { publicKey, contentType, fileName, multipartChunkSize = defaultSettings.multipartChunkSize, baseURL = '', secureSignature, secureExpire, store, signal, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes, metadata }) {
|
|
676
|
+
return retryIfFailed(() => request({
|
|
677
|
+
method: 'POST',
|
|
678
|
+
url: getUrl(baseURL, '/multipart/start/', { jsonerrors: 1 }),
|
|
679
|
+
headers: {
|
|
680
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
681
|
+
},
|
|
682
|
+
data: buildFormData({
|
|
683
|
+
filename: fileName || defaultFilename,
|
|
684
|
+
size: size,
|
|
685
|
+
content_type: contentType || defaultContentType,
|
|
686
|
+
part_size: multipartChunkSize,
|
|
687
|
+
UPLOADCARE_STORE: getStoreValue(store),
|
|
688
|
+
UPLOADCARE_PUB_KEY: publicKey,
|
|
689
|
+
signature: secureSignature,
|
|
690
|
+
expire: secureExpire,
|
|
691
|
+
source: source,
|
|
692
|
+
metadata
|
|
693
|
+
}),
|
|
694
|
+
signal
|
|
695
|
+
}).then(({ data, headers, request }) => {
|
|
696
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
697
|
+
if ('error' in response) {
|
|
698
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// convert to array
|
|
702
|
+
response.parts = Object.keys(response.parts).map((key) => response.parts[key]);
|
|
703
|
+
return response;
|
|
704
|
+
}
|
|
705
|
+
}), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** Complete multipart uploading. */
|
|
709
|
+
function multipartUpload(part, url, { contentType, signal, onProgress, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes }) {
|
|
710
|
+
return retryIfFailed(() => request({
|
|
711
|
+
method: 'PUT',
|
|
712
|
+
url,
|
|
713
|
+
data: part,
|
|
714
|
+
// Upload request can't be non-computable because we always know exact size
|
|
715
|
+
onProgress: onProgress,
|
|
716
|
+
signal,
|
|
717
|
+
headers: {
|
|
718
|
+
'Content-Type': contentType || defaultContentType
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
.then((result) => {
|
|
722
|
+
// hack for node ¯\_(ツ)_/¯
|
|
723
|
+
if (onProgress)
|
|
724
|
+
onProgress({
|
|
725
|
+
isComputable: true,
|
|
726
|
+
value: 1
|
|
727
|
+
});
|
|
728
|
+
return result;
|
|
729
|
+
})
|
|
730
|
+
.then(({ status }) => ({ code: status })), {
|
|
731
|
+
retryThrottledRequestMaxTimes,
|
|
732
|
+
retryNetworkErrorMaxTimes
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Complete multipart uploading. */
|
|
737
|
+
function multipartComplete(uuid, { publicKey, baseURL = defaultSettings.baseURL, source = 'local', signal, integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes = defaultSettings.retryNetworkErrorMaxTimes }) {
|
|
738
|
+
return retryIfFailed(() => request({
|
|
739
|
+
method: 'POST',
|
|
740
|
+
url: getUrl(baseURL, '/multipart/complete/', { jsonerrors: 1 }),
|
|
741
|
+
headers: {
|
|
742
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
743
|
+
},
|
|
744
|
+
data: buildFormData({
|
|
745
|
+
uuid: uuid,
|
|
746
|
+
UPLOADCARE_PUB_KEY: publicKey,
|
|
747
|
+
source: source
|
|
748
|
+
}),
|
|
749
|
+
signal
|
|
750
|
+
}).then(({ data, headers, request }) => {
|
|
751
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
752
|
+
if ('error' in response) {
|
|
753
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
return response;
|
|
757
|
+
}
|
|
758
|
+
}), { retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes });
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function isReadyPoll({ file, publicKey, baseURL, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, signal, onProgress }) {
|
|
762
|
+
return poll({
|
|
763
|
+
check: (signal) => info(file, {
|
|
764
|
+
publicKey,
|
|
765
|
+
baseURL,
|
|
766
|
+
signal,
|
|
767
|
+
source,
|
|
768
|
+
integration,
|
|
769
|
+
userAgent,
|
|
770
|
+
retryThrottledRequestMaxTimes,
|
|
771
|
+
retryNetworkErrorMaxTimes
|
|
772
|
+
}).then((response) => {
|
|
773
|
+
if (response.isReady) {
|
|
774
|
+
return response;
|
|
775
|
+
}
|
|
776
|
+
onProgress && onProgress({ isComputable: true, value: 1 });
|
|
777
|
+
return false;
|
|
778
|
+
}),
|
|
779
|
+
signal
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
class UploadcareFile {
|
|
784
|
+
uuid;
|
|
785
|
+
name = null;
|
|
786
|
+
size = null;
|
|
787
|
+
isStored = null;
|
|
788
|
+
isImage = null;
|
|
789
|
+
mimeType = null;
|
|
790
|
+
cdnUrl = null;
|
|
791
|
+
s3Url = null;
|
|
792
|
+
originalFilename = null;
|
|
793
|
+
imageInfo = null;
|
|
794
|
+
videoInfo = null;
|
|
795
|
+
contentInfo = null;
|
|
796
|
+
metadata = null;
|
|
797
|
+
s3Bucket = null;
|
|
798
|
+
constructor(fileInfo, { baseCDN = defaultSettings.baseCDN, fileName } = {}) {
|
|
799
|
+
const { uuid, s3Bucket } = fileInfo;
|
|
800
|
+
const cdnUrl = getUrl(baseCDN, `${uuid}/`);
|
|
801
|
+
const s3Url = s3Bucket
|
|
802
|
+
? getUrl(`https://${s3Bucket}.s3.amazonaws.com/`, `${uuid}/${fileInfo.filename}`)
|
|
803
|
+
: null;
|
|
804
|
+
this.uuid = uuid;
|
|
805
|
+
this.name = fileName || fileInfo.filename;
|
|
806
|
+
this.size = fileInfo.size;
|
|
807
|
+
this.isStored = fileInfo.isStored;
|
|
808
|
+
this.isImage = fileInfo.isImage;
|
|
809
|
+
this.mimeType = fileInfo.mimeType;
|
|
810
|
+
this.cdnUrl = cdnUrl;
|
|
811
|
+
this.originalFilename = fileInfo.originalFilename;
|
|
812
|
+
this.imageInfo = fileInfo.imageInfo;
|
|
813
|
+
this.videoInfo = fileInfo.videoInfo;
|
|
814
|
+
this.contentInfo = fileInfo.contentInfo;
|
|
815
|
+
this.metadata = fileInfo.metadata || null;
|
|
816
|
+
this.s3Bucket = s3Bucket || null;
|
|
817
|
+
this.s3Url = s3Url;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const uploadDirect = (file, { publicKey, fileName, baseURL, secureSignature, secureExpire, store, contentType, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN, metadata }) => {
|
|
822
|
+
return base(file, {
|
|
823
|
+
publicKey,
|
|
824
|
+
fileName,
|
|
825
|
+
contentType,
|
|
826
|
+
baseURL,
|
|
827
|
+
secureSignature,
|
|
828
|
+
secureExpire,
|
|
829
|
+
store,
|
|
830
|
+
signal,
|
|
831
|
+
onProgress,
|
|
832
|
+
source,
|
|
833
|
+
integration,
|
|
834
|
+
userAgent,
|
|
835
|
+
retryThrottledRequestMaxTimes,
|
|
836
|
+
retryNetworkErrorMaxTimes,
|
|
837
|
+
metadata
|
|
838
|
+
})
|
|
839
|
+
.then(({ file }) => {
|
|
840
|
+
return isReadyPoll({
|
|
841
|
+
file,
|
|
842
|
+
publicKey,
|
|
843
|
+
baseURL,
|
|
844
|
+
source,
|
|
845
|
+
integration,
|
|
846
|
+
userAgent,
|
|
847
|
+
retryThrottledRequestMaxTimes,
|
|
848
|
+
retryNetworkErrorMaxTimes,
|
|
849
|
+
onProgress,
|
|
850
|
+
signal
|
|
851
|
+
});
|
|
852
|
+
})
|
|
853
|
+
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const uploadFromUploaded = (uuid, { publicKey, fileName, baseURL, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, baseCDN }) => {
|
|
857
|
+
return info(uuid, {
|
|
858
|
+
publicKey,
|
|
859
|
+
baseURL,
|
|
860
|
+
signal,
|
|
861
|
+
source,
|
|
862
|
+
integration,
|
|
863
|
+
userAgent,
|
|
864
|
+
retryThrottledRequestMaxTimes,
|
|
865
|
+
retryNetworkErrorMaxTimes
|
|
866
|
+
})
|
|
867
|
+
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN, fileName }))
|
|
868
|
+
.then((result) => {
|
|
869
|
+
// hack for node ¯\_(ツ)_/¯
|
|
870
|
+
if (onProgress)
|
|
871
|
+
onProgress({
|
|
872
|
+
isComputable: true,
|
|
873
|
+
value: 1
|
|
874
|
+
});
|
|
875
|
+
return result;
|
|
876
|
+
});
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const race = (fns, { signal } = {}) => {
|
|
880
|
+
let lastError = null;
|
|
881
|
+
let winnerIndex = null;
|
|
882
|
+
const controllers = fns.map(() => new AbortController());
|
|
883
|
+
const createStopRaceCallback = (i) => () => {
|
|
884
|
+
winnerIndex = i;
|
|
885
|
+
controllers.forEach((controller, index) => index !== i && controller.abort());
|
|
886
|
+
};
|
|
887
|
+
onCancel(signal, () => {
|
|
888
|
+
controllers.forEach((controller) => controller.abort());
|
|
889
|
+
});
|
|
890
|
+
return Promise.all(fns.map((fn, i) => {
|
|
891
|
+
const stopRace = createStopRaceCallback(i);
|
|
892
|
+
return Promise.resolve()
|
|
893
|
+
.then(() => fn({ stopRace, signal: controllers[i].signal }))
|
|
894
|
+
.then((result) => {
|
|
895
|
+
stopRace();
|
|
896
|
+
return result;
|
|
897
|
+
})
|
|
898
|
+
.catch((error) => {
|
|
899
|
+
lastError = error;
|
|
900
|
+
return null;
|
|
901
|
+
});
|
|
902
|
+
})).then((results) => {
|
|
903
|
+
if (winnerIndex === null) {
|
|
904
|
+
throw lastError;
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
return results[winnerIndex];
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
var WebSocket = window.WebSocket;
|
|
913
|
+
|
|
914
|
+
class Events {
|
|
915
|
+
events = Object.create({});
|
|
916
|
+
emit(event, data) {
|
|
917
|
+
this.events[event]?.forEach((fn) => fn(data));
|
|
918
|
+
}
|
|
919
|
+
on(event, callback) {
|
|
920
|
+
this.events[event] = this.events[event] || [];
|
|
921
|
+
this.events[event].push(callback);
|
|
922
|
+
}
|
|
923
|
+
off(event, callback) {
|
|
924
|
+
if (callback) {
|
|
925
|
+
this.events[event] = this.events[event].filter((fn) => fn !== callback);
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
this.events[event] = [];
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const response = (type, data) => {
|
|
934
|
+
if (type === 'success') {
|
|
935
|
+
return { status: Status.Success, ...data };
|
|
936
|
+
}
|
|
937
|
+
if (type === 'progress') {
|
|
938
|
+
return { status: Status.Progress, ...data };
|
|
939
|
+
}
|
|
940
|
+
return { status: Status.Error, ...data };
|
|
941
|
+
};
|
|
942
|
+
class Pusher {
|
|
943
|
+
key;
|
|
944
|
+
disconnectTime;
|
|
945
|
+
ws = undefined;
|
|
946
|
+
queue = [];
|
|
947
|
+
isConnected = false;
|
|
948
|
+
subscribers = 0;
|
|
949
|
+
emmitter = new Events();
|
|
950
|
+
disconnectTimeoutId = null;
|
|
951
|
+
constructor(pusherKey, disconnectTime = 30000) {
|
|
952
|
+
this.key = pusherKey;
|
|
953
|
+
this.disconnectTime = disconnectTime;
|
|
954
|
+
}
|
|
955
|
+
connect() {
|
|
956
|
+
this.disconnectTimeoutId && clearTimeout(this.disconnectTimeoutId);
|
|
957
|
+
if (!this.isConnected && !this.ws) {
|
|
958
|
+
const pusherUrl = `wss://ws.pusherapp.com/app/${this.key}?protocol=5&client=js&version=1.12.2`;
|
|
959
|
+
this.ws = new WebSocket(pusherUrl);
|
|
960
|
+
this.ws.addEventListener('error', (error) => {
|
|
961
|
+
this.emmitter.emit('error', new Error(error.message));
|
|
962
|
+
});
|
|
963
|
+
this.emmitter.on('connected', () => {
|
|
964
|
+
this.isConnected = true;
|
|
965
|
+
this.queue.forEach((message) => this.send(message.event, message.data));
|
|
966
|
+
this.queue = [];
|
|
967
|
+
});
|
|
968
|
+
this.ws.addEventListener('message', (e) => {
|
|
969
|
+
const data = JSON.parse(e.data.toString());
|
|
970
|
+
switch (data.event) {
|
|
971
|
+
case 'pusher:connection_established': {
|
|
972
|
+
this.emmitter.emit('connected', undefined);
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
case 'pusher:ping': {
|
|
976
|
+
this.send('pusher:pong', {});
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
case 'progress':
|
|
980
|
+
case 'success':
|
|
981
|
+
case 'fail': {
|
|
982
|
+
this.emmitter.emit(data.channel, response(data.event, JSON.parse(data.data)));
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
disconnect() {
|
|
989
|
+
const actualDisconect = () => {
|
|
990
|
+
this.ws?.close();
|
|
991
|
+
this.ws = undefined;
|
|
992
|
+
this.isConnected = false;
|
|
993
|
+
};
|
|
994
|
+
if (this.disconnectTime) {
|
|
995
|
+
this.disconnectTimeoutId = setTimeout(() => {
|
|
996
|
+
actualDisconect();
|
|
997
|
+
}, this.disconnectTime);
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
actualDisconect();
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
send(event, data) {
|
|
1004
|
+
const str = JSON.stringify({ event, data });
|
|
1005
|
+
this.ws?.send(str);
|
|
1006
|
+
}
|
|
1007
|
+
subscribe(token, handler) {
|
|
1008
|
+
this.subscribers += 1;
|
|
1009
|
+
this.connect();
|
|
1010
|
+
const channel = `task-status-${token}`;
|
|
1011
|
+
const message = {
|
|
1012
|
+
event: 'pusher:subscribe',
|
|
1013
|
+
data: { channel }
|
|
1014
|
+
};
|
|
1015
|
+
this.emmitter.on(channel, handler);
|
|
1016
|
+
if (this.isConnected) {
|
|
1017
|
+
this.send(message.event, message.data);
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
this.queue.push(message);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
unsubscribe(token) {
|
|
1024
|
+
this.subscribers -= 1;
|
|
1025
|
+
const channel = `task-status-${token}`;
|
|
1026
|
+
const message = {
|
|
1027
|
+
event: 'pusher:unsubscribe',
|
|
1028
|
+
data: { channel }
|
|
1029
|
+
};
|
|
1030
|
+
this.emmitter.off(channel);
|
|
1031
|
+
if (this.isConnected) {
|
|
1032
|
+
this.send(message.event, message.data);
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
this.queue = this.queue.filter((msg) => msg.data.channel !== channel);
|
|
1036
|
+
}
|
|
1037
|
+
if (this.subscribers === 0) {
|
|
1038
|
+
this.disconnect();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
onError(callback) {
|
|
1042
|
+
this.emmitter.on('error', callback);
|
|
1043
|
+
return () => this.emmitter.off('error', callback);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
let pusher = null;
|
|
1047
|
+
const getPusher = (key) => {
|
|
1048
|
+
if (!pusher) {
|
|
1049
|
+
// no timeout for nodeJS and 30000 ms for browser
|
|
1050
|
+
const disconectTimeout = typeof window === 'undefined' ? 0 : 30000;
|
|
1051
|
+
pusher = new Pusher(key, disconectTimeout);
|
|
1052
|
+
}
|
|
1053
|
+
return pusher;
|
|
1054
|
+
};
|
|
1055
|
+
const preconnect = (key) => {
|
|
1056
|
+
getPusher(key).connect();
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
function pollStrategy({ token, publicKey, baseURL, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, onProgress, signal }) {
|
|
1060
|
+
return poll({
|
|
1061
|
+
check: (signal) => fromUrlStatus(token, {
|
|
1062
|
+
publicKey,
|
|
1063
|
+
baseURL,
|
|
1064
|
+
integration,
|
|
1065
|
+
userAgent,
|
|
1066
|
+
retryThrottledRequestMaxTimes,
|
|
1067
|
+
retryNetworkErrorMaxTimes,
|
|
1068
|
+
signal
|
|
1069
|
+
}).then((response) => {
|
|
1070
|
+
switch (response.status) {
|
|
1071
|
+
case Status.Error: {
|
|
1072
|
+
return new UploadClientError(response.error, response.errorCode);
|
|
1073
|
+
}
|
|
1074
|
+
case Status.Waiting: {
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
case Status.Unknown: {
|
|
1078
|
+
return new UploadClientError(`Token "${token}" was not found.`);
|
|
1079
|
+
}
|
|
1080
|
+
case Status.Progress: {
|
|
1081
|
+
if (onProgress) {
|
|
1082
|
+
if (response.total === 'unknown') {
|
|
1083
|
+
onProgress({ isComputable: false });
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
onProgress({
|
|
1087
|
+
isComputable: true,
|
|
1088
|
+
value: response.done / response.total
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
case Status.Success: {
|
|
1095
|
+
if (onProgress)
|
|
1096
|
+
onProgress({
|
|
1097
|
+
isComputable: true,
|
|
1098
|
+
value: response.done / response.total
|
|
1099
|
+
});
|
|
1100
|
+
return response;
|
|
1101
|
+
}
|
|
1102
|
+
default: {
|
|
1103
|
+
throw new Error('Unknown status');
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}),
|
|
1107
|
+
signal
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
const pushStrategy = ({ token, pusherKey, signal, onProgress }) => new Promise((resolve, reject) => {
|
|
1111
|
+
const pusher = getPusher(pusherKey);
|
|
1112
|
+
const unsubErrorHandler = pusher.onError(reject);
|
|
1113
|
+
const destroy = () => {
|
|
1114
|
+
unsubErrorHandler();
|
|
1115
|
+
pusher.unsubscribe(token);
|
|
1116
|
+
};
|
|
1117
|
+
onCancel(signal, () => {
|
|
1118
|
+
destroy();
|
|
1119
|
+
reject(new CancelError('pusher cancelled'));
|
|
1120
|
+
});
|
|
1121
|
+
pusher.subscribe(token, (result) => {
|
|
1122
|
+
switch (result.status) {
|
|
1123
|
+
case Status.Progress: {
|
|
1124
|
+
if (onProgress) {
|
|
1125
|
+
if (result.total === 'unknown') {
|
|
1126
|
+
onProgress({ isComputable: false });
|
|
1127
|
+
}
|
|
1128
|
+
else {
|
|
1129
|
+
onProgress({
|
|
1130
|
+
isComputable: true,
|
|
1131
|
+
value: result.done / result.total
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
case Status.Success: {
|
|
1138
|
+
destroy();
|
|
1139
|
+
if (onProgress)
|
|
1140
|
+
onProgress({
|
|
1141
|
+
isComputable: true,
|
|
1142
|
+
value: result.done / result.total
|
|
1143
|
+
});
|
|
1144
|
+
resolve(result);
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
case Status.Error: {
|
|
1148
|
+
destroy();
|
|
1149
|
+
reject(new UploadClientError(result.msg, result.error_code));
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, checkForUrlDuplicates, saveUrlForRecurrentUploads, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, pusherKey = defaultSettings.pusherKey, metadata }) => Promise.resolve(preconnect(pusherKey))
|
|
1155
|
+
.then(() => fromUrl(sourceUrl, {
|
|
1156
|
+
publicKey,
|
|
1157
|
+
fileName,
|
|
1158
|
+
baseURL,
|
|
1159
|
+
checkForUrlDuplicates,
|
|
1160
|
+
saveUrlForRecurrentUploads,
|
|
1161
|
+
secureSignature,
|
|
1162
|
+
secureExpire,
|
|
1163
|
+
store,
|
|
1164
|
+
signal,
|
|
1165
|
+
source,
|
|
1166
|
+
integration,
|
|
1167
|
+
userAgent,
|
|
1168
|
+
retryThrottledRequestMaxTimes,
|
|
1169
|
+
metadata
|
|
1170
|
+
}))
|
|
1171
|
+
.catch((error) => {
|
|
1172
|
+
const pusher = getPusher(pusherKey);
|
|
1173
|
+
pusher?.disconnect();
|
|
1174
|
+
return Promise.reject(error);
|
|
1175
|
+
})
|
|
1176
|
+
.then((urlResponse) => {
|
|
1177
|
+
if (urlResponse.type === TypeEnum.FileInfo) {
|
|
1178
|
+
return urlResponse;
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
return race([
|
|
1182
|
+
({ signal }) => pollStrategy({
|
|
1183
|
+
token: urlResponse.token,
|
|
1184
|
+
publicKey,
|
|
1185
|
+
baseURL,
|
|
1186
|
+
integration,
|
|
1187
|
+
userAgent,
|
|
1188
|
+
retryThrottledRequestMaxTimes,
|
|
1189
|
+
onProgress,
|
|
1190
|
+
signal
|
|
1191
|
+
}),
|
|
1192
|
+
({ signal }) => pushStrategy({
|
|
1193
|
+
token: urlResponse.token,
|
|
1194
|
+
pusherKey,
|
|
1195
|
+
signal,
|
|
1196
|
+
onProgress
|
|
1197
|
+
})
|
|
1198
|
+
], { signal });
|
|
1199
|
+
}
|
|
1200
|
+
})
|
|
1201
|
+
.then((result) => {
|
|
1202
|
+
if (result instanceof UploadClientError)
|
|
1203
|
+
throw result;
|
|
1204
|
+
return result;
|
|
1205
|
+
})
|
|
1206
|
+
.then((result) => isReadyPoll({
|
|
1207
|
+
file: result.uuid,
|
|
1208
|
+
publicKey,
|
|
1209
|
+
baseURL,
|
|
1210
|
+
integration,
|
|
1211
|
+
userAgent,
|
|
1212
|
+
retryThrottledRequestMaxTimes,
|
|
1213
|
+
onProgress,
|
|
1214
|
+
signal
|
|
1215
|
+
}))
|
|
1216
|
+
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
|
|
1217
|
+
|
|
1218
|
+
const memo = new WeakMap();
|
|
1219
|
+
const getBlobFromReactNativeAsset = async (asset) => {
|
|
1220
|
+
if (memo.has(asset)) {
|
|
1221
|
+
return memo.get(asset);
|
|
1222
|
+
}
|
|
1223
|
+
const blob = await fetch(asset.uri).then((res) => res.blob());
|
|
1224
|
+
memo.set(asset, blob);
|
|
1225
|
+
return blob;
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
const getFileSize = async (file) => {
|
|
1229
|
+
if (isBuffer(file)) {
|
|
1230
|
+
return file.length;
|
|
1231
|
+
}
|
|
1232
|
+
if (isFile(file) || isBlob(file)) {
|
|
1233
|
+
return file.size;
|
|
1234
|
+
}
|
|
1235
|
+
if (isReactNativeAsset(file)) {
|
|
1236
|
+
const blob = await getBlobFromReactNativeAsset(file);
|
|
1237
|
+
return blob.size;
|
|
1238
|
+
}
|
|
1239
|
+
throw new Error(`Unknown file type. Cannot determine file size.`);
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
/** Check if FileData is multipart data. */
|
|
1243
|
+
const isMultipart = (fileSize, multipartMinFileSize = defaultSettings.multipartMinFileSize) => {
|
|
1244
|
+
return fileSize >= multipartMinFileSize;
|
|
1245
|
+
};
|
|
1246
|
+
|
|
1247
|
+
/** Uuid type guard. */
|
|
1248
|
+
const isUuid = (data) => {
|
|
1249
|
+
const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
|
|
1250
|
+
const regExp = new RegExp(UUID_REGEX);
|
|
1251
|
+
return !isFileData(data) && regExp.test(data);
|
|
1252
|
+
};
|
|
1253
|
+
/**
|
|
1254
|
+
* Url type guard.
|
|
1255
|
+
*
|
|
1256
|
+
* @param {SupportedFileInput | Url | Uuid} data
|
|
1257
|
+
*/
|
|
1258
|
+
const isUrl = (data) => {
|
|
1259
|
+
const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$';
|
|
1260
|
+
const regExp = new RegExp(URL_REGEX);
|
|
1261
|
+
return !isFileData(data) && regExp.test(data);
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
const runWithConcurrency = (concurrency, tasks) => {
|
|
1265
|
+
return new Promise((resolve, reject) => {
|
|
1266
|
+
const results = [];
|
|
1267
|
+
let rejected = false;
|
|
1268
|
+
let settled = tasks.length;
|
|
1269
|
+
const forRun = [...tasks];
|
|
1270
|
+
const run = () => {
|
|
1271
|
+
const index = tasks.length - forRun.length;
|
|
1272
|
+
const next = forRun.shift();
|
|
1273
|
+
if (next) {
|
|
1274
|
+
next()
|
|
1275
|
+
.then((result) => {
|
|
1276
|
+
if (rejected)
|
|
1277
|
+
return;
|
|
1278
|
+
results[index] = result;
|
|
1279
|
+
settled -= 1;
|
|
1280
|
+
if (settled) {
|
|
1281
|
+
run();
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
resolve(results);
|
|
1285
|
+
}
|
|
1286
|
+
})
|
|
1287
|
+
.catch((error) => {
|
|
1288
|
+
rejected = true;
|
|
1289
|
+
reject(error);
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
for (let i = 0; i < concurrency; i++) {
|
|
1294
|
+
run();
|
|
1295
|
+
}
|
|
1296
|
+
});
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
const sliceChunk = (file, index, fileSize, chunkSize) => {
|
|
1300
|
+
const start = chunkSize * index;
|
|
1301
|
+
const end = Math.min(start + chunkSize, fileSize);
|
|
1302
|
+
return file.slice(start, end);
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* React-native hack for blob slicing
|
|
1307
|
+
*
|
|
1308
|
+
* We need to store references to sliced blobs to prevent source blob from being
|
|
1309
|
+
* deallocated until uploading complete. Access to deallocated blob causes app
|
|
1310
|
+
* crash.
|
|
1311
|
+
*
|
|
1312
|
+
* See https://github.com/uploadcare/uploadcare-js-api-clients/issues/306 and
|
|
1313
|
+
* https://github.com/facebook/react-native/issues/27543
|
|
1314
|
+
*/
|
|
1315
|
+
const prepareChunks = async (file, fileSize, chunkSize) => {
|
|
1316
|
+
let blob;
|
|
1317
|
+
if (isReactNativeAsset(file)) {
|
|
1318
|
+
blob = await getBlobFromReactNativeAsset(file);
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
blob = file;
|
|
1322
|
+
}
|
|
1323
|
+
return (index) => {
|
|
1324
|
+
const chunk = sliceChunk(blob, index, fileSize, chunkSize);
|
|
1325
|
+
return chunk;
|
|
1326
|
+
};
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
const uploadPart = (chunk, url, { publicKey, contentType, onProgress, signal, integration, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes }) => multipartUpload(chunk, url, {
|
|
1330
|
+
publicKey,
|
|
1331
|
+
contentType,
|
|
1332
|
+
onProgress,
|
|
1333
|
+
signal,
|
|
1334
|
+
integration,
|
|
1335
|
+
retryThrottledRequestMaxTimes,
|
|
1336
|
+
retryNetworkErrorMaxTimes
|
|
1337
|
+
});
|
|
1338
|
+
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 }) => {
|
|
1339
|
+
const size = fileSize ?? (await getFileSize(file));
|
|
1340
|
+
let progressValues;
|
|
1341
|
+
const createProgressHandler = (totalChunks, chunkIdx) => {
|
|
1342
|
+
if (!onProgress)
|
|
1343
|
+
return;
|
|
1344
|
+
if (!progressValues) {
|
|
1345
|
+
progressValues = Array(totalChunks).fill(0);
|
|
1346
|
+
}
|
|
1347
|
+
const sum = (values) => values.reduce((sum, next) => sum + next, 0);
|
|
1348
|
+
return (info) => {
|
|
1349
|
+
if (!info.isComputable) {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
progressValues[chunkIdx] = info.value;
|
|
1353
|
+
onProgress({
|
|
1354
|
+
isComputable: true,
|
|
1355
|
+
value: sum(progressValues) / totalChunks
|
|
1356
|
+
});
|
|
1357
|
+
};
|
|
1358
|
+
};
|
|
1359
|
+
contentType ||= getContentType(file);
|
|
1360
|
+
return multipartStart(size, {
|
|
1361
|
+
publicKey,
|
|
1362
|
+
contentType,
|
|
1363
|
+
fileName: fileName || getFileName(file),
|
|
1364
|
+
baseURL,
|
|
1365
|
+
secureSignature,
|
|
1366
|
+
secureExpire,
|
|
1367
|
+
store,
|
|
1368
|
+
signal,
|
|
1369
|
+
source,
|
|
1370
|
+
integration,
|
|
1371
|
+
userAgent,
|
|
1372
|
+
retryThrottledRequestMaxTimes,
|
|
1373
|
+
retryNetworkErrorMaxTimes,
|
|
1374
|
+
metadata
|
|
1375
|
+
})
|
|
1376
|
+
.then(async ({ uuid, parts }) => {
|
|
1377
|
+
const getChunk = await prepareChunks(file, size, multipartChunkSize);
|
|
1378
|
+
return Promise.all([
|
|
1379
|
+
uuid,
|
|
1380
|
+
runWithConcurrency(maxConcurrentRequests, parts.map((url, index) => () => uploadPart(getChunk(index), url, {
|
|
1381
|
+
publicKey,
|
|
1382
|
+
contentType,
|
|
1383
|
+
onProgress: createProgressHandler(parts.length, index),
|
|
1384
|
+
signal,
|
|
1385
|
+
integration,
|
|
1386
|
+
retryThrottledRequestMaxTimes,
|
|
1387
|
+
retryNetworkErrorMaxTimes
|
|
1388
|
+
})))
|
|
1389
|
+
]);
|
|
1390
|
+
})
|
|
1391
|
+
.then(([uuid]) => multipartComplete(uuid, {
|
|
1392
|
+
publicKey,
|
|
1393
|
+
baseURL,
|
|
1394
|
+
source,
|
|
1395
|
+
integration,
|
|
1396
|
+
userAgent,
|
|
1397
|
+
retryThrottledRequestMaxTimes,
|
|
1398
|
+
retryNetworkErrorMaxTimes
|
|
1399
|
+
}))
|
|
1400
|
+
.then((fileInfo) => {
|
|
1401
|
+
if (fileInfo.isReady) {
|
|
1402
|
+
return fileInfo;
|
|
1403
|
+
}
|
|
1404
|
+
else {
|
|
1405
|
+
return isReadyPoll({
|
|
1406
|
+
file: fileInfo.uuid,
|
|
1407
|
+
publicKey,
|
|
1408
|
+
baseURL,
|
|
1409
|
+
source,
|
|
1410
|
+
integration,
|
|
1411
|
+
userAgent,
|
|
1412
|
+
retryThrottledRequestMaxTimes,
|
|
1413
|
+
retryNetworkErrorMaxTimes,
|
|
1414
|
+
onProgress,
|
|
1415
|
+
signal
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
})
|
|
1419
|
+
.then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN }));
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
/** Uploads file from provided data. */
|
|
1423
|
+
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 }) {
|
|
1424
|
+
if (isFileData(data)) {
|
|
1425
|
+
const fileSize = await getFileSize(data);
|
|
1426
|
+
if (isMultipart(fileSize, multipartMinFileSize)) {
|
|
1427
|
+
return uploadMultipart(data, {
|
|
1428
|
+
publicKey,
|
|
1429
|
+
contentType,
|
|
1430
|
+
multipartChunkSize,
|
|
1431
|
+
fileSize,
|
|
1432
|
+
fileName,
|
|
1433
|
+
baseURL,
|
|
1434
|
+
secureSignature,
|
|
1435
|
+
secureExpire,
|
|
1436
|
+
store,
|
|
1437
|
+
signal,
|
|
1438
|
+
onProgress,
|
|
1439
|
+
source,
|
|
1440
|
+
integration,
|
|
1441
|
+
userAgent,
|
|
1442
|
+
maxConcurrentRequests,
|
|
1443
|
+
retryThrottledRequestMaxTimes,
|
|
1444
|
+
retryNetworkErrorMaxTimes,
|
|
1445
|
+
baseCDN,
|
|
1446
|
+
metadata
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
return uploadDirect(data, {
|
|
1450
|
+
publicKey,
|
|
1451
|
+
fileName,
|
|
1452
|
+
contentType,
|
|
1453
|
+
baseURL,
|
|
1454
|
+
secureSignature,
|
|
1455
|
+
secureExpire,
|
|
1456
|
+
store,
|
|
1457
|
+
signal,
|
|
1458
|
+
onProgress,
|
|
1459
|
+
source,
|
|
1460
|
+
integration,
|
|
1461
|
+
userAgent,
|
|
1462
|
+
retryThrottledRequestMaxTimes,
|
|
1463
|
+
retryNetworkErrorMaxTimes,
|
|
1464
|
+
baseCDN,
|
|
1465
|
+
metadata
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
if (isUrl(data)) {
|
|
1469
|
+
return uploadFromUrl(data, {
|
|
1470
|
+
publicKey,
|
|
1471
|
+
fileName,
|
|
1472
|
+
baseURL,
|
|
1473
|
+
baseCDN,
|
|
1474
|
+
checkForUrlDuplicates,
|
|
1475
|
+
saveUrlForRecurrentUploads,
|
|
1476
|
+
secureSignature,
|
|
1477
|
+
secureExpire,
|
|
1478
|
+
store,
|
|
1479
|
+
signal,
|
|
1480
|
+
onProgress,
|
|
1481
|
+
source,
|
|
1482
|
+
integration,
|
|
1483
|
+
userAgent,
|
|
1484
|
+
retryThrottledRequestMaxTimes,
|
|
1485
|
+
retryNetworkErrorMaxTimes,
|
|
1486
|
+
pusherKey,
|
|
1487
|
+
metadata
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
if (isUuid(data)) {
|
|
1491
|
+
return uploadFromUploaded(data, {
|
|
1492
|
+
publicKey,
|
|
1493
|
+
fileName,
|
|
1494
|
+
baseURL,
|
|
1495
|
+
signal,
|
|
1496
|
+
onProgress,
|
|
1497
|
+
source,
|
|
1498
|
+
integration,
|
|
1499
|
+
userAgent,
|
|
1500
|
+
retryThrottledRequestMaxTimes,
|
|
1501
|
+
retryNetworkErrorMaxTimes,
|
|
1502
|
+
baseCDN
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
throw new TypeError(`File uploading from "${data}" is not supported`);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
class UploadcareGroup {
|
|
1509
|
+
uuid;
|
|
1510
|
+
filesCount;
|
|
1511
|
+
totalSize;
|
|
1512
|
+
isStored;
|
|
1513
|
+
isImage;
|
|
1514
|
+
cdnUrl;
|
|
1515
|
+
files;
|
|
1516
|
+
createdAt;
|
|
1517
|
+
storedAt = null;
|
|
1518
|
+
constructor(groupInfo, files) {
|
|
1519
|
+
this.uuid = groupInfo.id;
|
|
1520
|
+
this.filesCount = groupInfo.filesCount;
|
|
1521
|
+
this.totalSize = Object.values(groupInfo.files).reduce((acc, file) => acc + file.size, 0);
|
|
1522
|
+
this.isStored = !!groupInfo.datetimeStored;
|
|
1523
|
+
this.isImage = !!Object.values(groupInfo.files).filter((file) => file.isImage).length;
|
|
1524
|
+
this.cdnUrl = groupInfo.cdnUrl;
|
|
1525
|
+
this.files = files;
|
|
1526
|
+
this.createdAt = groupInfo.datetimeCreated;
|
|
1527
|
+
this.storedAt = groupInfo.datetimeStored;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/** FileData type guard. */
|
|
1532
|
+
const isFileDataArray = (data) => {
|
|
1533
|
+
for (const item of data) {
|
|
1534
|
+
if (!isFileData(item)) {
|
|
1535
|
+
return false;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return true;
|
|
1539
|
+
};
|
|
1540
|
+
/** Uuid type guard. */
|
|
1541
|
+
const isUuidArray = (data) => {
|
|
1542
|
+
for (const item of data) {
|
|
1543
|
+
if (!isUuid(item)) {
|
|
1544
|
+
return false;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return true;
|
|
1548
|
+
};
|
|
1549
|
+
/** Url type guard. */
|
|
1550
|
+
const isUrlArray = (data) => {
|
|
1551
|
+
for (const item of data) {
|
|
1552
|
+
if (!isUrl(item)) {
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return true;
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
function uploadFileGroup(data, { publicKey, fileName, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source, integration, userAgent, retryThrottledRequestMaxTimes, retryNetworkErrorMaxTimes, contentType, multipartChunkSize = defaultSettings.multipartChunkSize, baseCDN = defaultSettings.baseCDN, checkForUrlDuplicates, saveUrlForRecurrentUploads, jsonpCallback }) {
|
|
1560
|
+
if (!isFileDataArray(data) && !isUrlArray(data) && !isUuidArray(data)) {
|
|
1561
|
+
throw new TypeError(`Group uploading from "${data}" is not supported`);
|
|
1562
|
+
}
|
|
1563
|
+
let progressValues;
|
|
1564
|
+
let isStillComputable = true;
|
|
1565
|
+
const filesCount = data.length;
|
|
1566
|
+
const createProgressHandler = (size, index) => {
|
|
1567
|
+
if (!onProgress)
|
|
1568
|
+
return;
|
|
1569
|
+
if (!progressValues) {
|
|
1570
|
+
progressValues = Array(size).fill(0);
|
|
1571
|
+
}
|
|
1572
|
+
const normalize = (values) => values.reduce((sum, next) => sum + next) / size;
|
|
1573
|
+
return (info) => {
|
|
1574
|
+
if (!info.isComputable || !isStillComputable) {
|
|
1575
|
+
isStillComputable = false;
|
|
1576
|
+
onProgress({ isComputable: false });
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
progressValues[index] = info.value;
|
|
1580
|
+
onProgress({ isComputable: true, value: normalize(progressValues) });
|
|
1581
|
+
};
|
|
1582
|
+
};
|
|
1583
|
+
return Promise.all(data.map((file, index) => uploadFile(file, {
|
|
1584
|
+
publicKey,
|
|
1585
|
+
fileName,
|
|
1586
|
+
baseURL,
|
|
1587
|
+
secureSignature,
|
|
1588
|
+
secureExpire,
|
|
1589
|
+
store,
|
|
1590
|
+
signal,
|
|
1591
|
+
onProgress: createProgressHandler(filesCount, index),
|
|
1592
|
+
source,
|
|
1593
|
+
integration,
|
|
1594
|
+
userAgent,
|
|
1595
|
+
retryThrottledRequestMaxTimes,
|
|
1596
|
+
retryNetworkErrorMaxTimes,
|
|
1597
|
+
contentType,
|
|
1598
|
+
multipartChunkSize,
|
|
1599
|
+
baseCDN,
|
|
1600
|
+
checkForUrlDuplicates,
|
|
1601
|
+
saveUrlForRecurrentUploads
|
|
1602
|
+
}))).then((files) => {
|
|
1603
|
+
const uuids = files.map((file) => file.uuid);
|
|
1604
|
+
return group(uuids, {
|
|
1605
|
+
publicKey,
|
|
1606
|
+
baseURL,
|
|
1607
|
+
jsonpCallback,
|
|
1608
|
+
secureSignature,
|
|
1609
|
+
secureExpire,
|
|
1610
|
+
signal,
|
|
1611
|
+
source,
|
|
1612
|
+
integration,
|
|
1613
|
+
userAgent,
|
|
1614
|
+
retryThrottledRequestMaxTimes,
|
|
1615
|
+
retryNetworkErrorMaxTimes
|
|
1616
|
+
})
|
|
1617
|
+
.then((groupInfo) => new UploadcareGroup(groupInfo, files))
|
|
1618
|
+
.then((group) => {
|
|
1619
|
+
onProgress && onProgress({ isComputable: true, value: 1 });
|
|
1620
|
+
return group;
|
|
1621
|
+
});
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/** Populate options with settings. */
|
|
1626
|
+
const populateOptionsWithSettings = (options, settings) => ({
|
|
1627
|
+
...settings,
|
|
1628
|
+
...options
|
|
1629
|
+
});
|
|
1630
|
+
class UploadClient {
|
|
1631
|
+
settings;
|
|
1632
|
+
constructor(settings) {
|
|
1633
|
+
this.settings = Object.assign({}, defaultSettings, settings);
|
|
1634
|
+
}
|
|
1635
|
+
updateSettings(newSettings) {
|
|
1636
|
+
this.settings = Object.assign(this.settings, newSettings);
|
|
1637
|
+
}
|
|
1638
|
+
getSettings() {
|
|
1639
|
+
return this.settings;
|
|
1640
|
+
}
|
|
1641
|
+
base(file, options = {}) {
|
|
1642
|
+
const settings = this.getSettings();
|
|
1643
|
+
return base(file, populateOptionsWithSettings(options, settings));
|
|
1644
|
+
}
|
|
1645
|
+
info(uuid, options = {}) {
|
|
1646
|
+
const settings = this.getSettings();
|
|
1647
|
+
return info(uuid, populateOptionsWithSettings(options, settings));
|
|
1648
|
+
}
|
|
1649
|
+
fromUrl(sourceUrl, options = {}) {
|
|
1650
|
+
const settings = this.getSettings();
|
|
1651
|
+
return fromUrl(sourceUrl, populateOptionsWithSettings(options, settings));
|
|
1652
|
+
}
|
|
1653
|
+
fromUrlStatus(token, options = {}) {
|
|
1654
|
+
const settings = this.getSettings();
|
|
1655
|
+
return fromUrlStatus(token, populateOptionsWithSettings(options, settings));
|
|
1656
|
+
}
|
|
1657
|
+
group(uuids, options = {}) {
|
|
1658
|
+
const settings = this.getSettings();
|
|
1659
|
+
return group(uuids, populateOptionsWithSettings(options, settings));
|
|
1660
|
+
}
|
|
1661
|
+
groupInfo(id, options = {}) {
|
|
1662
|
+
const settings = this.getSettings();
|
|
1663
|
+
return groupInfo(id, populateOptionsWithSettings(options, settings));
|
|
1664
|
+
}
|
|
1665
|
+
multipartStart(size, options = {}) {
|
|
1666
|
+
const settings = this.getSettings();
|
|
1667
|
+
return multipartStart(size, populateOptionsWithSettings(options, settings));
|
|
1668
|
+
}
|
|
1669
|
+
multipartUpload(part, url, options = {}) {
|
|
1670
|
+
const settings = this.getSettings();
|
|
1671
|
+
return multipartUpload(part, url, populateOptionsWithSettings(options, settings));
|
|
1672
|
+
}
|
|
1673
|
+
multipartComplete(uuid, options = {}) {
|
|
1674
|
+
const settings = this.getSettings();
|
|
1675
|
+
return multipartComplete(uuid, populateOptionsWithSettings(options, settings));
|
|
1676
|
+
}
|
|
1677
|
+
uploadFile(data, options = {}) {
|
|
1678
|
+
const settings = this.getSettings();
|
|
1679
|
+
return uploadFile(data, populateOptionsWithSettings(options, settings));
|
|
1680
|
+
}
|
|
1681
|
+
uploadFileGroup(data, options = {}) {
|
|
1682
|
+
const settings = this.getSettings();
|
|
1683
|
+
return uploadFileGroup(data, populateOptionsWithSettings(options, settings));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
exports.UploadClient = UploadClient;
|
|
1688
|
+
exports.UploadClientError = UploadClientError;
|
|
1689
|
+
exports.UploadcareFile = UploadcareFile;
|
|
1690
|
+
exports.UploadcareGroup = UploadcareGroup;
|
|
1691
|
+
exports.UploadcareNetworkError = UploadcareNetworkError;
|
|
1692
|
+
exports.base = base;
|
|
1693
|
+
exports.fromUrl = fromUrl;
|
|
1694
|
+
exports.fromUrlStatus = fromUrlStatus;
|
|
1695
|
+
exports.getUserAgent = getUserAgent$1;
|
|
1696
|
+
exports.group = group;
|
|
1697
|
+
exports.groupInfo = groupInfo;
|
|
1698
|
+
exports.info = info;
|
|
1699
|
+
exports.multipartComplete = multipartComplete;
|
|
1700
|
+
exports.multipartStart = multipartStart;
|
|
1701
|
+
exports.multipartUpload = multipartUpload;
|
|
1702
|
+
exports.uploadDirect = uploadDirect;
|
|
1703
|
+
exports.uploadFile = uploadFile;
|
|
1704
|
+
exports.uploadFileGroup = uploadFileGroup;
|
|
1705
|
+
exports.uploadFromUploaded = uploadFromUploaded;
|
|
1706
|
+
exports.uploadFromUrl = uploadFromUrl;
|
|
1707
|
+
exports.uploadMultipart = uploadMultipart;
|