index.js 5.5 KB

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