Multipart.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. const Plugin = require('./Plugin')
  2. const UppySocket = require('../core/UppySocket')
  3. const Utils = require('../core/Utils')
  4. module.exports = class Multipart extends Plugin {
  5. constructor (core, opts) {
  6. super(core, opts)
  7. this.type = 'uploader'
  8. this.id = 'Multipart'
  9. this.title = 'Multipart'
  10. // Default options
  11. const defaultOptions = {
  12. fieldName: 'files[]',
  13. metaFields: null,
  14. responseUrlFieldName: 'url',
  15. bundle: true,
  16. headers: {},
  17. getUploadUrl (xhr) {
  18. const resp = JSON.parse(xhr.response)
  19. return resp[this.responseUrlFieldName]
  20. }
  21. }
  22. // Merge default options with the ones set by user
  23. this.opts = Object.assign({}, defaultOptions, opts)
  24. this.handleUpload = this.handleUpload.bind(this)
  25. }
  26. upload (file, current, total) {
  27. const opts = Object.assign({}, this.opts, file.multipart || {})
  28. this.core.log(`uploading ${current} of ${total}`)
  29. return new Promise((resolve, reject) => {
  30. // turn file into an array so we can use bundle
  31. // if (!opts.bundle) {
  32. // files = [files[current]]
  33. // }
  34. // for (let i in files) {
  35. // formPost.append(opts.fieldName, files[i])
  36. // }
  37. const formPost = new FormData()
  38. const metaFields = Array.isArray(opts.metaFields)
  39. ? opts.metaFields
  40. // Send along all fields by default.
  41. : Object.keys(file.meta)
  42. metaFields.forEach((item) => {
  43. formPost.append(item, file.meta[item])
  44. })
  45. formPost.append(opts.fieldName, file.data)
  46. const xhr = new XMLHttpRequest()
  47. xhr.upload.addEventListener('progress', (ev) => {
  48. if (ev.lengthComputable) {
  49. this.core.emitter.emit('core:upload-progress', {
  50. uploader: this,
  51. id: file.id,
  52. bytesUploaded: ev.loaded,
  53. bytesTotal: ev.total
  54. })
  55. }
  56. })
  57. xhr.addEventListener('load', (ev) => {
  58. if (ev.target.status >= 200 && ev.target.status < 300) {
  59. const uploadURL = opts.getUploadUrl(xhr)
  60. this.core.emitter.emit('core:upload-success', file.id, uploadURL)
  61. if (uploadURL) {
  62. this.core.log(`Download ${file.name} from ${file.uploadURL}`)
  63. }
  64. return resolve(file)
  65. } else {
  66. this.core.emitter.emit('core:upload-error', file.id, xhr)
  67. return reject('Upload error')
  68. }
  69. // var upload = {}
  70. //
  71. // if (opts.bundle) {
  72. // upload = {files: files}
  73. // } else {
  74. // upload = {file: files[current]}
  75. // }
  76. })
  77. xhr.addEventListener('error', (ev) => {
  78. this.core.emitter.emit('core:upload-error', file.id)
  79. return reject('Upload error')
  80. })
  81. xhr.open('POST', opts.endpoint, true)
  82. Object.keys(opts.headers).forEach((header) => {
  83. xhr.setRequestHeader(header, opts.headers[header])
  84. })
  85. xhr.send(formPost)
  86. this.core.emitter.on('core:upload-cancel', (fileID) => {
  87. if (fileID === file.id) {
  88. xhr.abort()
  89. }
  90. })
  91. this.core.emitter.on('core:cancel-all', () => {
  92. // const files = this.core.getState().files
  93. // if (!files[file.id]) return
  94. xhr.abort()
  95. })
  96. this.core.emitter.emit('core:upload-started', file.id)
  97. })
  98. }
  99. uploadRemote (file, current, total) {
  100. const opts = Object.assign({}, this.opts, file.multipart || {})
  101. return new Promise((resolve, reject) => {
  102. this.core.emitter.emit('core:upload-started', file.id)
  103. fetch(file.remote.url, {
  104. method: 'post',
  105. credentials: 'include',
  106. headers: {
  107. 'Accept': 'application/json',
  108. 'Content-Type': 'application/json'
  109. },
  110. body: JSON.stringify(Object.assign({}, file.remote.body, {
  111. endpoint: opts.endpoint,
  112. size: file.data.size,
  113. fieldname: opts.fieldName
  114. }))
  115. })
  116. .then((res) => {
  117. if (res.status < 200 && res.status > 300) {
  118. return reject(res.statusText)
  119. }
  120. res.json().then((data) => {
  121. const token = data.token
  122. const host = Utils.getSocketHost(file.remote.host)
  123. const socket = new UppySocket({ target: `${host}/api/${token}` })
  124. socket.on('progress', (progressData) => Utils.emitSocketProgress(this, progressData, file))
  125. socket.on('success', (data) => {
  126. this.core.emitter.emit('core:upload-success', file.id, data.url)
  127. socket.close()
  128. return resolve()
  129. })
  130. })
  131. })
  132. })
  133. }
  134. selectForUpload (files) {
  135. files.forEach((file, i) => {
  136. const current = parseInt(i, 10) + 1
  137. const total = files.length
  138. if (file.isRemote) {
  139. this.uploadRemote(file, current, total)
  140. } else {
  141. this.upload(file, current, total)
  142. }
  143. })
  144. // if (this.opts.bundle) {
  145. // uploaders.push(this.upload(files, 0, files.length))
  146. // } else {
  147. // for (let i in files) {
  148. // uploaders.push(this.upload(files, i, files.length))
  149. // }
  150. // }
  151. }
  152. handleUpload (fileIDs) {
  153. if (fileIDs.length === 0) {
  154. this.core.log('Multipart: no files to upload!')
  155. return Promise.resolve()
  156. }
  157. this.core.log('Multipart is uploading...')
  158. const files = fileIDs.map(getFile, this)
  159. function getFile (fileID) {
  160. return this.core.state.files[fileID]
  161. }
  162. this.selectForUpload(files)
  163. return new Promise((resolve) => {
  164. this.core.bus.once('core:upload-complete', resolve)
  165. })
  166. }
  167. install () {
  168. this.core.addUploader(this.handleUpload)
  169. }
  170. uninstall () {
  171. this.core.removeUploader(this.handleUpload)
  172. }
  173. }