@uppy/core
Advanced tools
Comparing version 2.1.5 to 2.1.6
# @uppy/core | ||
## 2.1.6 | ||
Released: 2022-03-16 | ||
Included in: Uppy v2.8.0 | ||
- @uppy/core: Abstract restriction logic in a new Restricter class (Merlijn Vos / #3532) | ||
## 2.1.5 | ||
@@ -4,0 +11,0 @@ |
357
lib/Uppy.js
@@ -24,6 +24,2 @@ /* eslint-disable max-classes-per-file */ | ||
const prettierBytes = require('@transloadit/prettier-bytes'); | ||
const match = require('mime-match'); | ||
const DefaultStore = require('@uppy/store-default'); | ||
@@ -46,31 +42,10 @@ | ||
const { | ||
Restricter, | ||
defaultOptions: defaultRestrictionOptions, | ||
RestrictionError | ||
} = require('./Restricter'); | ||
const locale = require('./locale'); // Exported from here. | ||
class RestrictionError extends Error { | ||
constructor() { | ||
super(...arguments); | ||
this.isRestriction = true; | ||
} | ||
} | ||
if (typeof AggregateError === 'undefined') { | ||
// eslint-disable-next-line no-global-assign | ||
globalThis.AggregateError = class AggregateError extends Error { | ||
constructor(errors, message) { | ||
super(message); | ||
this.errors = errors; | ||
} | ||
}; | ||
} | ||
class AggregateRestrictionError extends AggregateError { | ||
constructor() { | ||
super(...arguments); | ||
this.isRestriction = true; | ||
} | ||
} | ||
/** | ||
@@ -85,2 +60,4 @@ * Uppy Core module. | ||
var _restricter = /*#__PURE__*/_classPrivateFieldLooseKey("restricter"); | ||
var _storeUnsubscribe = /*#__PURE__*/_classPrivateFieldLooseKey("storeUnsubscribe"); | ||
@@ -96,6 +73,4 @@ | ||
var _checkRestrictions = /*#__PURE__*/_classPrivateFieldLooseKey("checkRestrictions"); | ||
var _informAndEmit = /*#__PURE__*/_classPrivateFieldLooseKey("informAndEmit"); | ||
var _checkMinNumberOfFiles = /*#__PURE__*/_classPrivateFieldLooseKey("checkMinNumberOfFiles"); | ||
var _checkRequiredMetaFieldsOnFile = /*#__PURE__*/_classPrivateFieldLooseKey("checkRequiredMetaFieldsOnFile"); | ||
@@ -105,4 +80,2 @@ | ||
var _showOrLogErrorAndThrow = /*#__PURE__*/_classPrivateFieldLooseKey("showOrLogErrorAndThrow"); | ||
var _assertNewUploadAllowed = /*#__PURE__*/_classPrivateFieldLooseKey("assertNewUploadAllowed"); | ||
@@ -164,5 +137,2 @@ | ||
}); | ||
Object.defineProperty(this, _showOrLogErrorAndThrow, { | ||
value: _showOrLogErrorAndThrow2 | ||
}); | ||
Object.defineProperty(this, _checkRequiredMetaFields, { | ||
@@ -174,8 +144,5 @@ value: _checkRequiredMetaFields2 | ||
}); | ||
Object.defineProperty(this, _checkMinNumberOfFiles, { | ||
value: _checkMinNumberOfFiles2 | ||
Object.defineProperty(this, _informAndEmit, { | ||
value: _informAndEmit2 | ||
}); | ||
Object.defineProperty(this, _checkRestrictions, { | ||
value: _checkRestrictions2 | ||
}); | ||
Object.defineProperty(this, _plugins, { | ||
@@ -185,2 +152,6 @@ writable: true, | ||
}); | ||
Object.defineProperty(this, _restricter, { | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, _storeUnsubscribe, { | ||
@@ -221,11 +192,3 @@ writable: true, | ||
debug: false, | ||
restrictions: { | ||
maxFileSize: null, | ||
minFileSize: null, | ||
maxTotalFileSize: null, | ||
maxNumberOfFiles: null, | ||
minNumberOfFiles: null, | ||
allowedFileTypes: null, | ||
requiredMetaFields: [] | ||
}, | ||
restrictions: defaultRestrictionOptions, | ||
meta: {}, | ||
@@ -255,7 +218,2 @@ onBeforeFileAdded: currentFile => currentFile, | ||
this.log(`Using Core v${this.constructor.VERSION}`); | ||
if (this.opts.restrictions.allowedFileTypes && this.opts.restrictions.allowedFileTypes !== null && !Array.isArray(this.opts.restrictions.allowedFileTypes)) { | ||
throw new TypeError('`restrictions.allowedFileTypes` must be an array'); | ||
} | ||
this.i18nInit(); // ___Why throttle at 500ms? | ||
@@ -290,2 +248,3 @@ // - We must throttle at >250ms for superfocus in Dashboard to work well | ||
}); | ||
_classPrivateFieldLooseBase(this, _restricter)[_restricter] = new Restricter(() => this.opts, this.i18n); | ||
_classPrivateFieldLooseBase(this, _storeUnsubscribe)[_storeUnsubscribe] = this.store.subscribe((prevState, nextState, patch) => { | ||
@@ -571,15 +530,24 @@ this.emit('state-update', prevState, nextState, patch); | ||
} | ||
/** | ||
* A public wrapper for _checkRestrictions — checks if a file passes a set of restrictions. | ||
* For use in UI pluigins (like Providers), to disallow selecting files that won’t pass restrictions. | ||
* | ||
* @param {object} file object to check | ||
* @param {Array} [files] array to check maxNumberOfFiles and maxTotalFileSize | ||
* @returns {object} { result: true/false, reason: why file didn’t pass restrictions } | ||
*/ | ||
/* | ||
* @constructs | ||
* @param { Error } error | ||
* @param { undefined } file | ||
*/ | ||
/* | ||
* @constructs | ||
* @param { RestrictionError } error | ||
* @param { UppyFile | undefined } file | ||
*/ | ||
validateRestrictions(file, files) { | ||
if (files === void 0) { | ||
files = this.getFiles(); | ||
} | ||
// TODO: directly return the Restriction error in next major version. | ||
// we create RestrictionError's just to discard immediately, which doesn't make sense. | ||
try { | ||
_classPrivateFieldLooseBase(this, _checkRestrictions)[_checkRestrictions](file, files); | ||
_classPrivateFieldLooseBase(this, _restricter)[_restricter].validate(file, files); | ||
@@ -596,12 +564,3 @@ return { | ||
} | ||
/** | ||
* Check if file passes a set of restrictions set in options: maxFileSize, minFileSize, | ||
* maxNumberOfFiles and allowedFileTypes. | ||
* | ||
* @param {object} file object to check | ||
* @param {Array} [files] array to check maxNumberOfFiles and maxTotalFileSize | ||
* @private | ||
*/ | ||
checkIfFileAlreadyExists(fileID) { | ||
@@ -1339,8 +1298,15 @@ const { | ||
return Promise.resolve().then(() => { | ||
_classPrivateFieldLooseBase(this, _checkMinNumberOfFiles)[_checkMinNumberOfFiles](files); | ||
return Promise.resolve().then(() => _classPrivateFieldLooseBase(this, _restricter)[_restricter].validateMinNumberOfFiles(files)).catch(err => { | ||
_classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](err); | ||
_classPrivateFieldLooseBase(this, _checkRequiredMetaFields)[_checkRequiredMetaFields](files); | ||
throw err; | ||
}).then(() => { | ||
if (!_classPrivateFieldLooseBase(this, _checkRequiredMetaFields)[_checkRequiredMetaFields](files)) { | ||
throw new RestrictionError(this.i18n('missingRequiredMetaField')); | ||
} | ||
}).catch(err => { | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](err); | ||
// Doing this in a separate catch because we already emited and logged | ||
// all the errors in `checkRequiredMetaFields` so we only throw a generic | ||
// missing fields error here. | ||
throw err; | ||
}).then(() => { | ||
@@ -1365,5 +1331,5 @@ const { | ||
}).catch(err => { | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](err, { | ||
showInformer: false | ||
}); | ||
this.emit('error', err); | ||
this.log(err, 'error'); | ||
throw err; | ||
}); | ||
@@ -1374,173 +1340,49 @@ } | ||
function _checkRestrictions2(file, files) { | ||
if (files === void 0) { | ||
files = this.getFiles(); | ||
} | ||
function _informAndEmit2(error, file) { | ||
const { | ||
maxFileSize, | ||
minFileSize, | ||
maxTotalFileSize, | ||
maxNumberOfFiles, | ||
allowedFileTypes | ||
} = this.opts.restrictions; | ||
message, | ||
details = '' | ||
} = error; | ||
if (maxNumberOfFiles) { | ||
if (files.length + 1 > maxNumberOfFiles) { | ||
throw new RestrictionError(`${this.i18n('youCanOnlyUploadX', { | ||
smart_count: maxNumberOfFiles | ||
})}`); | ||
} | ||
if (error.isRestriction) { | ||
this.emit('restriction-failed', file, error); | ||
} else { | ||
this.emit('error', error); | ||
} | ||
if (allowedFileTypes) { | ||
const isCorrectFileType = allowedFileTypes.some(type => { | ||
// check if this is a mime-type | ||
if (type.indexOf('/') > -1) { | ||
if (!file.type) return false; | ||
return match(file.type.replace(/;.*?$/, ''), type); | ||
} // otherwise this is likely an extension | ||
if (type[0] === '.' && file.extension) { | ||
return file.extension.toLowerCase() === type.substr(1).toLowerCase(); | ||
} | ||
return false; | ||
}); | ||
if (!isCorrectFileType) { | ||
const allowedFileTypesString = allowedFileTypes.join(', '); | ||
throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', { | ||
types: allowedFileTypesString | ||
})); | ||
} | ||
} // We can't check maxTotalFileSize if the size is unknown. | ||
if (maxTotalFileSize && file.size != null) { | ||
let totalFilesSize = 0; | ||
totalFilesSize += file.size; | ||
files.forEach(f => { | ||
totalFilesSize += f.size; | ||
}); | ||
if (totalFilesSize > maxTotalFileSize) { | ||
throw new RestrictionError(this.i18n('exceedsSize', { | ||
size: prettierBytes(maxTotalFileSize), | ||
file: file.name | ||
})); | ||
} | ||
} // We can't check maxFileSize if the size is unknown. | ||
if (maxFileSize && file.size != null) { | ||
if (file.size > maxFileSize) { | ||
throw new RestrictionError(this.i18n('exceedsSize', { | ||
size: prettierBytes(maxFileSize), | ||
file: file.name | ||
})); | ||
} | ||
} // We can't check minFileSize if the size is unknown. | ||
if (minFileSize && file.size != null) { | ||
if (file.size < minFileSize) { | ||
throw new RestrictionError(this.i18n('inferiorSize', { | ||
size: prettierBytes(minFileSize) | ||
})); | ||
} | ||
} | ||
this.info({ | ||
message, | ||
details | ||
}, 'error', this.opts.infoTimeout); | ||
this.log(`${message} ${details}`.trim(), 'error'); | ||
} | ||
function _checkMinNumberOfFiles2(files) { | ||
const { | ||
minNumberOfFiles | ||
} = this.opts.restrictions; | ||
if (Object.keys(files).length < minNumberOfFiles) { | ||
throw new RestrictionError(`${this.i18n('youHaveToAtLeastSelectX', { | ||
smart_count: minNumberOfFiles | ||
})}`); | ||
} | ||
} | ||
function _checkRequiredMetaFieldsOnFile2(file) { | ||
const { | ||
requiredMetaFields | ||
} = this.opts.restrictions; | ||
const { | ||
hasOwnProperty | ||
} = Object.prototype; | ||
const errors = []; | ||
const missingFields = []; | ||
missingFields, | ||
error | ||
} = _classPrivateFieldLooseBase(this, _restricter)[_restricter].getMissingRequiredMetaFields(file); | ||
for (let i = 0; i < requiredMetaFields.length; i++) { | ||
if (!hasOwnProperty.call(file.meta, requiredMetaFields[i]) || file.meta[requiredMetaFields[i]] === '') { | ||
const err = new RestrictionError(`${this.i18n('missingRequiredMetaFieldOnFile', { | ||
fileName: file.name | ||
})}`); | ||
errors.push(err); | ||
missingFields.push(requiredMetaFields[i]); | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](err, { | ||
file, | ||
showInformer: false, | ||
throwErr: false | ||
}); | ||
} | ||
if (missingFields.length > 0) { | ||
this.setFileState(file.id, { | ||
missingRequiredMetaFields: missingFields | ||
}); | ||
this.log(error.message); | ||
this.emit('restriction-failed', file, error); | ||
return false; | ||
} | ||
this.setFileState(file.id, { | ||
missingRequiredMetaFields: missingFields | ||
}); | ||
return errors; | ||
return true; | ||
} | ||
function _checkRequiredMetaFields2(files) { | ||
const errors = Object.keys(files).flatMap(fileID => { | ||
const file = this.getFile(fileID); | ||
return _classPrivateFieldLooseBase(this, _checkRequiredMetaFieldsOnFile)[_checkRequiredMetaFieldsOnFile](file); | ||
}); | ||
let success = true; | ||
if (errors.length) { | ||
throw new AggregateRestrictionError(errors, `${this.i18n('missingRequiredMetaField')}`); | ||
for (const file of Object.values(files)) { | ||
if (!_classPrivateFieldLooseBase(this, _checkRequiredMetaFieldsOnFile)[_checkRequiredMetaFieldsOnFile](file)) { | ||
success = false; | ||
} | ||
} | ||
} | ||
function _showOrLogErrorAndThrow2(err, _temp) { | ||
let { | ||
showInformer = true, | ||
file = null, | ||
throwErr = true | ||
} = _temp === void 0 ? {} : _temp; | ||
const message = typeof err === 'object' ? err.message : err; | ||
const details = typeof err === 'object' && err.details ? err.details : ''; // Restriction errors should be logged, but not as errors, | ||
// as they are expected and shown in the UI. | ||
let logMessageWithDetails = message; | ||
if (details) { | ||
logMessageWithDetails += ` ${details}`; | ||
} | ||
if (err.isRestriction) { | ||
this.log(logMessageWithDetails); | ||
this.emit('restriction-failed', file, err); | ||
} else { | ||
this.log(logMessageWithDetails, 'error'); | ||
} // Sometimes informer has to be shown manually by the developer, | ||
// for example, in `onBeforeFileAdded`. | ||
if (showInformer) { | ||
this.info({ | ||
message, | ||
details | ||
}, 'error', this.opts.infoTimeout); | ||
} | ||
if (throwErr) { | ||
throw typeof err === 'object' ? err : new Error(err); | ||
} | ||
return success; | ||
} | ||
@@ -1554,5 +1396,7 @@ | ||
if (allowNewUpload === false) { | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](new RestrictionError(this.i18n('noMoreFilesAllowed')), { | ||
file | ||
}); | ||
const error = new RestrictionError(this.i18n('noMoreFilesAllowed')); | ||
_classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](error, file); | ||
throw error; | ||
} | ||
@@ -1575,5 +1419,5 @@ } | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](error, { | ||
file: fileDescriptor | ||
}); | ||
_classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](error, fileDescriptor); | ||
throw error; | ||
} | ||
@@ -1612,6 +1456,5 @@ | ||
// Don’t show UI info for this error, as it should be done by the developer | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](new RestrictionError('Cannot add the file because onBeforeFileAdded returned false.'), { | ||
showInformer: false, | ||
fileDescriptor | ||
}); | ||
const error = new RestrictionError('Cannot add the file because onBeforeFileAdded returned false.'); | ||
this.emit('restriction-failed', fileDescriptor, error); | ||
throw error; | ||
} else if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult !== null) { | ||
@@ -1624,7 +1467,7 @@ newFile = onBeforeFileAddedResult; | ||
_classPrivateFieldLooseBase(this, _checkRestrictions)[_checkRestrictions](newFile, filesArray); | ||
_classPrivateFieldLooseBase(this, _restricter)[_restricter].validate(newFile, filesArray); | ||
} catch (err) { | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](err, { | ||
file: newFile | ||
}); | ||
_classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](err, newFile); | ||
throw err; | ||
} | ||
@@ -1689,9 +1532,5 @@ | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](newError, { | ||
throwErr: false | ||
}); | ||
_classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](newError); | ||
} else { | ||
_classPrivateFieldLooseBase(this, _showOrLogErrorAndThrow)[_showOrLogErrorAndThrow](error, { | ||
throwErr: false | ||
}); | ||
_classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](error); | ||
} | ||
@@ -1909,4 +1748,2 @@ }); | ||
} catch (err) { | ||
this.emit('error', err); | ||
_classPrivateFieldLooseBase(this, _removeUpload)[_removeUpload](uploadID); | ||
@@ -1969,3 +1806,3 @@ | ||
Uppy.VERSION = "2.1.5"; | ||
Uppy.VERSION = "2.1.6"; | ||
module.exports = Uppy; |
{ | ||
"name": "@uppy/core", | ||
"description": "Core module for the extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:", | ||
"version": "2.1.5", | ||
"version": "2.1.6", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "main": "lib/index.js", |
301
src/Uppy.js
@@ -10,4 +10,2 @@ /* eslint-disable max-classes-per-file */ | ||
const throttle = require('lodash.throttle') | ||
const prettierBytes = require('@transloadit/prettier-bytes') | ||
const match = require('mime-match') | ||
const DefaultStore = require('@uppy/store-default') | ||
@@ -20,2 +18,7 @@ const getFileType = require('@uppy/utils/lib/getFileType') | ||
const { justErrorsLogger, debugLogger } = require('./loggers') | ||
const { | ||
Restricter, | ||
defaultOptions: defaultRestrictionOptions, | ||
RestrictionError, | ||
} = require('./Restricter') | ||
@@ -25,25 +28,3 @@ const locale = require('./locale') | ||
// Exported from here. | ||
class RestrictionError extends Error { | ||
constructor (...args) { | ||
super(...args) | ||
this.isRestriction = true | ||
} | ||
} | ||
if (typeof AggregateError === 'undefined') { | ||
// eslint-disable-next-line no-global-assign | ||
globalThis.AggregateError = class AggregateError extends Error { | ||
constructor (errors, message) { | ||
super(message) | ||
this.errors = errors | ||
} | ||
} | ||
} | ||
class AggregateRestrictionError extends AggregateError { | ||
constructor (...args) { | ||
super(...args) | ||
this.isRestriction = true | ||
} | ||
} | ||
/** | ||
@@ -61,2 +42,4 @@ * Uppy Core module. | ||
#restricter | ||
#storeUnsubscribe | ||
@@ -89,11 +72,3 @@ | ||
debug: false, | ||
restrictions: { | ||
maxFileSize: null, | ||
minFileSize: null, | ||
maxTotalFileSize: null, | ||
maxNumberOfFiles: null, | ||
minNumberOfFiles: null, | ||
allowedFileTypes: null, | ||
requiredMetaFields: [], | ||
}, | ||
restrictions: defaultRestrictionOptions, | ||
meta: {}, | ||
@@ -128,8 +103,2 @@ onBeforeFileAdded: (currentFile) => currentFile, | ||
if (this.opts.restrictions.allowedFileTypes | ||
&& this.opts.restrictions.allowedFileTypes !== null | ||
&& !Array.isArray(this.opts.restrictions.allowedFileTypes)) { | ||
throw new TypeError('`restrictions.allowedFileTypes` must be an array') | ||
} | ||
this.i18nInit() | ||
@@ -163,2 +132,4 @@ | ||
this.#restricter = new Restricter(() => this.opts, this.i18n) | ||
this.#storeUnsubscribe = this.store.subscribe((prevState, nextState, patch) => { | ||
@@ -410,185 +381,57 @@ this.emit('state-update', prevState, nextState, patch) | ||
/** | ||
* A public wrapper for _checkRestrictions — checks if a file passes a set of restrictions. | ||
* For use in UI pluigins (like Providers), to disallow selecting files that won’t pass restrictions. | ||
* | ||
* @param {object} file object to check | ||
* @param {Array} [files] array to check maxNumberOfFiles and maxTotalFileSize | ||
* @returns {object} { result: true/false, reason: why file didn’t pass restrictions } | ||
*/ | ||
validateRestrictions (file, files) { | ||
try { | ||
this.#checkRestrictions(file, files) | ||
return { | ||
result: true, | ||
} | ||
} catch (err) { | ||
return { | ||
result: false, | ||
reason: err.message, | ||
} | ||
} | ||
} | ||
/* | ||
* @constructs | ||
* @param { Error } error | ||
* @param { undefined } file | ||
*/ | ||
/* | ||
* @constructs | ||
* @param { RestrictionError } error | ||
* @param { UppyFile | undefined } file | ||
*/ | ||
#informAndEmit (error, file) { | ||
const { message, details = '' } = error | ||
/** | ||
* Check if file passes a set of restrictions set in options: maxFileSize, minFileSize, | ||
* maxNumberOfFiles and allowedFileTypes. | ||
* | ||
* @param {object} file object to check | ||
* @param {Array} [files] array to check maxNumberOfFiles and maxTotalFileSize | ||
* @private | ||
*/ | ||
#checkRestrictions (file, files = this.getFiles()) { | ||
const { maxFileSize, minFileSize, maxTotalFileSize, maxNumberOfFiles, allowedFileTypes } = this.opts.restrictions | ||
if (maxNumberOfFiles) { | ||
if (files.length + 1 > maxNumberOfFiles) { | ||
throw new RestrictionError(`${this.i18n('youCanOnlyUploadX', { smart_count: maxNumberOfFiles })}`) | ||
} | ||
if (error.isRestriction) { | ||
this.emit('restriction-failed', file, error) | ||
} else { | ||
this.emit('error', error) | ||
} | ||
if (allowedFileTypes) { | ||
const isCorrectFileType = allowedFileTypes.some((type) => { | ||
// check if this is a mime-type | ||
if (type.indexOf('/') > -1) { | ||
if (!file.type) return false | ||
return match(file.type.replace(/;.*?$/, ''), type) | ||
} | ||
// otherwise this is likely an extension | ||
if (type[0] === '.' && file.extension) { | ||
return file.extension.toLowerCase() === type.substr(1).toLowerCase() | ||
} | ||
return false | ||
}) | ||
if (!isCorrectFileType) { | ||
const allowedFileTypesString = allowedFileTypes.join(', ') | ||
throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString })) | ||
} | ||
} | ||
// We can't check maxTotalFileSize if the size is unknown. | ||
if (maxTotalFileSize && file.size != null) { | ||
let totalFilesSize = 0 | ||
totalFilesSize += file.size | ||
files.forEach((f) => { | ||
totalFilesSize += f.size | ||
}) | ||
if (totalFilesSize > maxTotalFileSize) { | ||
throw new RestrictionError(this.i18n('exceedsSize', { | ||
size: prettierBytes(maxTotalFileSize), | ||
file: file.name, | ||
})) | ||
} | ||
} | ||
// We can't check maxFileSize if the size is unknown. | ||
if (maxFileSize && file.size != null) { | ||
if (file.size > maxFileSize) { | ||
throw new RestrictionError(this.i18n('exceedsSize', { | ||
size: prettierBytes(maxFileSize), | ||
file: file.name, | ||
})) | ||
} | ||
} | ||
// We can't check minFileSize if the size is unknown. | ||
if (minFileSize && file.size != null) { | ||
if (file.size < minFileSize) { | ||
throw new RestrictionError(this.i18n('inferiorSize', { | ||
size: prettierBytes(minFileSize), | ||
})) | ||
} | ||
} | ||
this.info({ message, details }, 'error', this.opts.infoTimeout) | ||
this.log(`${message} ${details}`.trim(), 'error') | ||
} | ||
/** | ||
* Check if minNumberOfFiles restriction is reached before uploading. | ||
* | ||
* @private | ||
*/ | ||
#checkMinNumberOfFiles (files) { | ||
const { minNumberOfFiles } = this.opts.restrictions | ||
if (Object.keys(files).length < minNumberOfFiles) { | ||
throw new RestrictionError(`${this.i18n('youHaveToAtLeastSelectX', { smart_count: minNumberOfFiles })}`) | ||
validateRestrictions (file, files = this.getFiles()) { | ||
// TODO: directly return the Restriction error in next major version. | ||
// we create RestrictionError's just to discard immediately, which doesn't make sense. | ||
try { | ||
this.#restricter.validate(file, files) | ||
return { result: true } | ||
} catch (err) { | ||
return { result: false, reason: err.message } | ||
} | ||
} | ||
/** | ||
* Check if requiredMetaField restriction is met for a specific file. | ||
* | ||
*/ | ||
#checkRequiredMetaFieldsOnFile (file) { | ||
const { requiredMetaFields } = this.opts.restrictions | ||
const { hasOwnProperty } = Object.prototype | ||
const { missingFields, error } = this.#restricter.getMissingRequiredMetaFields(file) | ||
const errors = [] | ||
const missingFields = [] | ||
for (let i = 0; i < requiredMetaFields.length; i++) { | ||
if (!hasOwnProperty.call(file.meta, requiredMetaFields[i]) || file.meta[requiredMetaFields[i]] === '') { | ||
const err = new RestrictionError(`${this.i18n('missingRequiredMetaFieldOnFile', { fileName: file.name })}`) | ||
errors.push(err) | ||
missingFields.push(requiredMetaFields[i]) | ||
this.#showOrLogErrorAndThrow(err, { file, showInformer: false, throwErr: false }) | ||
} | ||
if (missingFields.length > 0) { | ||
this.setFileState(file.id, { missingRequiredMetaFields: missingFields }) | ||
this.log(error.message) | ||
this.emit('restriction-failed', file, error) | ||
return false | ||
} | ||
this.setFileState(file.id, { missingRequiredMetaFields: missingFields }) | ||
return errors | ||
return true | ||
} | ||
/** | ||
* Check if requiredMetaField restriction is met before uploading. | ||
* | ||
*/ | ||
#checkRequiredMetaFields (files) { | ||
const errors = Object.keys(files).flatMap((fileID) => { | ||
const file = this.getFile(fileID) | ||
return this.#checkRequiredMetaFieldsOnFile(file) | ||
}) | ||
if (errors.length) { | ||
throw new AggregateRestrictionError(errors, `${this.i18n('missingRequiredMetaField')}`) | ||
let success = true | ||
for (const file of Object.values(files)) { | ||
if (!this.#checkRequiredMetaFieldsOnFile(file)) { | ||
success = false | ||
} | ||
} | ||
return success | ||
} | ||
/** | ||
* Logs an error, sets Informer message, then throws the error. | ||
* Emits a 'restriction-failed' event if it’s a restriction error | ||
* | ||
* @param {object | string} err — Error object or plain string message | ||
* @param {object} [options] | ||
* @param {boolean} [options.showInformer=true] — Sometimes developer might want to show Informer manually | ||
* @param {object} [options.file=null] — File object used to emit the restriction error | ||
* @param {boolean} [options.throwErr=true] — Errors shouldn’t be thrown, for example, in `upload-error` event | ||
* @private | ||
*/ | ||
#showOrLogErrorAndThrow (err, { showInformer = true, file = null, throwErr = true } = {}) { | ||
const message = typeof err === 'object' ? err.message : err | ||
const details = (typeof err === 'object' && err.details) ? err.details : '' | ||
// Restriction errors should be logged, but not as errors, | ||
// as they are expected and shown in the UI. | ||
let logMessageWithDetails = message | ||
if (details) { | ||
logMessageWithDetails += ` ${details}` | ||
} | ||
if (err.isRestriction) { | ||
this.log(logMessageWithDetails) | ||
this.emit('restriction-failed', file, err) | ||
} else { | ||
this.log(logMessageWithDetails, 'error') | ||
} | ||
// Sometimes informer has to be shown manually by the developer, | ||
// for example, in `onBeforeFileAdded`. | ||
if (showInformer) { | ||
this.info({ message, details }, 'error', this.opts.infoTimeout) | ||
} | ||
if (throwErr) { | ||
throw (typeof err === 'object' ? err : new Error(err)) | ||
} | ||
} | ||
#assertNewUploadAllowed (file) { | ||
@@ -598,3 +441,5 @@ const { allowNewUpload } = this.getState() | ||
if (allowNewUpload === false) { | ||
this.#showOrLogErrorAndThrow(new RestrictionError(this.i18n('noMoreFilesAllowed')), { file }) | ||
const error = new RestrictionError(this.i18n('noMoreFilesAllowed')) | ||
this.#informAndEmit(error, file) | ||
throw error | ||
} | ||
@@ -632,3 +477,4 @@ } | ||
const error = new RestrictionError(this.i18n('noDuplicates', { fileName })) | ||
this.#showOrLogErrorAndThrow(error, { file: fileDescriptor }) | ||
this.#informAndEmit(error, fileDescriptor) | ||
throw error | ||
} | ||
@@ -671,3 +517,5 @@ | ||
// Don’t show UI info for this error, as it should be done by the developer | ||
this.#showOrLogErrorAndThrow(new RestrictionError('Cannot add the file because onBeforeFileAdded returned false.'), { showInformer: false, fileDescriptor }) | ||
const error = new RestrictionError('Cannot add the file because onBeforeFileAdded returned false.') | ||
this.emit('restriction-failed', fileDescriptor, error) | ||
throw error | ||
} else if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult !== null) { | ||
@@ -679,5 +527,6 @@ newFile = onBeforeFileAddedResult | ||
const filesArray = Object.keys(files).map(i => files[i]) | ||
this.#checkRestrictions(newFile, filesArray) | ||
this.#restricter.validate(newFile, filesArray) | ||
} catch (err) { | ||
this.#showOrLogErrorAndThrow(err, { file: newFile }) | ||
this.#informAndEmit(err, newFile) | ||
throw err | ||
} | ||
@@ -1134,9 +983,5 @@ | ||
newError.message = this.i18n('failedToUpload', { file: file.name }) | ||
this.#showOrLogErrorAndThrow(newError, { | ||
throwErr: false, | ||
}) | ||
this.#informAndEmit(newError) | ||
} else { | ||
this.#showOrLogErrorAndThrow(error, { | ||
throwErr: false, | ||
}) | ||
this.#informAndEmit(error) | ||
} | ||
@@ -1608,3 +1453,2 @@ }) | ||
} catch (err) { | ||
this.emit('error', err) | ||
this.#removeUpload(uploadID) | ||
@@ -1687,8 +1531,17 @@ throw err | ||
return Promise.resolve() | ||
.then(() => this.#restricter.validateMinNumberOfFiles(files)) | ||
.catch((err) => { | ||
this.#informAndEmit(err) | ||
throw err | ||
}) | ||
.then(() => { | ||
this.#checkMinNumberOfFiles(files) | ||
this.#checkRequiredMetaFields(files) | ||
if (!this.#checkRequiredMetaFields(files)) { | ||
throw new RestrictionError(this.i18n('missingRequiredMetaField')) | ||
} | ||
}) | ||
.catch((err) => { | ||
this.#showOrLogErrorAndThrow(err) | ||
// Doing this in a separate catch because we already emited and logged | ||
// all the errors in `checkRequiredMetaFields` so we only throw a generic | ||
// missing fields error here. | ||
throw err | ||
}) | ||
@@ -1713,5 +1566,5 @@ .then(() => { | ||
.catch((err) => { | ||
this.#showOrLogErrorAndThrow(err, { | ||
showInformer: false, | ||
}) | ||
this.emit('error', err) | ||
this.log(err, 'error') | ||
throw err | ||
}) | ||
@@ -1718,0 +1571,0 @@ } |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable max-classes-per-file */ | ||
import * as UppyUtils from '@uppy/utils' | ||
@@ -218,3 +219,4 @@ | ||
export type UploadRetryCallback = (fileID: string) => void; | ||
export type RestrictionFailedCallback<TMeta> = (file: UppyFile<TMeta>, error: Error) => void; | ||
// TODO: reverse the order in the next major version | ||
export type RestrictionFailedCallback<TMeta> = (file: UppyFile<TMeta> | undefined, error: Error) => void; | ||
@@ -221,0 +223,0 @@ export interface UppyEventMap<TMeta = Record<string, unknown>> { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
59
365263
6369