index.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. const got = require('got').default
  2. const Provider = require('../Provider')
  3. const adaptData = require('./adapter')
  4. const { withProviderErrorHandling } = require('../providerErrors')
  5. const { prepareStream } = require('../../helpers/utils')
  6. const BOX_FILES_FIELDS = 'id,modified_at,name,permissions,size,type'
  7. const BOX_THUMBNAIL_SIZE = 256
  8. const getClient = ({ token }) => got.extend({
  9. prefixUrl: 'https://api.box.com/2.0',
  10. headers: {
  11. authorization: `Bearer ${token}`,
  12. },
  13. })
  14. async function getUserInfo ({ token }) {
  15. return getClient({ token }).get('users/me', { responseType: 'json' }).json()
  16. }
  17. async function list ({ directory, query, token }) {
  18. const rootFolderID = '0'
  19. // https://developer.box.com/reference/resources/items/
  20. return getClient({ token }).get(`folders/${directory || rootFolderID}/items`, { searchParams: { fields: BOX_FILES_FIELDS, offset: query.cursor, limit: 1000 }, responseType: 'json' }).json()
  21. }
  22. /**
  23. * Adapter for API https://developer.box.com/reference/
  24. */
  25. class Box extends Provider {
  26. constructor (options) {
  27. super(options)
  28. // needed for the thumbnails fetched via companion
  29. this.needsCookieAuth = true
  30. }
  31. static get authProvider () {
  32. return 'box'
  33. }
  34. /**
  35. * Lists files and folders from Box API
  36. *
  37. * @param {object} options
  38. * @param {string} options.directory
  39. * @param {any} options.query
  40. * @param {string} options.token
  41. * @param {unknown} options.companion
  42. */
  43. async list ({ directory, token, query, companion }) {
  44. return this.#withErrorHandling('provider.box.list.error', async () => {
  45. const [userInfo, files] = await Promise.all([
  46. getUserInfo({ token }),
  47. list({ directory, query, token }),
  48. ])
  49. return adaptData(files, userInfo.login, companion)
  50. })
  51. }
  52. async download ({ id, token }) {
  53. return this.#withErrorHandling('provider.box.download.error', async () => {
  54. const stream = getClient({ token }).stream.get(`files/${id}/content`, { responseType: 'json' })
  55. await prepareStream(stream)
  56. return { stream }
  57. })
  58. }
  59. async thumbnail ({ id, token }) {
  60. return this.#withErrorHandling('provider.box.thumbnail.error', async () => {
  61. const extension = 'jpg' // you can set this to png to more easily reproduce http 202 retry-after
  62. // From box API docs:
  63. // Sometimes generating a thumbnail can take a few seconds.
  64. // In these situations the API returns a Location-header pointing to a placeholder graphic
  65. // for this file type.
  66. // The placeholder graphic can be used in a user interface until the thumbnail generation has completed.
  67. // The Retry-After-header indicates when to the thumbnail will be ready.
  68. // At that time, retry this endpoint to retrieve the thumbnail.
  69. //
  70. // This can be reproduced more easily by changing extension to png and trying on a newly uploaded image
  71. const stream = getClient({ token }).stream.get(`files/${id}/thumbnail.${extension}`, {
  72. searchParams: { max_height: BOX_THUMBNAIL_SIZE, max_width: BOX_THUMBNAIL_SIZE },
  73. responseType: 'json',
  74. })
  75. await prepareStream(stream)
  76. return { stream, contentType: 'image/jpeg' }
  77. })
  78. }
  79. async size ({ id, token }) {
  80. return this.#withErrorHandling('provider.box.size.error', async () => {
  81. const { size } = await getClient({ token }).get(`files/${id}`, { responseType: 'json' }).json()
  82. return parseInt(size, 10)
  83. })
  84. }
  85. logout ({ companion, token }) {
  86. return this.#withErrorHandling('provider.box.logout.error', async () => {
  87. const { key, secret } = companion.options.providerOptions.box
  88. await getClient({ token }).post('oauth2/revoke', {
  89. prefixUrl: 'https://api.box.com',
  90. form: {
  91. client_id: key,
  92. client_secret: secret,
  93. token,
  94. },
  95. responseType: 'json',
  96. })
  97. return { revoked: true }
  98. })
  99. }
  100. // eslint-disable-next-line class-methods-use-this
  101. async #withErrorHandling (tag, fn) {
  102. return withProviderErrorHandling({
  103. fn,
  104. tag,
  105. providerName: Box.authProvider,
  106. isAuthError: (response) => response.statusCode === 401,
  107. getJsonErrorMessage: (body) => body?.message,
  108. })
  109. }
  110. }
  111. module.exports = Box