UNPKG

io3fix

Version:

toolkit for interior apps

209 lines (177 loc) 6.28 kB
import FormData from '../utils/io/form-data' import runtime from '../core/runtime.js' import callServices from '../utils/services/call.js' import getShortId from '../utils/short-id.js' import auth from '../utils/auth.js' import Promise from 'bluebird' import fetch from '../utils/io/fetch.js' import getMimeTypeFromFileName from '../utils/file/get-mime-type-from-filename.js' import configs from '../core/configs' // configs var ANONYMOUS_USER_ID = 'anonymous-uploads' var KEY_USER_ID_PLACEHOLDER = '{{userId}}' // main export default function putToStorage (files, options) { options = options || {} if (!Array.isArray(files)) { // upload single file return putSingleFileToStore(files, options) } else { // upload multiple files and bundle progress events // TODO: add dir option var totalSize_ = 0 var progress_ = [] var onProgress_ = options.onProgress return Promise.map(files, function(file, i){ totalSize_ += file.size return putSingleFileToStore(file, { dir: options.dir, onProgress: function(progress, total){ progress_[i] = progress if (onProgress_) onProgress_(progress_.reduce(function(a, b) { return a+b; }, 0), totalSize_) } }) }) } } // private function putSingleFileToStore (file, options) { // API var key = options.key var dir = options.dir var fileName = options.filename || options.fileName || file.name || 'unnamed.txt' var onProgress = options.onProgress return resolveKey(key, dir, fileName) .then(validateKey) .then(function (key) { return getCredentials(file, key, fileName) }) .then(function (credentials) { return uploadFile(file, credentials, onProgress) }) } function resolveKey (key, dir, fileName) { // prefer key. fallback to dir + fileName key = key ? key : (dir ? (dir[dir.length - 1] === '/' ? dir : dir + '/') + fileName : null) var isTemplateKey = !!(key && key.indexOf(KEY_USER_ID_PLACEHOLDER) > -1) // full key including userId provided if (key && !isTemplateKey) return Promise.resolve(key) // get user id return auth.getSession().then(function(session){ if (isTemplateKey) { if (session.isAuthenticated || configs.secretApiKey) { // replace user id in template key return key.replace('{{userId}}', session.user.id || configs.secretApiKey ) } else { console.error('Using key parameter with template syntax requires authentication.') // reject with user friendly error message return Promise.reject('Please log in to upload file.') } } else { // key not provided var uploadFolder = getFormattedDate() + '_' + getShortId() if (session.isAuthenticated) { // construct new user specific key return '/' + session.user.id + '/' + uploadFolder + '/' + fileName } else { // construct anonymous key var k = '/' + ANONYMOUS_USER_ID + '/' + uploadFolder + '/' + fileName return null } } }) } var keyValidationRegex = /^\/([a-zA-Z0-9\.\-\_]+\/)+([a-zA-Z0-9\.\-\_]+)$/ function validateKey (key) { if (!key) { return Promise.resolve(null) } else if (keyValidationRegex.test(key)) { return Promise.resolve(key) } else { return Promise.reject( 'Key format validation failed.\n' + key + '\n' + 'Key must match the following pattern\n' + '- must start with a slash\n' + '- must not end with a slash\n' + '- must have one or more directories\n' + '- must not include double slashes like: "//"\n' + '- allowed characters are: a-z A-Z 0-9 _ - . /' ) } } function getCredentials (file, key, fileName) { // strip leading slash if (key && key[0] === '/') key = key.substring(1) // get credentials for upload var params = { contentLength: file.size || file.length } if (key) { params.contentType = getMimeTypeFromFileName(key) params.key = key } else if (fileName) { params.contentType = getMimeTypeFromFileName(fileName) params.fileName = fileName } else { return Promise.reject('Key or fileName param must be provided.') } return callServices('S3.getCredentials', params) } function uploadFile (file, credentials, onProgress) { // upload directly to S3 using credentials var fd = new FormData() fd.append('key', credentials.key) fd.append('AWSAccessKeyId', credentials.AWSAccessKeyId) fd.append('acl', credentials.acl) fd.append('Content-Type', credentials.contentType) fd.append('policy', credentials.policy) fd.append('signature', credentials.signature) fd.append('success_action_status', '201') if (credentials.contentEncoding) { fd.append('Content-Encoding', credentials.contentEncoding) } fd.append('file', file) if (runtime.isBrowser) { // upload using XHR (in order to provide progress info) return new Promise(function(resolve, reject){ var xhr = new XMLHttpRequest() xhr.crossOrigin = 'Anonymous' xhr.onload = function (event) { if (xhr.status >= 200 && xhr.status < 300) { var key = getKeyFromS3Response(xhr.responseText) key ? resolve(key) : reject ('Error Uploading File: '+xhr.responseText) } else { reject ('Error Uploading File: '+xhr.responseText) } } xhr.onerror = function (event) { reject(event) } if (onProgress) { xhr.upload.addEventListener('progress', function(e){ onProgress(e.loaded, e.total) }, false) } xhr.open('POST', credentials.url, true) xhr.send(fd) }) } else { // node environment: upload using fetch return fetch(credentials.url, {method: 'POST', body: fd}).then(function (res) { return res.text() }).then(function(str){ return getKeyFromS3Response(str) || Promise.reject('Error Uploading File: '+str) }) } } function getKeyFromS3Response (str) { // get file key from response var s = /<Key>(.*)<\/Key>/gi.exec(str) return s ? '/'+s[1] : false } function getFormattedDate() { var d = new Date() return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + '_' + d.getHours() + '-' + d.getMinutes() // + '-' + d.getSeconds() }