|
@@ -26,6 +26,25 @@ const tusDefaultOptions = {
|
|
retryDelays: null
|
|
retryDelays: null
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * Create a wrapper around an event emitter with a `remove` method to remove
|
|
|
|
+ * all events that were added using the wrapped emitter.
|
|
|
|
+ */
|
|
|
|
+function createEventTracker (emitter) {
|
|
|
|
+ const events = []
|
|
|
|
+ return {
|
|
|
|
+ on (event, fn) {
|
|
|
|
+ events.push([ event, fn ])
|
|
|
|
+ return emitter.on(event, fn)
|
|
|
|
+ },
|
|
|
|
+ remove () {
|
|
|
|
+ events.forEach(([ event, fn ]) => {
|
|
|
|
+ emitter.off(event, fn)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Tus resumable file uploader
|
|
* Tus resumable file uploader
|
|
*
|
|
*
|
|
@@ -47,6 +66,10 @@ module.exports = class Tus10 extends Plugin {
|
|
// merge default options with the ones set by user
|
|
// merge default options with the ones set by user
|
|
this.opts = Object.assign({}, defaultOptions, opts)
|
|
this.opts = Object.assign({}, defaultOptions, opts)
|
|
|
|
|
|
|
|
+ this.uploaders = Object.create(null)
|
|
|
|
+ this.uploaderEvents = Object.create(null)
|
|
|
|
+ this.uploaderSockets = Object.create(null)
|
|
|
|
+
|
|
this.handleResetProgress = this.handleResetProgress.bind(this)
|
|
this.handleResetProgress = this.handleResetProgress.bind(this)
|
|
this.handleUpload = this.handleUpload.bind(this)
|
|
this.handleUpload = this.handleUpload.bind(this)
|
|
}
|
|
}
|
|
@@ -65,6 +88,25 @@ module.exports = class Tus10 extends Plugin {
|
|
this.core.setState({ files })
|
|
this.core.setState({ files })
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Clean up all references for a file's upload: the tus.Upload instance,
|
|
|
|
+ * any events related to the file, and the uppy-server WebSocket connection.
|
|
|
|
+ */
|
|
|
|
+ resetUploaderReferences (fileID) {
|
|
|
|
+ if (this.uploaders[fileID]) {
|
|
|
|
+ this.uploaders[fileID].abort()
|
|
|
|
+ this.uploaders[fileID] = null
|
|
|
|
+ }
|
|
|
|
+ if (this.uploaderEvents[fileID]) {
|
|
|
|
+ this.uploaderEvents[fileID].remove()
|
|
|
|
+ this.uploaderEvents[fileID] = null
|
|
|
|
+ }
|
|
|
|
+ if (this.uploaderSockets[fileID]) {
|
|
|
|
+ this.uploaderSockets[fileID].close()
|
|
|
|
+ this.uploaderSockets[fileID] = null
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Create a new Tus upload
|
|
* Create a new Tus upload
|
|
*
|
|
*
|
|
@@ -76,6 +118,8 @@ module.exports = class Tus10 extends Plugin {
|
|
upload (file, current, total) {
|
|
upload (file, current, total) {
|
|
this.core.log(`uploading ${current} of ${total}`)
|
|
this.core.log(`uploading ${current} of ${total}`)
|
|
|
|
|
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
|
|
+
|
|
// Create a new tus upload
|
|
// Create a new tus upload
|
|
return new Promise((resolve, reject) => {
|
|
return new Promise((resolve, reject) => {
|
|
const optsTus = Object.assign(
|
|
const optsTus = Object.assign(
|
|
@@ -90,6 +134,8 @@ module.exports = class Tus10 extends Plugin {
|
|
this.core.log(err)
|
|
this.core.log(err)
|
|
this.core.emit('core:upload-error', file.id, err)
|
|
this.core.emit('core:upload-error', file.id, err)
|
|
err.message = `Failed because: ${err.message}`
|
|
err.message = `Failed because: ${err.message}`
|
|
|
|
+
|
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
reject(err)
|
|
reject(err)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -110,14 +156,17 @@ module.exports = class Tus10 extends Plugin {
|
|
this.core.log('Download ' + upload.file.name + ' from ' + upload.url)
|
|
this.core.log('Download ' + upload.file.name + ' from ' + upload.url)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
resolve(upload)
|
|
resolve(upload)
|
|
}
|
|
}
|
|
optsTus.metadata = file.meta
|
|
optsTus.metadata = file.meta
|
|
|
|
|
|
const upload = new tus.Upload(file.data, optsTus)
|
|
const upload = new tus.Upload(file.data, optsTus)
|
|
|
|
+ this.uploaders[file.id] = upload
|
|
|
|
+ this.uploaderEvents[file.id] = createEventTracker(this.core)
|
|
|
|
|
|
this.onFileRemove(file.id, (targetFileID) => {
|
|
this.onFileRemove(file.id, (targetFileID) => {
|
|
- upload.abort()
|
|
|
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
resolve(`upload ${targetFileID} was removed`)
|
|
resolve(`upload ${targetFileID} was removed`)
|
|
})
|
|
})
|
|
|
|
|
|
@@ -130,8 +179,7 @@ module.exports = class Tus10 extends Plugin {
|
|
})
|
|
})
|
|
|
|
|
|
this.onCancelAll(file.id, () => {
|
|
this.onCancelAll(file.id, () => {
|
|
- upload.abort()
|
|
|
|
- this.removeUploadURL(file.id)
|
|
|
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
})
|
|
})
|
|
|
|
|
|
this.onResumeAll(file.id, () => {
|
|
this.onResumeAll(file.id, () => {
|
|
@@ -147,6 +195,8 @@ module.exports = class Tus10 extends Plugin {
|
|
}
|
|
}
|
|
|
|
|
|
uploadRemote (file, current, total) {
|
|
uploadRemote (file, current, total) {
|
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
|
|
+
|
|
return new Promise((resolve, reject) => {
|
|
return new Promise((resolve, reject) => {
|
|
this.core.log(file.remote.url)
|
|
this.core.log(file.remote.url)
|
|
if (file.serverToken) {
|
|
if (file.serverToken) {
|
|
@@ -195,6 +245,8 @@ module.exports = class Tus10 extends Plugin {
|
|
const token = file.serverToken
|
|
const token = file.serverToken
|
|
const host = getSocketHost(file.remote.host)
|
|
const host = getSocketHost(file.remote.host)
|
|
const socket = new UppySocket({ target: `${host}/api/${token}` })
|
|
const socket = new UppySocket({ target: `${host}/api/${token}` })
|
|
|
|
+ this.uploaderSockets[file.id] = socket
|
|
|
|
+ this.uploaderEvents[file.id] = createEventTracker(this.core)
|
|
|
|
|
|
this.onFileRemove(file.id, () => socket.send('pause', {}))
|
|
this.onFileRemove(file.id, () => socket.send('pause', {}))
|
|
|
|
|
|
@@ -227,7 +279,7 @@ module.exports = class Tus10 extends Plugin {
|
|
|
|
|
|
socket.on('success', (data) => {
|
|
socket.on('success', (data) => {
|
|
this.core.emitter.emit('core:upload-success', file.id, data, data.url)
|
|
this.core.emitter.emit('core:upload-success', file.id, data, data.url)
|
|
- socket.close()
|
|
|
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
@@ -256,27 +308,14 @@ module.exports = class Tus10 extends Plugin {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- removeUploadURL (fileID) {
|
|
|
|
- const file = this.core.getFile(fileID)
|
|
|
|
-
|
|
|
|
- // No need to change state if we didn't have an `uploadUrl`.
|
|
|
|
- if (!file || !file.tus || !file.tus.uploadUrl) return
|
|
|
|
-
|
|
|
|
- this.updateFile(Object.assign({}, file, {
|
|
|
|
- tus: Object.assign({}, file.tus, {
|
|
|
|
- uploadUrl: null
|
|
|
|
- })
|
|
|
|
- }))
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
onFileRemove (fileID, cb) {
|
|
onFileRemove (fileID, cb) {
|
|
- this.core.on('core:file-removed', (targetFileID) => {
|
|
|
|
|
|
+ this.uploaderEvents[fileID].on('core:file-removed', (targetFileID) => {
|
|
if (fileID === targetFileID) cb(targetFileID)
|
|
if (fileID === targetFileID) cb(targetFileID)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
onPause (fileID, cb) {
|
|
onPause (fileID, cb) {
|
|
- this.core.on('core:upload-pause', (targetFileID, isPaused) => {
|
|
|
|
|
|
+ this.uploaderEvents[fileID].on('core:upload-pause', (targetFileID, isPaused) => {
|
|
if (fileID === targetFileID) {
|
|
if (fileID === targetFileID) {
|
|
// const isPaused = this.core.pauseResume(fileID)
|
|
// const isPaused = this.core.pauseResume(fileID)
|
|
cb(isPaused)
|
|
cb(isPaused)
|
|
@@ -285,7 +324,7 @@ module.exports = class Tus10 extends Plugin {
|
|
}
|
|
}
|
|
|
|
|
|
onRetry (fileID, cb) {
|
|
onRetry (fileID, cb) {
|
|
- this.core.on('core:upload-retry', (targetFileID) => {
|
|
|
|
|
|
+ this.uploaderEvents[fileID].on('core:upload-retry', (targetFileID) => {
|
|
if (fileID === targetFileID) {
|
|
if (fileID === targetFileID) {
|
|
cb()
|
|
cb()
|
|
}
|
|
}
|
|
@@ -293,28 +332,28 @@ module.exports = class Tus10 extends Plugin {
|
|
}
|
|
}
|
|
|
|
|
|
onRetryAll (fileID, cb) {
|
|
onRetryAll (fileID, cb) {
|
|
- this.core.on('core:retry-all', (filesToRetry) => {
|
|
|
|
|
|
+ this.uploaderEvents[fileID].on('core:retry-all', (filesToRetry) => {
|
|
if (!this.core.getFile(fileID)) return
|
|
if (!this.core.getFile(fileID)) return
|
|
cb()
|
|
cb()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
onPauseAll (fileID, cb) {
|
|
onPauseAll (fileID, cb) {
|
|
- this.core.on('core:pause-all', () => {
|
|
|
|
|
|
+ this.uploaderEvents[fileID].on('core:pause-all', () => {
|
|
if (!this.core.getFile(fileID)) return
|
|
if (!this.core.getFile(fileID)) return
|
|
cb()
|
|
cb()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
onCancelAll (fileID, cb) {
|
|
onCancelAll (fileID, cb) {
|
|
- this.core.on('core:cancel-all', () => {
|
|
|
|
|
|
+ this.uploaderEvents[fileID].on('core:cancel-all', () => {
|
|
if (!this.core.getFile(fileID)) return
|
|
if (!this.core.getFile(fileID)) return
|
|
cb()
|
|
cb()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
onResumeAll (fileID, cb) {
|
|
onResumeAll (fileID, cb) {
|
|
- this.core.on('core:resume-all', () => {
|
|
|
|
|
|
+ this.uploaderEvents[fileID].on('core:resume-all', () => {
|
|
if (!this.core.getFile(fileID)) return
|
|
if (!this.core.getFile(fileID)) return
|
|
cb()
|
|
cb()
|
|
})
|
|
})
|