Explorar o código

Merge pull request #647 from transloadit/chore/fileID-to-file

Emit full file object instead of fileID in events like uppy.on('event', file, data)
Artur Paikin %!s(int64=7) %!d(string=hai) anos
pai
achega
47cd7a778b

+ 35 - 49
src/core/Core.js

@@ -437,6 +437,7 @@ class Uppy {
 
     this._calculateTotalProgress()
     this.emit('file-removed', removedFile)
+    this.log(`File removed: ${removedFile.id}`)
 
     // Clean up object URLs.
     if (removedFile.preview && Utils.isObjectURL(removedFile.preview)) {
@@ -552,17 +553,14 @@ class Uppy {
     this.cancelAll()
   }
 
-  _calculateProgress (data) {
-    const fileID = data.id
-
-    // skip progress event for a file that’s been removed
-    if (!this.getFile(fileID)) {
-      this.log(`Not setting progress for a file that has been removed: ${fileID}`)
+  _calculateProgress (file, data) {
+    if (!this.getFile(file.id)) {
+      this.log(`Not setting progress for a file that has been removed: ${file.id}`)
       return
     }
 
-    this.setFileState(fileID, {
-      progress: Object.assign({}, this.getState().files[fileID].progress, {
+    this.setFileState(file.id, {
+      progress: Object.assign({}, this.getFile(file.id).progress, {
         bytesUploaded: data.bytesUploaded,
         bytesTotal: data.bytesTotal,
         percentage: Math.floor((data.bytesUploaded / data.bytesTotal * 100).toFixed(2))
@@ -618,8 +616,7 @@ class Uppy {
       this.setFileState(file.id, { error: error.message })
       this.setState({ error: error.message })
 
-      const fileName = file.name
-      let message = `Failed to upload ${fileName}`
+      let message = `Failed to upload ${file.name}`
       if (typeof error === 'object' && error.message) {
         message = { message: message, details: error.message }
       }
@@ -630,22 +627,13 @@ class Uppy {
       this.setState({ error: null })
     })
 
-    // this.on('file-add', (data) => {
-    //   this.addFile(data)
-    // })
-
-    this.on('file-remove', (fileID) => {
-      this.removeFile(fileID)
-    })
-
-    this.on('upload-started', (fileID, upload) => {
-      if (!this.getFile(fileID)) {
-        this.log(`Not setting progress for a file that has been removed: ${fileID}`)
+    this.on('upload-started', (file, upload) => {
+      if (!this.getFile(file.id)) {
+        this.log(`Not setting progress for a file that has been removed: ${file.id}`)
         return
       }
-      const file = this.getFile(fileID)
-      this.setFileState(fileID, {
-        progress: Object.assign({}, file.progress, {
+      this.setFileState(file.id, {
+        progress: Object.assign({}, this.getFile(file.id), {
           uploadStarted: Date.now(),
           uploadComplete: false,
           percentage: 0,
@@ -665,7 +653,7 @@ class Uppy {
 
     this.on('upload-success', (file, uploadResp, uploadURL) => {
       this.setFileState(file.id, {
-        progress: Object.assign({}, this.state.files[file.id].progress, {
+        progress: Object.assign({}, this.getFile(file.id).progress, {
           uploadComplete: true,
           percentage: 100
         }),
@@ -676,54 +664,54 @@ class Uppy {
       this._calculateTotalProgress()
     })
 
-    this.on('preprocess-progress', (fileID, progress) => {
-      if (!this.getFile(fileID)) {
-        this.log(`Not setting progress for a file that has been removed: ${fileID}`)
+    this.on('preprocess-progress', (file, progress) => {
+      if (!this.getFile(file.id)) {
+        this.log(`Not setting progress for a file that has been removed: ${file.id}`)
         return
       }
-      this.setFileState(fileID, {
-        progress: Object.assign({}, this.getState().files[fileID].progress, {
+      this.setFileState(file.id, {
+        progress: Object.assign({}, this.getFile(file.id).progress, {
           preprocess: progress
         })
       })
     })
 
-    this.on('preprocess-complete', (fileID) => {
-      if (!this.getFile(fileID)) {
-        this.log(`Not setting progress for a file that has been removed: ${fileID}`)
+    this.on('preprocess-complete', (file) => {
+      if (!this.getFile(file.id)) {
+        this.log(`Not setting progress for a file that has been removed: ${file.id}`)
         return
       }
       const files = Object.assign({}, this.getState().files)
-      files[fileID] = Object.assign({}, files[fileID], {
-        progress: Object.assign({}, files[fileID].progress)
+      files[file.id] = Object.assign({}, files[file.id], {
+        progress: Object.assign({}, files[file.id].progress)
       })
-      delete files[fileID].progress.preprocess
+      delete files[file.id].progress.preprocess
 
       this.setState({ files: files })
     })
 
-    this.on('postprocess-progress', (fileID, progress) => {
-      if (!this.getFile(fileID)) {
-        this.log(`Not setting progress for a file that has been removed: ${fileID}`)
+    this.on('postprocess-progress', (file, progress) => {
+      if (!this.getFile(file.id)) {
+        this.log(`Not setting progress for a file that has been removed: ${file.id}`)
         return
       }
-      this.setFileState(fileID, {
-        progress: Object.assign({}, this.getState().files[fileID].progress, {
+      this.setFileState(file.id, {
+        progress: Object.assign({}, this.getState().files[file.id].progress, {
           postprocess: progress
         })
       })
     })
 
-    this.on('postprocess-complete', (fileID) => {
-      if (!this.getFile(fileID)) {
-        this.log(`Not setting progress for a file that has been removed: ${fileID}`)
+    this.on('postprocess-complete', (file) => {
+      if (!this.getFile(file.id)) {
+        this.log(`Not setting progress for a file that has been removed: ${file.id}`)
         return
       }
       const files = Object.assign({}, this.getState().files)
-      files[fileID] = Object.assign({}, files[fileID], {
-        progress: Object.assign({}, files[fileID].progress)
+      files[file.id] = Object.assign({}, files[file.id], {
+        progress: Object.assign({}, files[file.id].progress)
       })
-      delete files[fileID].progress.postprocess
+      delete files[file.id].progress.postprocess
       // TODO should we set some kind of `fullyComplete` property on the file object
       // so it's easier to see that the file is upload…fully complete…rather than
       // what we have to do now (`uploadComplete && !postprocess`)
@@ -1099,8 +1087,6 @@ class Uppy {
 
       const result = currentUploads[uploadID].result
       this.emit('complete', result)
-      // Compatibility with pre-0.21
-      this.emit('success', fileIDs)
 
       this._removeUpload(uploadID)
 

+ 21 - 18
src/core/Core.test.js

@@ -355,7 +355,8 @@ describe('src/Core', () => {
         })
         .then(() => {
           const fileId = Object.keys(core.state.files)[0]
-          core.emit('preprocess-progress', fileId, {
+          const file = core.getFile(fileId)
+          core.emit('preprocess-progress', file, {
             mode: 'determinate',
             message: 'something',
             value: 0
@@ -382,13 +383,14 @@ describe('src/Core', () => {
           data: utils.dataURItoFile(sampleImageDataURI, {})
         })
         .then(() => {
-          const fileId = Object.keys(core.state.files)[0]
-          core.emit('preprocess-complete', fileId, {
+          const fileID = Object.keys(core.state.files)[0]
+          const file = core.state.files[fileID]
+          core.emit('preprocess-complete', file, {
             mode: 'determinate',
             message: 'something',
             value: 0
           })
-          expect(core.state.files[fileId].progress).toEqual({
+          expect(core.state.files[fileID].progress).toEqual({
             percentage: 0,
             bytesUploaded: 0,
             bytesTotal: 17175,
@@ -465,7 +467,8 @@ describe('src/Core', () => {
         })
         .then(() => {
           const fileId = Object.keys(core.state.files)[0]
-          core.emit('postprocess-progress', fileId, {
+          const file = core.getFile(fileId)
+          core.emit('postprocess-progress', file, {
             mode: 'determinate',
             message: 'something',
             value: 0
@@ -493,7 +496,8 @@ describe('src/Core', () => {
         })
         .then(() => {
           const fileId = Object.keys(core.state.files)[0]
-          core.emit('postprocess-complete', fileId, {
+          const file = core.state.files[fileId]
+          core.emit('postprocess-complete', file, {
             mode: 'determinate',
             message: 'something',
             value: 0
@@ -804,8 +808,8 @@ describe('src/Core', () => {
         })
         .then(() => {
           const fileId = Object.keys(core.state.files)[0]
-          core._calculateProgress({
-            id: fileId,
+          const file = core.getFile(fileId)
+          core._calculateProgress(file, {
             bytesUploaded: 12345,
             bytesTotal: 17175
           })
@@ -817,8 +821,7 @@ describe('src/Core', () => {
             uploadStarted: false
           })
 
-          core._calculateProgress({
-            id: fileId,
+          core._calculateProgress(file, {
             bytesUploaded: 17175,
             bytesTotal: 17175
           })
@@ -852,17 +855,17 @@ describe('src/Core', () => {
         }).then(() => {
           const fileId1 = Object.keys(core.state.files)[0]
           const fileId2 = Object.keys(core.state.files)[1]
+          const file1 = core.state.files[fileId1]
+          const file2 = core.state.files[fileId2]
           core.state.files[fileId1].progress.uploadStarted = new Date()
           core.state.files[fileId2].progress.uploadStarted = new Date()
 
-          core._calculateProgress({
-            id: fileId1,
+          core._calculateProgress(file1, {
             bytesUploaded: 12345,
             bytesTotal: 17175
           })
 
-          core._calculateProgress({
-            id: fileId2,
+          core._calculateProgress(file2, {
             bytesUploaded: 10201,
             bytesTotal: 17175
           })
@@ -895,17 +898,17 @@ describe('src/Core', () => {
         }).then(() => {
           const fileId1 = Object.keys(core.state.files)[0]
           const fileId2 = Object.keys(core.state.files)[1]
+          const file1 = core.state.files[fileId1]
+          const file2 = core.state.files[fileId2]
           core.state.files[fileId1].progress.uploadStarted = new Date()
           core.state.files[fileId2].progress.uploadStarted = new Date()
 
-          core._calculateProgress({
-            id: fileId1,
+          core._calculateProgress(file1, {
             bytesUploaded: 12345,
             bytesTotal: 17175
           })
 
-          core._calculateProgress({
-            id: fileId2,
+          core._calculateProgress(file2, {
             bytesUploaded: 10201,
             bytesTotal: 17175
           })

+ 1 - 2
src/core/Utils.js

@@ -495,9 +495,8 @@ function _emitSocketProgress (uploader, progressData, file) {
   const { progress, bytesUploaded, bytesTotal } = progressData
   if (progress) {
     uploader.uppy.log(`Upload progress: ${progress}`)
-    uploader.uppy.emit('upload-progress', {
+    uploader.uppy.emit('upload-progress', file, {
       uploader,
-      id: file.id,
       bytesUploaded: bytesUploaded,
       bytesTotal: bytesTotal
     })

+ 6 - 4
src/plugins/AwsS3/index.js

@@ -59,7 +59,8 @@ module.exports = class AwsS3 extends Plugin {
 
   prepareUpload (fileIDs) {
     fileIDs.forEach((id) => {
-      this.uppy.emit('preprocess-progress', id, {
+      const file = this.uppy.getFile(id)
+      this.uppy.emit('preprocess-progress', file, {
         mode: 'determinate',
         message: this.i18n('preparingUpload'),
         value: 0
@@ -74,14 +75,14 @@ module.exports = class AwsS3 extends Plugin {
         const paramsPromise = Promise.resolve()
           .then(() => getUploadParameters(file))
         return paramsPromise.then((params) => {
-          this.uppy.emit('preprocess-progress', file.id, {
+          this.uppy.emit('preprocess-progress', file, {
             mode: 'determinate',
             message: this.i18n('preparingUpload'),
             value: 1
           })
           return params
         }).catch((error) => {
-          this.uppy.emit('upload-error', file.id, error)
+          this.uppy.emit('upload-error', file, error)
         })
       })
     ).then((responses) => {
@@ -122,7 +123,8 @@ module.exports = class AwsS3 extends Plugin {
       })
 
       fileIDs.forEach((id) => {
-        this.uppy.emit('preprocess-complete', id)
+        const file = this.uppy.getFile(id)
+        this.uppy.emit('preprocess-complete', file)
       })
     })
   }

+ 3 - 3
src/plugins/GoldenRetriever/index.js

@@ -235,14 +235,14 @@ module.exports = class GoldenRetriever extends Plugin {
       })
     })
 
-    this.uppy.on('file-removed', (fileID) => {
+    this.uppy.on('file-removed', (file) => {
       if (this.ServiceWorkerStore) {
-        this.ServiceWorkerStore.delete(fileID).catch((err) => {
+        this.ServiceWorkerStore.delete(file.id).catch((err) => {
           this.uppy.log('[GoldenRetriever] Failed to remove file', 'warning')
           this.uppy.log(err)
         })
       }
-      this.IndexedDBStore.delete(fileID).catch((err) => {
+      this.IndexedDBStore.delete(file.id).catch((err) => {
         this.uppy.log('[GoldenRetriever] Failed to remove file', 'warning')
         this.uppy.log(err)
       })

+ 2 - 2
src/plugins/Provider/view/index.js

@@ -394,7 +394,7 @@ module.exports = class View {
    * main screen, and will do nothing when you uncheck folder directly, since
    * it's already been done in removeFolder method.
    */
-  updateFolderState (fileId) {
+  updateFolderState (file) {
     let state = this.plugin.getPluginState()
     let folders = state.selectedFolders || {}
     for (let folderId in folders) {
@@ -402,7 +402,7 @@ module.exports = class View {
       if (folder.loading) {
         continue
       }
-      let i = folder.files.indexOf(fileId)
+      let i = folder.files.indexOf(file.id)
       if (i > -1) {
         folder.files.splice(i, 1)
       }

+ 13 - 24
src/plugins/Transloadit/index.js

@@ -56,7 +56,6 @@ module.exports = class Transloadit extends Plugin {
     this.onFileUploadURLAvailable = this.onFileUploadURLAvailable.bind(this)
     this.onRestored = this.onRestored.bind(this)
     this.getPersistentData = this.getPersistentData.bind(this)
-    this.removeFileFromPluginState = this.removeFileFromPluginState.bind(this)
 
     if (this.opts.params) {
       this.validateParams(this.opts.params)
@@ -257,8 +256,7 @@ module.exports = class Transloadit extends Plugin {
    * Used when `importFromUploadURLs` is enabled: adds files to the assembly
    * once they have been fully uploaded.
    */
-  onFileUploadURLAvailable (fileID) {
-    const file = this.uppy.getFile(fileID)
+  onFileUploadURLAvailable (file) {
     if (!file || !file.transloadit || !file.transloadit.assembly) {
       return
     }
@@ -582,7 +580,8 @@ module.exports = class Transloadit extends Plugin {
     fileIDs = fileIDs.filter((file) => !file.error)
 
     fileIDs.forEach((fileID) => {
-      this.uppy.emit('preprocess-progress', fileID, {
+      const file = this.uppy.getFile(fileID)
+      this.uppy.emit('preprocess-progress', file, {
         mode: 'indeterminate',
         message: this.i18n('creatingAssembly')
       })
@@ -595,14 +594,16 @@ module.exports = class Transloadit extends Plugin {
         }
       }).then(() => {
         fileIDs.forEach((fileID) => {
-          this.uppy.emit('preprocess-complete', fileID)
+          const file = this.uppy.getFile(fileID)
+          this.uppy.emit('preprocess-complete', file)
         })
       }).catch((err) => {
         // Clear preprocessing state when the assembly could not be created,
         // otherwise the UI gets confused about the lingering progress keys
         fileIDs.forEach((fileID) => {
-          this.uppy.emit('preprocess-complete', fileID)
-          this.uppy.emit('upload-error', fileID, err)
+          const file = this.uppy.getFile(fileID)
+          this.uppy.emit('preprocess-complete', file)
+          this.uppy.emit('upload-error', file, err)
         })
         throw err
       })
@@ -678,7 +679,8 @@ module.exports = class Transloadit extends Plugin {
 
     return new Promise((resolve, reject) => {
       fileIDs.forEach((fileID) => {
-        this.uppy.emit('postprocess-progress', fileID, {
+        const file = this.uppy.getFile(fileID)
+        this.uppy.emit('postprocess-progress', file, {
           mode: 'indeterminate',
           message: this.i18n('encoding')
         })
@@ -698,7 +700,7 @@ module.exports = class Transloadit extends Plugin {
 
         const files = this.getAssemblyFiles(assembly.assembly_id)
         files.forEach((file) => {
-          this.uppy.emit('postprocess-complete', file.id)
+          this.uppy.emit('postprocess-complete', file)
         })
 
         checkAllComplete()
@@ -717,9 +719,9 @@ module.exports = class Transloadit extends Plugin {
         const files = this.getAssemblyFiles(assembly.assembly_id)
         files.forEach((file) => {
           // TODO Maybe make a postprocess-error event here?
-          this.uppy.emit('upload-error', file.id, error)
+          this.uppy.emit('upload-error', file, error)
 
-          this.uppy.emit('postprocess-complete', file.id)
+          this.uppy.emit('postprocess-complete', file)
         })
 
         checkAllComplete()
@@ -769,15 +771,6 @@ module.exports = class Transloadit extends Plugin {
     })
   }
 
-  removeFileFromPluginState (fileID) {
-    const updatedFiles = this.getPluginState().files
-    delete updatedFiles[fileID]
-
-    this.setPluginState({
-      files: updatedFiles
-    })
-  }
-
   install () {
     this.uppy.addPreProcessor(this.prepareUpload)
     this.uppy.addPostProcessor(this.afterUpload)
@@ -798,8 +791,6 @@ module.exports = class Transloadit extends Plugin {
     this.uppy.on('restore:get-data', this.getPersistentData)
     this.uppy.on('restored', this.onRestored)
 
-    // this.uppy.on('file-removed', this.removeFileFromPluginState)
-
     this.setPluginState({
       // Contains assembly status objects, indexed by their ID.
       assemblies: {},
@@ -816,8 +807,6 @@ module.exports = class Transloadit extends Plugin {
     this.uppy.removePreProcessor(this.prepareUpload)
     this.uppy.removePostProcessor(this.afterUpload)
 
-    // this.uppy.off('file-removed', this.removeFileFromPluginState)
-
     if (this.opts.importFromUploadURLs) {
       this.uppy.off('upload-success', this.onFileUploadURLAvailable)
     }

+ 5 - 6
src/plugins/Tus.js

@@ -139,9 +139,8 @@ module.exports = class Tus extends Plugin {
 
       optsTus.onProgress = (bytesUploaded, bytesTotal) => {
         this.onReceiveUploadUrl(file, upload.url)
-        this.uppy.emit('upload-progress', {
+        this.uppy.emit('upload-progress', file, {
           uploader: this,
-          id: file.id,
           bytesUploaded: bytesUploaded,
           bytesTotal: bytesTotal
         })
@@ -195,7 +194,7 @@ module.exports = class Tus extends Plugin {
         upload.start()
       }
       if (!file.isRestored) {
-        this.uppy.emit('upload-started', file.id, upload)
+        this.uppy.emit('upload-started', file, upload)
       }
     })
   }
@@ -218,7 +217,7 @@ module.exports = class Tus extends Plugin {
           .catch(reject)
       }
 
-      this.uppy.emit('upload-started', file.id)
+      this.uppy.emit('upload-started', file)
 
       fetch(file.remote.url, {
         method: 'post',
@@ -343,8 +342,8 @@ module.exports = class Tus extends Plugin {
   }
 
   onFileRemove (fileID, cb) {
-    this.uploaderEvents[fileID].on('file-removed', (targetFileID) => {
-      if (fileID === targetFileID) cb(targetFileID)
+    this.uploaderEvents[fileID].on('file-removed', (file) => {
+      if (fileID === file.id) cb(file.id)
     })
   }
 

+ 14 - 8
src/plugins/XHRUpload.js

@@ -63,7 +63,7 @@ module.exports = class XHRUpload extends Plugin {
         try {
           response = JSON.parse(responseContent)
         } catch (err) {
-          this.uppy.log(err, 'error')
+          console.log(err)
         }
 
         return response
@@ -202,9 +202,8 @@ module.exports = class XHRUpload extends Plugin {
         timer.progress()
 
         if (ev.lengthComputable) {
-          this.uppy.emit('upload-progress', {
+          this.uppy.emit('upload-progress', file, {
             uploader: this,
-            id: file.id,
             bytesUploaded: ev.loaded,
             bytesTotal: ev.total
           })
@@ -267,8 +266,16 @@ module.exports = class XHRUpload extends Plugin {
 
       xhr.send(data)
 
+      this.uppy.on('file-removed', (removedFile) => {
+        if (removedFile.id === file.id) {
+          timer.done()
+          xhr.abort()
+        }
+      })
+
       this.uppy.on('upload-cancel', (fileID) => {
         if (fileID === file.id) {
+          timer.done()
           xhr.abort()
         }
       })
@@ -376,9 +383,8 @@ module.exports = class XHRUpload extends Plugin {
         if (!ev.lengthComputable) return
 
         files.forEach((file) => {
-          this.uppy.emit('upload-progress', {
+          this.uppy.emit('upload-progress', file, {
             uploader: this,
-            id: file.id,
             bytesUploaded: ev.loaded,
             bytesTotal: ev.total
           })
@@ -423,7 +429,7 @@ module.exports = class XHRUpload extends Plugin {
       xhr.send(formData)
 
       files.forEach((file) => {
-        this.uppy.emit('upload-started', file.id)
+        this.uppy.emit('upload-started', file)
       })
     })
   }
@@ -438,10 +444,10 @@ module.exports = class XHRUpload extends Plugin {
       } else if (file.isRemote) {
         // We emit upload-started here, so that it's also emitted for files
         // that have to wait due to the `limit` option.
-        this.uppy.emit('upload-started', file.id)
+        this.uppy.emit('upload-started', file)
         return this.uploadRemote.bind(this, file, current, total)
       } else {
-        this.uppy.emit('upload-started', file.id)
+        this.uppy.emit('upload-started', file)
         return this.upload.bind(this, file, current, total)
       }
     })

+ 43 - 12
website/src/docs/uppy.md

@@ -350,19 +350,14 @@ uppy.on('upload', () => {
 
 ### `upload-progress`
 
-Fired each time file upload progress is available, `data` object looks like this:
+Fired each time file upload progress is available:
 
-```javascript
-data = {
-  id: myimg12321323,
-  bytesUploaded: 2323254,
-  bytesTotal
-}
-```
 
 ```javascript
-uppy.on('upload-progress', (data) => {
-  console.log(data.id, data.bytesUploaded, data.bytesTotal)
+uppy.on('upload-progress', (file, progress) => {
+  // file: { id, name, type, ... }
+  // progress: { uploader, bytesUploaded, bytesTotal }
+  console.log(file.id, progress.bytesUploaded, progress.bytesTotal)
 })
 ```
 
@@ -371,8 +366,8 @@ uppy.on('upload-progress', (data) => {
 Fired each time a single upload is complete.
 
 ``` javascript
-uppy.on('upload-success', (fileId, resp, uploadURL) => {
-  console.log(uploadURL)
+uppy.on('upload-success', (file, resp, uploadURL) => {
+  console.log(file.name, uploadURL)
   var img = new Image()
   img.width = 300
   img.alt = fileId
@@ -393,3 +388,39 @@ uppy.on('complete', (result) => {
   console.log('failed files:', result.failed)
 })
 ```
+
+### `error`
+
+Fired when Uppy fails to upload/encode the whole upload. That error is then set to `uppy.state.error`.
+
+### `upload-error`
+
+Fired when an error occures with a specific file:
+
+``` javascript
+uppy.on('upload-error', (file, error) => {
+  console.log('error with file:', file.id)
+  console.log('error message:', error)
+})
+```
+
+### `info-visible`
+
+Fired when “info” message should be visible in the UI. By default `Informer` plugin is displaying these messages (enabled by default in `Dashboard` plugin). You can use this event to show messages in your custom UI:
+
+``` javascript
+uppy.on('info-visible', () => {
+  const info = uppy.getState().info
+  // info: {
+  //  isHidden: false,
+  //  type: 'error',
+  //  message: 'Failed to upload',
+  //  details: 'Error description'
+  // }
+  alert(`${info.message} ${info.details}`)
+})
+```
+
+### `info-hidden`
+
+Fired when “info” message should be hidden in the UI. See [`info-visible`](#info-visible).

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

@@ -81,9 +81,9 @@ uppy.getFile(fileID).response
 // { status: HTTP status code,
 //   body: extracted response data }
 
-uppy.on('upload-success', (fileID, body) => {
+uppy.on('upload-success', (file, body) => {
   // do something with extracted response data
-  // (`body` is equivalent to `uppy.getFile(fileID).response.body`)
+  // (`body` is equivalent to `file.response.body` or `uppy.getFile(fileID).response.body`)
 })
 ```