Browse Source

Merge pull request #385 from goto-bus-stop/chore/rename-golden-retriever

Rename RestoreFiles → GoldenRetriever, and Tus10 → Tus.
Renée Kooi 7 years ago
parent
commit
914bcc0837

+ 1 - 1
CHANGELOG.md

@@ -48,7 +48,6 @@ Ideas that will be planned and find their way into a release at one point
 - [ ] an uploader plugin to receive files in a callback instead of uploading them
 - [ ] consider iframe / more security for Transloadit/Uppy integration widget
 - [ ] statusbar: add option to always show
-- [ ] tus: Rename Tus10 → Tus
 - [ ] have a `resetProgress` method for resetting a single file, and call it before starting an upload. see comment in #393
 
 ## 1.0 Goals
@@ -109,6 +108,7 @@ To be released: 2017-10-27
 - [ ] goldenretriever: add “ghost” files (@arturi)
 - [ ] xhrupload: set a timeout in the onprogress event handler to detect stale network (@goto-bus-stop)
 - [ ] tus: Review b3cc48130e292f08c2a09f2f0adf6b6332bf7692
+- [x] tus: Rename Tus10 → Tus
 
 ## 0.20.3
 

+ 5 - 5
README.md

@@ -25,13 +25,13 @@ const Uppy = require('uppy/lib/core')
 const Dashboard = require('uppy/lib/plugins/Dashboard')
 const Webcam = require('uppy/lib/plugins/Webcam')
 const GoogleDrive = require('uppy/lib/plugins/GoogleDrive')
-const Tus10 = require('uppy/lib/plugins/Tus10')
- 
+const Tus = require('uppy/lib/plugins/Tus')
+
 const uppy = Uppy({ autoProceed: false })
   .use(Dashboard, { trigger: '#select-files' })
   .use(Webcam, { target: Dashboard })
   .use(GoogleDrive, { target: Dashboard, host: 'https://server.uppy.io' })
-  .use(Tus10, { endpoint: '://master.tus.io/files/' })
+  .use(Tus, { endpoint: '://master.tus.io/files/' })
   .run()
   .on('core:success', files => console.log(`Successfully uploaded these files: ${files}`))
 ```
@@ -82,7 +82,7 @@ If you like, you can also use a pre-built bundle, for example from [unpkg CDN](h
 <script>
   var uppy = Uppy.Core()
   uppy.use(Uppy.DragDrop, {target: '.UppyDragDrop'})
-  uppy.use(Uppy.Tus10, {endpoint: '//master.tus.io/files/'})
+  uppy.use(Uppy.Tus, {endpoint: '//master.tus.io/files/'})
   uppy.run()
 </script>
 ```
