Selaa lähdekoodia

Transloadit refactors + socket fallback (#1011)

* transloadit: Add an Assembly class to handle status updates.

* transloadit: Replace StatusSocket with new Assembly class.

* transloadit: Draft new Golden Retriever integration

* transloadit: Refetch Assembly status on events.

* transloadit: Update `assemblies` state when new status was fetched.

* transloadit: Move assembly options extraction to a class

* transloadit: use AssemblyOptions class directly for assembly options tests

* transloadit: move attachAssemblyMetadata to a class method.

* transloadit: Remove obsolete dependency.

* transloadit: Reconnect to assemblies after restore.

* tus: Always store uploadUrl

This was changed in: https://github.com/transloadit/uppy/pull/507
but actually, the uploadUrl is required by GoldenRetriever, _especially_
when `resume` is false.

* transloadit: Move `afterUpload()` closures to AssemblyWatcher class

* transloadit: Prefix private methods with _

* transloadit: Replace 2kb url-parse with custom helper.

* transloadit: Semi consistent comment width

* transloadit: Capitalise "Assembly".
Renée Kooi 6 vuotta sitten
vanhempi
commit
ed5f73c6f5

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

@@ -32,9 +32,8 @@
     "@uppy/provider-views": "0.27.1",
     "@uppy/tus": "0.27.1",
     "@uppy/utils": "0.27.0",
-    "namespace-emitter": "^2.0.1",
-    "socket.io-client": "^2.1.1",
-    "url-parse": "^1.4.1"
+    "component-emitter": "^1.2.1",
+    "socket.io-client": "^2.1.1"
   },
   "devDependencies": {
     "@uppy/core": "0.27.0"

+ 238 - 0
packages/@uppy/transloadit/src/Assembly.js

@@ -0,0 +1,238 @@
+const io = require('socket.io-client')
+const Emitter = require('component-emitter')
+const parseUrl = require('./parseUrl')
+
+const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING'
+const ASSEMBLY_EXECUTING = 'ASSEMBLY_EXECUTING'
+const ASSEMBLY_COMPLETED = 'ASSEMBLY_COMPLETED'
+
+const statusOrder = [
+  ASSEMBLY_UPLOADING,
+  ASSEMBLY_EXECUTING,
+  ASSEMBLY_COMPLETED
+]
+
+/**
+ * Check that an assembly status is equal to or larger than some desired status.
+ * It checks for things that are larger so that a comparison like this works,
+ * when the old assembly status is UPLOADING but the new is FINISHED:
+ *
+ * !isStatus(oldStatus, ASSEMBLY_EXECUTING) && isStatus(newState, ASSEMBLY_EXECUTING)
+ *
+ * …so that we can emit the 'executing' event even if the execution step was so
+ * fast that we missed it.
+ */
+function isStatus (status, test) {
+  return statusOrder.indexOf(status) >= statusOrder.indexOf(test)
+}
+
+class TransloaditAssembly extends Emitter {
+  constructor (assembly) {
+    super()
+
+    // The current assembly status.
+    this.status = assembly
+    // The socket.io connection.
+    this.socket = null
+    // The interval timer for full status updates.
+    this.pollInterval = null
+    // Whether this assembly has been closed (finished or errored)
+    this.closed = false
+  }
+
+  connect () {
+    this._connectSocket()
+    this._beginPolling()
+  }
+
+  _onFinished () {
+    this.emit('finished')
+    this.close()
+  }
+
+  _connectSocket () {
+    const parsed = parseUrl(this.status.websocket_url)
+    const socket = io.connect(parsed.origin, {
+      path: parsed.pathname
+    })
+
+    socket.on('connect', () => {
+      socket.emit('assembly_connect', {
+        id: this.status.assembly_id
+      })
+
+      this.emit('connect')
+    })
+    socket.on('error', () => {
+      socket.disconnect()
+      this.socket = null
+    })
+
+    socket.on('assembly_finished', () => {
+      this._onFinished()
+    })
+
+    socket.on('assembly_upload_finished', (file) => {
+      this.emit('upload', file)
+      this._fetchStatus({ diff: false })
+    })
+
+    socket.on('assembly_uploading_finished', () => {
+      this.emit('executing')
+      this._fetchStatus({ diff: false })
+    })
+
+    socket.on('assembly_upload_meta_data_extracted', () => {
+      this.emit('metadata')
+      this._fetchStatus({ diff: false })
+    })
+
+    socket.on('assembly_result_finished', (stepName, result) => {
+      this.emit('result', stepName, result)
+      this._fetchStatus({ diff: false })
+    })
+
+    socket.on('assembly_error', (err) => {
+      this._onError(err)
+      this._fetchStatus({ diff: false })
+    })
+
+    this.socket = socket
+  }
+
+  _onError (err) {
+    this.emit('error', Object.assign(new Error(err.message), err))
+  }
+
+  /**
+   * Begin polling for assembly status changes. This sends a request to the
+   * assembly status endpoint every so often, if the socket is not connected.
+   * If the socket connection fails or takes a long time, we won't miss any
+   * events.
+   */
+  _beginPolling () {
+    this.pollInterval = setInterval(() => {
+      if (!this.socket || !this.socket.connected) {
+        this._fetchStatus()
+      }
+    }, 2000)
+  }
+
+  /**
+   * Reload assembly status. Useful if the socket doesn't work.
+   *
+   * Pass `diff: false` to avoid emitting diff events, instead only emitting
+   * 'status'.
+   */
+  _fetchStatus ({ diff = true } = {}) {
+    return fetch(this.status.assembly_url)
+      .then((response) => response.json())
+      .then((status) => {
+        // Avoid updating if we closed during this request's lifetime.
+        if (this.closed) return
+        this.emit('status', status)
+
+        if (diff) {
+          this.updateStatus(status)
+        } else {
+          this.status = status
+        }
+      })
+  }
+
+  update () {
+    return this._fetchStatus({ diff: true })
+  }
+
+  /**
+   * Update this assembly's status with a full new object. Events will be
+   * emitted for status changes, new files, and new results.
+   *
+   * @param {Object} next The new assembly status object.
+   */
+  updateStatus (next) {
+    this._diffStatus(this.status, next)
+    this.status = next
+  }
+
+  /**
+   * Diff two assembly statuses, and emit the events necessary to go from `prev`
+   * to `next`.
+   *
+   * @param {Object} prev The previous assembly status.
+   * @param {Object} next The new assembly status.
+   */
+  _diffStatus (prev, next) {
+    const prevStatus = prev.ok
+    const nextStatus = next.ok
+
+    if (next.error && !prev.error) {
+      return this._onError(next)
+    }
+
+    // Desired emit order:
+    //  - executing
+    //  - (n × upload)
+    //  - metadata
+    //  - (m × result)
+    //  - finished
+    // The below checks run in this order, that way even if we jump from
+    // UPLOADING straight to FINISHED all the events are emitted as expected.
+
+    const nowExecuting =
+      isStatus(nextStatus, ASSEMBLY_EXECUTING) &&
+      !isStatus(prevStatus, ASSEMBLY_EXECUTING)
+    if (nowExecuting) {
+      // Without WebSockets, this is our only way to tell if uploading finished.
+      // Hence, we emit this just before the 'upload's and before the 'metadata'
+      // event for the most intuitive ordering, corresponding to the _usual_
+      // ordering (if not guaranteed) that you'd get on the WebSocket.
+      this.emit('executing')
+    }
+
+    // Find new uploaded files.
+    Object.keys(next.uploads)
+      .filter((upload) => (
+        !prev.uploads.hasOwnProperty(upload)
+      ))
+      .map((upload) => next.uploads[upload])
+      .forEach((upload) => {
+        this.emit('upload', upload)
+      })
+
+    if (nowExecuting) {
+      this.emit('metadata')
+    }
+
+    // Find new results.
+    Object.keys(next.results).forEach((stepName) => {
+      const nextResults = next.results[stepName]
+      const prevResults = prev.results[stepName]
+
+      nextResults
+        .filter((n) => !prevResults || !prevResults.some((p) => p.id === n.id))
+        .forEach((result) => {
+          this.emit('result', stepName, result)
+        })
+    })
+
+    if (isStatus(nextStatus, ASSEMBLY_COMPLETED) &&
+        !isStatus(prevStatus, ASSEMBLY_COMPLETED)) {
+      this.emit('finished')
+    }
+  }
+
+  /**
+   * Stop updating this assembly.
+   */
+  close () {
+    this.closed = true
+    if (this.socket) {
+      this.socket.disconnect()
+      this.socket = null
+    }
+    clearInterval(this.pollInterval)
+  }
+}
+
+module.exports = TransloaditAssembly

+ 182 - 0
packages/@uppy/transloadit/src/Assembly.test.js

@@ -0,0 +1,182 @@
+const Assembly = require('./Assembly')
+
+describe('Transloadit/Assembly', () => {
+  describe('status diffing', () => {
+    function attemptDiff (prev, next) {
+      const assembly = new Assembly(prev)
+      const events = []
+      assembly.emit = jest.fn((name, ...args) => {
+        events.push([name, ...args])
+      })
+
+      assembly.updateStatus(next)
+
+      return events
+    }
+
+    it('ASSEMBLY_UPLOADING → ASSEMBLY_EXECUTING', () => {
+      const result = attemptDiff({
+        ok: 'ASSEMBLY_UPLOADING',
+        uploads: {},
+        results: {}
+      }, {
+        ok: 'ASSEMBLY_EXECUTING',
+        uploads: {},
+        results: {}
+      })
+
+      expect(result[0]).toEqual(['executing'])
+    })
+
+    it('ASSEMBLY_EXECUTING → ASSEMBLY_COMPLETED', () => {
+      const result = attemptDiff({
+        ok: 'ASSEMBLY_EXECUTING',
+        uploads: {},
+        results: {}
+      }, {
+        ok: 'ASSEMBLY_COMPLETED',
+        uploads: {},
+        results: {}
+      })
+
+      expect(result[0]).toEqual(['finished'])
+    })
+
+    it('ASSEMBLY_UPLOADING → ASSEMBLY_COMPLETED', () => {
+      const result = attemptDiff({
+        ok: 'ASSEMBLY_UPLOADING',
+        uploads: {},
+        results: {}
+      }, {
+        ok: 'ASSEMBLY_COMPLETED',
+        uploads: {},
+        results: {}
+      })
+
+      expect(result[0]).toEqual(['executing'])
+      expect(result[1]).toEqual(['metadata'])
+      expect(result[2]).toEqual(['finished'])
+    })
+
+    it('emits events for new files', () => {
+      const result = attemptDiff({
+        ok: 'ASSEMBLY_UPLOADING',
+        uploads: {},
+        results: {}
+      }, {
+        ok: 'ASSEMBLY_UPLOADING',
+        uploads: {
+          some_id: { id: 'some_id' }
+        },
+        results: {}
+      })
+
+      expect(result[0]).toEqual(['upload', { id: 'some_id' }])
+    })
+
+    it('emits executing, then upload, on new files + status change', () => {
+      const result = attemptDiff({
+        ok: 'ASSEMBLY_UPLOADING',
+        uploads: {},
+        results: {}
+      }, {
+        ok: 'ASSEMBLY_EXECUTING',
+        uploads: {
+          some_id: { id: 'some_id' }
+        },
+        results: {}
+      })
+
+      expect(result[0]).toEqual(['executing'])
+      expect(result[1]).toEqual(['upload', { id: 'some_id' }])
+      expect(result[2]).toEqual(['metadata'])
+    })
+
+    it('emits new results', () => {
+      const one = {
+        ok: 'ASSEMBLY_EXECUTING',
+        uploads: {
+          cool_video: { id: 'cool_video' }
+        },
+        results: {}
+      }
+      const two = {
+        ok: 'ASSEMBLY_EXECUTING',
+        uploads: {
+          cool_video: { id: 'cool_video' }
+        },
+        results: {
+          step_one: [
+            { id: 'thumb1' },
+            { id: 'thumb2' },
+            { id: 'thumb3' }
+          ]
+        }
+      }
+      const three = {
+        ok: 'ASSEMBLY_EXECUTING',
+        uploads: {
+          cool_video: { id: 'cool_video' }
+        },
+        results: {
+          step_one: [
+            { id: 'thumb1' },
+            { id: 'thumb2' },
+            { id: 'thumb3' },
+            { id: 'thumb4' }
+          ],
+          step_two: [
+            { id: 'transcript' }
+          ]
+        }
+      }
+
+      const resultOne = attemptDiff(one, two)
+      const resultTwo = attemptDiff(two, three)
+
+      expect(resultOne[0]).toEqual(['result', 'step_one', { id: 'thumb1' }])
+      expect(resultOne[1]).toEqual(['result', 'step_one', { id: 'thumb2' }])
+      expect(resultOne[2]).toEqual(['result', 'step_one', { id: 'thumb3' }])
+
+      expect(resultTwo[0]).toEqual(['result', 'step_one', { id: 'thumb4' }])
+      expect(resultTwo[1]).toEqual(['result', 'step_two', { id: 'transcript' }])
+    })
+
+    it('emits correctly jumping straight from uploading to finished', () => {
+      const start = {
+        ok: 'ASSEMBLY_UPLOADING',
+        uploads: {},
+        results: {}
+      }
+      const end = {
+        ok: 'ASSEMBLY_COMPLETED',
+        uploads: {
+          cool_video: { id: 'cool_video' }
+        },
+        results: {
+          step_one: [
+            { id: 'thumb1' },
+            { id: 'thumb2' },
+            { id: 'thumb3' },
+            { id: 'thumb4' }
+          ],
+          step_two: [
+            { id: 'transcript' }
+          ]
+        }
+      }
+
+      const result = attemptDiff(start, end)
+
+      expect(result[0]).toEqual(['executing'])
+      expect(result[1]).toEqual(['upload', { id: 'cool_video' }])
+      expect(result[2]).toEqual(['metadata'])
+      expect(result[3]).toEqual(['result', 'step_one', { id: 'thumb1' }])
+      expect(result[4]).toEqual(['result', 'step_one', { id: 'thumb2' }])
+      expect(result[5]).toEqual(['result', 'step_one', { id: 'thumb3' }])
+      expect(result[6]).toEqual(['result', 'step_one', { id: 'thumb4' }])
+      expect(result[7]).toEqual(['result', 'step_two', { id: 'transcript' }])
+      expect(result[8]).toEqual(['finished'])
+    })
+  })
+})

+ 136 - 0
packages/@uppy/transloadit/src/AssemblyOptions.js

@@ -0,0 +1,136 @@
+/**
+ * Check that Assembly parameters are present and include all required fields.
+ */
+function validateParams (params) {
+  if (!params) {
+    throw new Error('Transloadit: The `params` option is required.')
+  }
+
+  if (typeof params === 'string') {
+    try {
+      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
+    }
+  }
+
+  if (!params.auth || !params.auth.key) {
+    throw new Error('Transloadit: The `params.auth.key` option is required. ' +
+      'You can find your Transloadit API key at https://transloadit.com/accounts/credentials.')
+  }
+}
+
+/**
+ * Turn Transloadit plugin options and a list of files into a list of Assembly
+ * options.
+ */
+class AssemblyOptions {
+  constructor (files, opts) {
+    this.files = files
+    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) {
+    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
+        }
+      })
+  }
+
+  /**
+   * 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]
+        }
+      }
+    })
+
+    return Object.keys(dedupeMap).map((id) => dedupeMap[id])
+  }
+
+  /**
+   * Generate a set of Assemblies that will handle the upload.
+   * Returns a Promise for an object with keys:
+   *  - fileIDs - an array of file IDs to add to this Assembly
+   *  - options - Assembly options
+   */
+  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)
+      })
+    }
+
+    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
+        }]
+      })
+    }
+
+    // If there are no files and we do not `alwaysRunAssembly`,
+    // don't do anything.
+    return Promise.resolve([])
+  }
+}
+
+module.exports = Object.assign(AssemblyOptions, { validateParams })

