UNPKG

uppy

Version:

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:

520 lines (418 loc) 16.5 kB
'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var _Promise = typeof Promise === 'undefined' ? require('es6-promise').Promise : Promise; var Plugin = require('../core/Plugin'); var cuid = require('cuid'); var Translator = require('../core/Translator'); var UppySocket = require('../core/UppySocket'); var _require = require('../core/Utils'), emitSocketProgress = _require.emitSocketProgress, getSocketHost = _require.getSocketHost, settle = _require.settle, limitPromises = _require.limitPromises; function buildResponseError(xhr, error) { // No error message if (!error) error = new Error('Upload error'); // Got an error message string if (typeof error === 'string') error = new Error(error); // Got something else if (!(error instanceof Error)) { error = _extends(new Error('Upload error'), { data: error }); } error.request = xhr; return error; } module.exports = function (_Plugin) { _inherits(XHRUpload, _Plugin); function XHRUpload(uppy, opts) { _classCallCheck(this, XHRUpload); var _this = _possibleConstructorReturn(this, _Plugin.call(this, uppy, opts)); _this.type = 'uploader'; _this.id = 'XHRUpload'; _this.title = 'XHRUpload'; var defaultLocale = { strings: { timedOut: 'Upload stalled for %{seconds} seconds, aborting.' } // Default options };var defaultOptions = { formData: true, fieldName: 'files[]', method: 'post', metaFields: null, responseUrlFieldName: 'url', bundle: false, headers: {}, locale: defaultLocale, timeout: 30 * 1000, limit: 0, /** * @typedef respObj * @property {string} responseText * @property {number} status * @property {string} statusText * @property {Object.<string, string>} headers * * @param {string} responseContent the response body * @param {XMLHttpRequest | respObj} responseObject the response object */ getResponseData: function getResponseData(responseContent, responseObject) { var response = {}; try { response = JSON.parse(responseContent); } catch (err) { console.log(err); } return response; }, /** * * @param {string} responseContent the response body * @param {XMLHttpRequest | respObj} responseObject the response object */ getResponseError: function getResponseError(responseContent, responseObject) { return new Error('Upload error'); } }; // Merge default options with the ones set by user _this.opts = _extends({}, defaultOptions, opts); _this.locale = _extends({}, defaultLocale, _this.opts.locale); _this.locale.strings = _extends({}, defaultLocale.strings, _this.opts.locale.strings); // i18n _this.translator = new Translator({ locale: _this.locale }); _this.i18n = _this.translator.translate.bind(_this.translator); _this.handleUpload = _this.handleUpload.bind(_this); // Simultaneous upload limiting is shared across all uploads with this plugin. if (typeof _this.opts.limit === 'number' && _this.opts.limit !== 0) { _this.limitUploads = limitPromises(_this.opts.limit); } else { _this.limitUploads = function (fn) { return fn; }; } if (_this.opts.bundle && !_this.opts.formData) { throw new Error('`opts.formData` must be true when `opts.bundle` is enabled.'); } return _this; } XHRUpload.prototype.getOptions = function getOptions(file) { var opts = _extends({}, this.opts, this.uppy.state.xhrUpload || {}, file.xhrUpload || {}); opts.headers = {}; _extends(opts.headers, this.opts.headers); if (this.uppy.state.xhrUpload) { _extends(opts.headers, this.uppy.state.xhrUpload.headers); } if (file.xhrUpload) { _extends(opts.headers, file.xhrUpload.headers); } return opts; }; // Helper to abort upload requests if there has not been any progress for `timeout` ms. // Create an instance using `timer = createProgressTimeout(10000, onTimeout)` // Call `timer.progress()` to signal that there has been progress of any kind. // Call `timer.done()` when the upload has completed. XHRUpload.prototype.createProgressTimeout = function createProgressTimeout(timeout, timeoutHandler) { var uppy = this.uppy; var self = this; function onTimedOut() { uppy.log('[XHRUpload] timed out'); var error = new Error(self.i18n('timedOut', { seconds: Math.ceil(timeout / 1000) })); timeoutHandler(error); } var aliveTimer = null; function progress() { if (timeout > 0) { done(); aliveTimer = setTimeout(onTimedOut, timeout); } } function done() { if (aliveTimer) { clearTimeout(aliveTimer); aliveTimer = null; } } return { progress: progress, done: done }; }; XHRUpload.prototype.createFormDataUpload = function createFormDataUpload(file, opts) { var formPost = new FormData(); var metaFields = Array.isArray(opts.metaFields) ? opts.metaFields // Send along all fields by default. : Object.keys(file.meta); metaFields.forEach(function (item) { formPost.append(item, file.meta[item]); }); formPost.append(opts.fieldName, file.data); return formPost; }; XHRUpload.prototype.createBareUpload = function createBareUpload(file, opts) { return file.data; }; XHRUpload.prototype.upload = function upload(file, current, total) { var _this2 = this; var opts = this.getOptions(file); this.uppy.log('uploading ' + current + ' of ' + total); return new _Promise(function (resolve, reject) { var data = opts.formData ? _this2.createFormDataUpload(file, opts) : _this2.createBareUpload(file, opts); var timer = _this2.createProgressTimeout(opts.timeout, function (error) { xhr.abort(); _this2.uppy.emit('upload-error', file, error); reject(error); }); var xhr = new XMLHttpRequest(); var id = cuid(); xhr.upload.addEventListener('loadstart', function (ev) { _this2.uppy.log('[XHRUpload] ' + id + ' started'); // Begin checking for timeouts when loading starts. timer.progress(); }); xhr.upload.addEventListener('progress', function (ev) { _this2.uppy.log('[XHRUpload] ' + id + ' progress: ' + ev.loaded + ' / ' + ev.total); timer.progress(); if (ev.lengthComputable) { _this2.uppy.emit('upload-progress', file, { uploader: _this2, bytesUploaded: ev.loaded, bytesTotal: ev.total }); } }); xhr.addEventListener('load', function (ev) { _this2.uppy.log('[XHRUpload] ' + id + ' finished'); timer.done(); if (ev.target.status >= 200 && ev.target.status < 300) { var body = opts.getResponseData(xhr.responseText, xhr); var uploadURL = body[opts.responseUrlFieldName]; var response = { status: ev.target.status, body: body, uploadURL: uploadURL }; _this2.uppy.setFileState(file.id, { response: response }); _this2.uppy.emit('upload-success', file, body, uploadURL); if (uploadURL) { _this2.uppy.log('Download ' + file.name + ' from ' + file.uploadURL); } return resolve(file); } else { var _body = opts.getResponseData(xhr.responseText, xhr); var error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr)); var _response = { status: ev.target.status, body: _body }; _this2.uppy.setFileState(file.id, { response: _response }); _this2.uppy.emit('upload-error', file, error); return reject(error); } }); xhr.addEventListener('error', function (ev) { _this2.uppy.log('[XHRUpload] ' + id + ' errored'); timer.done(); var error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr)); _this2.uppy.emit('upload-error', file, error); return reject(error); }); xhr.open(opts.method.toUpperCase(), opts.endpoint, true); Object.keys(opts.headers).forEach(function (header) { xhr.setRequestHeader(header, opts.headers[header]); }); xhr.send(data); _this2.uppy.on('file-removed', function (removedFile) { if (removedFile.id === file.id) { timer.done(); xhr.abort(); } }); _this2.uppy.on('upload-cancel', function (fileID) { if (fileID === file.id) { timer.done(); xhr.abort(); } }); _this2.uppy.on('cancel-all', function () { // const files = this.uppy.getState().files // if (!files[file.id]) return xhr.abort(); }); }); }; XHRUpload.prototype.uploadRemote = function uploadRemote(file, current, total) { var _this3 = this; var opts = this.getOptions(file); return new _Promise(function (resolve, reject) { var fields = {}; var metaFields = Array.isArray(opts.metaFields) ? opts.metaFields // Send along all fields by default. : Object.keys(file.meta); metaFields.forEach(function (name) { fields[name] = file.meta[name]; }); fetch(file.remote.url, { method: 'post', credentials: 'include', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(_extends({}, file.remote.body, { endpoint: opts.endpoint, size: file.data.size, fieldname: opts.fieldName, metadata: fields, headers: opts.headers })) }).then(function (res) { if (res.status < 200 && res.status > 300) { return reject(res.statusText); } res.json().then(function (data) { var token = data.token; var host = getSocketHost(file.remote.host); var socket = new UppySocket({ target: host + '/api/' + token }); socket.on('progress', function (progressData) { return emitSocketProgress(_this3, progressData, file); }); socket.on('success', function (data) { var resp = opts.getResponseData(data.response.responseText, data.response); var uploadURL = resp[opts.responseUrlFieldName]; _this3.uppy.emit('upload-success', file, resp, uploadURL); socket.close(); return resolve(); }); socket.on('error', function (errData) { var resp = errData.response; var error = resp ? opts.getResponseError(resp.responseText, resp) : new Error(errData.error); _this3.uppy.emit('upload-error', file, error); reject(new Error(errData.error)); }); }); }); }); }; XHRUpload.prototype.uploadBundle = function uploadBundle(files) { var _this4 = this; return new _Promise(function (resolve, reject) { var endpoint = _this4.opts.endpoint; var method = _this4.opts.method; var formData = new FormData(); files.forEach(function (file, i) { var opts = _this4.getOptions(file); formData.append(opts.fieldName, file.data); }); var xhr = new XMLHttpRequest(); var timer = _this4.createProgressTimeout(_this4.opts.timeout, function (error) { xhr.abort(); emitError(error); reject(error); }); var emitError = function emitError(error) { files.forEach(function (file) { _this4.uppy.emit('upload-error', file, error); }); }; xhr.upload.addEventListener('loadstart', function (ev) { _this4.uppy.log('[XHRUpload] started uploading bundle'); timer.progress(); }); xhr.upload.addEventListener('progress', function (ev) { timer.progress(); if (!ev.lengthComputable) return; files.forEach(function (file) { _this4.uppy.emit('upload-progress', file, { uploader: _this4, bytesUploaded: ev.loaded, bytesTotal: ev.total }); }); }); xhr.addEventListener('load', function (ev) { timer.done(); if (ev.target.status >= 200 && ev.target.status < 300) { var resp = _this4.opts.getResponseData(xhr.responseText, xhr); files.forEach(function (file) { _this4.uppy.emit('upload-success', file, resp); }); return resolve(); } var error = _this4.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error'); error.request = xhr; emitError(error); return reject(error); }); xhr.addEventListener('error', function (ev) { timer.done(); var error = _this4.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error'); emitError(error); return reject(error); }); _this4.uppy.on('cancel-all', function () { xhr.abort(); }); xhr.open(method.toUpperCase(), endpoint, true); Object.keys(_this4.opts.headers).forEach(function (header) { xhr.setRequestHeader(header, _this4.opts.headers[header]); }); xhr.send(formData); files.forEach(function (file) { _this4.uppy.emit('upload-started', file); }); }); }; XHRUpload.prototype.uploadFiles = function uploadFiles(files) { var _this5 = this; var actions = files.map(function (file, i) { var current = parseInt(i, 10) + 1; var total = files.length; if (file.error) { return function () { return _Promise.reject(new Error(file.error)); }; } else if (file.isRemote) { // We emit upload-started here, so that it's also emitted for files // that have to wait due to the `limit` option. _this5.uppy.emit('upload-started', file); return _this5.uploadRemote.bind(_this5, file, current, total); } else { _this5.uppy.emit('upload-started', file); return _this5.upload.bind(_this5, file, current, total); } }); var promises = actions.map(function (action) { var limitedAction = _this5.limitUploads(action); return limitedAction(); }); return settle(promises); }; XHRUpload.prototype.handleUpload = function handleUpload(fileIDs) { var _this6 = this; if (fileIDs.length === 0) { this.uppy.log('[XHRUpload] No files to upload!'); return _Promise.resolve(); } this.uppy.log('[XHRUpload] Uploading...'); var files = fileIDs.map(function (fileID) { return _this6.uppy.getFile(fileID); }); if (this.opts.bundle) { return this.uploadBundle(files); } return this.uploadFiles(files).then(function () { return null; }); }; XHRUpload.prototype.install = function install() { this.uppy.addUploader(this.handleUpload); }; XHRUpload.prototype.uninstall = function uninstall() { this.uppy.removeUploader(this.handleUpload); }; return XHRUpload; }(Plugin); //# sourceMappingURL=XHRUpload.js.map