@@ -106,7 +106,7 @@ If you like, you can also use a pre-built bundle, for example from [unpkg CDN](h
 - `ProgressBar` — minimal progress bar that fills itself when upload progresses
 - `StatusBar` — more detailed progress, pause/resume/cancel buttons, percentage, speed, uploaded/total sizes (included by default with `Dashboard`)
 - `Informer` — send notifications like “smile” before taking a selfie or “upload failed” when all is lost (also included by default with `Dashboard`)
-- `RestoreFiles` — restores files after a browser crash, like it’s nothing
+- `GoldenRetriever` — restores files after a browser crash, like it’s nothing
 - `ReduxDevTools` — for your emerging [time traveling](https://github.com/gaearon/redux-devtools) needs
 - `GoogleDrive` — select files from [Google Drive](https://www.google.com/drive/)
 - `Dropbox` — select files from [Dropbox](https://www.dropbox.com/)

+ 6 - 6
examples/bundled-example/main.js

@@ -1,17 +1,17 @@
-const Uppy = require('../../src/core/Core.js')
+const Uppy = require('../../src/core')
 const Dashboard = require('../../src/plugins/Dashboard')
 // const GoogleDrive = require('../../src/plugins/GoogleDrive')
 const Dropbox = require('../../src/plugins/Dropbox')
 const Instagram = require('../../src/plugins/Instagram')
 // const Webcam = require('../../src/plugins/Webcam')
-const Tus10 = require('../../src/plugins/Tus10')
-// const Multipart = require('../../src/plugins/Multipart')
+const Tus = require('../../src/plugins/Tus')
+// const XHRUpload = require('../../src/plugins/XHRUpload')
 // const FileInput = require('../../src/plugins/FileInput')
 const MetaData = require('../../src/plugins/MetaData')
 // const Informer = require('../../src/plugins/Informer')
 // const StatusBar = require('../../src/plugins/StatusBar')
 // const DragDrop = require('../../src/plugins/DragDrop')
-// const RestoreFiles = require('../../src/plugins/RestoreFiles')
+// const GoldenRetriever = require('../../src/plugins/GoldenRetriever')
 
 const PROTOCOL = location.protocol === 'https:' ? 'https' : 'http'
 const TUS_ENDPOINT = PROTOCOL + '://master.tus.io/files/'
@@ -61,14 +61,14 @@ const uppy = Uppy({
   // .use(GoogleDrive, {target: Dashboard, host: 'http://localhost:3020'})
   .use(Dropbox, {target: Dashboard, host: 'http://localhost:3020'})
   .use(Instagram, {target: Dashboard, host: 'http://localhost:3020'})
-  .use(Tus10, {endpoint: TUS_ENDPOINT, resume: true})
+  .use(Tus, {endpoint: TUS_ENDPOINT, resume: true})
   .use(MetaData, {
     fields: [
       { id: 'license', name: 'License', value: 'Creative Commons', placeholder: 'specify license' },
       { id: 'caption', name: 'Caption', value: 'none', placeholder: 'describe what the image is about' }
     ]
   })
-  // .use(RestoreFiles, {serviceWorker: true})
+  // .use(GoldenRetriever, {serviceWorker: true})
   .run()
 
 uppy.on('core:success', (fileList) => {

+ 4 - 4
examples/cdn-example/index.html

@@ -4,7 +4,7 @@
     <title></title>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <link href="https://unpkg.com/uppy/dist/uppy.min.css" rel="stylesheet"> 
+    <link href="https://unpkg.com/uppy/dist/uppy.min.css" rel="stylesheet">
   </head>
   <body>
     <button id="uppyModalOpener">Open Modal</button>
@@ -12,7 +12,7 @@
     <script>
       const Dashboard = Uppy.Dashboard
       const Webcam = Uppy.Webcam
-      const Tus10 = Uppy.Tus10
+      const Tus = Uppy.Tus
       const Informer = Uppy.Informer
 
       const uppy = Uppy.Core({debug: true, autoProceed: false})
@@ -21,7 +21,7 @@
         target: 'body'
       })
       .use(Webcam, {target: Dashboard})
-      .use(Tus10, {endpoint: 'https://master.tus.io/files/', resume: true})
+      .use(Tus, {endpoint: 'https://master.tus.io/files/', resume: true})
       .use(Informer, {target: Dashboard})
 
       uppy.run()
@@ -33,4 +33,4 @@
       document.querySelector('#uppyModalOpener').click()
     </script>
   </body>
-</html>
+</html>

+ 4 - 4
examples/multiple-instances/main.js

@@ -1,8 +1,8 @@
 const Uppy = require('uppy/lib/core')
 const Dashboard = require('uppy/lib/plugins/Dashboard')
-const RestoreFiles = require('uppy/lib/plugins/RestoreFiles')
+const GoldenRetriever = require('uppy/lib/plugins/GoldenRetriever')
 
-// Initialise two Uppy instances with the RestoreFiles plugin,
+// Initialise two Uppy instances with the GoldenRetriever plugin,
 // but with different `id`s.
 const a = Uppy({
   id: 'a',
@@ -13,7 +13,7 @@ const a = Uppy({
     inline: true,
     maxWidth: 400
   })
-  .use(RestoreFiles, { serviceWorker: false })
+  .use(GoldenRetriever, { serviceWorker: false })
   .run()
 
 const b = Uppy({
@@ -25,7 +25,7 @@ const b = Uppy({
     inline: true,
     maxWidth: 400
   })
-  .use(RestoreFiles, { serviceWorker: false })
+  .use(GoldenRetriever, { serviceWorker: false })
   .run()
 
 window.a = a

+ 3 - 3
examples/react-example/App.js

@@ -1,7 +1,7 @@
 /* eslint-disable */
 const React = require('react')
 const Uppy = require('uppy/lib/core')
-const Tus10 = require('uppy/lib/plugins/Tus10')
+const Tus = require('uppy/lib/plugins/Tus')
 const GoogleDrive = require('uppy/lib/plugins/GoogleDrive')
 const { Dashboard, DashboardModal, DragDrop, ProgressBar } = require('uppy/lib/react')
 
@@ -19,12 +19,12 @@ module.exports = class App extends React.Component {
 
   componentWillMount () {
     this.uppy = new Uppy({ autoProceed: false })
-      .use(Tus10, { endpoint: 'https://master.tus.io/files/' })
+      .use(Tus, { endpoint: 'https://master.tus.io/files/' })
       .use(GoogleDrive, { host: 'https://server.uppy.io' })
       .run()
 
     this.uppy2 = new Uppy({ autoProceed: false })
-      .use(Tus10, { endpoint: 'https://master.tus.io/files/' })
+      .use(Tus, { endpoint: 'https://master.tus.io/files/' })
       .run()
   }
 

+ 34 - 4
src/index.js

@@ -24,13 +24,13 @@ const Informer = require('./plugins/Informer.js')
 const MetaData = require('./plugins/MetaData.js')
 
 // Uploaders
-const Tus10 = require('./plugins/Tus10')
+const Tus = require('./plugins/Tus')
 const XHRUpload = require('./plugins/XHRUpload')
 const Transloadit = require('./plugins/Transloadit')
 const AwsS3 = require('./plugins/AwsS3')
 
 // Other?
-const RestoreFiles = require('./plugins/RestoreFiles')
+const GoldenRetriever = require('./plugins/GoldenRetriever')
 const ReduxDevTools = require('./plugins/ReduxDevTools')
 const ReduxStore = require('./plugins/Redux')
 
@@ -46,14 +46,44 @@ module.exports = {
   Dropbox,
   Instagram,
   FileInput,
-  Tus10,
+  Tus,
   XHRUpload,
   Transloadit,
   AwsS3,
   Dashboard,
   MetaData,
   Webcam,
-  RestoreFiles,
+  GoldenRetriever,
   ReduxDevTools,
   ReduxStore
 }
+
+Object.defineProperty(module.exports, 'RestoreFiles', {
+  enumerable: true,
+  configurable: true,
+  get: () => {
+    console.warn('Uppy.RestoreFiles is deprecated and will be removed in v0.22. Use Uppy.GoldenRetriever instead.')
+    Object.defineProperty(module.exports, 'RestoreFiles', {
+      enumerable: true,
+      configurable: true,
+      writable: true,
+      value: GoldenRetriever
+    })
+    return GoldenRetriever
+  }
+})
+
+Object.defineProperty(module.exports, 'Tus10', {
+  enumerable: true,
+  configurable: true,
+  get: () => {
+    console.warn('Uppy.Tus10 is deprecated and will be removed in v0.22. Use Uppy.Tus instead.')
+    Object.defineProperty(module.exports, 'Tus10', {
+      enumerable: true,
+      configurable: true,
+      writable: true,
+      value: Tus
+    })
+    return Tus
+  }
+})

+ 0 - 0
src/plugins/RestoreFiles/IndexedDBStore.js → src/plugins/GoldenRetriever/IndexedDBStore.js


+ 0 - 0
src/plugins/RestoreFiles/MetaDataStore.js → src/plugins/GoldenRetriever/MetaDataStore.js


+ 0 - 0
src/plugins/RestoreFiles/ServiceWorker.js → src/plugins/GoldenRetriever/ServiceWorker.js


+ 0 - 0
src/plugins/RestoreFiles/ServiceWorkerStore.js → src/plugins/GoldenRetriever/ServiceWorkerStore.js


+ 0 - 0
src/plugins/RestoreFiles/cleanup.js → src/plugins/GoldenRetriever/cleanup.js


+ 234 - 0
src/plugins/GoldenRetriever/index.js

@@ -0,0 +1,234 @@
+const Plugin = require('../Plugin')
+const ServiceWorkerStore = require('./ServiceWorkerStore')
+const IndexedDBStore = require('./IndexedDBStore')
+const MetaDataStore = require('./MetaDataStore')
+
+/**
+* The Golden Retriever plugin — restores selected files and resumes uploads
+* after a closed tab or a browser crash!
+*
+* Uses localStorage, IndexedDB and ServiceWorker to do its magic, read more:
+* https://uppy.io/blog/2017/07/golden-retriever/
+*/
+module.exports = class GoldenRetriever extends Plugin {
+  constructor (core, opts) {
+    super(core, opts)
+    this.type = 'debugger'
+    this.id = 'GoldenRetriever'
+    this.title = 'Restore Files'
+
+    const defaultOptions = {
+      expires: 24 * 60 * 60 * 1000, // 24 hours
+      serviceWorker: false
+    }
+
+    this.opts = Object.assign({}, defaultOptions, opts)
+
+    this.MetaDataStore = new MetaDataStore({
+      expires: this.opts.expires,
+      storeName: core.getID()
+    })
+    this.ServiceWorkerStore = null
+    if (this.opts.serviceWorker) {
+      this.ServiceWorkerStore = new ServiceWorkerStore({ storeName: core.getID() })
+    }
+    this.IndexedDBStore = new IndexedDBStore(Object.assign(
+      { expires: this.opts.expires },
+      opts.indexedDB || {},
+      { storeName: core.getID() }))
+
+    this.saveFilesStateToLocalStorage = this.saveFilesStateToLocalStorage.bind(this)
+    this.loadFilesStateFromLocalStorage = this.loadFilesStateFromLocalStorage.bind(this)
+    this.loadFileBlobsFromServiceWorker = this.loadFileBlobsFromServiceWorker.bind(this)
+    this.loadFileBlobsFromIndexedDB = this.loadFileBlobsFromIndexedDB.bind(this)
+    this.onBlobsLoaded = this.onBlobsLoaded.bind(this)
+  }
+
+  loadFilesStateFromLocalStorage () {
+    const savedState = this.MetaDataStore.load()
+
+    if (savedState) {
+      this.core.log('Recovered some state from Local Storage')
+      this.core.setState(savedState)
+    }
+  }
+
+  /**
+   * Get file objects that are currently waiting: they've been selected,
+   * but aren't yet being uploaded.
+   */
+  getWaitingFiles () {
+    const waitingFiles = {}
+
+    const allFiles = this.core.state.files
+    Object.keys(allFiles).forEach((fileID) => {
+      const file = this.core.getFile(fileID)
+      if (!file.progress || !file.progress.uploadStarted) {
+        waitingFiles[fileID] = file
+      }
+    })
+
+    return waitingFiles
+  }
+
+  /**
+   * Get file objects that are currently being uploaded. If a file has finished
+   * uploading, but the other files in the same batch have not, the finished
+   * file is also returned.
+   */
+  getUploadingFiles () {
+    const uploadingFiles = {}
+
+    const { currentUploads } = this.core.state
+    if (currentUploads) {
+      const uploadIDs = Object.keys(currentUploads)
+      uploadIDs.forEach((uploadID) => {
+        const filesInUpload = currentUploads[uploadID].fileIDs
+        filesInUpload.forEach((fileID) => {
+          uploadingFiles[fileID] = this.core.getFile(fileID)
+        })
+      })
+    }
+
+    return uploadingFiles
+  }
+
+  saveFilesStateToLocalStorage () {
+    const filesToSave = Object.assign(
+      this.getWaitingFiles(),
+      this.getUploadingFiles()
+    )
+
+    this.MetaDataStore.save({
+      currentUploads: this.core.state.currentUploads,
+      files: filesToSave
+    })
+  }
+
+  loadFileBlobsFromServiceWorker () {
+    this.ServiceWorkerStore.list().then((blobs) => {
+      const numberOfFilesRecovered = Object.keys(blobs).length
+      const numberOfFilesTryingToRecover = Object.keys(this.core.state.files).length
+      if (numberOfFilesRecovered === numberOfFilesTryingToRecover) {
+        this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Service Worker!`)
+        this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
+        this.onBlobsLoaded(blobs)
+      } else {
+        this.core.log('Failed to recover blobs from Service Worker, trying IndexedDB now...')
+        this.loadFileBlobsFromIndexedDB()
+      }
+    })
+  }
+
+  loadFileBlobsFromIndexedDB () {
+    this.IndexedDBStore.list().then((blobs) => {
+      const numberOfFilesRecovered = Object.keys(blobs).length
+
+      if (numberOfFilesRecovered > 0) {
+        this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Indexed DB!`)
+        this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
+        return this.onBlobsLoaded(blobs)
+      }
+      this.core.log('Couldn’t recover anything from IndexedDB :(')
+    })
+  }
+
+  onBlobsLoaded (blobs) {
+    const obsoleteBlobs = []
+    const updatedFiles = Object.assign({}, this.core.state.files)
+    Object.keys(blobs).forEach((fileID) => {
+      const originalFile = this.core.getFile(fileID)
+      if (!originalFile) {
+        obsoleteBlobs.push(fileID)
+        return
+      }
+
+      const cachedData = blobs[fileID]
+
+      const updatedFileData = {
+        data: cachedData,
+        isRestored: true
+      }
+      const updatedFile = Object.assign({}, originalFile, updatedFileData)
+      updatedFiles[fileID] = updatedFile
+
+      this.core.generatePreview(updatedFile)
+    })
+    this.core.setState({
+      files: updatedFiles
+    })
+    this.core.emit('core:restored')
+
+    if (obsoleteBlobs.length) {
+      this.deleteBlobs(obsoleteBlobs).then(() => {
+        this.core.log(`[GoldenRetriever] cleaned up ${obsoleteBlobs.length} old files`)
+      })
+    }
+  }
+
+  deleteBlobs (fileIDs) {
+    const promises = []
+    fileIDs.forEach((id) => {
+      if (this.ServiceWorkerStore) {
+        promises.push(this.ServiceWorkerStore.delete(id))
+      }
+      if (this.IndexedDBStore) {
+        promises.push(this.IndexedDBStore.delete(id))
+      }
+    })
+    return Promise.all(promises)
+  }
+
+  install () {
+    this.loadFilesStateFromLocalStorage()
+
+    if (Object.keys(this.core.state.files).length > 0) {
+      if (this.ServiceWorkerStore) {
+        this.core.log('Attempting to load files from Service Worker...')
+        this.loadFileBlobsFromServiceWorker()
+      } else {
+        this.core.log('Attempting to load files from Indexed DB...')
+        this.loadFileBlobsFromIndexedDB()
+      }
+    }
+
+    this.core.on('core:file-added', (file) => {
+      if (file.isRemote) return
+
+      if (this.ServiceWorkerStore) {
+        this.ServiceWorkerStore.put(file).catch((err) => {
+          this.core.log('Could not store file', 'error')
+          this.core.log(err)
+        })
+      }
+
+      this.IndexedDBStore.put(file).catch((err) => {
+        this.core.log('Could not store file', 'error')
+        this.core.log(err)
+      })
+    })
+
+    this.core.on('core:file-removed', (fileID) => {
+      if (this.ServiceWorkerStore) this.ServiceWorkerStore.delete(fileID)
+      this.IndexedDBStore.delete(fileID)
+    })
+
+    this.core.on('core:success', (fileIDs) => {
+      this.deleteBlobs(fileIDs).then(() => {
+        this.core.log(`[GoldenRetriever] removed ${fileIDs.length} files that finished uploading`)
+      })
+    })
+
+    this.core.on('core:state-update', this.saveFilesStateToLocalStorage)
+
+    this.core.on('core:restored', () => {
+      // start all uploads again when file blobs are restored
+      const { currentUploads } = this.core.getState()
+      if (currentUploads) {
+        Object.keys(currentUploads).forEach((uploadId) => {
+          this.core.restore(uploadId, currentUploads[uploadId])
+        })
+      }
+    })
+  }
+}

+ 3 - 232
src/plugins/RestoreFiles/index.js

@@ -1,234 +1,5 @@
-const Plugin = require('../Plugin')
-const ServiceWorkerStore = require('./ServiceWorkerStore')
-const IndexedDBStore = require('./IndexedDBStore')
-const MetaDataStore = require('./MetaDataStore')
+const GoldenRetriever = require('../GoldenRetriever')
 
-/**
-* Restore Files plugin — restores selected files and resumes uploads
-* after a closed tab or a browser crash!
-*
-* Uses localStorage, IndexedDB and ServiceWorker to do its magic, read more:
-* https://uppy.io/blog/2017/07/golden-retriever/
-*/
-module.exports = class RestoreFiles extends Plugin {
-  constructor (core, opts) {
-    super(core, opts)
-    this.type = 'debugger'
-    this.id = 'RestoreFiles'
-    this.title = 'Restore Files'
+console.warn('Using `uppy/lib/plugins/RestoreFiles` is deprecated and will be removed in v0.22. Please use `uppy/lib/plugins/GoldenRetriever` instead.')
 
-    const defaultOptions = {
-      expires: 24 * 60 * 60 * 1000, // 24 hours
-      serviceWorker: false
-    }
-
-    this.opts = Object.assign({}, defaultOptions, opts)
-
-    this.MetaDataStore = new MetaDataStore({
-      expires: this.opts.expires,
-      storeName: core.getID()
-    })
-    this.ServiceWorkerStore = null
-    if (this.opts.serviceWorker) {
-      this.ServiceWorkerStore = new ServiceWorkerStore({ storeName: core.getID() })
-    }
-    this.IndexedDBStore = new IndexedDBStore(Object.assign(
-      { expires: this.opts.expires },
-      opts.indexedDB || {},
-      { storeName: core.getID() }))
-
-    this.saveFilesStateToLocalStorage = this.saveFilesStateToLocalStorage.bind(this)
-    this.loadFilesStateFromLocalStorage = this.loadFilesStateFromLocalStorage.bind(this)
-    this.loadFileBlobsFromServiceWorker = this.loadFileBlobsFromServiceWorker.bind(this)
-    this.loadFileBlobsFromIndexedDB = this.loadFileBlobsFromIndexedDB.bind(this)
-    this.onBlobsLoaded = this.onBlobsLoaded.bind(this)
-  }
-
-  loadFilesStateFromLocalStorage () {
-    const savedState = this.MetaDataStore.load()
-
-    if (savedState) {
-      this.core.log('Recovered some state from Local Storage')
-      this.core.setState(savedState)
-    }
-  }
-
-  /**
-   * Get file objects that are currently waiting: they've been selected,
-   * but aren't yet being uploaded.
-   */
-  getWaitingFiles () {
-    const waitingFiles = {}
-
-    const allFiles = this.core.state.files
-    Object.keys(allFiles).forEach((fileID) => {
-      const file = this.core.getFile(fileID)
-      if (!file.progress || !file.progress.uploadStarted) {
-        waitingFiles[fileID] = file
-      }
-    })
-
-    return waitingFiles
-  }
-
-  /**
-   * Get file objects that are currently being uploaded. If a file has finished
-   * uploading, but the other files in the same batch have not, the finished
-   * file is also returned.
-   */
-  getUploadingFiles () {
-    const uploadingFiles = {}
-
-    const { currentUploads } = this.core.state
-    if (currentUploads) {
-      const uploadIDs = Object.keys(currentUploads)
-      uploadIDs.forEach((uploadID) => {
-        const filesInUpload = currentUploads[uploadID].fileIDs
-        filesInUpload.forEach((fileID) => {
-          uploadingFiles[fileID] = this.core.getFile(fileID)
-        })
-      })
-    }
-
-    return uploadingFiles
-  }
-
-  saveFilesStateToLocalStorage () {
-    const filesToSave = Object.assign(
-      this.getWaitingFiles(),
-      this.getUploadingFiles()
-    )
-
-    this.MetaDataStore.save({
-      currentUploads: this.core.state.currentUploads,
-      files: filesToSave
-    })
-  }
-
-  loadFileBlobsFromServiceWorker () {
-    this.ServiceWorkerStore.list().then((blobs) => {
-      const numberOfFilesRecovered = Object.keys(blobs).length
-      const numberOfFilesTryingToRecover = Object.keys(this.core.state.files).length
-      if (numberOfFilesRecovered === numberOfFilesTryingToRecover) {
-        this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Service Worker!`)
-        this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
-        this.onBlobsLoaded(blobs)
-      } else {
-        this.core.log('Failed to recover blobs from Service Worker, trying IndexedDB now...')
-        this.loadFileBlobsFromIndexedDB()
-      }
-    })
-  }
-
-  loadFileBlobsFromIndexedDB () {
-    this.IndexedDBStore.list().then((blobs) => {
-      const numberOfFilesRecovered = Object.keys(blobs).length
-
-      if (numberOfFilesRecovered > 0) {
-        this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Indexed DB!`)
-        this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
-        return this.onBlobsLoaded(blobs)
-      }
-      this.core.log('Couldn’t recover anything from IndexedDB :(')
-    })
-  }
-
-  onBlobsLoaded (blobs) {
-    const obsoleteBlobs = []
-    const updatedFiles = Object.assign({}, this.core.state.files)
-    Object.keys(blobs).forEach((fileID) => {
-      const originalFile = this.core.getFile(fileID)
-      if (!originalFile) {
-        obsoleteBlobs.push(fileID)
-        return
-      }
-
-      const cachedData = blobs[fileID]
-
-      const updatedFileData = {
-        data: cachedData,
-        isRestored: true
-      }
-      const updatedFile = Object.assign({}, originalFile, updatedFileData)
-      updatedFiles[fileID] = updatedFile
-
-      this.core.generatePreview(updatedFile)
-    })
-    this.core.setState({
-      files: updatedFiles
-    })
-    this.core.emit('core:restored')
-
-    if (obsoleteBlobs.length) {
-      this.deleteBlobs(obsoleteBlobs).then(() => {
-        this.core.log(`RestoreFiles: cleaned up ${obsoleteBlobs.length} old files`)
-      })
-    }
-  }
-
-  deleteBlobs (fileIDs) {
-    const promises = []
-    fileIDs.forEach((id) => {
-      if (this.ServiceWorkerStore) {
-        promises.push(this.ServiceWorkerStore.delete(id))
-      }
-      if (this.IndexedDBStore) {
-        promises.push(this.IndexedDBStore.delete(id))
-      }
-    })
-    return Promise.all(promises)
-  }
-
-  install () {
-    this.loadFilesStateFromLocalStorage()
-
-    if (Object.keys(this.core.state.files).length > 0) {
-      if (this.ServiceWorkerStore) {
-        this.core.log('Attempting to load files from Service Worker...')
-        this.loadFileBlobsFromServiceWorker()
-      } else {
-        this.core.log('Attempting to load files from Indexed DB...')
-        this.loadFileBlobsFromIndexedDB()
-      }
-    }
-
-    this.core.on('core:file-added', (file) => {
-      if (file.isRemote) return
-
-      if (this.ServiceWorkerStore) {
-        this.ServiceWorkerStore.put(file).catch((err) => {
-          this.core.log('Could not store file', 'error')
-          this.core.log(err)
-        })
-      }
-
-      this.IndexedDBStore.put(file).catch((err) => {
-        this.core.log('Could not store file', 'error')
-        this.core.log(err)
-      })
-    })
-
-    this.core.on('core:file-removed', (fileID) => {
-      if (this.ServiceWorkerStore) this.ServiceWorkerStore.delete(fileID)
-      this.IndexedDBStore.delete(fileID)
-    })
-
-    this.core.on('core:success', (fileIDs) => {
-      this.deleteBlobs(fileIDs).then(() => {
-        this.core.log(`RestoreFiles: removed ${fileIDs.length} files that finished uploading`)
-      })
-    })
-
-    this.core.on('core:state-update', this.saveFilesStateToLocalStorage)
-
-    this.core.on('core:restored', () => {
-      // start all uploads again when file blobs are restored
-      const { currentUploads } = this.core.getState()
-      if (currentUploads) {
-        Object.keys(currentUploads).forEach((uploadId) => {
-          this.core.restore(uploadId, currentUploads[uploadId])
-        })
-      }
-    })
-  }
-}
+module.exports = GoldenRetriever

+ 415 - 0
src/plugins/Tus.js

@@ -0,0 +1,415 @@
+const Plugin = require('./Plugin')
+const tus = require('tus-js-client')
+const UppySocket = require('../core/UppySocket')
+const {
+  emitSocketProgress,
+  getSocketHost,
+  settle
+} = require('../core/Utils')
+require('whatwg-fetch')
+
+// Extracted from https://github.com/tus/tus-js-client/blob/master/lib/upload.js#L13
+// excepted we removed 'fingerprint' key to avoid adding more dependencies
+const tusDefaultOptions = {
+  endpoint: '',
+  resume: true,
+  onProgress: null,
+  onChunkComplete: null,
+  onSuccess: null,
+  onError: null,
+  headers: {},
+  chunkSize: Infinity,
+  withCredentials: false,
+  uploadUrl: null,
+  uploadSize: null,
+  overridePatchMethod: false,
+  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
+ *
+ */
+module.exports = class Tus extends Plugin {
+  constructor (core, opts) {
+    super(core, opts)
+    this.type = 'uploader'
+    this.id = 'Tus'
+    this.title = 'Tus'
+
+    // set default options
+    const defaultOptions = {
+      resume: true,
+      autoRetry: true,
+      retryDelays: [0, 1000, 3000, 5000]
+    }
+
+    // merge default options with the ones set by user
+    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.handleUpload = this.handleUpload.bind(this)
+  }
+
+  handleResetProgress () {
+    const files = Object.assign({}, this.core.state.files)
+    Object.keys(files).forEach((fileID) => {
+      // Only clone the file object if it has a Tus `uploadUrl` attached.
+      if (files[fileID].tus && files[fileID].tus.uploadUrl) {
+        const tusState = Object.assign({}, files[fileID].tus)
+        delete tusState.uploadUrl
+        files[fileID] = Object.assign({}, files[fileID], { tus: tusState })
+      }
+    })
+
+    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
+   *
+   * @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) {
+    this.core.log(`uploading ${current} of ${total}`)
+
+    this.resetUploaderReferences(file.id)
+
+    // Create a new tus upload
+    return new Promise((resolve, reject) => {
+      const optsTus = Object.assign(
+        {},
+        tusDefaultOptions,
+        this.opts,
+        // Install file-specific upload overrides.
+        file.tus || {}
+      )
+
+      optsTus.onError = (err) => {
+        this.core.log(err)
+        this.core.emit('core:upload-error', file.id, err)
+        err.message = `Failed because: ${err.message}`
+
+        this.resetUploaderReferences(file.id)
+        reject(err)
+      }
+
+      optsTus.onProgress = (bytesUploaded, bytesTotal) => {
+        this.onReceiveUploadUrl(file, upload.url)
+        this.core.emit('core:upload-progress', {
+          uploader: this,
+          id: file.id,
+          bytesUploaded: bytesUploaded,
+          bytesTotal: bytesTotal
+        })
+      }
+
+      optsTus.onSuccess = () => {
+        this.core.emit('core:upload-success', file.id, upload, upload.url)
+
+        if (upload.url) {
+          this.core.log('Download ' + upload.file.name + ' from ' + upload.url)
+        }
+
+        this.resetUploaderReferences(file.id)
+        resolve(upload)
+      }
+      optsTus.metadata = file.meta
+
+      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.resetUploaderReferences(file.id)
+        resolve(`upload ${targetFileID} was removed`)
+      })
+
+      this.onPause(file.id, (isPaused) => {
+        isPaused ? upload.abort() : upload.start()
+      })
+
+      this.onPauseAll(file.id, () => {
+        upload.abort()
+      })
+
+      this.onCancelAll(file.id, () => {
+        this.resetUploaderReferences(file.id)
+      })
+
+      this.onResumeAll(file.id, () => {
+        if (file.error) {
+          upload.abort()
+        }
+        upload.start()
+      })
+
+      upload.start()
+      this.core.emit('core:upload-started', file.id, upload)
+    })
+  }
+
+  uploadRemote (file, current, total) {
+    this.resetUploaderReferences(file.id)
+
+    return new Promise((resolve, reject) => {
+      this.core.log(file.remote.url)
+      if (file.serverToken) {
+        this.connectToServerSocket(file)
+      } else {
+        let endpoint = this.opts.endpoint
+        if (file.tus && file.tus.endpoint) {
+          endpoint = file.tus.endpoint
+        }
+
+        this.core.emitter.emit('core:upload-started', file.id)
+
+        fetch(file.remote.url, {
+          method: 'post',
+          credentials: 'include',
+          headers: {
+            'Accept': 'application/json',
+            'Content-Type': 'application/json'
+          },
+          body: JSON.stringify(Object.assign({}, file.remote.body, {
+            endpoint,
+            protocol: 'tus',
+            size: file.data.size,
+            metadata: file.meta
+          }))
+        })
+        .then((res) => {
+          if (res.status < 200 && res.status > 300) {
+            return reject(res.statusText)
+          }
+
+          res.json().then((data) => {
+            const token = data.token
+            file = this.getFile(file.id)
+            file.serverToken = token
+            this.updateFile(file)
+            this.connectToServerSocket(file)
+            resolve()
+          })
+        })
+      }
+    })
+  }
+
+  connectToServerSocket (file) {
+    const token = file.serverToken
+    const host = getSocketHost(file.remote.host)
+    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.onPause(file.id, (isPaused) => {
+      isPaused ? socket.send('pause', {}) : socket.send('resume', {})
+    })
+
+    this.onPauseAll(file.id, () => socket.send('pause', {}))
+
+    this.onCancelAll(file.id, () => socket.send('pause', {}))
+
+    this.onResumeAll(file.id, () => {
+      if (file.error) {
+        socket.send('pause', {})
+      }
+      socket.send('resume', {})
+    })
+
+    this.onRetry(file.id, () => {
+      socket.send('pause', {})
+      socket.send('resume', {})
+    })
+
+    this.onRetryAll(file.id, () => {
+      socket.send('pause', {})
+      socket.send('resume', {})
+    })
+
+    socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file))
+
+    socket.on('success', (data) => {
+      this.core.emitter.emit('core:upload-success', file.id, data, data.url)
+      this.resetUploaderReferences(file.id)
+    })
+  }
+
+  getFile (fileID) {
+    return this.core.state.files[fileID]
+  }
+
+  updateFile (file) {
+    const files = Object.assign({}, this.core.state.files, {
+      [file.id]: file
+    })
+    this.core.setState({ files })
+  }
+
+  onReceiveUploadUrl (file, uploadURL) {
+    const currentFile = this.getFile(file.id)
+    if (!currentFile) return
+    // Only do the update if we didn't have an upload URL yet.
+    if (!currentFile.tus || currentFile.tus.uploadUrl !== uploadURL) {
+      const newFile = Object.assign({}, currentFile, {
+        tus: Object.assign({}, currentFile.tus, {
+          uploadUrl: uploadURL
+        })
+      })
+      this.updateFile(newFile)
+    }
+  }
+
+  onFileRemove (fileID, cb) {
+    this.uploaderEvents[fileID].on('core:file-removed', (targetFileID) => {
+      if (fileID === targetFileID) cb(targetFileID)
+    })
+  }
+
+  onPause (fileID, cb) {
+    this.uploaderEvents[fileID].on('core:upload-pause', (targetFileID, isPaused) => {
+      if (fileID === targetFileID) {
+        // const isPaused = this.core.pauseResume(fileID)
+        cb(isPaused)
+      }
+    })
+  }
+
+  onRetry (fileID, cb) {
+    this.uploaderEvents[fileID].on('core:upload-retry', (targetFileID) => {
+      if (fileID === targetFileID) {
+        cb()
+      }
+    })
+  }
+
+  onRetryAll (fileID, cb) {
+    this.uploaderEvents[fileID].on('core:retry-all', (filesToRetry) => {
+      if (!this.core.getFile(fileID)) return
+      cb()
+    })
+  }
+
+  onPauseAll (fileID, cb) {
+    this.uploaderEvents[fileID].on('core:pause-all', () => {
+      if (!this.core.getFile(fileID)) return
+      cb()
+    })
+  }
+
+  onCancelAll (fileID, cb) {
+    this.uploaderEvents[fileID].on('core:cancel-all', () => {
+      if (!this.core.getFile(fileID)) return
+      cb()
+    })
+  }
+
+  onResumeAll (fileID, cb) {
+    this.uploaderEvents[fileID].on('core:resume-all', () => {
+      if (!this.core.getFile(fileID)) return
+      cb()
+    })
+  }
+
+  uploadFiles (files) {
+    const promises = files.map((file, index) => {
+      const current = parseInt(index, 10) + 1
+      const total = files.length
+
+      if (!file.isRemote) {
+        return this.upload(file, current, total)
+      } else {
+        return this.uploadRemote(file, current, total)
+      }
+    })
+
+    return settle(promises)
+  }
+
+  handleUpload (fileIDs) {
+    if (fileIDs.length === 0) {
+      this.core.log('Tus: no files to upload!')
+      return Promise.resolve()
+    }
+
+    this.core.log('Tus is uploading...')
+    const filesToUpload = fileIDs.map((fileID) => this.core.getFile(fileID))
+
+    return this.uploadFiles(filesToUpload)
+  }
+
+  addResumableUploadsCapabilityFlag () {
+    const newCapabilities = Object.assign({}, this.core.getState().capabilities)
+    newCapabilities.resumableUploads = true
+    this.core.setState({
+      capabilities: newCapabilities
+    })
+  }
+
+  install () {
+    this.addResumableUploadsCapabilityFlag()
+    this.core.addUploader(this.handleUpload)
+
+    this.core.on('core:reset-progress', this.handleResetProgress)
+
+    if (this.opts.autoRetry) {
+      this.core.on('back-online', this.core.retryAll)
+    }
+  }
+
+  uninstall () {
+    this.core.removeUploader(this.handleUpload)
+
+    if (this.opts.autoRetry) {
+      this.core.off('back-online', this.core.retryAll)
+    }
+  }
+}

+ 3 - 413
src/plugins/Tus10.js

@@ -1,415 +1,5 @@
-const Plugin = require('./Plugin')
-const tus = require('tus-js-client')
-const UppySocket = require('../core/UppySocket')
-const {
-  emitSocketProgress,
-  getSocketHost,
-  settle
-} = require('../core/Utils')
-require('whatwg-fetch')
+const Tus = require('../Tus')
 
-// Extracted from https://github.com/tus/tus-js-client/blob/master/lib/upload.js#L13
-// excepted we removed 'fingerprint' key to avoid adding more dependencies
-const tusDefaultOptions = {
-  endpoint: '',
-  resume: true,
-  onProgress: null,
-  onChunkComplete: null,
-  onSuccess: null,
-  onError: null,
-  headers: {},
-  chunkSize: Infinity,
-  withCredentials: false,
-  uploadUrl: null,
-  uploadSize: null,
-  overridePatchMethod: false,
-  retryDelays: null
-}
+console.warn('Using `uppy/lib/plugins/Tus10` is deprecated and will be removed in v0.22. Please use `uppy/lib/plugins/Tus` instead.')
 
-/**
- * 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
- *
- */
-module.exports = class Tus10 extends Plugin {
-  constructor (core, opts) {
-    super(core, opts)
-    this.type = 'uploader'
-    this.id = 'Tus'
-    this.title = 'Tus'
-
-    // set default options
-    const defaultOptions = {
-      resume: true,
-      autoRetry: true,
-      retryDelays: [0, 1000, 3000, 5000]
-    }
-
-    // merge default options with the ones set by user
-    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.handleUpload = this.handleUpload.bind(this)
-  }
-
-  handleResetProgress () {
-    const files = Object.assign({}, this.core.state.files)
-    Object.keys(files).forEach((fileID) => {
-      // Only clone the file object if it has a Tus `uploadUrl` attached.
-      if (files[fileID].tus && files[fileID].tus.uploadUrl) {
-        const tusState = Object.assign({}, files[fileID].tus)
-        delete tusState.uploadUrl
-        files[fileID] = Object.assign({}, files[fileID], { tus: tusState })
-      }
-    })
-
-    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
-   *
-   * @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) {
-    this.core.log(`uploading ${current} of ${total}`)
-
-    this.resetUploaderReferences(file.id)
-
-    // Create a new tus upload
-    return new Promise((resolve, reject) => {
-      const optsTus = Object.assign(
-        {},
-        tusDefaultOptions,
-        this.opts,
-        // Install file-specific upload overrides.
-        file.tus || {}
-      )
-
-      optsTus.onError = (err) => {
-        this.core.log(err)
-        this.core.emit('core:upload-error', file.id, err)
-        err.message = `Failed because: ${err.message}`
-
-        this.resetUploaderReferences(file.id)
-        reject(err)
-      }
-
-      optsTus.onProgress = (bytesUploaded, bytesTotal) => {
-        this.onReceiveUploadUrl(file, upload.url)
-        this.core.emit('core:upload-progress', {
-          uploader: this,
-          id: file.id,
-          bytesUploaded: bytesUploaded,
-          bytesTotal: bytesTotal
-        })
-      }
-
-      optsTus.onSuccess = () => {
-        this.core.emit('core:upload-success', file.id, upload, upload.url)
-
-        if (upload.url) {
-          this.core.log('Download ' + upload.file.name + ' from ' + upload.url)
-        }
-
-        this.resetUploaderReferences(file.id)
-        resolve(upload)
-      }
-      optsTus.metadata = file.meta
-
-      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.resetUploaderReferences(file.id)
-        resolve(`upload ${targetFileID} was removed`)
-      })
-
-      this.onPause(file.id, (isPaused) => {
-        isPaused ? upload.abort() : upload.start()
-      })
-
-      this.onPauseAll(file.id, () => {
-        upload.abort()
-      })
-
-      this.onCancelAll(file.id, () => {
-        this.resetUploaderReferences(file.id)
-      })
-
-      this.onResumeAll(file.id, () => {
-        if (file.error) {
-          upload.abort()
-        }
-        upload.start()
-      })
-
-      upload.start()
-      this.core.emit('core:upload-started', file.id, upload)
-    })
-  }
-
-  uploadRemote (file, current, total) {
-    this.resetUploaderReferences(file.id)
-
-    return new Promise((resolve, reject) => {
-      this.core.log(file.remote.url)
-      if (file.serverToken) {
-        this.connectToServerSocket(file)
-      } else {
-        let endpoint = this.opts.endpoint
-        if (file.tus && file.tus.endpoint) {
-          endpoint = file.tus.endpoint
-        }
-
-        this.core.emitter.emit('core:upload-started', file.id)
-
-        fetch(file.remote.url, {
-          method: 'post',
-          credentials: 'include',
-          headers: {
-            'Accept': 'application/json',
-            'Content-Type': 'application/json'
-          },
-          body: JSON.stringify(Object.assign({}, file.remote.body, {
-            endpoint,
-            protocol: 'tus',
-            size: file.data.size,
-            metadata: file.meta
-          }))
-        })
-        .then((res) => {
-          if (res.status < 200 && res.status > 300) {
-            return reject(res.statusText)
-          }
-
-          res.json().then((data) => {
-            const token = data.token
-            file = this.getFile(file.id)
-            file.serverToken = token
-            this.updateFile(file)
-            this.connectToServerSocket(file)
-            resolve()
-          })
-        })
-      }
-    })
-  }
-
-  connectToServerSocket (file) {
-    const token = file.serverToken
-    const host = getSocketHost(file.remote.host)
-    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.onPause(file.id, (isPaused) => {
-      isPaused ? socket.send('pause', {}) : socket.send('resume', {})
-    })
-
-    this.onPauseAll(file.id, () => socket.send('pause', {}))
-
-    this.onCancelAll(file.id, () => socket.send('pause', {}))
-
-    this.onResumeAll(file.id, () => {
-      if (file.error) {
-        socket.send('pause', {})
-      }
-      socket.send('resume', {})
-    })
-
-    this.onRetry(file.id, () => {
-      socket.send('pause', {})
-      socket.send('resume', {})
-    })
-
-    this.onRetryAll(file.id, () => {
-      socket.send('pause', {})
-      socket.send('resume', {})
-    })
-
-    socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file))
-
-    socket.on('success', (data) => {
-      this.core.emitter.emit('core:upload-success', file.id, data, data.url)
-      this.resetUploaderReferences(file.id)
-    })
-  }
-
-  getFile (fileID) {
-    return this.core.state.files[fileID]
-  }
-
-  updateFile (file) {
-    const files = Object.assign({}, this.core.state.files, {
-      [file.id]: file
-    })
-    this.core.setState({ files })
-  }
-
-  onReceiveUploadUrl (file, uploadURL) {
-    const currentFile = this.getFile(file.id)
-    if (!currentFile) return
-    // Only do the update if we didn't have an upload URL yet.
-    if (!currentFile.tus || currentFile.tus.uploadUrl !== uploadURL) {
-      const newFile = Object.assign({}, currentFile, {
-        tus: Object.assign({}, currentFile.tus, {
-          uploadUrl: uploadURL
-        })
-      })
-      this.updateFile(newFile)
-    }
-  }
-
-  onFileRemove (fileID, cb) {
-    this.uploaderEvents[fileID].on('core:file-removed', (targetFileID) => {
-      if (fileID === targetFileID) cb(targetFileID)
-    })
-  }
-
-  onPause (fileID, cb) {
-    this.uploaderEvents[fileID].on('core:upload-pause', (targetFileID, isPaused) => {
-      if (fileID === targetFileID) {
-        // const isPaused = this.core.pauseResume(fileID)
-        cb(isPaused)
-      }
-    })
-  }
-
-  onRetry (fileID, cb) {
-    this.uploaderEvents[fileID].on('core:upload-retry', (targetFileID) => {
-      if (fileID === targetFileID) {
-        cb()
-      }
-    })
-  }
-
-  onRetryAll (fileID, cb) {
-    this.uploaderEvents[fileID].on('core:retry-all', (filesToRetry) => {
-      if (!this.core.getFile(fileID)) return
-      cb()
-    })
-  }
-
-  onPauseAll (fileID, cb) {
-    this.uploaderEvents[fileID].on('core:pause-all', () => {
-      if (!this.core.getFile(fileID)) return
-      cb()
-    })
-  }
-
-  onCancelAll (fileID, cb) {
-    this.uploaderEvents[fileID].on('core:cancel-all', () => {
-      if (!this.core.getFile(fileID)) return
-      cb()
-    })
-  }
-
-  onResumeAll (fileID, cb) {
-    this.uploaderEvents[fileID].on('core:resume-all', () => {
-      if (!this.core.getFile(fileID)) return
-      cb()
-    })
-  }
-
-  uploadFiles (files) {
-    const promises = files.map((file, index) => {
-      const current = parseInt(index, 10) + 1
-      const total = files.length
-
-      if (!file.isRemote) {
-        return this.upload(file, current, total)
-      } else {
-        return this.uploadRemote(file, current, total)
-      }
-    })
-
-    return settle(promises)
-  }
-
-  handleUpload (fileIDs) {
-    if (fileIDs.length === 0) {
-      this.core.log('Tus: no files to upload!')
-      return Promise.resolve()
-    }
-
-    this.core.log('Tus is uploading...')
-    const filesToUpload = fileIDs.map((fileID) => this.core.getFile(fileID))
-
-    return this.uploadFiles(filesToUpload)
-  }
-
-  addResumableUploadsCapabilityFlag () {
-    const newCapabilities = Object.assign({}, this.core.getState().capabilities)
-    newCapabilities.resumableUploads = true
-    this.core.setState({
-      capabilities: newCapabilities
-    })
-  }
-
-  install () {
-    this.addResumableUploadsCapabilityFlag()
-    this.core.addUploader(this.handleUpload)
-
-    this.core.on('core:reset-progress', this.handleResetProgress)
-
-    if (this.opts.autoRetry) {
-      this.core.on('back-online', this.core.retryAll)
-    }
-  }
-
-  uninstall () {
-    this.core.removeUploader(this.handleUpload)
-
-    if (this.opts.autoRetry) {
-      this.core.off('back-online', this.core.retryAll)
-    }
-  }
-}
+module.exports = Tus

+ 1 - 1
website/src/_posts/2017-08-0.18.md

@@ -16,7 +16,7 @@ Documentation for Uppy has been re-written, and now features chapters on Uppy’
 
 ## The Golden Retriever
 
-Golden Retriever, also known as `RestoreFiles` plugin, has been released as a public beta. For details, please refer to the previous post, [The Golden Retriever: Making uploads survive browser crashes](https://uppy.io/blog/2017/07/golden-retriever/), but the gist is: when enabled, this plugin will save uppy-state to localStorage on every change, and then if your browser crashes, or you accidentaly navigate away from a tab,later when you return, your uploads will resume like nothing happened. Spoiler: it uses Service Workers and IndexedDB.
+The Golden Retriever has been released as a public beta. For details, please refer to the previous post, [The Golden Retriever: Making uploads survive browser crashes](https://uppy.io/blog/2017/07/golden-retriever/), but the gist is: when enabled, this plugin will save uppy-state to localStorage on every change, and then if your browser crashes, or you accidentaly navigate away from a tab,later when you return, your uploads will resume like nothing happened. Spoiler: it uses Service Workers and IndexedDB.
 
 <img class="border" src="/images/blog/0.18/golden-retriever.jpg">
 

+ 4 - 4
website/src/_posts/2017-10-0.20.md

@@ -6,7 +6,7 @@ author: renee
 published: true
 ---
 
-We are proud to present Uppy `0.20`. This one focuses on React and Redux support, adding storage expirations to `RestoreFiles` (The Golden Retriever) and upload retries. Enjoy!
+We are proud to present Uppy `0.20`. This one focuses on React and Redux support, adding storage expirations to `GoldenRetriever` and upload retries. Enjoy!
 
 ## Uppy React components
 
@@ -119,11 +119,11 @@ uppy.use(DragDrop, {
 
 ## The Golden Retriever cleans up after itself
 
-We recently released the `RestoreFiles` plugin, codenamed "Golden Retriever", which stores selected files on the client so that it can recover them after a browser crash. Previously, these stored files would stay around forever, and clog up the user's disk space. As of 0.20.0, files will be removed from client-side storage when they have been uploaded. Files that have had nothing happen to them for longer than 24 hours will be cleaned up automatically. This timeframe can be configured using the new `expires` option:
+We recently released the `GoldenRetriever` plugin, which stores selected files on the client so that it can recover them after a browser crash. Previously, these stored files would stay around forever, and clog up the user's disk space. As of 0.20.0, files will be removed from client-side storage when they have been uploaded. Files that have had nothing happen to them for longer than 24 hours will be cleaned up automatically. This timeframe can be configured using the new `expires` option:
 
 ```js
 const ms = require('ms')
-uppy.use(RestoreFiles, {
+uppy.use(GoldenRetriever, {
   expires: ms('4 hours')
 })
 ```
@@ -133,7 +133,7 @@ uppy.use(RestoreFiles, {
 This will clean up files when Uppy runs, but perhaps not every page of your app uses Uppy. If a user selected some files, but then never came back to that same page, files could still hang around for a long time. To aid this, there's a new module that you can call to clean up Uppy's cache without needing an Uppy instance:
 
 ```js
-const cleanup = require('uppy/lib/plugins/RestoreFiles/cleanup')
+const cleanup = require('uppy/lib/plugins/GoldenRetriever/cleanup')
 cleanup()
 ```
 

+ 2 - 2
website/src/docs/architecture.md

@@ -12,7 +12,7 @@ Uppy file uploader consists of a lean [Core](https://github.com/transloadit/uppy
 
 ## Core
 
-1. Core module orchestrates Uppy plugins, stores `state` with `files`, and exposes useful methods like `addFile`, `setState`, `upload` to the user and plugins. Plugins are added to Uppy with `.use(Plugin, opts)` API, like so: `.use(DragDrop, {target: 'body'})`. 
+1. Core module orchestrates Uppy plugins, stores `state` with `files`, and exposes useful methods like `addFile`, `setState`, `upload` to the user and plugins. Plugins are added to Uppy with `.use(Plugin, opts)` API, like so: `.use(DragDrop, {target: 'body'})`.
 
 2. Internally Core then instantiates plugins via `new Plugin(this, opts)`, passing options to them, then places them in `plugins` object, nested by type: `uploader`, `progressindicator`, `acquirer`, etc. Core then iterates over `plugins` and calls `install` on each of them. In it’s `install` method a plugin can set event listeners to react to things happening in Uppy (upload progress, file was removed), or do anything else needed on init.
 
@@ -141,7 +141,7 @@ a plugin can extend global state with its own state (like `{ modal: { isHidden:
 
 ### uploader
 
-- **Tus10** — tus resumable file uploads, see http://tus.io
+- **Tus** — tus resumable file uploads, see http://tus.io
 - **Multipart** — regular form/multipart/xhr uploads
 
 ### modifier

+ 4 - 3
website/src/docs/golden-retriever.md

@@ -5,22 +5,23 @@ permalink: docs/golden-retriever/
 order: 40
 ---
 
-Golden Retriever plugin, also known and used as `RestoreFiles`, saves selected files in your browser cache (Local Storage for metadata, then Service Worker for all blobs + IndexedDB for small blobs), so that if the browser crashes, Uppy can restore everything and continue uploading like nothing happened. Read more about it [on the blog](https://uppy.io/blog/2017/07/golden-retriever/).
+Golden Retriever plugin saves selected files in your browser cache (Local Storage for metadata, then Service Worker for all blobs + IndexedDB for small blobs), so that if the browser crashes, Uppy can restore everything and continue uploading like nothing happened. Read more about it [on the blog](https://uppy.io/blog/2017/07/golden-retriever/).
 
 1\. Bundle your own service worker `sw.js` file with Uppy Golden Retriever’s service worker. If you’re using Browserify, just bundle it separately, for Webpack there is a plugin [serviceworker-webpack-plugin](https://github.com/oliviertassinari/serviceworker-webpack-plugin).
 
 ```js
 // sw.js
 
-require('uppy/lib/RestoreFiles/ServiceWorker.js')
+require('uppy/lib/plugins/GoldenRetriever/ServiceWorker.js')
 ```
 
 2\. Register it in your app entry point:
 
 ```js
 // you app.js entry point
+const GoldenRetriever = require('uppy/lib/plugins/GoldenRetriever')
 
-uppy.use(RestoreFiles, {serviceWorker: true})
+uppy.use(GoldenRetriever, {serviceWorker: true})
 uppy.run()
 
 if ('serviceWorker' in navigator) {

+ 3 - 3
website/src/docs/index.md

@@ -13,13 +13,13 @@ Uppy consists of a core module and [various plugins](/docs/plugins/) for selecti
 ```js
 const Uppy = require('uppy/lib/core')
 const Dashboard = require('uppy/lib/plugins/Dashboard')
-const Tus10 = require('uppy/lib/plugins/Tus10')
+const Tus = require('uppy/lib/plugins/Tus')
  
 const uppy = Uppy({ autoProceed: false })
   .use(Dashboard, {
     trigger: '#select-files'
   })
-  .use(Tus10, {endpoint: '//master.tus.io/files/'})
+  .use(Tus, {endpoint: '//master.tus.io/files/'})
   .run()
  
 uppy.on('core:success', (files) => {
@@ -62,7 +62,7 @@ If you like, you can also use a pre-built bundle, for example from [unpkg CDN](h
 <script>
   var uppy = Uppy.Core({ autoProceed: false })
   uppy.use(Uppy.DragDrop, {target: '.UppyDragDrop'})
-  uppy.use(Uppy.Tus10, {endpoint: '//master.tus.io/files/'})
+  uppy.use(Uppy.Tus, {endpoint: '//master.tus.io/files/'})
   uppy.run()
 </script>
 ```

+ 1 - 1
website/src/docs/plugins.md

@@ -13,7 +13,7 @@ Plugins are what makes Uppy useful: they help select, manipulate and upload file
   - FileInput — even more plain and simple, just a button
   - [Provider Plugins](#Provider-Plugins) (remote sources that work through [Uppy Server](/docs/uppy-server/)): Instagram, GoogleDrive, Dropbox
 - **Uploaders:**
-  - Tus10 — uploads using the tus resumable upload protocol
+  - Tus — uploads using the tus resumable upload protocol
   - XHRUpload — classic multipart form uploads or binary uploads using XMLHTTPRequest
   - [AwsS3](/docs/aws-s3) — uploader for AWS S3
 - **Progress:**

+ 2 - 2
website/src/docs/react.md

@@ -16,7 +16,7 @@ All other props are passed as options to the plugin.
 
 ```js
 const Uppy = require('uppy/lib/core')
-const Tus10 = require('uppy/lib/plugins/Tus10')
+const Tus = require('uppy/lib/plugins/Tus')
 const DragDrop = require('uppy/lib/react/DragDrop')
 
 const uppy = Uppy({
@@ -25,7 +25,7 @@ const uppy = Uppy({
   autoProceed: true
 })
 
-uppy.use(Tus10, { endpoint: '/upload' })
+uppy.use(Tus, { endpoint: '/upload' })
 
 uppy.on('core:success', (fileIDs) => {
   const url = uppy.getFile(fileIDs[0]).uploadURL

+ 3 - 3
website/src/docs/transloadit.md

@@ -7,12 +7,12 @@ permalink: docs/transloadit/
 
 The Transloadit plugin can be used to upload files to [Transloadit](https://transloadit.com/) for all kinds of processing, such as transcoding video, resizing images, zipping/unzipping, [and more](https://transloadit.com/services/).
 
-The Transloadit plugin uses the Tus plugin for the uploading itself.
+The Transloadit plugin uses the [Tus plugin](/docs/tus) for the uploading itself.
 To upload files to Transloadit directly, both the Tus and Transloadit plugins must be used:
 
 ```js
 // The `resume: false` option _must_ be provided to the Tus plugin.
-uppy.use(Tus10, {
+uppy.use(Tus, {
   resume: false
 })
 uppy.use(Transloadit, {
@@ -20,7 +20,7 @@ uppy.use(Transloadit, {
 })
 ```
 
-NB: It is not required to use the `Tus10` plugin if [importFromUploadURLs](#importFromUploadURLs) is enabled.
+NB: It is not required to use the `Tus` plugin if [importFromUploadURLs](#importFromUploadURLs) is enabled.
 
 ## Options
 

+ 4 - 4
website/src/docs/tus.md

@@ -1,14 +1,14 @@
 ---
 type: docs
 order: 30
-title: "Tus10"
+title: "Tus"
 permalink: docs/tus/
 ---
 
-The Tus10 plugin brings [tus.io](http://tus.io) resumable file uploading to Uppy by wrapping the [tus-js-client][].
+The Tus plugin brings [tus.io](http://tus.io) resumable file uploading to Uppy by wrapping the [tus-js-client][].
 
 ```js
-uppy.use(Tus10, {
+uppy.use(Tus, {
   resume: true,
   autoRetry: true,
   retryDelays: [0, 1000, 3000, 5000]
@@ -17,7 +17,7 @@ uppy.use(Tus10, {
 
 ## Options
 
-The Tus10 plugin supports all of [tus-js-client][]'s options.
+The Tus plugin supports all of [tus-js-client][]'s options.
 Additionally:
 
 ### `autoRetry: true`

+ 1 - 1
website/src/examples/bundle/app.html

@@ -5,7 +5,7 @@
 <script src="/uppy/uppy.min.js"></script>
 <script>
   var uppy = Uppy.Core({debug: true});
-  uppy.use(Uppy.Tus10, {endpoint: '//master.tus.io/files/'});
+  uppy.use(Uppy.Tus, {endpoint: '//master.tus.io/files/'});
   uppy.use(Uppy.DragDrop, {target: '#uppy-dnd'});
   uppy.use(Uppy.ProgressBar);
   uppy.run();

+ 2 - 2
website/src/examples/dashboard/app.es6

@@ -4,7 +4,7 @@ const GoogleDrive = require('uppy/lib/plugins/GoogleDrive')
 const Dropbox = require('uppy/lib/plugins/Dropbox')
 const Instagram = require('uppy/lib/plugins/Instagram')
 const Webcam = require('uppy/lib/plugins/Webcam')
-const Tus10 = require('uppy/lib/plugins/Tus10')
+const Tus = require('uppy/lib/plugins/Tus')
 const MetaData = require('uppy/lib/plugins/MetaData')
 
 const UPPY_SERVER = require('../env')
@@ -56,7 +56,7 @@ function uppyInit () {
     uppy.use(Webcam, {target: Dashboard})
   }
 
-  uppy.use(Tus10, {endpoint: TUS_ENDPOINT, resume: true})
+  uppy.use(Tus, {endpoint: TUS_ENDPOINT, resume: true})
   uppy.use(MetaData, {
     fields: [
       { id: 'license', name: 'License', value: 'Creative Commons', placeholder: 'specify license' },

+ 1 - 1
website/src/examples/dashboard/index.ejs

@@ -25,7 +25,7 @@ const GoogleDrive = require('uppy/lib/plugins/GoogleDrive')
 const Dropbox = require('uppy/lib/plugins/Dropbox')
 const Instagram = require('uppy/lib/plugins/Instagram')
 const Webcam = require('uppy/lib/plugins/Webcam')
-const Tus = require('uppy/lib/plugins/Tus10')
+const Tus = require('uppy/lib/plugins/Tus')
 const MetaData = require('uppy/lib/plugins/MetaData')
 
 const uppy = Uppy({

+ 3 - 3
website/src/examples/dragdrop/app.es6

@@ -1,19 +1,19 @@
 const Uppy = require('uppy/lib/core/Core')
 const DragDrop = require('uppy/lib/plugins/DragDrop')
 const ProgressBar = require('uppy/lib/plugins/ProgressBar')
-const Tus10 = require('uppy/lib/plugins/Tus10')
+const Tus = require('uppy/lib/plugins/Tus')
 
 const uppyOne = new Uppy({debug: true})
 uppyOne
   .use(DragDrop, {target: '.UppyDragDrop-One'})
-  .use(Tus10, {endpoint: '//master.tus.io/files/'})
+  .use(Tus, {endpoint: '//master.tus.io/files/'})
   .use(ProgressBar, {target: '.UppyDragDrop-One-Progress'})
   .run()
 
 const uppyTwo = new Uppy({debug: true, autoProceed: false})
 uppyTwo
   .use(DragDrop, {target: '#UppyDragDrop-Two'})
-  .use(Tus10, {endpoint: '//master.tus.io/files/'})
+  .use(Tus, {endpoint: '//master.tus.io/files/'})
   .use(ProgressBar, {target: '.UppyDragDrop-Two-Progress'})
   .run()
 

+ 3 - 3
website/src/examples/i18n/app.es6

@@ -1,10 +1,10 @@
 const Uppy = require('uppy/lib/core/Core')
-const Tus10 = require('uppy/lib/plugins/GoogleDrive')
+const Tus = require('uppy/lib/plugins/GoogleDrive')
 
 const uppy = new Uppy({debug: true, autoProceed: false})
 
 uppy
-  .use(Tus10, {endpoint: '//master.tus.io/files/'})
+  .use(Tus, {endpoint: '//master.tus.io/files/'})
   .run()
 
-console.log('--> Uppy Bundled version with Tus10 & Russian language pack has loaded')
+console.log('--> Uppy Bundled version with Tus & Russian language pack has loaded')

+ 2 - 2
website/src/examples/i18n/app.html

@@ -17,8 +17,8 @@
     }
   });
   uppy.use(Uppy.ProgressBar, {target: 'body', fixed: true})
-  uppy.use(Uppy.Tus10, {endpoint: '//master.tus.io/files/'});
+  uppy.use(Uppy.Tus, {endpoint: '//master.tus.io/files/'});
   uppy.run();
 
-  console.log('--> Uppy pre-built version with Tus10, DragDrop & Russian language pack has loaded');
+  console.log('--> Uppy pre-built version with Tus, DragDrop & Russian language pack has loaded');
 </script>

+ 2 - 2
website/src/examples/statusbar/app.es6

@@ -1,11 +1,11 @@
 const Uppy = require('uppy/lib/core/Core')
 const FileInput = require('uppy/lib/plugins/FileInput')
 const StatusBar = require('uppy/lib/plugins/StatusBar')
-const Tus10 = require('uppy/lib/plugins/Tus10')
+const Tus = require('uppy/lib/plugins/Tus')
 
 const uppyOne = new Uppy({debug: true})
 uppyOne
   .use(FileInput, {target: '.UppyInput'})
-  .use(Tus10, {endpoint: '//master.tus.io/files/'})
+  .use(Tus, {endpoint: '//master.tus.io/files/'})
   .use(StatusBar, {target: '.UppyInput-Progress'})
   .run()