+ 105 - 0
packages/@uppy/transloadit/src/AssemblyOptions.test.js

@@ -0,0 +1,105 @@
+const AssemblyOptions = require('./AssemblyOptions')
+
+describe('Transloadit/AssemblyOptions', () => {
+  it('Validates response from getAssemblyOptions()', () => {
+    const options = new AssemblyOptions([
+      { name: 'testfile' }
+    ], {
+      getAssemblyOptions: (file) => {
+        expect(file.name).toBe('testfile')
+        return {
+          params: '{"some":"json"}'
+        }
+      }
+    })
+
+    return expect(options.build()).rejects.toThrow(
+      /The `params\.auth\.key` option is required/
+    )
+  })
+
+  it('Uses different assemblies for different params', () => {
+    const data = Buffer.alloc(10)
+    data.size = data.byteLength
+
+    const options = new AssemblyOptions([
+      { name: 'a.png', data },
+      { name: 'b.png', data },
+      { name: 'c.png', data },
+      { name: 'd.png', data }
+    ], {
+      getAssemblyOptions: (file) => ({
+        params: {
+          auth: { key: 'fake key' },
+          steps: {
+            fake_step: { data: file.name }
+          }
+        }
+      })
+    })
+
+    return options.build().then((assemblies) => {
+      expect(assemblies).toHaveLength(4)
+      expect(assemblies[0].options.params.steps.fake_step.data).toBe('a.png')
+      expect(assemblies[1].options.params.steps.fake_step.data).toBe('b.png')
+      expect(assemblies[2].options.params.steps.fake_step.data).toBe('c.png')
+      expect(assemblies[3].options.params.steps.fake_step.data).toBe('d.png')
+    })
+  })
+
+  it('Should merge files with same parameters into one Assembly', () => {
+    const data = Buffer.alloc(10)
+    const data2 = Buffer.alloc(20)
+
+    const options = new AssemblyOptions([
+      { name: 'a.png', data, size: data.byteLength },
+      { name: 'b.png', data, size: data.byteLength },
+      { name: 'c.png', data, size: data.byteLength },
+      { name: 'd.png', data: data2, size: data2.byteLength }
+    ], {
+      getAssemblyOptions: (file) => ({
+        params: {
+          auth: { key: 'fake key' },
+          steps: {
+            fake_step: { data: file.size }
+          }
+        }
+      })
+    })
+
+    return options.build().then((assemblies) => {
+      expect(assemblies).toHaveLength(2)
+      expect(assemblies[0].fileIDs).toHaveLength(3)
+      expect(assemblies[1].fileIDs).toHaveLength(1)
+      expect(assemblies[0].options.params.steps.fake_step.data).toBe(10)
+      expect(assemblies[1].options.params.steps.fake_step.data).toBe(20)
+    })
+  })
+
+  it('Does not create an Assembly if no files are being uploaded', () => {
+    const options = new AssemblyOptions([], {
+      getAssemblyOptions () {
+        throw new Error('should not create Assembly')
+      }
+    })
+
+    return expect(options.build()).resolves.toEqual([])
+  })
+
+  it('Creates an Assembly if no files are being uploaded but `alwaysRunAssembly` is enabled', () => {
+    const options = new AssemblyOptions([], {
+      alwaysRunAssembly: true,
+      getAssemblyOptions (file) {
+        expect(file).toBe(null)
+        return {
+          params: {
+            auth: { key: 'fake key' },
+            template_id: 'example'
+          }
+        }
+      }
+    })
+
+    return expect(options.build()).resolves.toHaveLength(1)
+  })
+})

