Last active
June 3, 2017 05:01
-
-
Save hedgerh/2297951deed8cee17e4566ce62783582 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Client = require('./Client') | |
const StatusSocket = require('./Socket') | |
/** | |
* Upload files to Transloadit using Tus. | |
*/ | |
module.exports = class Transloadit extends EventEmitter { | |
constructor (core, opts) { | |
super(core, opts) | |
this.type = 'uploader' | |
this.id = 'Transloadit' | |
this.title = 'Transloadit' | |
const defaultOptions = { | |
waitForEncoding: false, | |
waitForMetadata: false, | |
signature: null, | |
params: null, | |
fields: {} | |
} | |
this.opts = Object.assign({}, defaultOptions, opts) | |
if (!this.opts.params) { | |
throw new Error('Transloadit: The `params` option is required.') | |
} | |
let params = this.opts.params | |
if (typeof params === 'string') { | |
try { | |
params = JSON.parse(params) | |
} catch (err) { | |
// Tell the user that this is not an Uppy bug! | |
err.message = 'Transloadit: The `params` option is a malformed JSON string: ' + | |
err.message | |
throw err | |
} | |
} | |
if (!params.auth || !params.auth.key) { | |
throw new Error('Transloadit: The `params.auth.key` option is required. ' + | |
'You can find your Transloadit API key at https://transloadit.com/accounts/credentials.') | |
} | |
this.client = new Client() | |
} | |
createAssembly (files) { | |
const expectedFiles = Object.keys(files).reduce((count, fileID) => { | |
if (!files[fileID].progress.uploadStarted || files[fileID].isRemote) { | |
return count + 1 | |
} | |
return count | |
}, 0) | |
const filesWithAssemblyMetadata = {} | |
return this.client.createAssembly({ | |
params: this.opts.params, | |
fields: this.opts.fields, | |
expectedFiles, | |
signature: this.opts.signature | |
}).then((assembly) => { | |
this.connectSocket(assembly).then(() => { | |
this.emit('createAssemblyComplete') | |
this.emit('start') | |
}).catch((err) => { | |
this.emit('createAssemblyFailed', err) | |
throw err | |
}) | |
function attachAssemblyMetadata (file, assembly) { | |
// Attach meta parameters for the Tus plugin. See: | |
// https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus | |
// TODO Should this `meta` be moved to a `tus.meta` property instead? | |
// If the MetaData plugin can add eg. resize parameters, it doesn't | |
// make much sense to set those as upload-metadata for tus. | |
const meta = Object.assign({}, file.meta, { | |
assembly_url: assembly.assembly_url, | |
filename: file.name, | |
fieldname: 'file' | |
}) | |
// Add assembly-specific Tus endpoint. | |
const tus = Object.assign({}, file.tus, { | |
endpoint: assembly.tus_url | |
}) | |
return Object.assign( | |
{}, | |
file, | |
{ meta, tus } | |
) | |
} | |
const filesWithAssemblyMetadata = Object.keys(files).reduce((prev, id) => { | |
return Object.assign({}, prev, { | |
[id]: attachAssemblyMetadata(files[id], assembly) | |
}) | |
}) | |
return filesWithAssemblyMetadata | |
}) | |
} | |
shouldWait () { | |
return this.opts.waitForEncoding || this.opts.waitForMetadata | |
} | |
connectSocket (assembly) { | |
this.socket = new StatusSocket( | |
assembly.websocket_url, | |
assembly | |
) | |
this.socket.on('upload', (file) => this.emit('uploadComplete', file)) | |
if (this.opts.waitForEncoding) { | |
this.socket.on('result', (stepName, result) => | |
this.emit('result', stepName, result) | |
) | |
} | |
this.assemblyReady = new Promise((resolve, reject) => { | |
if (this.opts.waitForEncoding) { | |
this.socket.on('finished', () => resolve(assembly)) | |
} else if (this.opts.waitForMetadata) { | |
this.socket.on('metadata', () => resolve(assembly)) | |
} | |
this.socket.on('error', reject) | |
}) | |
return new Promise((resolve, reject) => { | |
this.socket.on('connect', resolve) | |
this.socket.on('error', reject) | |
}) | |
} | |
start (files) { | |
return this.createAssembly(files) | |
} | |
end () { | |
if (!this.shouldWait()) { | |
this.socket.close() | |
this.emit('complete') | |
return | |
} | |
return this.assemblyReady.then((assembly) => { | |
return this.client.getAssemblyStatus(assembly.assembly_ssl_url) | |
}).then((status) => { | |
// not 100% what to do here yet | |
this.emit('complete') | |
return status | |
}) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
import tus from 'tus-js-client' | |
import UppySocket from '../utils/UppySocket' | |
import EventEmitter from 'events' | |
/** | |
* Tus resumable file uploader | |
*/ | |
export default class Tus10 extends EventEmitter { | |
constructor (opts) { | |
super() | |
// set default options | |
const defaultOptions = { | |
resume: true, | |
allowPause: true | |
} | |
// merge default options with the ones set by user | |
this.opts = Object.assign({}, defaultOptions, opts) | |
this.preProcess = opts.before || (files) => files | |
this.postProcess = opts.after || (result) => result | |
} | |
/** | |
* Start uploading for batch of files. | |
* @param {Array} files Files to upload | |
* @return {Promise} Resolves when all uploads succeed/fail | |
*/ | |
start (files) { | |
const total = files.length | |
const uploaders = this.preProcess(files).map((file, index) => { | |
const current = parseInt(index, 10) + 1 | |
if (file.isRemote) { | |
return this.uploadRemote(file, current, total) | |
} | |
return this.upload(file, current, total) | |
}) | |
return Promise.all(uploaders).then(() => { | |
const result = { | |
uploadedCount: files.length | |
} | |
this.after() | |
this.emit('upload-complete', result) | |
return result | |
}) | |
} | |
/** | |
* Create a new Tus upload | |
* | |
* @param {object} file for use with upload | |
* @param {integer} current file in a queue | |
* @param {integer} total number of files in a queue | |
* @returns {Promise} | |
*/ | |
upload (file, current, total) { | |
// Create a new tus upload | |
return new Promise((resolve, reject) => { | |
const upload = new tus.Upload(file.data, { | |
// TODO merge this.opts or this.opts.tus here | |
metadata: file.meta, | |
resume: this.opts.resume, | |
endpoint: this.opts.endpoint, | |
onError: (err) => { | |
reject('Failed because: ' + err) | |
}, | |
onProgress: (bytesUploaded, bytesTotal) => { | |
console.log('progress:') | |
console.log(bytesUploaded / bytesTotal) | |
// Dispatch progress event | |
this.emit('progress', { | |
uploader: this, | |
id: file.id, | |
bytesUploaded: bytesUploaded, | |
bytesTotal: bytesTotal | |
}) | |
}, | |
onSuccess: () => { | |
console.log('success!') | |
this.emit('success', file.id, upload.url) | |
resolve(upload) | |
} | |
}) | |
this.on('abort', (fileID) => { | |
// If no fileID provided, abort all uploads | |
if (fileID === file.id || !fileID) { | |
console.log('aborting file upload: ', fileID) | |
upload.abort() | |
resolve(`upload ${fileID} was aborted`) | |
} | |
}) | |
this.on('pause', (fileID) => { | |
// If no fileID provided, pause all uploads | |
if (fileID === file.id || !fileID) { | |
upload.abort() | |
} | |
}) | |
this.on('resume', (fileID) => { | |
// If no fileID provided, resume all uploads | |
if (fileID === file.id || !fileID) { | |
upload.start() | |
} | |
}) | |
upload.start() | |
this.emit('file-upload-started', file.id, upload) | |
}) | |
} | |
uploadRemote (file, current, total) { | |
return new Promise((resolve, reject) => { | |
const remoteHost = this.opts.remoteHost ? this.opts.remoteHost : file.remote.host | |
fetch(`${remoteHost}/${file.remote.provider}/get`, { | |
method: 'post', | |
credentials: 'include', | |
headers: { | |
'Accept': 'application/json', | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(Object.assign({}, file.remote.body, { | |
target: this.opts.endpoint, | |
protocol: 'tus' | |
})) | |
}) | |
.then((res) => { | |
if (res.status < 200 && res.status > 300) { | |
return reject(res.statusText) | |
} | |
res.json() | |
.then((data) => { | |
// get the host domain | |
var regex = /^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)/ | |
var host = regex.exec(remoteHost)[1] | |
var token = data.token | |
var socket = new UppySocket({ | |
target: `ws://${host}:3020/api/${token}` | |
}) | |
socket.on('progress', (progressData) => { | |
const {progress, bytesUploaded, bytesTotal} = progressData | |
if (progress) { | |
console.log(progress) | |
// Dispatch progress event | |
this.emit('progress', { | |
uploader: this, | |
id: file.id, | |
bytesUploaded: bytesUploaded, | |
bytesTotal: bytesTotal | |
}) | |
if (progress === '100.00') { | |
socket.close() | |
return resolve() | |
} | |
} | |
}) | |
}) | |
}) | |
}) | |
} | |
abort (fileID) { | |
this.emit('abort', fileID) | |
} | |
pause (fileID) { | |
this.emit('pause', fileID) | |
} | |
resume (fileID) { | |
this.emit('resume', fileID) | |
} | |
abortAll () { | |
this.abort() | |
} | |
pauseAll () { | |
this.pause() | |
} | |
resumeAll () { | |
this.resume() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { Transloadit, Tus10, addMetadata } from 'uppy'; | |
class Dashboard extends React.Component { | |
constructor () { | |
super() | |
this.addFile = this.addFile.bind(this) | |
this.startUpload = this.startUpload.bind(this) | |
this.onAssemblyComplete = this.onAssemblyComplete.bind(this) | |
this.onUploadProgress = this.onProgress.bind(this) | |
this.onUploadComplete = this.onUploadComplete.bind(this) | |
this.state = { | |
files: [] | |
} | |
} | |
componentDidMount () { | |
this.transloadit = new Transloadit({ | |
params: { | |
template_id: 'faceDetectTemplate', | |
auth: { | |
key: 'demoAccountKey', | |
}, | |
}, | |
waitForEncoding: true | |
}) | |
this.transloadit.on('complete', this.onAssemblyComplete) | |
this.uploader = new Tus10({ | |
before: this.transloadit.start, | |
after: this.transloadit.end, | |
resume: false | |
}) | |
this.uploader.on('progress', this.onUploadProgress) | |
} | |
startUpload () { | |
this.uploader.start(this.state.files) | |
.then(this.onUploadComplete) | |
} | |
addFile (fileObj) { | |
const fileWithMetadata = addMetadata(file, { name: 'foo' }) | |
this.setState({ | |
files: this.state.files.concat(fileWithMetadata) | |
}) | |
} | |
onAssemblyComplete () { | |
this.setState({ | |
assemblyComplete: true | |
}) | |
} | |
onUploadProgress (progress) { | |
this.setState({ /* update progress */ }) | |
} | |
onUploadComplete (err, res) { | |
if (err) { | |
this.setState({ | |
error: true, | |
errorMessage: err | |
}) | |
return err | |
} | |
this.setState({ | |
success: res, | |
files: [] | |
}) | |
return res | |
} | |
render () { | |
return ( | |
<div className='Dashboard'> | |
<FileUI | |
files={this.state.files} | |
addFile={this.addFile}/> | |
<button onClick={this.startUpload}>Start Upload</button> | |
</div> | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment