@uploadcare/upload-client 4.0.1 → 4.1.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/index.browser.js +85 -86
- package/dist/index.node.js +85 -86
- package/dist/index.react-native.js +85 -86
- package/dist/types.d.ts +1 -1
- package/package.json +10 -34
- package/LICENSE +0 -21
package/dist/index.browser.js
CHANGED
|
@@ -29,7 +29,7 @@ const onCancel = (signal, callback) => {
|
|
|
29
29
|
|
|
30
30
|
const request = ({ method, url, data, headers = {}, signal, onProgress }) => new Promise((resolve, reject) => {
|
|
31
31
|
const xhr = new XMLHttpRequest();
|
|
32
|
-
const requestMethod =
|
|
32
|
+
const requestMethod = method?.toUpperCase() || 'GET';
|
|
33
33
|
let aborted = false;
|
|
34
34
|
xhr.open(requestMethod, url);
|
|
35
35
|
if (headers) {
|
|
@@ -197,34 +197,36 @@ function buildFormData(options) {
|
|
|
197
197
|
return formData;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
200
|
+
const buildSearchParams = (query) => {
|
|
201
|
+
const searchParams = new URLSearchParams();
|
|
202
|
+
for (const [key, value] of Object.entries(query)) {
|
|
203
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
204
|
+
Object.entries(value)
|
|
205
|
+
.filter((entry) => entry[1] ?? false)
|
|
206
|
+
.forEach((entry) => searchParams.set(`${key}[${entry[0]}]`, String(entry[1])));
|
|
207
|
+
}
|
|
208
|
+
else if (Array.isArray(value)) {
|
|
209
|
+
value.forEach((val) => {
|
|
210
|
+
searchParams.append(`${key}[]`, val);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else if (typeof value === 'string' && value) {
|
|
214
|
+
searchParams.set(key, value);
|
|
215
|
+
}
|
|
216
|
+
else if (typeof value === 'number') {
|
|
217
|
+
searchParams.set(key, value.toString());
|
|
218
|
+
}
|
|
212
219
|
}
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
return searchParams.toString();
|
|
221
|
+
};
|
|
222
|
+
const getUrl = (base, path, query) => {
|
|
223
|
+
const url = new URL(base);
|
|
224
|
+
url.pathname = path;
|
|
225
|
+
if (query) {
|
|
226
|
+
url.search = buildSearchParams(query);
|
|
215
227
|
}
|
|
216
|
-
return
|
|
217
|
-
}
|
|
218
|
-
.filter((x) => !!x)
|
|
219
|
-
.join('&');
|
|
220
|
-
const getUrl = (base, path, query) => [
|
|
221
|
-
base,
|
|
222
|
-
path,
|
|
223
|
-
query && Object.keys(query).length > 0 ? '?' : '',
|
|
224
|
-
query && createQuery(query)
|
|
225
|
-
]
|
|
226
|
-
.filter(Boolean)
|
|
227
|
-
.join('');
|
|
228
|
+
return url.toString();
|
|
229
|
+
};
|
|
228
230
|
|
|
229
231
|
/*
|
|
230
232
|
Settings for future support:
|
|
@@ -246,7 +248,7 @@ const defaultSettings = {
|
|
|
246
248
|
const defaultContentType = 'application/octet-stream';
|
|
247
249
|
const defaultFilename = 'original';
|
|
248
250
|
|
|
249
|
-
var version = '4.0
|
|
251
|
+
var version = '4.1.0';
|
|
250
252
|
|
|
251
253
|
/**
|
|
252
254
|
* Returns User Agent based on version and settings.
|
|
@@ -274,6 +276,13 @@ function getUserAgent({ userAgent, publicKey = '', integration = '' } = {}) {
|
|
|
274
276
|
return `${mainInfo} (${additionInfo})`;
|
|
275
277
|
}
|
|
276
278
|
|
|
279
|
+
/**
|
|
280
|
+
* setTimeout as Promise.
|
|
281
|
+
*
|
|
282
|
+
* @param {number} ms Timeout in milliseconds.
|
|
283
|
+
*/
|
|
284
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
285
|
+
|
|
277
286
|
const SEPARATOR = /\W|_/g;
|
|
278
287
|
/**
|
|
279
288
|
* Transforms a string to camelCased.
|
|
@@ -300,13 +309,6 @@ function camelizeKeys(source) {
|
|
|
300
309
|
}, {});
|
|
301
310
|
}
|
|
302
311
|
|
|
303
|
-
/**
|
|
304
|
-
* setTimeout as Promise.
|
|
305
|
-
*
|
|
306
|
-
* @param {number} ms Timeout in milliseconds.
|
|
307
|
-
*/
|
|
308
|
-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
309
|
-
|
|
310
312
|
const defaultOptions = {
|
|
311
313
|
factor: 2,
|
|
312
314
|
time: 100
|
|
@@ -314,8 +316,8 @@ const defaultOptions = {
|
|
|
314
316
|
function retrier(fn, options = defaultOptions) {
|
|
315
317
|
let attempts = 0;
|
|
316
318
|
function runAttempt(fn) {
|
|
317
|
-
const defaultDelayTime = Math.round(options.time *
|
|
318
|
-
const retry = (ms) => delay(ms
|
|
319
|
+
const defaultDelayTime = Math.round(options.time * options.factor ** attempts);
|
|
320
|
+
const retry = (ms) => delay(ms ?? defaultDelayTime).then(() => {
|
|
319
321
|
attempts += 1;
|
|
320
322
|
return runAttempt(fn);
|
|
321
323
|
});
|
|
@@ -338,7 +340,7 @@ function getTimeoutFromThrottledRequest(error) {
|
|
|
338
340
|
function retryIfThrottled(fn, retryThrottledMaxTimes) {
|
|
339
341
|
return retrier(({ attempt, retry }) => fn().catch((error) => {
|
|
340
342
|
if ('response' in error &&
|
|
341
|
-
|
|
343
|
+
error?.code === REQUEST_WAS_THROTTLED_CODE &&
|
|
342
344
|
attempt < retryThrottledMaxTimes) {
|
|
343
345
|
return retry(getTimeoutFromThrottledRequest(error));
|
|
344
346
|
}
|
|
@@ -355,41 +357,38 @@ function getStoreValue(store) {
|
|
|
355
357
|
* Can be canceled and has progress.
|
|
356
358
|
*/
|
|
357
359
|
function base(file, { publicKey, fileName, contentType, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, metadata }) {
|
|
358
|
-
return retryIfThrottled(() => {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
|
|
360
|
+
return retryIfThrottled(() => request({
|
|
361
|
+
method: 'POST',
|
|
362
|
+
url: getUrl(baseURL, '/base/', {
|
|
363
|
+
jsonerrors: 1
|
|
364
|
+
}),
|
|
365
|
+
headers: {
|
|
366
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
367
|
+
},
|
|
368
|
+
data: buildFormData({
|
|
369
|
+
file: {
|
|
370
|
+
data: file,
|
|
371
|
+
name: fileName ?? file.name ?? defaultFilename,
|
|
372
|
+
contentType
|
|
367
373
|
},
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
return response;
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
}, retryThrottledRequestMaxTimes);
|
|
374
|
+
UPLOADCARE_PUB_KEY: publicKey,
|
|
375
|
+
UPLOADCARE_STORE: getStoreValue(store),
|
|
376
|
+
signature: secureSignature,
|
|
377
|
+
expire: secureExpire,
|
|
378
|
+
source: source,
|
|
379
|
+
metadata
|
|
380
|
+
}),
|
|
381
|
+
signal,
|
|
382
|
+
onProgress
|
|
383
|
+
}).then(({ data, headers, request }) => {
|
|
384
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
385
|
+
if ('error' in response) {
|
|
386
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
return response;
|
|
390
|
+
}
|
|
391
|
+
}), retryThrottledRequestMaxTimes);
|
|
393
392
|
}
|
|
394
393
|
|
|
395
394
|
var TypeEnum;
|
|
@@ -568,9 +567,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
|
|
|
568
567
|
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
569
568
|
},
|
|
570
569
|
data: buildFormData({
|
|
571
|
-
filename: fileName
|
|
570
|
+
filename: fileName ?? defaultFilename,
|
|
572
571
|
size: size,
|
|
573
|
-
content_type: contentType
|
|
572
|
+
content_type: contentType ?? defaultContentType,
|
|
574
573
|
part_size: multipartChunkSize,
|
|
575
574
|
UPLOADCARE_STORE: getStoreValue(store),
|
|
576
575
|
UPLOADCARE_PUB_KEY: publicKey,
|
|
@@ -802,8 +801,7 @@ class Events {
|
|
|
802
801
|
this.events = Object.create({});
|
|
803
802
|
}
|
|
804
803
|
emit(event, data) {
|
|
805
|
-
|
|
806
|
-
(_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.forEach((fn) => fn(data));
|
|
804
|
+
this.events[event]?.forEach((fn) => fn(data));
|
|
807
805
|
}
|
|
808
806
|
on(event, callback) {
|
|
809
807
|
this.events[event] = this.events[event] || [];
|
|
@@ -821,12 +819,12 @@ class Events {
|
|
|
821
819
|
|
|
822
820
|
const response = (type, data) => {
|
|
823
821
|
if (type === 'success') {
|
|
824
|
-
return
|
|
822
|
+
return { status: Status.Success, ...data };
|
|
825
823
|
}
|
|
826
824
|
if (type === 'progress') {
|
|
827
|
-
return
|
|
825
|
+
return { status: Status.Progress, ...data };
|
|
828
826
|
}
|
|
829
|
-
return
|
|
827
|
+
return { status: Status.Error, ...data };
|
|
830
828
|
};
|
|
831
829
|
class Pusher {
|
|
832
830
|
constructor(pusherKey, disconnectTime = 30000) {
|
|
@@ -874,8 +872,7 @@ class Pusher {
|
|
|
874
872
|
}
|
|
875
873
|
disconnect() {
|
|
876
874
|
const actualDisconect = () => {
|
|
877
|
-
|
|
878
|
-
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
875
|
+
this.ws?.close();
|
|
879
876
|
this.ws = undefined;
|
|
880
877
|
this.isConnected = false;
|
|
881
878
|
};
|
|
@@ -889,9 +886,8 @@ class Pusher {
|
|
|
889
886
|
}
|
|
890
887
|
}
|
|
891
888
|
send(event, data) {
|
|
892
|
-
var _a;
|
|
893
889
|
const str = JSON.stringify({ event, data });
|
|
894
|
-
|
|
890
|
+
this.ws?.send(str);
|
|
895
891
|
}
|
|
896
892
|
subscribe(token, handler) {
|
|
897
893
|
this.subscribers += 1;
|
|
@@ -1058,7 +1054,7 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
|
|
|
1058
1054
|
}))
|
|
1059
1055
|
.catch((error) => {
|
|
1060
1056
|
const pusher = getPusher(pusherKey);
|
|
1061
|
-
pusher
|
|
1057
|
+
pusher?.disconnect();
|
|
1062
1058
|
return Promise.reject(error);
|
|
1063
1059
|
})
|
|
1064
1060
|
.then((urlResponse) => {
|
|
@@ -1218,7 +1214,7 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
|
|
|
1218
1214
|
return multipartStart(size, {
|
|
1219
1215
|
publicKey,
|
|
1220
1216
|
contentType,
|
|
1221
|
-
fileName: fileName
|
|
1217
|
+
fileName: fileName ?? file.name,
|
|
1222
1218
|
baseURL,
|
|
1223
1219
|
secureSignature,
|
|
1224
1220
|
secureExpire,
|
|
@@ -1470,7 +1466,10 @@ function uploadFileGroup(data, { publicKey, fileName, baseURL = defaultSettings.
|
|
|
1470
1466
|
/**
|
|
1471
1467
|
* Populate options with settings.
|
|
1472
1468
|
*/
|
|
1473
|
-
const populateOptionsWithSettings = (options, settings) => (
|
|
1469
|
+
const populateOptionsWithSettings = (options, settings) => ({
|
|
1470
|
+
...settings,
|
|
1471
|
+
...options
|
|
1472
|
+
});
|
|
1474
1473
|
class UploadClient {
|
|
1475
1474
|
constructor(settings) {
|
|
1476
1475
|
this.settings = Object.assign({}, defaultSettings, settings);
|
package/dist/index.node.js
CHANGED
|
@@ -150,7 +150,7 @@ const getFileOptions = ({ name, contentType }) => [
|
|
|
150
150
|
contentType
|
|
151
151
|
})
|
|
152
152
|
.filter(([, value]) => !!value)
|
|
153
|
-
.reduce((acc, [key, value]) => (
|
|
153
|
+
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
|
154
154
|
].filter((value) => !!value);
|
|
155
155
|
const transformFile = identity;
|
|
156
156
|
var getFormData = () => new NodeFormData();
|
|
@@ -231,34 +231,36 @@ function buildFormData(options) {
|
|
|
231
231
|
return formData;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
234
|
+
const buildSearchParams = (query) => {
|
|
235
|
+
const searchParams = new URLSearchParams();
|
|
236
|
+
for (const [key, value] of Object.entries(query)) {
|
|
237
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
238
|
+
Object.entries(value)
|
|
239
|
+
.filter((entry) => entry[1] ?? false)
|
|
240
|
+
.forEach((entry) => searchParams.set(`${key}[${entry[0]}]`, String(entry[1])));
|
|
241
|
+
}
|
|
242
|
+
else if (Array.isArray(value)) {
|
|
243
|
+
value.forEach((val) => {
|
|
244
|
+
searchParams.append(`${key}[]`, val);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else if (typeof value === 'string' && value) {
|
|
248
|
+
searchParams.set(key, value);
|
|
249
|
+
}
|
|
250
|
+
else if (typeof value === 'number') {
|
|
251
|
+
searchParams.set(key, value.toString());
|
|
252
|
+
}
|
|
246
253
|
}
|
|
247
|
-
|
|
248
|
-
|
|
254
|
+
return searchParams.toString();
|
|
255
|
+
};
|
|
256
|
+
const getUrl = (base, path, query) => {
|
|
257
|
+
const url = new URL(base);
|
|
258
|
+
url.pathname = path;
|
|
259
|
+
if (query) {
|
|
260
|
+
url.search = buildSearchParams(query);
|
|
249
261
|
}
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
.filter((x) => !!x)
|
|
253
|
-
.join('&');
|
|
254
|
-
const getUrl = (base, path, query) => [
|
|
255
|
-
base,
|
|
256
|
-
path,
|
|
257
|
-
query && Object.keys(query).length > 0 ? '?' : '',
|
|
258
|
-
query && createQuery(query)
|
|
259
|
-
]
|
|
260
|
-
.filter(Boolean)
|
|
261
|
-
.join('');
|
|
262
|
+
return url.toString();
|
|
263
|
+
};
|
|
262
264
|
|
|
263
265
|
/*
|
|
264
266
|
Settings for future support:
|
|
@@ -280,7 +282,7 @@ const defaultSettings = {
|
|
|
280
282
|
const defaultContentType = 'application/octet-stream';
|
|
281
283
|
const defaultFilename = 'original';
|
|
282
284
|
|
|
283
|
-
var version = '4.0
|
|
285
|
+
var version = '4.1.0';
|
|
284
286
|
|
|
285
287
|
/**
|
|
286
288
|
* Returns User Agent based on version and settings.
|
|
@@ -308,6 +310,13 @@ function getUserAgent({ userAgent, publicKey = '', integration = '' } = {}) {
|
|
|
308
310
|
return `${mainInfo} (${additionInfo})`;
|
|
309
311
|
}
|
|
310
312
|
|
|
313
|
+
/**
|
|
314
|
+
* setTimeout as Promise.
|
|
315
|
+
*
|
|
316
|
+
* @param {number} ms Timeout in milliseconds.
|
|
317
|
+
*/
|
|
318
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
319
|
+
|
|
311
320
|
const SEPARATOR = /\W|_/g;
|
|
312
321
|
/**
|
|
313
322
|
* Transforms a string to camelCased.
|
|
@@ -334,13 +343,6 @@ function camelizeKeys(source) {
|
|
|
334
343
|
}, {});
|
|
335
344
|
}
|
|
336
345
|
|
|
337
|
-
/**
|
|
338
|
-
* setTimeout as Promise.
|
|
339
|
-
*
|
|
340
|
-
* @param {number} ms Timeout in milliseconds.
|
|
341
|
-
*/
|
|
342
|
-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
343
|
-
|
|
344
346
|
const defaultOptions = {
|
|
345
347
|
factor: 2,
|
|
346
348
|
time: 100
|
|
@@ -348,8 +350,8 @@ const defaultOptions = {
|
|
|
348
350
|
function retrier(fn, options = defaultOptions) {
|
|
349
351
|
let attempts = 0;
|
|
350
352
|
function runAttempt(fn) {
|
|
351
|
-
const defaultDelayTime = Math.round(options.time *
|
|
352
|
-
const retry = (ms) => delay(ms
|
|
353
|
+
const defaultDelayTime = Math.round(options.time * options.factor ** attempts);
|
|
354
|
+
const retry = (ms) => delay(ms ?? defaultDelayTime).then(() => {
|
|
353
355
|
attempts += 1;
|
|
354
356
|
return runAttempt(fn);
|
|
355
357
|
});
|
|
@@ -372,7 +374,7 @@ function getTimeoutFromThrottledRequest(error) {
|
|
|
372
374
|
function retryIfThrottled(fn, retryThrottledMaxTimes) {
|
|
373
375
|
return retrier(({ attempt, retry }) => fn().catch((error) => {
|
|
374
376
|
if ('response' in error &&
|
|
375
|
-
|
|
377
|
+
error?.code === REQUEST_WAS_THROTTLED_CODE &&
|
|
376
378
|
attempt < retryThrottledMaxTimes) {
|
|
377
379
|
return retry(getTimeoutFromThrottledRequest(error));
|
|
378
380
|
}
|
|
@@ -389,41 +391,38 @@ function getStoreValue(store) {
|
|
|
389
391
|
* Can be canceled and has progress.
|
|
390
392
|
*/
|
|
391
393
|
function base(file, { publicKey, fileName, contentType, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, metadata }) {
|
|
392
|
-
return retryIfThrottled(() => {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
|
|
394
|
+
return retryIfThrottled(() => request({
|
|
395
|
+
method: 'POST',
|
|
396
|
+
url: getUrl(baseURL, '/base/', {
|
|
397
|
+
jsonerrors: 1
|
|
398
|
+
}),
|
|
399
|
+
headers: {
|
|
400
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
401
|
+
},
|
|
402
|
+
data: buildFormData({
|
|
403
|
+
file: {
|
|
404
|
+
data: file,
|
|
405
|
+
name: fileName ?? file.name ?? defaultFilename,
|
|
406
|
+
contentType
|
|
401
407
|
},
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
421
|
-
}
|
|
422
|
-
else {
|
|
423
|
-
return response;
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
}, retryThrottledRequestMaxTimes);
|
|
408
|
+
UPLOADCARE_PUB_KEY: publicKey,
|
|
409
|
+
UPLOADCARE_STORE: getStoreValue(store),
|
|
410
|
+
signature: secureSignature,
|
|
411
|
+
expire: secureExpire,
|
|
412
|
+
source: source,
|
|
413
|
+
metadata
|
|
414
|
+
}),
|
|
415
|
+
signal,
|
|
416
|
+
onProgress
|
|
417
|
+
}).then(({ data, headers, request }) => {
|
|
418
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
419
|
+
if ('error' in response) {
|
|
420
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
return response;
|
|
424
|
+
}
|
|
425
|
+
}), retryThrottledRequestMaxTimes);
|
|
427
426
|
}
|
|
428
427
|
|
|
429
428
|
var TypeEnum;
|
|
@@ -602,9 +601,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
|
|
|
602
601
|
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
603
602
|
},
|
|
604
603
|
data: buildFormData({
|
|
605
|
-
filename: fileName
|
|
604
|
+
filename: fileName ?? defaultFilename,
|
|
606
605
|
size: size,
|
|
607
|
-
content_type: contentType
|
|
606
|
+
content_type: contentType ?? defaultContentType,
|
|
608
607
|
part_size: multipartChunkSize,
|
|
609
608
|
UPLOADCARE_STORE: getStoreValue(store),
|
|
610
609
|
UPLOADCARE_PUB_KEY: publicKey,
|
|
@@ -834,8 +833,7 @@ class Events {
|
|
|
834
833
|
this.events = Object.create({});
|
|
835
834
|
}
|
|
836
835
|
emit(event, data) {
|
|
837
|
-
|
|
838
|
-
(_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.forEach((fn) => fn(data));
|
|
836
|
+
this.events[event]?.forEach((fn) => fn(data));
|
|
839
837
|
}
|
|
840
838
|
on(event, callback) {
|
|
841
839
|
this.events[event] = this.events[event] || [];
|
|
@@ -853,12 +851,12 @@ class Events {
|
|
|
853
851
|
|
|
854
852
|
const response = (type, data) => {
|
|
855
853
|
if (type === 'success') {
|
|
856
|
-
return
|
|
854
|
+
return { status: Status.Success, ...data };
|
|
857
855
|
}
|
|
858
856
|
if (type === 'progress') {
|
|
859
|
-
return
|
|
857
|
+
return { status: Status.Progress, ...data };
|
|
860
858
|
}
|
|
861
|
-
return
|
|
859
|
+
return { status: Status.Error, ...data };
|
|
862
860
|
};
|
|
863
861
|
class Pusher {
|
|
864
862
|
constructor(pusherKey, disconnectTime = 30000) {
|
|
@@ -906,8 +904,7 @@ class Pusher {
|
|
|
906
904
|
}
|
|
907
905
|
disconnect() {
|
|
908
906
|
const actualDisconect = () => {
|
|
909
|
-
|
|
910
|
-
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
907
|
+
this.ws?.close();
|
|
911
908
|
this.ws = undefined;
|
|
912
909
|
this.isConnected = false;
|
|
913
910
|
};
|
|
@@ -921,9 +918,8 @@ class Pusher {
|
|
|
921
918
|
}
|
|
922
919
|
}
|
|
923
920
|
send(event, data) {
|
|
924
|
-
var _a;
|
|
925
921
|
const str = JSON.stringify({ event, data });
|
|
926
|
-
|
|
922
|
+
this.ws?.send(str);
|
|
927
923
|
}
|
|
928
924
|
subscribe(token, handler) {
|
|
929
925
|
this.subscribers += 1;
|
|
@@ -1090,7 +1086,7 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
|
|
|
1090
1086
|
}))
|
|
1091
1087
|
.catch((error) => {
|
|
1092
1088
|
const pusher = getPusher(pusherKey);
|
|
1093
|
-
pusher
|
|
1089
|
+
pusher?.disconnect();
|
|
1094
1090
|
return Promise.reject(error);
|
|
1095
1091
|
})
|
|
1096
1092
|
.then((urlResponse) => {
|
|
@@ -1250,7 +1246,7 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
|
|
|
1250
1246
|
return multipartStart(size, {
|
|
1251
1247
|
publicKey,
|
|
1252
1248
|
contentType,
|
|
1253
|
-
fileName: fileName
|
|
1249
|
+
fileName: fileName ?? file.name,
|
|
1254
1250
|
baseURL,
|
|
1255
1251
|
secureSignature,
|
|
1256
1252
|
secureExpire,
|
|
@@ -1502,7 +1498,10 @@ function uploadFileGroup(data, { publicKey, fileName, baseURL = defaultSettings.
|
|
|
1502
1498
|
/**
|
|
1503
1499
|
* Populate options with settings.
|
|
1504
1500
|
*/
|
|
1505
|
-
const populateOptionsWithSettings = (options, settings) => (
|
|
1501
|
+
const populateOptionsWithSettings = (options, settings) => ({
|
|
1502
|
+
...settings,
|
|
1503
|
+
...options
|
|
1504
|
+
});
|
|
1506
1505
|
class UploadClient {
|
|
1507
1506
|
constructor(settings) {
|
|
1508
1507
|
this.settings = Object.assign({}, defaultSettings, settings);
|
|
@@ -29,7 +29,7 @@ const onCancel = (signal, callback) => {
|
|
|
29
29
|
|
|
30
30
|
const request = ({ method, url, data, headers = {}, signal, onProgress }) => new Promise((resolve, reject) => {
|
|
31
31
|
const xhr = new XMLHttpRequest();
|
|
32
|
-
const requestMethod =
|
|
32
|
+
const requestMethod = method?.toUpperCase() || 'GET';
|
|
33
33
|
let aborted = false;
|
|
34
34
|
xhr.open(requestMethod, url);
|
|
35
35
|
if (headers) {
|
|
@@ -200,34 +200,36 @@ function buildFormData(options) {
|
|
|
200
200
|
return formData;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
203
|
+
const buildSearchParams = (query) => {
|
|
204
|
+
const searchParams = new URLSearchParams();
|
|
205
|
+
for (const [key, value] of Object.entries(query)) {
|
|
206
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
207
|
+
Object.entries(value)
|
|
208
|
+
.filter((entry) => entry[1] ?? false)
|
|
209
|
+
.forEach((entry) => searchParams.set(`${key}[${entry[0]}]`, String(entry[1])));
|
|
210
|
+
}
|
|
211
|
+
else if (Array.isArray(value)) {
|
|
212
|
+
value.forEach((val) => {
|
|
213
|
+
searchParams.append(`${key}[]`, val);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
else if (typeof value === 'string' && value) {
|
|
217
|
+
searchParams.set(key, value);
|
|
218
|
+
}
|
|
219
|
+
else if (typeof value === 'number') {
|
|
220
|
+
searchParams.set(key, value.toString());
|
|
221
|
+
}
|
|
215
222
|
}
|
|
216
|
-
|
|
217
|
-
|
|
223
|
+
return searchParams.toString();
|
|
224
|
+
};
|
|
225
|
+
const getUrl = (base, path, query) => {
|
|
226
|
+
const url = new URL(base);
|
|
227
|
+
url.pathname = path;
|
|
228
|
+
if (query) {
|
|
229
|
+
url.search = buildSearchParams(query);
|
|
218
230
|
}
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
.filter((x) => !!x)
|
|
222
|
-
.join('&');
|
|
223
|
-
const getUrl = (base, path, query) => [
|
|
224
|
-
base,
|
|
225
|
-
path,
|
|
226
|
-
query && Object.keys(query).length > 0 ? '?' : '',
|
|
227
|
-
query && createQuery(query)
|
|
228
|
-
]
|
|
229
|
-
.filter(Boolean)
|
|
230
|
-
.join('');
|
|
231
|
+
return url.toString();
|
|
232
|
+
};
|
|
231
233
|
|
|
232
234
|
/*
|
|
233
235
|
Settings for future support:
|
|
@@ -249,7 +251,7 @@ const defaultSettings = {
|
|
|
249
251
|
const defaultContentType = 'application/octet-stream';
|
|
250
252
|
const defaultFilename = 'original';
|
|
251
253
|
|
|
252
|
-
var version = '4.0
|
|
254
|
+
var version = '4.1.0';
|
|
253
255
|
|
|
254
256
|
/**
|
|
255
257
|
* Returns User Agent based on version and settings.
|
|
@@ -277,6 +279,13 @@ function getUserAgent({ userAgent, publicKey = '', integration = '' } = {}) {
|
|
|
277
279
|
return `${mainInfo} (${additionInfo})`;
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
/**
|
|
283
|
+
* setTimeout as Promise.
|
|
284
|
+
*
|
|
285
|
+
* @param {number} ms Timeout in milliseconds.
|
|
286
|
+
*/
|
|
287
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
288
|
+
|
|
280
289
|
const SEPARATOR = /\W|_/g;
|
|
281
290
|
/**
|
|
282
291
|
* Transforms a string to camelCased.
|
|
@@ -303,13 +312,6 @@ function camelizeKeys(source) {
|
|
|
303
312
|
}, {});
|
|
304
313
|
}
|
|
305
314
|
|
|
306
|
-
/**
|
|
307
|
-
* setTimeout as Promise.
|
|
308
|
-
*
|
|
309
|
-
* @param {number} ms Timeout in milliseconds.
|
|
310
|
-
*/
|
|
311
|
-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
312
|
-
|
|
313
315
|
const defaultOptions = {
|
|
314
316
|
factor: 2,
|
|
315
317
|
time: 100
|
|
@@ -317,8 +319,8 @@ const defaultOptions = {
|
|
|
317
319
|
function retrier(fn, options = defaultOptions) {
|
|
318
320
|
let attempts = 0;
|
|
319
321
|
function runAttempt(fn) {
|
|
320
|
-
const defaultDelayTime = Math.round(options.time *
|
|
321
|
-
const retry = (ms) => delay(ms
|
|
322
|
+
const defaultDelayTime = Math.round(options.time * options.factor ** attempts);
|
|
323
|
+
const retry = (ms) => delay(ms ?? defaultDelayTime).then(() => {
|
|
322
324
|
attempts += 1;
|
|
323
325
|
return runAttempt(fn);
|
|
324
326
|
});
|
|
@@ -341,7 +343,7 @@ function getTimeoutFromThrottledRequest(error) {
|
|
|
341
343
|
function retryIfThrottled(fn, retryThrottledMaxTimes) {
|
|
342
344
|
return retrier(({ attempt, retry }) => fn().catch((error) => {
|
|
343
345
|
if ('response' in error &&
|
|
344
|
-
|
|
346
|
+
error?.code === REQUEST_WAS_THROTTLED_CODE &&
|
|
345
347
|
attempt < retryThrottledMaxTimes) {
|
|
346
348
|
return retry(getTimeoutFromThrottledRequest(error));
|
|
347
349
|
}
|
|
@@ -358,41 +360,38 @@ function getStoreValue(store) {
|
|
|
358
360
|
* Can be canceled and has progress.
|
|
359
361
|
*/
|
|
360
362
|
function base(file, { publicKey, fileName, contentType, baseURL = defaultSettings.baseURL, secureSignature, secureExpire, store, signal, onProgress, source = 'local', integration, userAgent, retryThrottledRequestMaxTimes = defaultSettings.retryThrottledRequestMaxTimes, metadata }) {
|
|
361
|
-
return retryIfThrottled(() => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
|
|
363
|
+
return retryIfThrottled(() => request({
|
|
364
|
+
method: 'POST',
|
|
365
|
+
url: getUrl(baseURL, '/base/', {
|
|
366
|
+
jsonerrors: 1
|
|
367
|
+
}),
|
|
368
|
+
headers: {
|
|
369
|
+
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
370
|
+
},
|
|
371
|
+
data: buildFormData({
|
|
372
|
+
file: {
|
|
373
|
+
data: file,
|
|
374
|
+
name: fileName ?? file.name ?? defaultFilename,
|
|
375
|
+
contentType
|
|
370
376
|
},
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
return response;
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
}, retryThrottledRequestMaxTimes);
|
|
377
|
+
UPLOADCARE_PUB_KEY: publicKey,
|
|
378
|
+
UPLOADCARE_STORE: getStoreValue(store),
|
|
379
|
+
signature: secureSignature,
|
|
380
|
+
expire: secureExpire,
|
|
381
|
+
source: source,
|
|
382
|
+
metadata
|
|
383
|
+
}),
|
|
384
|
+
signal,
|
|
385
|
+
onProgress
|
|
386
|
+
}).then(({ data, headers, request }) => {
|
|
387
|
+
const response = camelizeKeys(JSON.parse(data));
|
|
388
|
+
if ('error' in response) {
|
|
389
|
+
throw new UploadClientError(response.error.content, response.error.errorCode, request, response, headers);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
return response;
|
|
393
|
+
}
|
|
394
|
+
}), retryThrottledRequestMaxTimes);
|
|
396
395
|
}
|
|
397
396
|
|
|
398
397
|
var TypeEnum;
|
|
@@ -571,9 +570,9 @@ function multipartStart(size, { publicKey, contentType, fileName, multipartChunk
|
|
|
571
570
|
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
|
|
572
571
|
},
|
|
573
572
|
data: buildFormData({
|
|
574
|
-
filename: fileName
|
|
573
|
+
filename: fileName ?? defaultFilename,
|
|
575
574
|
size: size,
|
|
576
|
-
content_type: contentType
|
|
575
|
+
content_type: contentType ?? defaultContentType,
|
|
577
576
|
part_size: multipartChunkSize,
|
|
578
577
|
UPLOADCARE_STORE: getStoreValue(store),
|
|
579
578
|
UPLOADCARE_PUB_KEY: publicKey,
|
|
@@ -805,8 +804,7 @@ class Events {
|
|
|
805
804
|
this.events = Object.create({});
|
|
806
805
|
}
|
|
807
806
|
emit(event, data) {
|
|
808
|
-
|
|
809
|
-
(_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.forEach((fn) => fn(data));
|
|
807
|
+
this.events[event]?.forEach((fn) => fn(data));
|
|
810
808
|
}
|
|
811
809
|
on(event, callback) {
|
|
812
810
|
this.events[event] = this.events[event] || [];
|
|
@@ -824,12 +822,12 @@ class Events {
|
|
|
824
822
|
|
|
825
823
|
const response = (type, data) => {
|
|
826
824
|
if (type === 'success') {
|
|
827
|
-
return
|
|
825
|
+
return { status: Status.Success, ...data };
|
|
828
826
|
}
|
|
829
827
|
if (type === 'progress') {
|
|
830
|
-
return
|
|
828
|
+
return { status: Status.Progress, ...data };
|
|
831
829
|
}
|
|
832
|
-
return
|
|
830
|
+
return { status: Status.Error, ...data };
|
|
833
831
|
};
|
|
834
832
|
class Pusher {
|
|
835
833
|
constructor(pusherKey, disconnectTime = 30000) {
|
|
@@ -877,8 +875,7 @@ class Pusher {
|
|
|
877
875
|
}
|
|
878
876
|
disconnect() {
|
|
879
877
|
const actualDisconect = () => {
|
|
880
|
-
|
|
881
|
-
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
878
|
+
this.ws?.close();
|
|
882
879
|
this.ws = undefined;
|
|
883
880
|
this.isConnected = false;
|
|
884
881
|
};
|
|
@@ -892,9 +889,8 @@ class Pusher {
|
|
|
892
889
|
}
|
|
893
890
|
}
|
|
894
891
|
send(event, data) {
|
|
895
|
-
var _a;
|
|
896
892
|
const str = JSON.stringify({ event, data });
|
|
897
|
-
|
|
893
|
+
this.ws?.send(str);
|
|
898
894
|
}
|
|
899
895
|
subscribe(token, handler) {
|
|
900
896
|
this.subscribers += 1;
|
|
@@ -1061,7 +1057,7 @@ const uploadFromUrl = (sourceUrl, { publicKey, fileName, baseURL, baseCDN, check
|
|
|
1061
1057
|
}))
|
|
1062
1058
|
.catch((error) => {
|
|
1063
1059
|
const pusher = getPusher(pusherKey);
|
|
1064
|
-
pusher
|
|
1060
|
+
pusher?.disconnect();
|
|
1065
1061
|
return Promise.reject(error);
|
|
1066
1062
|
})
|
|
1067
1063
|
.then((urlResponse) => {
|
|
@@ -1234,7 +1230,7 @@ const uploadMultipart = (file, { publicKey, fileName, fileSize, baseURL, secureS
|
|
|
1234
1230
|
return multipartStart(size, {
|
|
1235
1231
|
publicKey,
|
|
1236
1232
|
contentType,
|
|
1237
|
-
fileName: fileName
|
|
1233
|
+
fileName: fileName ?? file.name,
|
|
1238
1234
|
baseURL,
|
|
1239
1235
|
secureSignature,
|
|
1240
1236
|
secureExpire,
|
|
@@ -1486,7 +1482,10 @@ function uploadFileGroup(data, { publicKey, fileName, baseURL = defaultSettings.
|
|
|
1486
1482
|
/**
|
|
1487
1483
|
* Populate options with settings.
|
|
1488
1484
|
*/
|
|
1489
|
-
const populateOptionsWithSettings = (options, settings) => (
|
|
1485
|
+
const populateOptionsWithSettings = (options, settings) => ({
|
|
1486
|
+
...settings,
|
|
1487
|
+
...options
|
|
1488
|
+
});
|
|
1490
1489
|
class UploadClient {
|
|
1491
1490
|
constructor(settings) {
|
|
1492
1491
|
this.settings = Object.assign({}, defaultSettings, settings);
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadcare/upload-client",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Library for work with Uploadcare Upload API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.node.js",
|
|
@@ -9,22 +9,21 @@
|
|
|
9
9
|
"react-native": "dist/index.react-native.js",
|
|
10
10
|
"types": "dist/types.d.ts",
|
|
11
11
|
"sideEffects": false,
|
|
12
|
-
"files": [
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
"files": ["dist/*"],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=16"
|
|
15
|
+
},
|
|
15
16
|
"scripts": {
|
|
16
|
-
"check-env-vars": "node
|
|
17
|
+
"check-env-vars": "node ../../checkvars.js",
|
|
17
18
|
"mock:start": "node --loader ts-node/esm ./mock-server/server.ts --silent",
|
|
18
19
|
"clean": "rimraf dist",
|
|
19
|
-
"lint": "eslint ./src ./mock-server --ext=ts",
|
|
20
20
|
"test": "start-server-and-test mock:start :3000 test:jest",
|
|
21
|
-
"test:production": "npm run check-env-vars && TEST_ENV=production jest",
|
|
22
|
-
"test:jest": "jest",
|
|
21
|
+
"test:production": "npm run check-env-vars && TEST_ENV=production npm run test:jest",
|
|
22
|
+
"test:jest": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js",
|
|
23
23
|
"prebuild": "npm run clean",
|
|
24
24
|
"build": "npm run build:types && npm run build:compile",
|
|
25
25
|
"build:types": "dts-bundle-generator --project tsconfig.dts.json -o dist/types.d.ts src/index.ts",
|
|
26
|
-
"build:compile": "rollup -c"
|
|
27
|
-
"release": "shipjs prepare"
|
|
26
|
+
"build:compile": "rollup -c"
|
|
28
27
|
},
|
|
29
28
|
"repository": {
|
|
30
29
|
"type": "git",
|
|
@@ -48,43 +47,20 @@
|
|
|
48
47
|
"devDependencies": {
|
|
49
48
|
"@koa/cors": "3.3.0",
|
|
50
49
|
"@koa/router": "10.1.1",
|
|
51
|
-
"@rollup/plugin-alias": "^3.1.9",
|
|
52
|
-
"@rollup/plugin-node-resolve": "^13.3.0",
|
|
53
|
-
"@rollup/plugin-typescript": "^8.3.2",
|
|
54
50
|
"@types/express-serve-static-core": "^4.17.28",
|
|
55
|
-
"@types/form-data": "2.5.0",
|
|
56
|
-
"@types/jest": "27.0.0",
|
|
57
51
|
"@types/koa": "2.13.4",
|
|
58
|
-
"@types/node": "17.0.38",
|
|
59
|
-
"@types/promise": "7.1.30",
|
|
60
52
|
"@types/ws": "8.5.3",
|
|
61
|
-
"@typescript-eslint/eslint-plugin": "5.27.0",
|
|
62
|
-
"@typescript-eslint/parser": "5.27.0",
|
|
63
|
-
"chalk": "4.1.0",
|
|
64
53
|
"data-uri-to-buffer": "3.0.1",
|
|
65
54
|
"dataurl-to-blob": "0.0.1",
|
|
66
55
|
"dotenv": "8.2.0",
|
|
67
|
-
"dts-bundle-generator": "6.9.0",
|
|
68
|
-
"eslint": "8.2.0",
|
|
69
|
-
"eslint-config-prettier": "8.3.0",
|
|
70
|
-
"eslint-plugin-prettier": "4.0.0",
|
|
71
|
-
"jest": "^28.1.0",
|
|
72
56
|
"jest-environment-jsdom": "28.1.0",
|
|
73
57
|
"jest-websocket-mock": "2.3.0",
|
|
74
58
|
"koa": "2.13.4",
|
|
75
59
|
"koa-add-trailing-slashes": "2.0.1",
|
|
76
60
|
"koa-body": "5.0.0",
|
|
77
61
|
"mock-socket": "9.0.3",
|
|
78
|
-
"prettier": "2.2.1",
|
|
79
|
-
"prettier-config-standard": "4.0.0",
|
|
80
|
-
"rimraf": "3.0.2",
|
|
81
|
-
"rollup": "^2.75.5",
|
|
82
|
-
"shipjs": "0.24.0",
|
|
83
62
|
"start-server-and-test": "1.11.7",
|
|
84
|
-
"
|
|
85
|
-
"ts-node": "^10.8.0",
|
|
86
|
-
"tslib": "^2.4.0",
|
|
87
|
-
"typescript": "^4.7.2"
|
|
63
|
+
"@uploadcare/api-client-utils": "^4.1.0"
|
|
88
64
|
},
|
|
89
65
|
"dependencies": {
|
|
90
66
|
"form-data": "^4.0.0",
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2019 Uploadcare Inc.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|