ApiCaller.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import axios from 'axios'
  16. import type { AxiosXHRConfig, $AxiosXHR } from 'axios'
  17. import cookie from 'js-cookie'
  18. import logger from './ApiLogger'
  19. import notificationStore from '../stores/NotificationStore'
  20. type Cancelable = {
  21. requestId: string,
  22. cancel: () => void,
  23. }
  24. type RequestOptions = {
  25. url: string,
  26. method?: string,
  27. cancelId?: string,
  28. headers?: { [string]: string },
  29. data?: any,
  30. responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream',
  31. quietError?: boolean,
  32. skipLog?: boolean,
  33. }
  34. let cancelables: Cancelable[] = []
  35. const CancelToken = axios.CancelToken
  36. const addCancelable = (cancelable: Cancelable) => {
  37. cancelables.unshift(cancelable)
  38. if (cancelables.length > 100) {
  39. cancelables.pop()
  40. }
  41. }
  42. const isOnLoginPage = (): boolean => {
  43. if (window.env.ENV === 'development') {
  44. return window.location.hash === '#/' || window.location.hash === '#/login'
  45. }
  46. return window.location.pathname === '/' || window.location.pathname === '/login'
  47. }
  48. class ApiCaller {
  49. constructor() {
  50. axios.defaults.headers.common['Content-Type'] = 'application/json'
  51. }
  52. get projectId(): string {
  53. return cookie.get('projectId') || 'undefined'
  54. }
  55. cancelRequests(cancelRequestId: string) {
  56. const filteredCancelables = cancelables.filter(r => r.requestId === cancelRequestId)
  57. filteredCancelables.forEach(c => {
  58. c.cancel()
  59. })
  60. cancelables = cancelables.filter(r => r.requestId !== cancelRequestId)
  61. }
  62. get(url: string): Promise<$AxiosXHR<any>> {
  63. return this.send({ url })
  64. }
  65. send(options: RequestOptions): Promise<$AxiosXHR<any>> {
  66. return new Promise((resolve, reject) => {
  67. const axiosOptions: AxiosXHRConfig<any> = {
  68. url: options.url,
  69. method: options.method || 'GET',
  70. headers: options.headers || {},
  71. data: options.data || null,
  72. responseType: options.responseType || 'json',
  73. }
  74. if (options.cancelId) {
  75. let cancel = () => { }
  76. axiosOptions.cancelToken = new CancelToken(c => {
  77. cancel = c
  78. })
  79. addCancelable({ requestId: options.cancelId, cancel })
  80. }
  81. if (!options.skipLog) {
  82. logger.log({
  83. url: axiosOptions.url,
  84. method: axiosOptions.method || 'GET',
  85. type: 'REQUEST',
  86. })
  87. }
  88. axios(axiosOptions).then((response) => {
  89. if (!options.skipLog) {
  90. console.log(`%cResponse ${axiosOptions.url}`, 'color: #0044CA', response.data)
  91. logger.log({
  92. url: axiosOptions.url,
  93. method: axiosOptions.method || 'GET',
  94. type: 'RESPONSE',
  95. requestStatus: 200,
  96. })
  97. }
  98. resolve(response)
  99. }).catch(error => {
  100. if (error.response) {
  101. // The request was made and the server responded with a status code
  102. // that falls out of the range of 2xx
  103. if (
  104. (error.response.status !== 401 || !isOnLoginPage()) &&
  105. !options.quietError) {
  106. let data = error.response.data
  107. let message = (data && data.error && data.error.message) || (data && data.description)
  108. message = message || `${error.response.statusText || error.response.status} ${options.url}`
  109. if (message) {
  110. notificationStore.alert(message, 'error')
  111. }
  112. }
  113. if (error.response.status === 401 && !isOnLoginPage() && error.request.responseURL.indexOf('/proxy/') === -1) {
  114. window.location.href = '/'
  115. }
  116. logger.log({
  117. url: axiosOptions.url,
  118. method: axiosOptions.method || 'GET',
  119. type: 'RESPONSE',
  120. requestStatus: error.response.status,
  121. requestError: error,
  122. })
  123. reject(error.response)
  124. } else if (error.request) {
  125. // The request was made but no response was received
  126. // `error.request` is an instance of XMLHttpRequest
  127. if (!isOnLoginPage() && !options.quietError) {
  128. notificationStore.alert(`Request failed, there might be a problem with the connection to the server. ${options.url}`, 'error')
  129. }
  130. logger.log({
  131. url: axiosOptions.url,
  132. method: axiosOptions.method || 'GET',
  133. type: 'RESPONSE',
  134. description: 'No response',
  135. requestStatus: 500,
  136. requestError: error,
  137. })
  138. reject({})
  139. } else {
  140. let canceled = error.constructor.name === 'Cancel'
  141. reject({ canceled })
  142. if (canceled) {
  143. logger.log({
  144. url: axiosOptions.url,
  145. method: axiosOptions.method || 'GET',
  146. type: 'RESPONSE',
  147. requestStatus: 'canceled',
  148. })
  149. return
  150. }
  151. // Something happened in setting up the request that triggered an Error
  152. logger.log({
  153. url: axiosOptions.url,
  154. method: axiosOptions.method || 'GET',
  155. type: 'RESPONSE',
  156. description: 'Something happened in setting up the request',
  157. requestStatus: 500,
  158. })
  159. notificationStore.alert(`Request failed, there might be a problem with the connection to the server. ${options.url}`, 'error')
  160. }
  161. })
  162. })
  163. }
  164. setDefaultHeader(name: string, value: ?string) {
  165. axios.defaults.headers.common[name] = value
  166. }
  167. }
  168. export default new ApiCaller()