+ 98 - 0
packages/@uppy/transloadit/src/AssemblyWatcher.js

@@ -0,0 +1,98 @@
+const Emitter = require('component-emitter')
+
+/**
+ * Track completion of multiple assemblies.
+ *
+ * Emits 'assembly-complete' when an assembly completes.
+ * Emits 'assembly-error' when an assembly fails.
+ * Exposes a `.promise` property that resolves when all assemblies have
+ * completed (or failed).
+ */
+class TransloaditAssemblyWatcher extends Emitter {
+  constructor (uppy, assemblyIDs) {
+    super()
+
+    this._uppy = uppy
+    this._assemblyIDs = assemblyIDs
+    this._remaining = assemblyIDs.length
+
+    this.promise = new Promise((resolve, reject) => {
+      this._resolve = resolve
+      this._reject = reject
+    })
+
+    this._onAssemblyComplete = this._onAssemblyComplete.bind(this)
+    this._onAssemblyError = this._onAssemblyError.bind(this)
+    this._onImportError = this._onImportError.bind(this)
+
+    this._addListeners()
+  }
+
+  /**
+   * Are we watching this assembly ID?
+   */
+  _watching (id) {
+    return this._assemblyIDs.indexOf(id) !== -1
+  }
+
+  _onAssemblyComplete (assembly) {
+    if (!this._watching(assembly.assembly_id)) {
+      return
+    }
+
+    this._uppy.log(`[Transloadit] AssemblyWatcher: Got Assembly finish ${assembly.assembly_id}`)
+
+    this.emit('assembly-complete', assembly.assembly_id)
+
+    this._checkAllComplete()
+  }
+
+  _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.emit('assembly-error', assembly.assembly_id, error)
+
+    this._checkAllComplete()
+  }
+
+  _onImportError (assembly, fileID, error) {
+    if (!this._watching(assembly.assembly_id)) {
+      return
+    }
+
+    // Not sure if we should be doing something when it's just one file failing.
+    // ATM, the only options are 1) ignoring or 2) failing the entire upload.
+    // 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)
+  }
+
+  _checkAllComplete () {
+    this._remaining -= 1
+    if (this._remaining === 0) {
+      // We're done, these listeners can be removed
+      this._removeListeners()
+      this._resolve()
+    }
+  }
+
+  _removeListeners () {
+    this._uppy.off('transloadit:complete', this._onAssemblyComplete)
+    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-error', this._onAssemblyError)
+    this._uppy.on('transloadit:import-error', this._onImportError)
+  }
+}
+
+module.exports = TransloaditAssemblyWatcher

