Browse Source

@uppy/transloadit: refactor to use private properties (#3019)

Antoine du Hamel 3 years ago
parent
commit
9a46b0d2da

+ 5 - 1
package-lock.json

@@ -76243,6 +76243,9 @@
         "component-emitter": "^1.2.1",
         "socket.io-client": "~2.2.0"
       },
+      "devDependencies": {
+        "whatwg-fetch": "^3.6.2"
+      },
       "peerDependencies": {
         "@uppy/core": "^1.0.0"
       }
@@ -89983,7 +89986,8 @@
         "@uppy/tus": "file:../tus",
         "@uppy/utils": "file:../utils",
         "component-emitter": "^1.2.1",
-        "socket.io-client": "~2.2.0"
+        "socket.io-client": "~2.2.0",
+        "whatwg-fetch": "^3.6.2"
       }
     },
     "@uppy/tus": {

+ 3 - 0
packages/@uppy/transloadit/package.json

@@ -36,5 +36,8 @@
   },
   "peerDependencies": {
     "@uppy/core": "^1.0.0"
+  },
+  "devDependencies": {
+    "whatwg-fetch": "^3.6.2"
   }
 }

+ 24 - 26
packages/@uppy/transloadit/src/Assembly.js

@@ -1,4 +1,3 @@
-const io = requireSocketIo
 const Emitter = require('component-emitter')
 const has = require('@uppy/utils/lib/hasProperty')
 const NetworkError = require('@uppy/utils/lib/NetworkError')
@@ -12,10 +11,8 @@ const parseUrl = require('./parseUrl')
 // at all…)
 let socketIo
 function requireSocketIo () {
-  if (!socketIo) {
-    socketIo = require('socket.io-client')
-  }
-  return socketIo
+  // eslint-disable-next-line global-require
+  return socketIo ??= require('socket.io-client')
 }
 
 const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING'
@@ -57,18 +54,18 @@ class TransloaditAssembly extends Emitter {
   }
 
   connect () {
-    this._connectSocket()
-    this._beginPolling()
+    this.#connectSocket()
+    this.#beginPolling()
   }
 
-  _onFinished () {
+  #onFinished () {
     this.emit('finished')
     this.close()
   }
 
