RequestClient.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. 'use strict'
  2. const AuthError = require('./AuthError')
  3. const fetchWithNetworkError = require('@uppy/utils/lib/fetchWithNetworkError')
  4. // Remove the trailing slash so we can always safely append /xyz.
  5. function stripSlash (url) {
  6. return url.replace(/\/$/, '')
  7. }
  8. module.exports = class RequestClient {
  9. static VERSION = require('../package.json').version
  10. constructor (uppy, opts) {
  11. this.uppy = uppy
  12. this.opts = opts
  13. this.onReceiveResponse = this.onReceiveResponse.bind(this)
  14. this.allowedHeaders = ['accept', 'content-type', 'uppy-auth-token']
  15. this.preflightDone = false
  16. }
  17. get hostname () {
  18. const { companion } = this.uppy.getState()
  19. const host = this.opts.companionUrl
  20. return stripSlash(companion && companion[host] ? companion[host] : host)
  21. }
  22. get defaultHeaders () {
  23. return {
  24. Accept: 'application/json',
  25. 'Content-Type': 'application/json',
  26. 'Uppy-Versions': `@uppy/companion-client=${RequestClient.VERSION}`,
  27. }
  28. }
  29. headers () {
  30. const userHeaders = this.opts.companionHeaders || this.opts.serverHeaders || {}
  31. return Promise.resolve({
  32. ...this.defaultHeaders,
  33. ...userHeaders,
  34. })
  35. }
  36. _getPostResponseFunc (skip) {
  37. return (response) => {
  38. if (!skip) {
  39. return this.onReceiveResponse(response)
  40. }
  41. return response
  42. }
  43. }
  44. onReceiveResponse (response) {
  45. const state = this.uppy.getState()
  46. const companion = state.companion || {}
  47. const host = this.opts.companionUrl
  48. const headers = response.headers
  49. // Store the self-identified domain name for the Companion instance we just hit.
  50. if (headers.has('i-am') && headers.get('i-am') !== companion[host]) {
  51. this.uppy.setState({
  52. companion: { ...companion, [host]: headers.get('i-am') },
  53. })
  54. }
  55. return response
  56. }
  57. _getUrl (url) {
  58. if (/^(https?:|)\/\//.test(url)) {
  59. return url
  60. }
  61. return `${this.hostname}/${url}`
  62. }
  63. _json (res) {
  64. if (res.status === 401) {
  65. throw new AuthError()
  66. }
  67. if (res.status < 200 || res.status > 300) {
  68. let errMsg = `Failed request with status: ${res.status}. ${res.statusText}`
  69. return res.json()
  70. .then((errData) => {
  71. errMsg = errData.message ? `${errMsg} message: ${errData.message}` : errMsg
  72. errMsg = errData.requestId ? `${errMsg} request-Id: ${errData.requestId}` : errMsg
  73. throw new Error(errMsg)
  74. }).catch(() => { throw new Error(errMsg) })
  75. }
  76. return res.json()
  77. }
  78. preflight (path) {
  79. if (this.preflightDone) {
  80. return Promise.resolve(this.allowedHeaders.slice())
  81. }
  82. return fetch(this._getUrl(path), {
  83. method: 'OPTIONS',
  84. })
  85. .then((response) => {
  86. if (response.headers.has('access-control-allow-headers')) {
  87. this.allowedHeaders = response.headers.get('access-control-allow-headers')
  88. .split(',').map((headerName) => headerName.trim().toLowerCase())
  89. }
  90. this.preflightDone = true
  91. return this.allowedHeaders.slice()
  92. })
  93. .catch((err) => {
  94. this.uppy.log(`[CompanionClient] unable to make preflight request ${err}`, 'warning')
  95. this.preflightDone = true
  96. return this.allowedHeaders.slice()
  97. })
  98. }
  99. preflightAndHeaders (path) {
  100. return Promise.all([this.preflight(path), this.headers()])
  101. .then(([allowedHeaders, headers]) => {
  102. // filter to keep only allowed Headers
  103. Object.keys(headers).forEach((header) => {
  104. if (allowedHeaders.indexOf(header.toLowerCase()) === -1) {
  105. this.uppy.log(`[CompanionClient] excluding unallowed header ${header}`)
  106. delete headers[header]
  107. }
  108. })
  109. return headers
  110. })
  111. }
  112. get (path, skipPostResponse) {
  113. return this.preflightAndHeaders(path)
  114. .then((headers) =>
  115. fetchWithNetworkError(this._getUrl(path), {
  116. method: 'get',
  117. headers,
  118. credentials: this.opts.companionCookiesRule || 'same-origin',
  119. }))
  120. .then(this._getPostResponseFunc(skipPostResponse))
  121. .then((res) => this._json(res))
  122. .catch((err) => {
  123. if (!err.isAuthError) {
  124. err.message = `Could not get ${this._getUrl(path)}. ${err.message}`
  125. }
  126. return Promise.reject(err)
  127. })
  128. }
  129. post (path, data, skipPostResponse) {
  130. return this.preflightAndHeaders(path)
  131. .then((headers) =>
  132. fetchWithNetworkError(this._getUrl(path), {
  133. method: 'post',
  134. headers,
  135. credentials: this.opts.companionCookiesRule || 'same-origin',
  136. body: JSON.stringify(data),
  137. }))
  138. .then(this._getPostResponseFunc(skipPostResponse))
  139. .then((res) => this._json(res))
  140. .catch((err) => {
  141. if (!err.isAuthError) {
  142. err.message = `Could not post ${this._getUrl(path)}. ${err.message}`
  143. }
  144. return Promise.reject(err)
  145. })
  146. }
  147. delete (path, data, skipPostResponse) {
  148. return this.preflightAndHeaders(path)
  149. .then((headers) =>
  150. fetchWithNetworkError(`${this.hostname}/${path}`, {
  151. method: 'delete',
  152. headers,
  153. credentials: this.opts.companionCookiesRule || 'same-origin',
  154. body: data ? JSON.stringify(data) : null,
  155. }))
  156. .then(this._getPostResponseFunc(skipPostResponse))
  157. .then((res) => this._json(res))
  158. .catch((err) => {
  159. if (!err.isAuthError) {
  160. err.message = `Could not delete ${this._getUrl(path)}. ${err.message}`
  161. }
  162. return Promise.reject(err)
  163. })
  164. }
  165. }