+ 0 - 64
packages/@uppy/transloadit/src/Socket.js

@@ -1,64 +0,0 @@
-const io = require('socket.io-client')
-const Emitter = require('namespace-emitter')
-const parseUrl = require('url-parse')
-
-/**
- * WebSocket status API client for Transloadit.
- */
-module.exports = class TransloaditSocket {
-  constructor (url, assembly) {
-    const emitter = Emitter()
-    this.on = emitter.on.bind(emitter)
-    this.off = emitter.off.bind(emitter)
-    this.emit = emitter.emit.bind(emitter)
-
-    const parsed = parseUrl(url)
-
-    this.assembly = assembly
-    this.socket = io.connect(parsed.origin, {
-      path: parsed.pathname
-    })
-
-    this.attachDefaultHandlers()
-  }
-
-  attachDefaultHandlers () {
-    this.socket.on('connect', () => {
-      this.socket.emit('assembly_connect', {
-        id: this.assembly.assembly_id
-      })
-
-      this.emit('connect')
-    })
-
-    this.socket.on('assembly_finished', () => {
-      this.emit('finished')
-
-      this.close()
-    })
-
-    this.socket.on('assembly_upload_finished', (file) => {
-      this.emit('upload', file)
-    })
-
-    this.socket.on('assembly_uploading_finished', () => {
-      this.emit('executing')
-    })
-
-    this.socket.on('assembly_upload_meta_data_extracted', () => {
-      this.emit('metadata')
-    })
-
-    this.socket.on('assembly_result_finished', (stepName, result) => {
-      this.emit('result', stepName, result)
-    })
-
-    this.socket.on('assembly_error', (err) => {
-      this.emit('error', Object.assign(new Error(err.message), err))
-    })
-  }
-
-  close () {
-    this.socket.disconnect()
-  }
-}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 275 - 456
packages/@uppy/transloadit/src/index.js


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

