index.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. const Translator = require('@uppy/utils/lib/Translator')
  2. const { Plugin } = require('@uppy/core')
  3. const Tus = require('@uppy/tus')
  4. const Assembly = require('./Assembly')
  5. const Client = require('./Client')
  6. const AssemblyOptions = require('./AssemblyOptions')
  7. const AssemblyWatcher = require('./AssemblyWatcher')
  8. function defaultGetAssemblyOptions (file, options) {
  9. return {
  10. params: options.params,
  11. signature: options.signature,
  12. fields: options.fields
  13. }
  14. }
  15. const COMPANION = 'https://api2.transloadit.com/companion'
  16. // Regex matching acceptable postMessage() origins for authentication feedback from companion.
  17. const ALLOWED_COMPANION_PATTERN = /\.transloadit\.com$/
  18. // Regex used to check if a Companion address is run by Transloadit.
  19. const TL_COMPANION = /https?:\/\/api2(?:-\w+)?\.transloadit\.com\/companion/
  20. const TL_UPPY_SERVER = /https?:\/\/api2(?:-\w+)?\.transloadit\.com\/uppy-server/
  21. /**
  22. * Upload files to Transloadit using Tus.
  23. */
  24. module.exports = class Transloadit extends Plugin {
  25. constructor (uppy, opts) {
  26. super(uppy, opts)
  27. this.type = 'uploader'
  28. this.id = 'Transloadit'
  29. this.title = 'Transloadit'
  30. const defaultLocale = {
  31. strings: {
  32. creatingAssembly: 'Preparing upload...',
  33. creatingAssemblyFailed: 'Transloadit: Could not create Assembly',
  34. encoding: 'Encoding...'
  35. }
  36. }
  37. const defaultOptions = {
  38. service: 'https://api2.transloadit.com',
  39. waitForEncoding: false,
  40. waitForMetadata: false,
  41. alwaysRunAssembly: false,
  42. importFromUploadURLs: false,
  43. signature: null,
  44. params: null,
  45. fields: {},
  46. getAssemblyOptions: defaultGetAssemblyOptions,
  47. locale: defaultLocale
  48. }
  49. this.opts = Object.assign({}, defaultOptions, opts)
  50. // i18n
  51. this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
  52. this.i18n = this.translator.translate.bind(this.translator)
  53. this.i18nArray = this.translator.translateArray.bind(this.translator)
  54. this._prepareUpload = this._prepareUpload.bind(this)
  55. this._afterUpload = this._afterUpload.bind(this)
  56. this._handleError = this._handleError.bind(this)
  57. this._onFileUploadURLAvailable = this._onFileUploadURLAvailable.bind(this)
  58. this._onRestored = this._onRestored.bind(this)
  59. this._getPersistentData = this._getPersistentData.bind(this)
  60. const hasCustomAssemblyOptions = this.opts.getAssemblyOptions !== defaultOptions.getAssemblyOptions
  61. if (this.opts.params) {
  62. AssemblyOptions.validateParams(this.opts.params)
  63. } else if (!hasCustomAssemblyOptions) {
  64. // Throw the same error that we'd throw if the `params` returned from a
  65. // `getAssemblyOptions()` function is null.
  66. AssemblyOptions.validateParams(null)
  67. }
  68. this.client = new Client({
  69. service: this.opts.service
  70. })
  71. // Contains Assembly instances for in-progress Assemblies.
  72. this.activeAssemblies = {}
  73. }
  74. /**
  75. * Attach metadata to files to configure the Tus plugin to upload to Transloadit.
  76. * Also use Transloadit's Companion
  77. *
  78. * See: https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus
  79. *
  80. * @param {Object} file
  81. * @param {Object} status
  82. */
  83. _attachAssemblyMetadata (file, status) {
  84. // Add the metadata parameters Transloadit needs.
  85. const meta = {
  86. ...file.meta,
  87. assembly_url: status.assembly_url,
  88. filename: file.name,
  89. fieldname: 'file'
  90. }
  91. // Add Assembly-specific Tus endpoint.
  92. const tus = {
  93. ...file.tus,
  94. endpoint: status.tus_url
  95. }
  96. // Set Companion location. We only add this, if 'file' has the attribute
  97. // remote, because this is the criteria to identify remote files.
  98. // We only replace the hostname for Transloadit's companions, so that
  99. // people can also self-host them while still using Transloadit for encoding.
  100. let remote = file.remote
  101. if (file.remote && TL_UPPY_SERVER.test(file.remote.serverUrl)) {
  102. const err = new Error(
  103. 'The https://api2.transloadit.com/uppy-server endpoint was renamed to ' +
  104. 'https://api2.transloadit.com/companion, please update your `serverUrl` ' +
  105. 'options accordingly.')
  106. // Explicitly log this error here because it is caught by the `createAssembly`
  107. // Promise further along.
  108. // That's fine, but createAssembly only shows the informer, we need something a
  109. // little more noisy.
  110. this.uppy.log(err)
  111. throw err
  112. }
  113. if (file.remote && TL_COMPANION.test(file.remote.serverUrl)) {
  114. const newHost = status.companion_url
  115. .replace(/\/$/, '')
  116. const path = file.remote.url
  117. .replace(file.remote.serverUrl, '')
  118. .replace(/^\//, '')
  119. remote = {
  120. ...file.remote,
  121. serverUrl: newHost,
  122. url: `${newHost}/${path}`
  123. }
  124. }
  125. // Store the Assembly ID this file is in on the file under the `transloadit` key.
  126. const newFile = {
  127. ...file,
  128. transloadit: {
  129. assembly: status.assembly_id
  130. }
  131. }
  132. // Only configure the Tus plugin if we are uploading straight to Transloadit (the default).
  133. if (!this.opts.importFromUploadURLs) {
  134. Object.assign(newFile, { meta, tus, remote })
  135. }
  136. return newFile
  137. }
  138. _createAssembly (fileIDs, uploadID, options) {
  139. this.uppy.log('[Transloadit] create Assembly')
  140. return this.client.createAssembly({
  141. params: options.params,
  142. fields: options.fields,
  143. expectedFiles: fileIDs.length,
  144. signature: options.signature
  145. }).then((newAssembly) => {
  146. const assembly = new Assembly(newAssembly)
  147. const status = assembly.status
  148. const { assemblies, uploadsAssemblies } = this.getPluginState()
  149. this.setPluginState({
  150. // Store the Assembly status.
  151. assemblies: {
  152. ...assemblies,
  153. [status.assembly_id]: status
  154. },
  155. // Store the list of Assemblies related to this upload.
  156. uploadsAssemblies: {
  157. ...uploadsAssemblies,
  158. [uploadID]: [
  159. ...uploadsAssemblies[uploadID],
  160. status.assembly_id
  161. ]
  162. }
  163. })
  164. const { files } = this.uppy.getState()
  165. const updatedFiles = {}
  166. fileIDs.forEach((id) => {
  167. updatedFiles[id] = this._attachAssemblyMetadata(this.uppy.getFile(id), status)
  168. })
  169. this.uppy.setState({
  170. files: {
  171. ...files,
  172. ...updatedFiles
  173. }
  174. })
  175. this.uppy.emit('transloadit:assembly-created', status, fileIDs)
  176. this._connectAssembly(assembly)
  177. this.uppy.log(`[Transloadit] Created Assembly ${status.assembly_id}`)
  178. return assembly
  179. }).catch((err) => {
  180. err.message = `${this.i18n('creatingAssemblyFailed')}: ${err.message}`
  181. // Reject the promise.
  182. throw err
  183. })
  184. }
  185. _shouldWaitAfterUpload () {
  186. return this.opts.waitForEncoding || this.opts.waitForMetadata
  187. }
  188. /**
  189. * Used when `importFromUploadURLs` is enabled: reserves all files in
  190. * the Assembly.
  191. */
  192. _reserveFiles (assembly, fileIDs) {
  193. return Promise.all(fileIDs.map((fileID) => {
  194. const file = this.uppy.getFile(fileID)
  195. return this.client.reserveFile(assembly, file)
  196. }))
  197. }
  198. /**
  199. * Used when `importFromUploadURLs` is enabled: adds files to the Assembly
  200. * once they have been fully uploaded.
  201. */
  202. _onFileUploadURLAvailable (file) {
  203. if (!file || !file.transloadit || !file.transloadit.assembly) {
  204. return
  205. }
  206. const { assemblies } = this.getPluginState()
  207. const assembly = assemblies[file.transloadit.assembly]
  208. this.client.addFile(assembly, file).catch((err) => {
  209. this.uppy.log(err)
  210. this.uppy.emit('transloadit:import-error', assembly, file.id, err)
  211. })
  212. }
  213. _findFile (uploadedFile) {
  214. const files = this.uppy.getFiles()
  215. for (let i = 0; i < files.length; i++) {
  216. const file = files[i]
  217. // Completed file upload.
  218. if (file.uploadURL === uploadedFile.tus_upload_url) {
  219. return file
  220. }
  221. // In-progress file upload.
  222. if (file.tus && file.tus.uploadUrl === uploadedFile.tus_upload_url) {
  223. return file
  224. }
  225. if (!uploadedFile.is_tus_file) {
  226. // Fingers-crossed check for non-tus uploads, eg imported from S3.
  227. if (file.name === uploadedFile.name && file.size === uploadedFile.size) {
  228. return file
  229. }
  230. }
  231. }
  232. }
  233. _onFileUploadComplete (assemblyId, uploadedFile) {
  234. const state = this.getPluginState()
  235. const file = this._findFile(uploadedFile)
  236. if (!file) {
  237. this.uppy.log('[Transloadit] Couldn’t file the file, it was likely removed in the process')
  238. return
  239. }
  240. this.setPluginState({
  241. files: Object.assign({}, state.files, {
  242. [uploadedFile.id]: {
  243. assembly: assemblyId,
  244. id: file.id,
  245. uploadedFile
  246. }
  247. })
  248. })
  249. this.uppy.emit('transloadit:upload', uploadedFile, this.getAssembly(assemblyId))
  250. }
  251. /**
  252. * Callback when a new Assembly result comes in.
  253. *
  254. * @param {string} assemblyId
  255. * @param {string} stepName
  256. * @param {Object} result
  257. */
  258. _onResult (assemblyId, stepName, result) {
  259. const state = this.getPluginState()
  260. const file = state.files[result.original_id]
  261. // The `file` may not exist if an import robot was used instead of a file upload.
  262. result.localId = file ? file.id : null
  263. const entry = {
  264. result,
  265. stepName,
  266. id: result.id,
  267. assembly: assemblyId
  268. }
  269. this.setPluginState({
  270. results: [...state.results, entry]
  271. })
  272. this.uppy.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId))
  273. }
  274. /**
  275. * When an Assembly has finished processing, get the final state
  276. * and emit it.
  277. *
  278. * @param {Object} status
  279. */
  280. _onAssemblyFinished (status) {
  281. const url = status.assembly_ssl_url
  282. this.client.getAssemblyStatus(url).then((finalStatus) => {
  283. const state = this.getPluginState()
  284. this.setPluginState({
  285. assemblies: Object.assign({}, state.assemblies, {
  286. [finalStatus.assembly_id]: finalStatus
  287. })
  288. })
  289. this.uppy.emit('transloadit:complete', finalStatus)
  290. })
  291. }
  292. /**
  293. * Custom state serialization for the Golden Retriever plugin.
  294. * It will pass this back to the `_onRestored` function.
  295. *
  296. * @param {function} setData
  297. */
  298. _getPersistentData (setData) {
  299. const state = this.getPluginState()
  300. const assemblies = state.assemblies
  301. const uploadsAssemblies = state.uploadsAssemblies
  302. setData({
  303. [this.id]: {
  304. assemblies,
  305. uploadsAssemblies
  306. }
  307. })
  308. }
  309. _onRestored (pluginData) {
  310. const savedState = pluginData && pluginData[this.id] ? pluginData[this.id] : {}
  311. const previousAssemblies = savedState.assemblies || {}
  312. const uploadsAssemblies = savedState.uploadsAssemblies || {}
  313. if (Object.keys(uploadsAssemblies).length === 0) {
  314. // Nothing to restore.
  315. return
  316. }
  317. // Convert loaded Assembly statuses to a Transloadit plugin state object.
  318. const restoreState = (assemblies) => {
  319. const files = {}
  320. const results = []
  321. Object.keys(assemblies).forEach((id) => {
  322. const status = assemblies[id]
  323. status.uploads.forEach((uploadedFile) => {
  324. const file = this._findFile(uploadedFile)
  325. files[uploadedFile.id] = {
  326. id: file.id,
  327. assembly: id,
  328. uploadedFile
  329. }
  330. })
  331. const state = this.getPluginState()
  332. Object.keys(status.results).forEach((stepName) => {
  333. status.results[stepName].forEach((result) => {
  334. const file = state.files[result.original_id]
  335. result.localId = file ? file.id : null
  336. results.push({
  337. id: result.id,
  338. result,
  339. stepName,
  340. assembly: id
  341. })
  342. })
  343. })
  344. })
  345. this.setPluginState({
  346. assemblies,
  347. files,
  348. results,
  349. uploadsAssemblies
  350. })
  351. }
  352. // Set up the Assembly instances for existing Assemblies.
  353. const restoreAssemblies = () => {
  354. const { assemblies } = this.getPluginState()
  355. Object.keys(assemblies).forEach((id) => {
  356. const assembly = new Assembly(assemblies[id])
  357. this._connectAssembly(assembly)
  358. })
  359. }
  360. // Force-update all Assemblies to check for missed events.
  361. const updateAssemblies = () => {
  362. const { assemblies } = this.getPluginState()
  363. return Promise.all(
  364. Object.keys(assemblies).map((id) => {
  365. return this.activeAssemblies[id].update()
  366. })
  367. )
  368. }
  369. // Restore all Assembly state.
  370. this.restored = Promise.resolve().then(() => {
  371. restoreState(previousAssemblies)
  372. restoreAssemblies()
  373. return updateAssemblies()
  374. })
  375. this.restored.then(() => {
  376. this.restored = null
  377. })
  378. }
  379. _connectAssembly (assembly) {
  380. const { status } = assembly
  381. const id = status.assembly_id
  382. this.activeAssemblies[id] = assembly
  383. // Sync local `assemblies` state
  384. assembly.on('status', (newStatus) => {
  385. const { assemblies } = this.getPluginState()
  386. this.setPluginState({
  387. assemblies: {
  388. ...assemblies,
  389. [id]: newStatus
  390. }
  391. })
  392. })
  393. assembly.on('upload', (file) => {
  394. this._onFileUploadComplete(id, file)
  395. })
  396. assembly.on('error', (error) => {
  397. this.uppy.emit('transloadit:assembly-error', assembly.status, error)
  398. })
  399. assembly.on('executing', () => {
  400. this.uppy.emit('transloadit:assembly-executing', assembly.status)
  401. })
  402. if (this.opts.waitForEncoding) {
  403. assembly.on('result', (stepName, result) => {
  404. this._onResult(id, stepName, result)
  405. })
  406. }
  407. if (this.opts.waitForEncoding) {
  408. assembly.on('finished', () => {
  409. this._onAssemblyFinished(assembly.status)
  410. })
  411. } else if (this.opts.waitForMetadata) {
  412. assembly.on('metadata', () => {
  413. this._onAssemblyFinished(assembly.status)
  414. })
  415. }
  416. // No need to connect to the socket if the Assembly has completed by now.
  417. if (assembly.ok === 'ASSEMBLY_COMPLETE') {
  418. return assembly
  419. }
  420. // TODO Do we still need this for anything…?
  421. // eslint-disable-next-line no-unused-vars
  422. const connected = new Promise((resolve, reject) => {
  423. assembly.once('connect', resolve)
  424. assembly.once('status', resolve)
  425. assembly.once('error', reject)
  426. }).then(() => {
  427. this.uppy.log('[Transloadit] Socket is ready')
  428. })
  429. assembly.connect()
  430. return assembly
  431. }
  432. _prepareUpload (fileIDs, uploadID) {
  433. // Only use files without errors
  434. fileIDs = fileIDs.filter((file) => !file.error)
  435. fileIDs.forEach((fileID) => {
  436. const file = this.uppy.getFile(fileID)
  437. this.uppy.emit('preprocess-progress', file, {
  438. mode: 'indeterminate',
  439. message: this.i18n('creatingAssembly')
  440. })
  441. })
  442. const createAssembly = ({ fileIDs, options }) => {
  443. return this._createAssembly(fileIDs, uploadID, options).then((assembly) => {
  444. if (this.opts.importFromUploadURLs) {
  445. return this._reserveFiles(assembly, fileIDs)
  446. }
  447. }).then(() => {
  448. fileIDs.forEach((fileID) => {
  449. const file = this.uppy.getFile(fileID)
  450. this.uppy.emit('preprocess-complete', file)
  451. })
  452. }).catch((err) => {
  453. fileIDs.forEach((fileID) => {
  454. const file = this.uppy.getFile(fileID)
  455. // Clear preprocessing state when the Assembly could not be created,
  456. // otherwise the UI gets confused about the lingering progress keys
  457. this.uppy.emit('preprocess-complete', file)
  458. this.uppy.emit('upload-error', file, err)
  459. })
  460. throw err
  461. })
  462. }
  463. const { uploadsAssemblies } = this.getPluginState()
  464. this.setPluginState({
  465. uploadsAssemblies: {
  466. ...uploadsAssemblies,
  467. [uploadID]: []
  468. }
  469. })
  470. const files = fileIDs.map((id) => this.uppy.getFile(id))
  471. const assemblyOptions = new AssemblyOptions(files, this.opts)
  472. return assemblyOptions.build().then(
  473. (assemblies) => Promise.all(
  474. assemblies.map(createAssembly)
  475. ),
  476. // If something went wrong before any Assemblies could be created,
  477. // clear all processing state.
  478. (err) => {
  479. fileIDs.forEach((fileID) => {
  480. const file = this.uppy.getFile(fileID)
  481. this.uppy.emit('preprocess-complete', file)
  482. this.uppy.emit('upload-error', file, err)
  483. })
  484. throw err
  485. }
  486. )
  487. }
  488. _afterUpload (fileIDs, uploadID) {
  489. // Only use files without errors
  490. fileIDs = fileIDs.filter((file) => !file.error)
  491. const state = this.getPluginState()
  492. // If we're still restoring state, wait for that to be done.
  493. if (this.restored) {
  494. return this.restored.then(() => {
  495. return this._afterUpload(fileIDs, uploadID)
  496. })
  497. }
  498. const assemblyIDs = state.uploadsAssemblies[uploadID]
  499. // If we don't have to wait for encoding metadata or results, we can close
  500. // the socket immediately and finish the upload.
  501. if (!this._shouldWaitAfterUpload()) {
  502. assemblyIDs.forEach((assemblyID) => {
  503. const assembly = this.activeAssemblies[assemblyID]
  504. assembly.close()
  505. delete this.activeAssemblies[assemblyID]
  506. })
  507. const assemblies = assemblyIDs.map((id) => this.getAssembly(id))
  508. this.uppy.addResultData(uploadID, { transloadit: assemblies })
  509. return Promise.resolve()
  510. }
  511. // If no Assemblies were created for this upload, we also do not have to wait.
  512. // There's also no sockets or anything to close, so just return immediately.
  513. if (assemblyIDs.length === 0) {
  514. this.uppy.addResultData(uploadID, { transloadit: [] })
  515. return Promise.resolve()
  516. }
  517. // AssemblyWatcher tracks completion state of all Assemblies in this upload.
  518. const watcher = new AssemblyWatcher(this.uppy, assemblyIDs)
  519. fileIDs.forEach((fileID) => {
  520. const file = this.uppy.getFile(fileID)
  521. this.uppy.emit('postprocess-progress', file, {
  522. mode: 'indeterminate',
  523. message: this.i18n('encoding')
  524. })
  525. })
  526. watcher.on('assembly-complete', (id) => {
  527. const files = this.getAssemblyFiles(id)
  528. files.forEach((file) => {
  529. this.uppy.emit('postprocess-complete', file)
  530. })
  531. })
  532. watcher.on('assembly-error', (id, error) => {
  533. // Clear postprocessing state for all our files.
  534. const files = this.getAssemblyFiles(id)
  535. files.forEach((file) => {
  536. // TODO Maybe make a postprocess-error event here?
  537. this.uppy.emit('upload-error', file, error)
  538. this.uppy.emit('postprocess-complete', file)
  539. })
  540. })
  541. return watcher.promise.then(() => {
  542. const assemblies = assemblyIDs.map((id) => this.getAssembly(id))
  543. // Remove the Assembly ID list for this upload,
  544. // it's no longer going to be used anywhere.
  545. const state = this.getPluginState()
  546. const uploadsAssemblies = { ...state.uploadsAssemblies }
  547. delete uploadsAssemblies[uploadID]
  548. this.setPluginState({ uploadsAssemblies })
  549. this.uppy.addResultData(uploadID, {
  550. transloadit: assemblies
  551. })
  552. })
  553. }
  554. _handleError (err, uploadID) {
  555. this.uppy.log(`[Transloadit] _handleError in upload ${uploadID}`)
  556. this.uppy.log(err)
  557. const state = this.getPluginState()
  558. const assemblyIDs = state.uploadsAssemblies[uploadID]
  559. assemblyIDs.forEach((assemblyID) => {
  560. if (this.activeAssemblies[assemblyID]) {
  561. this.activeAssemblies[assemblyID].close()
  562. }
  563. })
  564. }
  565. install () {
  566. this.uppy.addPreProcessor(this._prepareUpload)
  567. this.uppy.addPostProcessor(this._afterUpload)
  568. // We may need to close socket.io connections on error.
  569. this.uppy.on('error', this._handleError)
  570. if (this.opts.importFromUploadURLs) {
  571. // No uploader needed when importing; instead we take the upload URL from an existing uploader.
  572. this.uppy.on('upload-success', this._onFileUploadURLAvailable)
  573. } else {
  574. this.uppy.use(Tus, {
  575. // Disable tus-js-client fingerprinting, otherwise uploading the same file at different times
  576. // will upload to the same Assembly.
  577. resume: false,
  578. // Disable Companion's retry optimisation; we need to change the endpoint on retry
  579. // so it can't just reuse the same tus.Upload instance server-side.
  580. useFastRemoteRetry: false,
  581. // Only send Assembly metadata to the tus endpoint.
  582. metaFields: ['assembly_url', 'filename', 'fieldname']
  583. })
  584. }
  585. this.uppy.on('restore:get-data', this._getPersistentData)
  586. this.uppy.on('restored', this._onRestored)
  587. this.setPluginState({
  588. // Contains Assembly status objects, indexed by their ID.
  589. assemblies: {},
  590. // Contains arrays of Assembly IDs, indexed by the upload ID that they belong to.
  591. uploadsAssemblies: {},
  592. // Contains file data from Transloadit, indexed by their Transloadit-assigned ID.
  593. files: {},
  594. // Contains result data from Transloadit.
  595. results: []
  596. })
  597. }
  598. uninstall () {
  599. this.uppy.removePreProcessor(this._prepareUpload)
  600. this.uppy.removePostProcessor(this._afterUpload)
  601. this.uppy.off('error', this._handleError)
  602. if (this.opts.importFromUploadURLs) {
  603. this.uppy.off('upload-success', this._onFileUploadURLAvailable)
  604. }
  605. }
  606. getAssembly (id) {
  607. const state = this.getPluginState()
  608. return state.assemblies[id]
  609. }
  610. getAssemblyFiles (assemblyID) {
  611. return this.uppy.getFiles().filter((file) => {
  612. return file && file.transloadit && file.transloadit.assembly === assemblyID
  613. })
  614. }
  615. }
  616. module.exports.COMPANION = COMPANION
  617. module.exports.UPPY_SERVER = COMPANION
  618. module.exports.COMPANION_PATTERN = ALLOWED_COMPANION_PATTERN