UNPKG

@uppy/core

Version:

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:

126 lines (125 loc) 5.13 kB
import prettierBytes from '@transloadit/prettier-bytes'; // @ts-ignore untyped import match from 'mime-match'; const defaultOptions = { maxFileSize: null, minFileSize: null, maxTotalFileSize: null, maxNumberOfFiles: null, minNumberOfFiles: null, allowedFileTypes: null, requiredMetaFields: [], }; class RestrictionError extends Error { isUserFacing; file; constructor(message, opts) { super(message); this.isUserFacing = opts?.isUserFacing ?? true; if (opts?.file) { this.file = opts.file; // only some restriction errors are related to a particular file } } isRestriction = true; } class Restricter { getI18n; getOpts; constructor(getOpts, getI18n) { this.getI18n = getI18n; this.getOpts = () => { const opts = getOpts(); if (opts.restrictions?.allowedFileTypes != null && !Array.isArray(opts.restrictions.allowedFileTypes)) { throw new TypeError('`restrictions.allowedFileTypes` must be an array'); } return opts; }; } // Because these operations are slow, we cannot run them for every file (if we are adding multiple files) validateAggregateRestrictions(existingFiles, addingFiles) { const { maxTotalFileSize, maxNumberOfFiles } = this.getOpts().restrictions; if (maxNumberOfFiles) { const nonGhostFiles = existingFiles.filter((f) => !f.isGhost); if (nonGhostFiles.length + addingFiles.length > maxNumberOfFiles) { throw new RestrictionError(`${this.getI18n()('youCanOnlyUploadX', { smart_count: maxNumberOfFiles, })}`); } } if (maxTotalFileSize) { const totalFilesSize = [...existingFiles, ...addingFiles].reduce((total, f) => total + (f.size ?? 0), 0); if (totalFilesSize > maxTotalFileSize) { throw new RestrictionError(this.getI18n()('aggregateExceedsSize', { sizeAllowed: prettierBytes(maxTotalFileSize), size: prettierBytes(totalFilesSize), })); } } } validateSingleFile(file) { const { maxFileSize, minFileSize, allowedFileTypes } = this.getOpts().restrictions; if (allowedFileTypes) { const isCorrectFileType = allowedFileTypes.some((type) => { // check if this is a mime-type if (type.includes('/')) { 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.slice(1).toLowerCase(); } return false; }); if (!isCorrectFileType) { const allowedFileTypesString = allowedFileTypes.join(', '); throw new RestrictionError(this.getI18n()('youCanOnlyUploadFileTypes', { types: allowedFileTypesString, }), { file }); } } // We can't check maxFileSize if the size is unknown. if (maxFileSize && file.size != null && file.size > maxFileSize) { throw new RestrictionError(this.getI18n()('exceedsSize', { size: prettierBytes(maxFileSize), file: file.name ?? this.getI18n()('unnamed'), }), { file }); } // We can't check minFileSize if the size is unknown. if (minFileSize && file.size != null && file.size < minFileSize) { throw new RestrictionError(this.getI18n()('inferiorSize', { size: prettierBytes(minFileSize), }), { file }); } } validate(existingFiles, addingFiles) { addingFiles.forEach((addingFile) => { this.validateSingleFile(addingFile); }); this.validateAggregateRestrictions(existingFiles, addingFiles); } validateMinNumberOfFiles(files) { const { minNumberOfFiles } = this.getOpts().restrictions; if (minNumberOfFiles && Object.keys(files).length < minNumberOfFiles) { throw new RestrictionError(this.getI18n()('youHaveToAtLeastSelectX', { smart_count: minNumberOfFiles, })); } } getMissingRequiredMetaFields(file) { const error = new RestrictionError(this.getI18n()('missingRequiredMetaFieldOnFile', { fileName: file.name ?? this.getI18n()('unnamed'), })); const { requiredMetaFields } = this.getOpts().restrictions; const missingFields = []; for (const field of requiredMetaFields) { if (!Object.hasOwn(file.meta, field) || file.meta[field] === '') { missingFields.push(field); } } return { missingFields, error }; } } export { Restricter, defaultOptions, RestrictionError };