@@ -31,140 +31,6 @@ describe('Transloadit', () => {
     }).not.toThrowError(/The `params\.auth\.key` option is required/)
   })
 
-  it('Validates response from getAssemblyOptions()', () => {
-    const uppy = new Core()
-
-    uppy.use(Transloadit, {
-      getAssemblyOptions: (file) => {
-        expect(file.name).toBe('testfile')
-        return {
-          params: '{"some":"json"}'
-        }
-      }
-    })
-
-    const data = Buffer.alloc(4000)
-    data.size = data.byteLength
-    uppy.addFile({
-      name: 'testfile',
-      data
-    })
-    return uppy.upload().then(() => {
-      throw new Error('should have rejected')
-    }, (err) => {
-      expect(err.message).toMatch(/The `params\.auth\.key` option is required/)
-    })
-  })
-
-  it('Uses different assemblies for different params', () => {
-    const uppy = new Core()
-
-    uppy.use(Transloadit, {
-      getAssemblyOptions: (file) => ({
-        params: {
-          auth: { key: 'fake key' },
-          steps: {
-            fake_step: { data: file.name }
-          }
-        }
-      })
-    })
-
-    const tl = uppy.getPlugin('Transloadit')
-    const files = ['a.png', 'b.png', 'c.png', 'd.png']
-    let i = 0
-    tl.client.createAssembly = (opts) => {
-      expect(opts.params.steps.fake_step.data).toEqual(files[i])
-      i++
-      // Short-circuit upload
-      return Promise.reject('short-circuit') // eslint-disable-line prefer-promise-reject-errors
-    }
-
-    const data = Buffer.alloc(10)
-    data.size = data.byteLength
-
-    uppy.addFile({ name: 'a.png', data })
-    uppy.addFile({ name: 'b.png', data })
-    uppy.addFile({ name: 'c.png', data })
-    uppy.addFile({ name: 'd.png', data })
-
-    return uppy.upload().then(() => {
-      throw new Error('upload should have been rejected')
-    }, () => {
-      expect(i).toBe(4)
-    })
-  })
-
-  it('Should merge files with same parameters into one Assembly', () => {
-    const uppy = new Core()
-
-    uppy.use(Transloadit, {
-      getAssemblyOptions: (file) => ({
-        params: {
-          auth: { key: 'fake key' },
-          steps: {
-            fake_step: { data: file.size }
-          }
-        }
-      })
-    })
-
-    const tl = uppy.getPlugin('Transloadit')
-    const assemblies = [
-      { data: 10, files: ['a.png', 'b.png', 'c.png'] },
-      { data: 20, files: ['d.png'] }
-    ]
-    let i = 0
-    tl.client.createAssembly = (opts) => {
-      const assembly = assemblies[i]
-      expect(opts.params.steps.fake_step.data).toBe(assembly.data)
-      i++
-      // Short-circuit upload
-      return Promise.reject('short-circuit') // eslint-disable-line prefer-promise-reject-errors
-    }
-
-    const data = Buffer.alloc(10)
-    data.size = data.byteLength
-    const data2 = Buffer.alloc(20)
-    data2.size = data2.byteLength
-
-    uppy.addFile({ name: 'a.png', data })
-    uppy.addFile({ name: 'b.png', data })
-    uppy.addFile({ name: 'c.png', data })
-    uppy.addFile({ name: 'd.png', data: data2 })
-
-    return uppy.upload().then(() => {
-      throw new Error('Upload should have been rejected')
-    }, () => {
-      expect(i).toBe(2)
-    })
-  })
-
-  it('Does not create an Assembly if no files are being uploaded', () => {
-    const uppy = new Core()
-    uppy.use(Transloadit, {
-      getAssemblyOptions () {
-        throw new Error('should not create Assembly')
-      }
-    })
-
-    return uppy.upload()
-  })
-
-  it('Creates an Assembly if no files are being uploaded but `alwaysRunAssembly` is enabled', () => {
-    const uppy = new Core()
-    uppy.use(Transloadit, {
-      alwaysRunAssembly: true,
-      getAssemblyOptions (file) {
-        // should call getAssemblyOptions with `null`
-        expect(file).toBe(null)
-        return Promise.reject('short-circuited') // eslint-disable-line prefer-promise-reject-errors
-      }
-    })
-
-    return expect(uppy.upload()).rejects.toEqual(new Error('short-circuited'))
-  })
-
   it('Does not leave lingering progress if getAssemblyOptions fails', () => {
     const uppy = new Core()
     uppy.use(Transloadit, {

+ 19 - 0
packages/@uppy/transloadit/src/parseUrl.js

@@ -0,0 +1,19 @@
+module.exports = function parseUrl (url) {
+  const scheme = /^\w+:\/\//.exec(url)
+  let i = 0
+  if (scheme) {
+    i = scheme[0].length + 1
+  }
+  const slashIndex = url.indexOf('/', i)
+  if (slashIndex === -1) {
+    return {
+      origin: url,
+      pathname: '/'
+    }
+  }
+
+  return {
+    origin: url.slice(0, slashIndex),
+    pathname: url.slice(slashIndex)
+  }
+}

+ 17 - 0
packages/@uppy/transloadit/src/parseUrl.test.js

@@ -0,0 +1,17 @@
+const parseUrl = require('./parseUrl')
+
+describe('Transloadit/parseUrl', () => {
+  it('splits a url into origin and pathname', () => {
+    expect(parseUrl('http://api2.transloadit.com/ws2012')).toEqual({
+      origin: 'http://api2.transloadit.com',
+      pathname: '/ws2012'
+    })
+  })
+
+  it('defaults to pathname=/ if absent', () => {
+    expect(parseUrl('http://api2.transloadit.com')).toEqual({
+      origin: 'http://api2.transloadit.com',
+      pathname: '/'
+    })
+  })
+})

+ 6 - 3
packages/@uppy/tus/src/index.js

@@ -334,12 +334,15 @@ module.exports = class Tus extends Plugin {
     })
   }
 
+  /**
+   * Store the uploadUrl on the file options, so that when Golden Retriever
+   * restores state, we will continue uploading to the correct URL.
+   */
   onReceiveUploadUrl (file, uploadURL) {
     const currentFile = this.uppy.getFile(file.id)
     if (!currentFile) return
-    // Only do the update if we didn't have an upload URL yet,
-    // or resume: false in options
-    if ((!currentFile.tus || currentFile.tus.uploadUrl !== uploadURL) && this.opts.resume) {
+    // Only do the update if we didn't have an upload URL yet.
+    if (!currentFile.tus || currentFile.tus.uploadUrl !== uploadURL) {
       this.uppy.log('[Tus] Storing upload url')
       this.uppy.setFileState(currentFile.id, {
         tus: Object.assign({}, currentFile.tus, {

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä