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:

192 lines (165 loc) 5.74 kB
const Plugin = require('../../core/Plugin') const Translator = require('../../core/Translator') const { limitPromises } = require('../../core/Utils') const XHRUpload = require('../XHRUpload') function isXml (xhr) { const contentType = xhr.getResponseHeader('Content-Type') return typeof contentType === 'string' && contentType.toLowerCase() === 'application/xml' } module.exports = class AwsS3 extends Plugin { constructor (uppy, opts) { super(uppy, opts) this.type = 'uploader' this.id = 'AwsS3' this.title = 'AWS S3' const defaultLocale = { strings: { preparingUpload: 'Preparing upload...' } } const defaultOptions = { timeout: 30 * 1000, limit: 0, getUploadParameters: this.getUploadParameters.bind(this), locale: defaultLocale } this.opts = Object.assign({}, defaultOptions, opts) this.locale = Object.assign({}, defaultLocale, this.opts.locale) this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings) this.translator = new Translator({ locale: this.locale }) this.i18n = this.translator.translate.bind(this.translator) this.prepareUpload = this.prepareUpload.bind(this) if (typeof this.opts.limit === 'number' && this.opts.limit !== 0) { this.limitRequests = limitPromises(this.opts.limit) } else { this.limitRequests = (fn) => fn } } getUploadParameters (file) { if (!this.opts.host) { throw new Error('Expected a `host` option containing an uppy-server address.') } const filename = encodeURIComponent(file.name) const type = encodeURIComponent(file.type) return fetch(`${this.opts.host}/s3/params?filename=${filename}&type=${type}`, { method: 'get', headers: { accept: 'application/json' } }).then((response) => response.json()) } validateParameters (file, params) { const valid = typeof params === 'object' && params && typeof params.url === 'string' && (typeof params.fields === 'object' || params.fields == null) && (params.method == null || /^(put|post)$/i.test(params.method)) if (!valid) { const err = new TypeError(`AwsS3: got incorrect result from 'getUploadParameters()' for file '${file.name}', expected an object '{ url, method, fields }'.\nSee https://uppy.io/docs/aws-s3/#getUploadParameters-file for more on the expected format.`) console.error(err) throw err } return params } prepareUpload (fileIDs) { fileIDs.forEach((id) => { const file = this.uppy.getFile(id) this.uppy.emit('preprocess-progress', file, { mode: 'determinate', message: this.i18n('preparingUpload'), value: 0 }) }) const getUploadParameters = this.limitRequests(this.opts.getUploadParameters) return Promise.all( fileIDs.map((id) => { const file = this.uppy.getFile(id) const paramsPromise = Promise.resolve() .then(() => getUploadParameters(file)) return paramsPromise.then((params) => { return this.validateParameters(file, params) }).then((params) => { this.uppy.emit('preprocess-progress', file, { mode: 'determinate', message: this.i18n('preparingUpload'), value: 1 }) return params }).catch((error) => { this.uppy.emit('upload-error', file, error) }) }) ).then((responses) => { const updatedFiles = {} fileIDs.forEach((id, index) => { const file = this.uppy.getFile(id) if (file.error) { return } const { method = 'post', url, fields, headers } = responses[index] const xhrOpts = { method, formData: method.toLowerCase() === 'post', endpoint: url, metaFields: Object.keys(fields) } if (headers) { xhrOpts.headers = headers } const updatedFile = Object.assign({}, file, { meta: Object.assign({}, file.meta, fields), xhrUpload: xhrOpts }) updatedFiles[id] = updatedFile }) this.uppy.setState({ files: Object.assign({}, this.uppy.getState().files, updatedFiles) }) fileIDs.forEach((id) => { const file = this.uppy.getFile(id) this.uppy.emit('preprocess-complete', file) }) }) } install () { this.uppy.addPreProcessor(this.prepareUpload) this.uppy.use(XHRUpload, { fieldName: 'file', responseUrlFieldName: 'location', timeout: this.opts.timeout, limit: this.opts.limit, getResponseData (content, xhr) { // If no response, we've hopefully done a PUT request to the file // in the bucket on its full URL. if (!isXml(xhr)) { return { location: xhr.responseURL } } function getValue (key) { const el = xhr.responseXML.querySelector(key) return el ? el.textContent : '' } return { location: getValue('Location'), bucket: getValue('Bucket'), key: getValue('Key'), etag: getValue('ETag') } }, getResponseError (content, xhr) { // If no response, we don't have a specific error message, use the default. if (!isXml(xhr)) { return } const error = xhr.responseXML.querySelector('Error > Message') return new Error(error.textContent) } }) } uninstall () { const uploader = this.uppy.getPlugin('XHRUpload') this.uppy.removePlugin(uploader) this.uppy.removePreProcessor(this.prepareUpload) } }