UNPKG

@rapidimages/storage-api-client

Version:
223 lines (206 loc) 5.69 kB
import fs from 'fs'; import FormData from 'form-data'; import once from 'once'; import fetch from 'node-fetch'; import crypto from 'crypto'; import concat from 'concat-stream'; function progress (files) { let offset = 0; const offsets = files.map(x => { offset += x.size; return { name: x.name, offset: offset } }); return onProgress function onProgress (e) { const percentage = (e.loaded / e.total) * 100; const file = offsets.filter(x => e.loaded <= x.offset)[0] || offsets.slice(-1)[0]; return { percentage: percentage, total: e.total, loaded: e.loaded, totalMB: (e.total / 1e6).toFixed(2) + ' MB', uploadedMB: (e.loaded / 1e6).toFixed(2) + ' MB', file: file ? file.name : '' } } } function onProgress (e) { const percentage = (e.loaded / e.total) * 100; return { file: e.file, total: e.total, loaded: e.loaded, percentage } } var defaults = opts => Object.assign( { onUploadProgress () {}, onHashProgress () {}, onUnknown () {}, onRequest () {} }, opts ); const debug = require('debug')('@rapidimages/storage-api-client'); var index = url => { if (!url) throw new Error('url must be specified') return { upload } function upload (files = [], opts) { const { onUploadProgress, onHashProgress, onUnknown, onRequest } = defaults( opts ); files = files.map(file => (file.path ? file.path : file)); return new Promise((resolve, reject) => { if (!files.length) reject(new Error('no files specified')); getKeys(files, onHashProgress, (err, keys) => { if (err) return reject(err) getUnknownKeys(url, keys, (err, unknown) => { if (err) return reject(err) onUnknown(unknown); getStats(files, (err, stats) => { if (err) return reject(err) const form = new FormData(); files.forEach(file => { if (unknown[file]) { form.append(file, fs.createReadStream(file)); } else { debug(`file ${file} already known sending details only`); form.append( file, JSON.stringify({ name: file, key: keys[file], size: stats[file].size }) ); } }); const progress$1 = progress( Object.keys(stats).reduce((sum, key) => { if (unknown[key]) { sum.push({ size: stats[key].size, name: key }); } return sum }, []) ); const request = form.submit(`${url}/upload`, (err, res) => { if (err || res.statusCode !== 200) { return reject(new Error(`failed to upload ${err}`)) } res.pipe(concat(key => resolve(key))); }); onRequest(request); let total; let loaded = 0; const pendingProgress = []; form.on('error', reject); form.on('data', data => { loaded += data.length; if (total) { onUploadProgress(progress$1({ loaded, total })); } else { pendingProgress.push(loaded); } }); form.getLength((err, length) => { if (!err) { total = length; pendingProgress.forEach(loaded => onUploadProgress(progress$1({ loaded, total })) ); } }); }); }); }); }) } }; function getStats (files, cb) { let pending = files.length; const stats = {}; files.forEach(file => { fs.stat(file, (err, stat) => { if (err) return cb(err) stats[file] = stat; done(); }); }); function done () { if (!--pending) { debug('gettings stats for files %j got %j', files, stats); cb(null, stats); } } } function getKeys (files, onHashProgress, cb) { cb = once(cb); let loaded = 0; const keys = {}; files.forEach(file => { progress(file); const sha1 = crypto.createHash('sha1'); sha1.setEncoding('hex'); fs.createReadStream(file) .on('data', sha1.write.bind(sha1)) .on('end', () => { sha1.end(); keys[file] = sha1.read(); done(); }) .on('err', cb); }); function progress (file) { onHashProgress( onProgress({ file, total: files.length, loaded: loaded }) ); } function done (file) { loaded++; progress(file); if (loaded === files.length) { debug('gettings keys for files %j got %j', files, keys); cb(null, keys); } } } function getUnknownKeys (url, details, cb) { cb = once(cb); const keys = Object.keys(details).map(x => details[x]); fetch(`${url}/unknown`, { method: 'POST', redirect: 'manual', body: JSON.stringify(keys) }) .then(checkStatus) .then(res => res.json()) .then(data => { const unknown = {}; data.forEach(key => { const file = Object.keys(details).filter(x => details[x] === key)[0]; unknown[file] = key; }); debug('checking unknown %j and got %j', keys, unknown); cb(null, unknown); }) .catch(cb); } function checkStatus (res) { return res.status === 200 ? Promise.resolve(res) : res.text().then(text => Promise.reject(text)) } export default index;