RequestClient.js 5.5 KB

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