index.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. const request = require('request')
  2. const purest = require('purest')({ request })
  3. const logger = require('../../logger')
  4. const adapter = require('./adapter')
  5. const AuthError = require('../error')
  6. // From https://www.dropbox.com/developers/reference/json-encoding:
  7. //
  8. // This function is simple and has OK performance compared to more
  9. // complicated ones: http://jsperf.com/json-escape-unicode/4
  10. const charsToEncode = /[\u007f-\uffff]/g
  11. function httpHeaderSafeJson (v) {
  12. return JSON.stringify(v).replace(charsToEncode,
  13. function (c) {
  14. return '\\u' + ('000' + c.charCodeAt(0).toString(16)).slice(-4)
  15. }
  16. )
  17. }
  18. class DropBox {
  19. constructor (options) {
  20. this.authProvider = options.provider = DropBox.authProvider
  21. this.client = purest(options)
  22. }
  23. static get authProvider () {
  24. return 'dropbox'
  25. }
  26. _userInfo ({ token }, done) {
  27. this.client
  28. .post('users/get_current_account')
  29. .options({ version: '2' })
  30. .auth(token)
  31. .request(done)
  32. }
  33. /**
  34. * Makes 2 requests in parallel - 1. to get files, 2. to get user email
  35. * it then waits till both requests are done before proceeding with the callback
  36. *
  37. * @param {object} options
  38. * @param {function} done
  39. */
  40. list (options, done) {
  41. let userInfoDone = false
  42. let statsDone = false
  43. let userInfo
  44. let stats
  45. let reqErr
  46. const finishReq = () => {
  47. if (reqErr || stats.statusCode !== 200) {
  48. const err = this._error(reqErr, stats)
  49. logger.error(err, 'provider.dropbox.list.error')
  50. done(err)
  51. } else {
  52. stats.body.user_email = userInfo.body.email
  53. done(null, this.adaptData(stats.body, options.uppy))
  54. }
  55. }
  56. this.stats(options, (err, resp) => {
  57. statsDone = true
  58. stats = resp
  59. reqErr = reqErr || err
  60. if (userInfoDone) {
  61. finishReq()
  62. }
  63. })
  64. this._userInfo(options, (err, resp) => {
  65. userInfoDone = true
  66. userInfo = resp
  67. reqErr = reqErr || err
  68. if (statsDone) {
  69. finishReq()
  70. }
  71. })
  72. }
  73. stats ({ directory, query, token }, done) {
  74. this.client
  75. .post('files/list_folder')
  76. .options({version: '2'})
  77. .where(query)
  78. .auth(token)
  79. .json({
  80. path: `${directory || ''}`,
  81. include_media_info: true
  82. })
  83. .request(done)
  84. }
  85. download ({ id, token }, onData) {
  86. return this.client
  87. .post('https://content.dropboxapi.com/2/files/download')
  88. .options({
  89. version: '2',
  90. headers: {
  91. 'Dropbox-API-Arg': httpHeaderSafeJson({path: `${id}`})
  92. }
  93. })
  94. .auth(token)
  95. .request()
  96. .on('data', onData)
  97. .on('end', () => onData(null))
  98. .on('error', (err) => {
  99. logger.error(err, 'provider.dropbox.download.error')
  100. })
  101. }
  102. thumbnail ({id, token}, done) {
  103. return this.client
  104. .post('https://content.dropboxapi.com/2/files/get_thumbnail')
  105. .options({
  106. version: '2',
  107. headers: {
  108. 'Dropbox-API-Arg': httpHeaderSafeJson({path: `${id}`})
  109. }
  110. })
  111. .auth(token)
  112. .request()
  113. .on('response', (resp) => {
  114. if (resp.statusCode !== 200) {
  115. const err = this._error(null, resp)
  116. logger.error(err, 'provider.dropbox.thumbnail.error')
  117. return done(err)
  118. }
  119. done(null, resp)
  120. })
  121. .on('error', (err) => {
  122. logger.error(err, 'provider.dropbox.thumbnail.error')
  123. })
  124. }
  125. size ({id, token}, done) {
  126. return this.client
  127. .post('files/get_metadata')
  128. .options({ version: '2' })
  129. .auth(token)
  130. .json({
  131. path: id,
  132. include_media_info: true
  133. })
  134. .request((err, resp, body) => {
  135. if (err || resp.statusCode !== 200) {
  136. err = this._error(err, resp)
  137. logger.error(err, 'provider.dropbox.size.error')
  138. return done(err)
  139. }
  140. done(null, parseInt(body.size))
  141. })
  142. }
  143. adaptData (res, uppy) {
  144. const data = { username: adapter.getUsername(res), items: [] }
  145. const items = adapter.getItemSubList(res)
  146. items.forEach((item) => {
  147. data.items.push({
  148. isFolder: adapter.isFolder(item),
  149. icon: adapter.getItemIcon(item),
  150. name: adapter.getItemName(item),
  151. mimeType: adapter.getMimeType(item),
  152. id: adapter.getItemId(item),
  153. thumbnail: uppy.buildURL(adapter.getItemThumbnailUrl(item), true),
  154. requestPath: adapter.getItemRequestPath(item),
  155. modifiedDate: adapter.getItemModifiedDate(item)
  156. })
  157. })
  158. data.nextPagePath = null
  159. return data
  160. }
  161. _error (err, resp) {
  162. if (resp) {
  163. const errMsg = `request to ${this.authProvider} returned ${resp.statusCode}`
  164. return resp.statusCode === 401 ? new AuthError() : new Error(errMsg)
  165. }
  166. return err
  167. }
  168. }
  169. module.exports = DropBox