-  _connectSocket () {
+  #connectSocket () {
     const parsed = parseUrl(this.status.websocket_url)
-    const socket = io().connect(parsed.origin, {
+    const socket = requireSocketIo().connect(parsed.origin, {
       transports: ['websocket'],
       path: parsed.pathname,
     })
@@ -82,7 +79,7 @@ class TransloaditAssembly extends Emitter {
     })
 
     socket.on('connect_failed', () => {
-      this._onError(new NetworkError('Transloadit Socket.io connection error'))
+      this.#onError(new NetworkError('Transloadit Socket.io connection error'))
       this.socket = null
     })
 
@@ -92,7 +89,7 @@ class TransloaditAssembly extends Emitter {
     })
 
     socket.on('assembly_finished', () => {
-      this._onFinished()
+      this.#onFinished()
     })
 
     socket.on('assembly_upload_finished', (file) => {
@@ -106,7 +103,7 @@ class TransloaditAssembly extends Emitter {
 
     socket.on('assembly_upload_meta_data_extracted', () => {
       this.emit('metadata')
-      this._fetchStatus({ diff: false })
+      this.#fetchStatus({ diff: false })
     })
 
     socket.on('assembly_result_finished', (stepName, result) => {
@@ -118,15 +115,15 @@ class TransloaditAssembly extends Emitter {
     })
 
     socket.on('assembly_error', (err) => {
-      this._onError(err)
+      this.#onError(err)
       // Refetch for updated status code
-      this._fetchStatus({ diff: false })
+      this.#fetchStatus({ diff: false })
     })
 
     this.socket = socket
   }
 
-  _onError (err) {
+  #onError (err) {
     this.emit('error', Object.assign(new Error(err.message), err))
   }
 
@@ -136,10 +133,10 @@ class TransloaditAssembly extends Emitter {
    * If the socket connection fails or takes a long time, we won't miss any
    * events.
    */
-  _beginPolling () {
+  #beginPolling () {
     this.pollInterval = setInterval(() => {
       if (!this.socket || !this.socket.connected) {
-        this._fetchStatus()
+        this.#fetchStatus()
       }
     }, 2000)
   }
@@ -150,7 +147,7 @@ class TransloaditAssembly extends Emitter {
    * Pass `diff: false` to avoid emitting diff events, instead only emitting
    * 'status'.
    */
-  _fetchStatus ({ diff = true } = {}) {
+  #fetchStatus ({ diff = true } = {}) {
     return fetchWithNetworkError(this.status.assembly_ssl_url)
       .then((response) => response.json())
       .then((status) => {
@@ -164,11 +161,11 @@ class TransloaditAssembly extends Emitter {
           this.status = status
         }
       })
-      .catch((err) => this._onError(err))
+      .catch((err) => this.#onError(err))
   }
 
   update () {
-    return this._fetchStatus({ diff: true })
+    return this.#fetchStatus({ diff: true })
   }
 
   /**
@@ -178,7 +175,7 @@ class TransloaditAssembly extends Emitter {
    * @param {object} next The new assembly status object.
    */
   updateStatus (next) {
-    this._diffStatus(this.status, next)
+    this.#diffStatus(this.status, next)
     this.status = next
   }
 
@@ -189,12 +186,12 @@ class TransloaditAssembly extends Emitter {
    * @param {object} prev The previous assembly status.
    * @param {object} next The new assembly status.
    */
-  _diffStatus (prev, next) {
+  #diffStatus (prev, next) {
     const prevStatus = prev.ok
     const nextStatus = next.ok
 
     if (next.error && !prev.error) {
-      return this._onError(next)
+      return this.#onError(next)
     }
 
     // Desired emit order:
@@ -220,9 +217,8 @@ class TransloaditAssembly extends Emitter {
     // Find new uploaded files.
     Object.keys(next.uploads)
       .filter((upload) => !has(prev.uploads, upload))
-      .map((upload) => next.uploads[upload])
       .forEach((upload) => {
-        this.emit('upload', upload)
+        this.emit('upload', next.uploads[upload])
       })
 
     if (nowExecuting) {
@@ -245,6 +241,8 @@ class TransloaditAssembly extends Emitter {
         && !isStatus(prevStatus, ASSEMBLY_COMPLETED)) {
       this.emit('finished')
     }
+
+    return undefined
   }
 
   /**

+ 61 - 74
packages/@uppy/transloadit/src/AssemblyOptions.js

@@ -2,18 +2,19 @@
  * Check that Assembly parameters are present and include all required fields.
  */
 function validateParams (params) {
-  if (!params) {
+  if (params == null) {
     throw new Error('Transloadit: The `params` option is required.')
   }
 
   if (typeof params === 'string') {
     try {
+      // eslint-disable-next-line no-param-reassign
       params = JSON.parse(params)
     } catch (err) {
       // Tell the user that this is not an Uppy bug!
-      err.message = `Transloadit: The \`params\` option is a malformed JSON string: ${
-        err.message}`
-      throw err
+      const error = new Error('Transloadit: The `params` option is a malformed JSON string.')
+      err.cause = err
+      throw error
     }
   }
 
@@ -23,6 +24,43 @@ function validateParams (params) {
   }
 }
 
+/**
+ * Normalize Uppy-specific Assembly option features to a Transloadit-
+ * compatible object.
+ */
+function normalizeAssemblyOptions (file, assemblyOptions) {
+  // eslint-disable-next-line no-param-reassign
+  assemblyOptions.fields = Array.isArray(assemblyOptions.fields)
+    ? Object.fromEntries(assemblyOptions.fields.map((fieldName) => [fieldName, file.meta[fieldName]]))
+    : {}
+
+  return assemblyOptions
+}
+
+/**
+ * Combine Assemblies with the same options into a single Assembly for all the
+ * relevant files.
+ */
+function dedupe (list) {
+  const dedupeMap = Object.create(null)
+  for (const { fileIDs, options } of list) {
+    const id = JSON.stringify(options)
+    if (id in dedupeMap) {
+      dedupeMap[id].fileIDArrays.push(fileIDs)
+    } else {
+      dedupeMap[id] = {
+        options,
+        fileIDArrays: [fileIDs],
+      }
+    }
+  }
+
+  return Object.values(dedupeMap).map(({ options, fileIDArrays }) => ({
+    options,
+    fileIDs: fileIDArrays.flat(1),
+  }))
+}
+
 /**
  * Turn Transloadit plugin options and a list of files into a list of Assembly
  * options.
@@ -33,68 +71,21 @@ class AssemblyOptions {
     this.opts = opts
   }
 
-  /**
-   * Normalize Uppy-specific Assembly option features to a Transloadit-
-   * compatible object.
-   */
-  _normalizeAssemblyOptions (file, assemblyOptions) {
-    if (Array.isArray(assemblyOptions.fields)) {
-      const fieldNames = assemblyOptions.fields
-      assemblyOptions.fields = {}
-      fieldNames.forEach((fieldName) => {
-        assemblyOptions.fields[fieldName] = file.meta[fieldName]
-      })
-    }
-
-    if (!assemblyOptions.fields) {
-      assemblyOptions.fields = {}
-    }
-
-    return assemblyOptions
-  }
-
   /**
    * Get Assembly options for a file.
    */
-  _getAssemblyOptions (file) {
+  async #getAssemblyOptions (file) {
     const options = this.opts
 
-    return Promise.resolve()
-      .then(() => {
-        return options.getAssemblyOptions(file, options)
-      })
-      .then((assemblyOptions) => {
-        return this._normalizeAssemblyOptions(file, assemblyOptions)
-      })
-      .then((assemblyOptions) => {
-        validateParams(assemblyOptions.params)
-
-        return {
-          fileIDs: [file.id],
-          options: assemblyOptions,
-        }
-      })
-  }
+    const assemblyOptions = await options.getAssemblyOptions(file, options)
 
-  /**
-   * Combine Assemblies with the same options into a single Assembly for all the
-   * relevant files.
-   */
-  _dedupe (list) {
-    const dedupeMap = Object.create(null)
-    list.forEach(({ fileIDs, options }) => {
-      const id = JSON.stringify(options)
-      if (dedupeMap[id]) {
-        dedupeMap[id].fileIDs.push(...fileIDs)
-      } else {
-        dedupeMap[id] = {
-          options,
-          fileIDs: [...fileIDs],
-        }
-      }
-    })
+    validateParams(assemblyOptions.params)
+    normalizeAssemblyOptions(file, assemblyOptions)
 
-    return Object.keys(dedupeMap).map((id) => dedupeMap[id])
+    return {
+      fileIDs: [file.id],
+      options: assemblyOptions,
+    }
   }
 
   /**
@@ -103,33 +94,29 @@ class AssemblyOptions {
    *  - fileIDs - an array of file IDs to add to this Assembly
    *  - options - Assembly options
    */
-  build () {
+  async build () {
     const options = this.opts
 
     if (this.files.length > 0) {
       return Promise.all(
-        this.files.map((file) => this._getAssemblyOptions(file))
-      ).then((list) => {
-        return this._dedupe(list)
-      })
+        this.files.map((file) => this.#getAssemblyOptions(file))
+      ).then(dedupe)
     }
 
     if (options.alwaysRunAssembly) {
       // No files, just generate one Assembly
-      return Promise.resolve(
-        options.getAssemblyOptions(null, options)
-      ).then((assemblyOptions) => {
-        validateParams(assemblyOptions.params)
-        return [{
-          fileIDs: this.files.map((file) => file.id),
-          options: assemblyOptions,
-        }]
-      })
+      const assemblyOptions = await options.getAssemblyOptions(null, options)
+
+      validateParams(assemblyOptions.params)
+      return [{
+        fileIDs: this.files.map((file) => file.id),
+        options: assemblyOptions,
+      }]
     }
 
     // If there are no files and we do not `alwaysRunAssembly`,
     // don't do anything.
-    return Promise.resolve([])
+    return []
   }
 }
 

+ 48 - 43
packages/@uppy/transloadit/src/AssemblyWatcher.js

@@ -9,68 +9,73 @@ const Emitter = require('component-emitter')
  * completed (or failed).
  */
 class TransloaditAssemblyWatcher extends Emitter {
+  #assemblyIDs
+
+  #reject
+
+  #remaining
+
+  #resolve
+
+  #uppy
+
   constructor (uppy, assemblyIDs) {
     super()
 
-    this._uppy = uppy
-    this._assemblyIDs = assemblyIDs
-    this._remaining = assemblyIDs.length
+    this.#uppy = uppy
+    this.#assemblyIDs = assemblyIDs
+    this.#remaining = assemblyIDs.length
 
     this.promise = new Promise((resolve, reject) => {
-      this._resolve = resolve
-      this._reject = reject
+      this.#resolve = resolve
+      this.#reject = reject
     })
 
-    this._onAssemblyComplete = this._onAssemblyComplete.bind(this)
-    this._onAssemblyCancel = this._onAssemblyCancel.bind(this)
-    this._onAssemblyError = this._onAssemblyError.bind(this)
-    this._onImportError = this._onImportError.bind(this)
-
-    this._addListeners()
+    this.#addListeners()
   }
 
   /**
    * Are we watching this assembly ID?
    */
-  _watching (id) {
-    return this._assemblyIDs.indexOf(id) !== -1
+  #watching (id) {
+    return this.#assemblyIDs.indexOf(id) !== -1
   }
 
-  _onAssemblyComplete (assembly) {
-    if (!this._watching(assembly.assembly_id)) {
+  #onAssemblyComplete =(assembly) => {
+    if (!this.#watching(assembly.assembly_id)) {
       return
     }
 
-    this._uppy.log(`[Transloadit] AssemblyWatcher: Got Assembly finish ${assembly.assembly_id}`)
+    this.#uppy.log(`[Transloadit] AssemblyWatcher: Got Assembly finish ${assembly.assembly_id}`)
 
     this.emit('assembly-complete', assembly.assembly_id)
 
-    this._checkAllComplete()
+    this.#checkAllComplete()
   }
 
-  _onAssemblyCancel (assembly) {
-    if (!this._watching(assembly.assembly_id)) {
+  #onAssemblyCancel =(assembly) => {
+    if (!this.#watching(assembly.assembly_id)) {
       return
     }
 
-    this._checkAllComplete()
+    this.#checkAllComplete()
   }
 
-  _onAssemblyError (assembly, error) {
-    if (!this._watching(assembly.assembly_id)) {
+  #onAssemblyError =(assembly, error) => {
+    if (!this.#watching(assembly.assembly_id)) {
       return
     }
 
-    this._uppy.log(`[Transloadit] AssemblyWatcher: Got Assembly error ${assembly.assembly_id}`)
-    this._uppy.log(error)
+    this.#uppy.log(`[Transloadit] AssemblyWatcher: Got Assembly error ${assembly.assembly_id}`)
+    this.#uppy.log(error)
 
     this.emit('assembly-error', assembly.assembly_id, error)
 
-    this._checkAllComplete()
+    this.#checkAllComplete()
   }
 
-  _onImportError (assembly, fileID, error) {
-    if (!this._watching(assembly.assembly_id)) {
+  #onImportError =(assembly, fileID, error) => {
+    if (!this.#watching(assembly.assembly_id)) {
       return
     }
 
@@ -79,30 +84,30 @@ class TransloaditAssemblyWatcher extends Emitter {
     // I think failing the upload is better than silently ignoring.
     // In the future we should maybe have a way to resolve uploads with some failures,
     // like returning an object with `{ successful, failed }` uploads.
-    this._onAssemblyError(assembly, error)
+    this.#onAssemblyError(assembly, error)
   }
 
-  _checkAllComplete () {
-    this._remaining -= 1
-    if (this._remaining === 0) {
+  #checkAllComplete () {
+    this.#remaining -= 1
+    if (this.#remaining === 0) {
       // We're done, these listeners can be removed
-      this._removeListeners()
-      this._resolve()
+      this.#removeListeners()
+      this.#resolve()
     }
   }
 
-  _removeListeners () {
-    this._uppy.off('transloadit:complete', this._onAssemblyComplete)
-    this._uppy.off('transloadit:assembly-cancel', this._onAssemblyCancel)
-    this._uppy.off('transloadit:assembly-error', this._onAssemblyError)
-    this._uppy.off('transloadit:import-error', this._onImportError)
+  #removeListeners () {
+    this.#uppy.off('transloadit:complete', this.#onAssemblyComplete)
+    this.#uppy.off('transloadit:assembly-cancel', this.#onAssemblyCancel)
+    this.#uppy.off('transloadit:assembly-error', this.#onAssemblyError)
+    this.#uppy.off('transloadit:import-error', this.#onImportError)
   }
 
-  _addListeners () {
-    this._uppy.on('transloadit:complete', this._onAssemblyComplete)
-    this._uppy.on('transloadit:assembly-cancel', this._onAssemblyCancel)
-    this._uppy.on('transloadit:assembly-error', this._onAssemblyError)
-    this._uppy.on('transloadit:import-error', this._onImportError)
+  #addListeners () {
+    this.#uppy.on('transloadit:complete', this.#onAssemblyComplete)
+    this.#uppy.on('transloadit:assembly-cancel', this.#onAssemblyCancel)
+    this.#uppy.on('transloadit:assembly-error', this.#onAssemblyError)
+    this.#uppy.on('transloadit:import-error', this.#onImportError)
   }
 }
 

+ 18 - 18
packages/@uppy/transloadit/src/Client.js

@@ -4,13 +4,13 @@ const fetchWithNetworkError = require('@uppy/utils/lib/fetchWithNetworkError')
  * A Barebones HTTP API client for Transloadit.
  */
 module.exports = class Client {
+  #headers = {}
+
   constructor (opts = {}) {
     this.opts = opts
 
-    this._reportError = this._reportError.bind(this)
-
-    this._headers = {
-      'Transloadit-Client': this.opts.client,
+    if (this.opts.client != null) {
+      this.#headers['Transloadit-Client'] = this.opts.client
     }
   }
 
@@ -45,7 +45,7 @@ module.exports = class Client {
     const url = new URL('/assemblies', `${this.opts.service}`).href
     return fetchWithNetworkError(url, {
       method: 'post',
-      headers: this._headers,
+      headers: this.#headers,
       body: data,
     })
       .then((response) => response.json()).then((assembly) => {
@@ -54,14 +54,14 @@ module.exports = class Client {
           error.details = assembly.message
           error.assembly = assembly
           if (assembly.assembly_id) {
-            error.details += ' ' + `Assembly ID: ${assembly.assembly_id}`
+            error.details += ` Assembly ID: ${assembly.assembly_id}`
           }
           throw error
         }
 
         return assembly
       })
-      .catch((err) => this._reportError(err, { url, type: 'API_ERROR' }))
+      .catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
   }
 
   /**
@@ -73,9 +73,9 @@ module.exports = class Client {
   reserveFile (assembly, file) {
     const size = encodeURIComponent(file.size)
     const url = `${assembly.assembly_ssl_url}/reserve_file?size=${size}`
-    return fetchWithNetworkError(url, { method: 'post', headers: this._headers })
+    return fetchWithNetworkError(url, { method: 'post', headers: this.#headers })
       .then((response) => response.json())
-      .catch((err) => this._reportError(err, { assembly, file, url, type: 'API_ERROR' }))
+      .catch((err) => this.#reportError(err, { assembly, file, url, type: 'API_ERROR' }))
   }
 
   /**
@@ -95,9 +95,9 @@ module.exports = class Client {
 
     const qs = `size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${uploadUrl}`
     const url = `${assembly.assembly_ssl_url}/add_file?${qs}`
-    return fetchWithNetworkError(url, { method: 'post', headers: this._headers })
+    return fetchWithNetworkError(url, { method: 'post', headers: this.#headers })
       .then((response) => response.json())
-      .catch((err) => this._reportError(err, { assembly, file, url, type: 'API_ERROR' }))
+      .catch((err) => this.#reportError(err, { assembly, file, url, type: 'API_ERROR' }))
   }
 
   /**
@@ -107,9 +107,9 @@ module.exports = class Client {
    */
   cancelAssembly (assembly) {
     const url = assembly.assembly_ssl_url
-    return fetchWithNetworkError(url, { method: 'delete', headers: this._headers })
+    return fetchWithNetworkError(url, { method: 'delete', headers: this.#headers })
       .then((response) => response.json())
-      .catch((err) => this._reportError(err, { url, type: 'API_ERROR' }))
+      .catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
   }
 
   /**
@@ -118,12 +118,12 @@ module.exports = class Client {
    * @param {string} url The status endpoint of the assembly.
    */
   getAssemblyStatus (url) {
-    return fetchWithNetworkError(url, { headers: this._headers })
+    return fetchWithNetworkError(url, { headers: this.#headers })
       .then((response) => response.json())
-      .catch((err) => this._reportError(err, { url, type: 'STATUS_ERROR' }))
+      .catch((err) => this.#reportError(err, { url, type: 'STATUS_ERROR' }))
   }
 
-  submitError (err, { endpoint, instance, assembly }) {
+  submitError (err, { endpoint, instance, assembly } = {}) {
     const message = err.details
       ? `${err.message} (${err.details})`
       : err.message
@@ -142,7 +142,7 @@ module.exports = class Client {
       .then((response) => response.json())
   }
 
-  _reportError (err, params) {
+  #reportError = (err, params) => {
     if (this.opts.errorReporting === false) {
       throw err
     }
@@ -158,7 +158,7 @@ module.exports = class Client {
       opts.endpoint = params.url
     }
 
-    this.submitError(err, opts).catch((_) => {
+    this.submitError(err, opts).catch(() => {
       // not much we can do then is there
     })
 

+ 80 - 90
packages/@uppy/transloadit/src/index.js

@@ -25,7 +25,7 @@ const TL_COMPANION = /https?:\/\/api2(?:-\w+)?\.transloadit\.com\/companion/
  * Upload files to Transloadit using Tus.
  */
 module.exports = class Transloadit extends BasePlugin {
-  static VERSION = require('../package.json').version
+  static VERSION = require('../package.json').version // eslint-disable-line global-require
 
   constructor (uppy, opts) {
     super(uppy, opts)
@@ -59,15 +59,6 @@ module.exports = class Transloadit extends BasePlugin {
 
     this.i18nInit()
 
-    this._prepareUpload = this._prepareUpload.bind(this)
-    this._afterUpload = this._afterUpload.bind(this)
-    this._onError = this._onError.bind(this)
-    this._onTusError = this._onTusError.bind(this)
-    this._onCancelAll = this._onCancelAll.bind(this)
-    this._onFileUploadURLAvailable = this._onFileUploadURLAvailable.bind(this)
-    this._onRestored = this._onRestored.bind(this)
-    this._getPersistentData = this._getPersistentData.bind(this)
-
     const hasCustomAssemblyOptions = this.opts.getAssemblyOptions !== defaultOptions.getAssemblyOptions
     if (this.opts.params) {
       AssemblyOptions.validateParams(this.opts.params)
@@ -79,14 +70,15 @@ module.exports = class Transloadit extends BasePlugin {
 
     this.client = new Client({
       service: this.opts.service,
-      client: this._getClientVersion(),
+      client: this.#getClientVersion(),
       errorReporting: this.opts.errorReporting,
     })
     // Contains Assembly instances for in-progress Assemblies.
     this.activeAssemblies = {}
     // Contains a mapping of uploadID to AssemblyWatcher
     this.assemblyWatchers = {}
-    // Contains a file IDs that have completed postprocessing before the upload they belong to has entered the postprocess stage.
+    // Contains a file IDs that have completed postprocessing before the upload
+    // they belong to has entered the postprocess stage.
     this.completedFiles = Object.create(null)
   }
 
@@ -102,7 +94,7 @@ module.exports = class Transloadit extends BasePlugin {
     this.setPluginState() // so that UI re-renders and we see the updated locale
   }
 
-  _getClientVersion () {
+  #getClientVersion () {
     const list = [
       `uppy-core:${this.uppy.constructor.VERSION}`,
       `uppy-transloadit:${this.constructor.VERSION}`,
@@ -143,7 +135,7 @@ module.exports = class Transloadit extends BasePlugin {
    * @param {object} file
    * @param {object} status
    */
-  _attachAssemblyMetadata (file, status) {
+  #attachAssemblyMetadata (file, status) {
     // Add the metadata parameters Transloadit needs.
     const meta = {
       ...file.meta,
@@ -193,7 +185,7 @@ module.exports = class Transloadit extends BasePlugin {
     return newFile
   }
 
-  _createAssembly (fileIDs, uploadID, options) {
+  #createAssembly (fileIDs, uploadID, options) {
     this.uppy.log('[Transloadit] Create Assembly')
 
     return this.client.createAssembly({
@@ -226,7 +218,7 @@ module.exports = class Transloadit extends BasePlugin {
       const { files } = this.uppy.getState()
       const updatedFiles = {}
       fileIDs.forEach((id) => {
-        updatedFiles[id] = this._attachAssemblyMetadata(this.uppy.getFile(id), status)
+        updatedFiles[id] = this.#attachAssemblyMetadata(this.uppy.getFile(id), status)
       })
       this.uppy.setState({
         files: {
@@ -240,13 +232,14 @@ module.exports = class Transloadit extends BasePlugin {
       this.uppy.log(`[Transloadit] Created Assembly ${assemblyID}`)
       return assembly
     }).catch((err) => {
-      err.message = `${this.i18n('creatingAssemblyFailed')}: ${err.message}`
+      const error = new Error(`${this.i18n('creatingAssemblyFailed')}: ${err.message}`)
+      error.cause = err
       // Reject the promise.
-      throw err
+      throw error
     })
   }
 
-  _createAssemblyWatcher (assemblyID, fileIDs, uploadID) {
+  #createAssemblyWatcher (assemblyID, fileIDs, uploadID) {
   // AssemblyWatcher tracks completion states of all Assemblies in this upload.
     const watcher = new AssemblyWatcher(this.uppy, assemblyID)
 
@@ -272,7 +265,7 @@ module.exports = class Transloadit extends BasePlugin {
     this.assemblyWatchers[uploadID] = watcher
   }
 
-  _shouldWaitAfterUpload () {
+  #shouldWaitAfterUpload () {
     return this.opts.waitForEncoding || this.opts.waitForMetadata
   }
 
@@ -280,7 +273,7 @@ module.exports = class Transloadit extends BasePlugin {
    * Used when `importFromUploadURLs` is enabled: reserves all files in
    * the Assembly.
    */
-  _reserveFiles (assembly, fileIDs) {
+  #reserveFiles (assembly, fileIDs) {
     return Promise.all(fileIDs.map((fileID) => {
       const file = this.uppy.getFile(fileID)
       return this.client.reserveFile(assembly.status, file)
@@ -291,7 +284,7 @@ module.exports = class Transloadit extends BasePlugin {
    * Used when `importFromUploadURLs` is enabled: adds files to the Assembly
    * once they have been fully uploaded.
    */
-  _onFileUploadURLAvailable (rawFile) {
+  #onFileUploadURLAvailable =(rawFile) => {
     const file = this.uppy.getFile(rawFile.id)
     if (!file || !file.transloadit || !file.transloadit.assembly) {
       return
@@ -306,7 +299,7 @@ module.exports = class Transloadit extends BasePlugin {
     })
   }
 
-  _findFile (uploadedFile) {
+  #findFile (uploadedFile) {
     const files = this.uppy.getFiles()
     for (let i = 0; i < files.length; i++) {
       const file = files[i]
@@ -325,11 +318,12 @@ module.exports = class Transloadit extends BasePlugin {
         }
       }
     }
+    return undefined
   }
 
-  _onFileUploadComplete (assemblyId, uploadedFile) {
+  #onFileUploadComplete (assemblyId, uploadedFile) {
     const state = this.getPluginState()
-    const file = this._findFile(uploadedFile)
+    const file = this.#findFile(uploadedFile)
     if (!file) {
       this.uppy.log('[Transloadit] Couldn’t file the file, it was likely removed in the process')
       return
@@ -354,11 +348,11 @@ module.exports = class Transloadit extends BasePlugin {
    * @param {string} stepName
    * @param {object} result
    */
-  _onResult (assemblyId, stepName, result) {
+  #onResult (assemblyId, stepName, result) {
     const state = this.getPluginState()
     const file = state.files[result.original_id]
     // The `file` may not exist if an import robot was used instead of a file upload.
-    result.localId = file ? file.id : null
+    result.localId = file ? file.id : null // eslint-disable-line no-param-reassign
 
     const entry = {
       result,
@@ -379,7 +373,7 @@ module.exports = class Transloadit extends BasePlugin {
    *
    * @param {object} status
    */
-  _onAssemblyFinished (status) {
+  #onAssemblyFinished (status) {
     const url = status.assembly_ssl_url
     this.client.getAssemblyStatus(url).then((finalStatus) => {
       const assemblyId = finalStatus.assembly_id
@@ -394,24 +388,23 @@ module.exports = class Transloadit extends BasePlugin {
     })
   }
 
-  _cancelAssembly (assembly) {
-    return this.client.cancelAssembly(assembly).then(() => {
-      // TODO bubble this through AssemblyWatcher so its event handlers can clean up correctly
-      this.uppy.emit('transloadit:assembly-cancelled', assembly)
-    })
+  async #cancelAssembly (assembly) {
+    await this.client.cancelAssembly(assembly)
+    // TODO bubble this through AssemblyWatcher so its event handlers can clean up correctly
+    this.uppy.emit('transloadit:assembly-cancelled', assembly)
   }
 
   /**
    * When all files are removed, cancel in-progress Assemblies.
    */
-  _onCancelAll () {
+  #onCancelAll =() => {
     const { uploadsAssemblies } = this.getPluginState()
 
     const assemblyIDs = Object.values(uploadsAssemblies)
 
     const cancelPromises = assemblyIDs.map((assemblyID) => {
       const assembly = this.getAssembly(assemblyID)
-      return this._cancelAssembly(assembly)
+      return this.#cancelAssembly(assembly)
     })
 
     Promise.all(cancelPromises).catch((err) => {
@@ -425,7 +418,7 @@ module.exports = class Transloadit extends BasePlugin {
    *
    * @param {Function} setData
    */
-  _getPersistentData (setData) {
+  #getPersistentData =(setData) => {
     const state = this.getPluginState()
     const { assemblies } = state
     const { uploadsAssemblies } = state
@@ -438,7 +431,7 @@ module.exports = class Transloadit extends BasePlugin {
     })
   }
 
-  _onRestored (pluginData) {
+  #onRestored =(pluginData) => {
     const savedState = pluginData && pluginData[this.id] ? pluginData[this.id] : {}
     const previousAssemblies = savedState.assemblies || {}
     const uploadsAssemblies = savedState.uploadsAssemblies || {}
@@ -452,11 +445,9 @@ module.exports = class Transloadit extends BasePlugin {
     const restoreState = (assemblies) => {
       const files = {}
       const results = []
-      Object.keys(assemblies).forEach((id) => {
-        const status = assemblies[id]
-
+      for (const [id, status] of Object.entries(assemblies))  {
         status.uploads.forEach((uploadedFile) => {
-          const file = this._findFile(uploadedFile)
+          const file = this.#findFile(uploadedFile)
           files[uploadedFile.id] = {
             id: file.id,
             assembly: id,
@@ -466,7 +457,7 @@ module.exports = class Transloadit extends BasePlugin {
 
         const state = this.getPluginState()
         Object.keys(status.results).forEach((stepName) => {
-          status.results[stepName].forEach((result) => {
+          for (const result of status.results[stepName]) {
             const file = state.files[result.original_id]
             result.localId = file ? file.id : null
             results.push({
@@ -475,9 +466,9 @@ module.exports = class Transloadit extends BasePlugin {
               stepName,
               assembly: id,
             })
-          })
+          }
         })
-      })
+      }
 
       this.setPluginState({
         assemblies,
@@ -489,6 +480,7 @@ module.exports = class Transloadit extends BasePlugin {
 
     // Set up the Assembly instances and AssemblyWatchers for existing Assemblies.
     const restoreAssemblies = () => {
+      // eslint-disable-next-line no-shadow
       const { assemblies, uploadsAssemblies } = this.getPluginState()
 
       // Set up the assembly watchers again for all the ongoing uploads.
@@ -499,13 +491,13 @@ module.exports = class Transloadit extends BasePlugin {
           acc.push(...fileIDsInAssembly)
           return acc
         }, [])
-        this._createAssemblyWatcher(assemblyIDs, fileIDsInUpload, uploadID)
+        this.#createAssemblyWatcher(assemblyIDs, fileIDsInUpload, uploadID)
       })
 
       const allAssemblyIDs = Object.keys(assemblies)
       allAssemblyIDs.forEach((id) => {
         const assembly = new Assembly(assemblies[id])
-        this._connectAssembly(assembly)
+        this.#connectAssembly(assembly)
       })
     }
 
@@ -531,7 +523,7 @@ module.exports = class Transloadit extends BasePlugin {
     })
   }
 
-  _connectAssembly (assembly) {
+  #connectAssembly (assembly) {
     const { status } = assembly
     const id = status.assembly_id
     this.activeAssemblies[id] = assembly
@@ -548,10 +540,10 @@ module.exports = class Transloadit extends BasePlugin {
     })
 
     assembly.on('upload', (file) => {
-      this._onFileUploadComplete(id, file)
+      this.#onFileUploadComplete(id, file)
     })
     assembly.on('error', (error) => {
-      error.assembly = assembly.status
+      error.assembly = assembly.status // eslint-disable-line no-param-reassign
       this.uppy.emit('transloadit:assembly-error', assembly.status, error)
     })
 
@@ -561,17 +553,17 @@ module.exports = class Transloadit extends BasePlugin {
 
     if (this.opts.waitForEncoding) {
       assembly.on('result', (stepName, result) => {
-        this._onResult(id, stepName, result)
+        this.#onResult(id, stepName, result)
       })
     }
 
     if (this.opts.waitForEncoding) {
       assembly.on('finished', () => {
-        this._onAssemblyFinished(assembly.status)
+        this.#onAssemblyFinished(assembly.status)
       })
     } else if (this.opts.waitForMetadata) {
       assembly.on('metadata', () => {
-        this._onAssemblyFinished(assembly.status)
+        this.#onAssemblyFinished(assembly.status)
       })
     }
 
@@ -584,32 +576,32 @@ module.exports = class Transloadit extends BasePlugin {
     return assembly
   }
 
-  _prepareUpload (fileIDs, uploadID) {
+  #prepareUpload = (fileIDs, uploadID) => {
     // Only use files without errors
-    fileIDs = fileIDs.filter((file) => !file.error)
+    const filteredFileIDs = fileIDs.filter((file) => !file.error)
 
-    fileIDs.forEach((fileID) => {
+    const files = filteredFileIDs.map(fileID => {
       const file = this.uppy.getFile(fileID)
       this.uppy.emit('preprocess-progress', file, {
         mode: 'indeterminate',
         message: this.i18n('creatingAssembly'),
       })
+      return file
     })
 
-    const createAssembly = ({ fileIDs, options }) => {
-      let createdAssembly
-      return this._createAssembly(fileIDs, uploadID, options).then((assembly) => {
-        createdAssembly = assembly
+    // eslint-disable-next-line no-shadow
+    const createAssembly = async ({ fileIDs, options }) => {
+      try {
+        const assembly = await this.#createAssembly(fileIDs, uploadID, options)
         if (this.opts.importFromUploadURLs) {
-          return this._reserveFiles(assembly, fileIDs)
+          await this.#reserveFiles(assembly, fileIDs)
         }
-      }).then(() => {
         fileIDs.forEach((fileID) => {
           const file = this.uppy.getFile(fileID)
           this.uppy.emit('preprocess-complete', file)
         })
-        return createdAssembly
-      }).catch((err) => {
+        return assembly
+      } catch (err)  {
         fileIDs.forEach((fileID) => {
           const file = this.uppy.getFile(fileID)
           // Clear preprocessing state when the Assembly could not be created,
@@ -618,7 +610,7 @@ module.exports = class Transloadit extends BasePlugin {
           this.uppy.emit('upload-error', file, err)
         })
         throw err
-      })
+      }
     }
 
     const { uploadsAssemblies } = this.getPluginState()
@@ -629,21 +621,19 @@ module.exports = class Transloadit extends BasePlugin {
       },
     })
 
-    const files = fileIDs.map((id) => this.uppy.getFile(id))
     const assemblyOptions = new AssemblyOptions(files, this.opts)
 
     return assemblyOptions.build()
       .then((assemblies) => Promise.all(assemblies.map(createAssembly)))
       .then((createdAssemblies) => {
         const assemblyIDs = createdAssemblies.map(assembly => assembly.status.assembly_id)
-        this._createAssemblyWatcher(assemblyIDs, fileIDs, uploadID)
-        return Promise.all(createdAssemblies.map(assembly => this._connectAssembly(assembly)))
+        this.#createAssemblyWatcher(assemblyIDs, filteredFileIDs, uploadID)
+        return Promise.all(createdAssemblies.map(assembly => this.#connectAssembly(assembly)))
       })
       // If something went wrong before any Assemblies could be created,
       // clear all processing state.
       .catch((err) => {
-        fileIDs.forEach((fileID) => {
-          const file = this.uppy.getFile(fileID)
+        files.forEach((file) => {
           this.uppy.emit('preprocess-complete', file)
           this.uppy.emit('upload-error', file, err)
         })
@@ -651,17 +641,17 @@ module.exports = class Transloadit extends BasePlugin {
       })
   }
 
-  _afterUpload (fileIDs, uploadID) {
+  #afterUpload =(fileIDs, uploadID) => {
     const files = fileIDs.map(fileID => this.uppy.getFile(fileID))
     // Only use files without errors
-    fileIDs = files.filter((file) => !file.error).map(file => file.id)
+    const filteredFileIDs = files.filter((file) => !file.error).map(file => file.id)
 
     const state = this.getPluginState()
 
     // If we're still restoring state, wait for that to be done.
     if (this.restored) {
       return this.restored.then(() => {
-        return this._afterUpload(fileIDs, uploadID)
+        return this.#afterUpload(filteredFileIDs, uploadID)
       })
     }
 
@@ -677,7 +667,7 @@ module.exports = class Transloadit extends BasePlugin {
 
     // If we don't have to wait for encoding metadata or results, we can close
     // the socket immediately and finish the upload.
-    if (!this._shouldWaitAfterUpload()) {
+    if (!this.#shouldWaitAfterUpload()) {
       closeSocketConnections()
       const assemblies = assemblyIDs.map((id) => this.getAssembly(id))
       this.uppy.addResultData(uploadID, { transloadit: assemblies })
@@ -707,8 +697,7 @@ module.exports = class Transloadit extends BasePlugin {
 
       // Remove the Assembly ID list for this upload,
       // it's no longer going to be used anywhere.
-      const state = this.getPluginState()
-      const uploadsAssemblies = { ...state.uploadsAssemblies }
+      const uploadsAssemblies = { ...this.getPluginState().uploadsAssemblies }
       delete uploadsAssemblies[uploadID]
       this.setPluginState({ uploadsAssemblies })
 
@@ -718,7 +707,7 @@ module.exports = class Transloadit extends BasePlugin {
     })
   }
 
-  _onError (err = null, uploadID) {
+  #onError = (err = null, uploadID) => {
     const state = this.getPluginState()
     const assemblyIDs = state.uploadsAssemblies[uploadID]
 
@@ -727,34 +716,35 @@ module.exports = class Transloadit extends BasePlugin {
         this.activeAssemblies[assemblyID].close()
       }
     })
+    this.client.submitError(err)
   }
 
-  _onTusError (err) {
+  #onTusError =(err) => {
     if (err && /^tus: /.test(err.message)) {
       const xhr = err.originalRequest ? err.originalRequest.getUnderlyingObject() : null
       const url = xhr && xhr.responseURL ? xhr.responseURL : null
-      this.client.submitError(err, { url, type: 'TUS_ERROR' }).then((_) => {
+      this.client.submitError(err, { url, type: 'TUS_ERROR' }).then(() => {
         // if we can't report the error that sucks
       })
     }
   }
 
   install () {
-    this.uppy.addPreProcessor(this._prepareUpload)
-    this.uppy.addPostProcessor(this._afterUpload)
+    this.uppy.addPreProcessor(this.#prepareUpload)
+    this.uppy.addPostProcessor(this.#afterUpload)
 
     // We may need to close socket.io connections on error.
-    this.uppy.on('error', this._onError)
+    this.uppy.on('error', this.#onError)
 
     // Handle cancellation.
-    this.uppy.on('cancel-all', this._onCancelAll)
+    this.uppy.on('cancel-all', this.#onCancelAll)
 
     // For error reporting.
-    this.uppy.on('upload-error', this._onTusError)
+    this.uppy.on('upload-error', this.#onTusError)
 
     if (this.opts.importFromUploadURLs) {
       // No uploader needed when importing; instead we take the upload URL from an existing uploader.
-      this.uppy.on('upload-success', this._onFileUploadURLAvailable)
+      this.uppy.on('upload-success', this.#onFileUploadURLAvailable)
     } else {
       this.uppy.use(Tus, {
         // Disable tus-js-client fingerprinting, otherwise uploading the same file at different times
@@ -778,8 +768,8 @@ module.exports = class Transloadit extends BasePlugin {
       })
     }
 
-    this.uppy.on('restore:get-data', this._getPersistentData)
-    this.uppy.on('restored', this._onRestored)
+    this.uppy.on('restore:get-data', this.#getPersistentData)
+    this.uppy.on('restored', this.#onRestored)
 
     this.setPluginState({
       // Contains Assembly status objects, indexed by their ID.
@@ -803,12 +793,12 @@ module.exports = class Transloadit extends BasePlugin {
   }
 
   uninstall () {
-    this.uppy.removePreProcessor(this._prepareUpload)
-    this.uppy.removePostProcessor(this._afterUpload)
-    this.uppy.off('error', this._onError)
+    this.uppy.removePreProcessor(this.#prepareUpload)
+    this.uppy.removePostProcessor(this.#afterUpload)
+    this.uppy.off('error', this.#onError)
 
     if (this.opts.importFromUploadURLs) {
-      this.uppy.off('upload-success', this._onFileUploadURLAvailable)
+      this.uppy.off('upload-success', this.#onFileUploadURLAvailable)
     }
 
     const { capabilities } = this.uppy.getState()

+ 1 - 0
packages/@uppy/transloadit/src/index.test.js

@@ -1,5 +1,6 @@
 const Core = require('@uppy/core')
 const Transloadit = require('.')
+require('whatwg-fetch')
 
 describe('Transloadit', () => {
   it('Throws errors if options are missing', () => {