RequestClient.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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=1.0.3'
  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. throw new Error(`Failed request to ${res.url}. ${res.statusText}`)
  68. }
  69. return res.json()
  70. }
  71. preflight (path) {
  72. return new Promise((resolve, reject) => {
  73. if (this.preflightDone) {
  74. return resolve(this.allowedHeaders.slice())
  75. }
  76. fetch(this._getUrl(path), {
  77. method: 'OPTIONS'
  78. })
  79. .then((response) => {
  80. if (response.headers.has('access-control-allow-headers')) {
  81. this.allowedHeaders = response.headers.get('access-control-allow-headers')
  82. .split(',').map((headerName) => headerName.trim().toLowerCase())
  83. }
  84. this.preflightDone = true
  85. resolve(this.allowedHeaders.slice())
  86. })
  87. .catch((err) => {
  88. this.uppy.log(`[CompanionClient] unable to make preflight request ${err}`, 'warning')
  89. this.preflightDone = true
  90. resolve(this.allowedHeaders.slice())
  91. })
  92. })
  93. }
  94. preflightAndHeaders (path) {
  95. return Promise.all([this.preflight(path), this.headers()])
  96. .then(([allowedHeaders, headers]) => {
  97. // filter to keep only allowed Headers
  98. Object.keys(headers).forEach((header) => {
  99. if (allowedHeaders.indexOf(header.toLowerCase()) === -1) {
  100. this.uppy.log(`[CompanionClient] excluding unallowed header ${header}`)
  101. delete headers[header]
  102. }
  103. })
  104. return headers
  105. })
  106. }
  107. get (path, skipPostResponse) {
  108. return new Promise((resolve, reject) => {
  109. this.preflightAndHeaders(path).then((headers) => {
  110. fetch(this._getUrl(path), {
  111. method: 'get',
  112. headers: headers,
  113. credentials: 'same-origin'
  114. })
  115. .then(this._getPostResponseFunc(skipPostResponse))
  116. .then((res) => this._json(res).then(resolve))
  117. .catch((err) => {
  118. err = err.isAuthError ? err : new Error(`Could not get ${this._getUrl(path)}. ${err}`)
  119. reject(err)
  120. })
  121. }).catch(reject)
  122. })
  123. }
  124. post (path, data, skipPostResponse) {
  125. return new Promise((resolve, reject) => {
  126. this.preflightAndHeaders(path).then((headers) => {
  127. fetch(this._getUrl(path), {
  128. method: 'post',
  129. headers: headers,
  130. credentials: 'same-origin',
  131. body: JSON.stringify(data)
  132. })
  133. .then(this._getPostResponseFunc(skipPostResponse))
  134. .then((res) => this._json(res).then(resolve))
  135. .catch((err) => {
  136. err = err.isAuthError ? err : new Error(`Could not post ${this._getUrl(path)}. ${err}`)
  137. reject(err)
  138. })
  139. }).catch(reject)
  140. })
  141. }
  142. delete (path, data, skipPostResponse) {
  143. return new Promise((resolve, reject) => {
  144. this.preflightAndHeaders(path).then((headers) => {
  145. fetch(`${this.hostname}/${path}`, {
  146. method: 'delete',
  147. headers: headers,
  148. credentials: 'same-origin',
  149. body: data ? JSON.stringify(data) : null
  150. })
  151. .then(this._getPostResponseFunc(skipPostResponse))
  152. .then((res) => this._json(res).then(resolve))
  153. .catch((err) => {
  154. err = err.isAuthError ? err : new Error(`Could not delete ${this._getUrl(path)}. ${err}`)
  155. reject(err)
  156. })
  157. }).catch(reject)
  158. })
  159. }
  160. }