|
|
@@ -14,167 +14,114 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
// @flow
|
|
|
|
|
|
-import NotificationStore from '../stores/NotificationStore'
|
|
|
-
|
|
|
-let apiInstance = null
|
|
|
+import axios from 'axios'
|
|
|
+import type { AxiosXHRConfig, $AxiosXHR } from 'axios'
|
|
|
|
|
|
-type RequestOptions = {
|
|
|
- method: string,
|
|
|
- url: string,
|
|
|
- requestId?: string,
|
|
|
- headers?: {[string]: mixed},
|
|
|
-}
|
|
|
+import NotificationStore from '../stores/NotificationStore'
|
|
|
|
|
|
-type RequestInfo = {
|
|
|
+type Cancelable = {
|
|
|
requestId: string,
|
|
|
- request: XMLHttpRequest,
|
|
|
+ cancel: () => void,
|
|
|
}
|
|
|
|
|
|
-class ApiCaller {
|
|
|
- defaultHeaders = {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
+type RequestOptions = {|
|
|
|
+ url: string,
|
|
|
+ method?: string,
|
|
|
+ cancelId?: string,
|
|
|
+ headers?: {[string]: string},
|
|
|
+ data?: any,
|
|
|
+ responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream',
|
|
|
+|}
|
|
|
+
|
|
|
+let cancelables: Cancelable[] = []
|
|
|
+const CancelToken = axios.CancelToken
|
|
|
+
|
|
|
+const addCancelable = (cancelable: Cancelable) => {
|
|
|
+ cancelables.unshift(cancelable)
|
|
|
+ if (cancelables.length > 100) {
|
|
|
+ cancelables.pop()
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- requests = []
|
|
|
-
|
|
|
+class ApiCaller {
|
|
|
constructor() {
|
|
|
- if (!apiInstance) {
|
|
|
- apiInstance = this
|
|
|
- }
|
|
|
-
|
|
|
- return apiInstance
|
|
|
+ axios.defaults.headers.common['Content-Type'] = 'application/json'
|
|
|
}
|
|
|
|
|
|
- addRequest(requestInfo: RequestInfo) {
|
|
|
- this.requests.unshift(requestInfo)
|
|
|
- if (this.requests.length > 100) {
|
|
|
- this.requests.pop()
|
|
|
- }
|
|
|
+ cancelRequests(cancelRequestId: string) {
|
|
|
+ const filteredCancelables = cancelables.filter(r => r.requestId === cancelRequestId)
|
|
|
+ filteredCancelables.forEach(c => {
|
|
|
+ c.cancel()
|
|
|
+ })
|
|
|
+ cancelables = cancelables.filter(r => r.requestId !== cancelRequestId)
|
|
|
}
|
|
|
|
|
|
- cancelRequest(requestInfo: RequestInfo) {
|
|
|
- requestInfo.request.abort()
|
|
|
- this.requests = this.requests.filter(r => r.requestId !== requestInfo.requestId)
|
|
|
+ get(url: string): Promise<$AxiosXHR<any>> {
|
|
|
+ return this.send({ url })
|
|
|
}
|
|
|
|
|
|
- sendAjaxRequest(options: RequestOptions): Promise<any> {
|
|
|
+ send(options: RequestOptions): Promise<$AxiosXHR<any>> {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
- let request = new XMLHttpRequest()
|
|
|
-
|
|
|
- if (options.requestId) {
|
|
|
- this.addRequest({ requestId: options.requestId, request })
|
|
|
+ const axiosOptions: AxiosXHRConfig<any> = {
|
|
|
+ url: options.url,
|
|
|
+ method: options.method || 'GET',
|
|
|
+ headers: options.headers || {},
|
|
|
+ data: options.data || null,
|
|
|
+ responseType: options.responseType || 'json',
|
|
|
}
|
|
|
|
|
|
- request.open(options.method, options.url)
|
|
|
-
|
|
|
- let headers = Object.assign({}, this.defaultHeaders)
|
|
|
-
|
|
|
- if (options.headers) {
|
|
|
- Object.keys(options.headers).forEach((key) => {
|
|
|
- headers[key] = options.headers ? options.headers[key] : ''
|
|
|
+ if (options.cancelId) {
|
|
|
+ let cancel = () => {}
|
|
|
+ axiosOptions.cancelToken = new CancelToken(c => {
|
|
|
+ cancel = c
|
|
|
})
|
|
|
+ addCancelable({ requestId: options.cancelId, cancel })
|
|
|
}
|
|
|
|
|
|
- Object.keys(headers).forEach((name) => {
|
|
|
- request.setRequestHeader(name, headers[name])
|
|
|
- })
|
|
|
-
|
|
|
- request.onreadystatechange = () => {
|
|
|
- if (request.readyState === 4) { // if complete
|
|
|
- if (!(request.status >= 200 && request.status <= 299)) { // check if "OK" (200)
|
|
|
- reject({ status: request.status })
|
|
|
- } else if (request.status === 0) { // check if aborted
|
|
|
- reject({ status: 0 })
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ console.log(`%cSending ${axiosOptions.method || 'GET'} Request to ${axiosOptions.url}`, 'color: #F5A623')
|
|
|
|
|
|
- console.log(`%cSending ${options.method} Request to ${options.url}`, 'color: #F5A623') // eslint-disable-line no-console
|
|
|
+ axios(axiosOptions).then((response) => {
|
|
|
+ console.log(`%cResponse ${axiosOptions.url}`, 'color: #0044CA', response.data)
|
|
|
+ resolve(response)
|
|
|
+ }).catch(error => {
|
|
|
+ const loginUrl = '#/'
|
|
|
|
|
|
- try {
|
|
|
- options.data ? request.send(JSON.stringify(options.data)) : request.send()
|
|
|
- } catch (err) {
|
|
|
- reject(err)
|
|
|
- }
|
|
|
-
|
|
|
- request.onload = () => {
|
|
|
- let data = null
|
|
|
+ if (error.response) {
|
|
|
+ // The request was made and the server responded with a status code
|
|
|
+ // that falls out of the range of 2xx
|
|
|
+ if (error.response.status !== 401 || window.location.hash !== loginUrl) {
|
|
|
+ NotificationStore.notify(error.response.data.error.message, 'error')
|
|
|
+ }
|
|
|
|
|
|
- if (options.json !== false && request.responseText) {
|
|
|
- try {
|
|
|
- data = JSON.parse(request.responseText)
|
|
|
- } catch (err) {
|
|
|
- reject({ message: 'Invalid server response!' })
|
|
|
+ if (error.response.status === 401 && window.location.hash !== loginUrl) {
|
|
|
+ window.location.href = `/${loginUrl}`
|
|
|
}
|
|
|
- } else if (request.responseText) {
|
|
|
- data = request.responseText
|
|
|
- }
|
|
|
|
|
|
- let result = {
|
|
|
- status: request.status,
|
|
|
- data,
|
|
|
- headers: ApiCaller.processHeaders(request.getAllResponseHeaders()),
|
|
|
- }
|
|
|
- if (result.status >= 200 && result.status <= 299) {
|
|
|
- console.log(`%cResponse ${options.url}`, 'color: #0044CA', result.data) // eslint-disable-line no-console
|
|
|
- resolve(result)
|
|
|
+ console.log(`%cError Response: ${axiosOptions.url}`, 'color: #D0021B', error.response)
|
|
|
+ reject(error.response)
|
|
|
+ } else if (error.request) {
|
|
|
+ // The request was made but no response was received
|
|
|
+ // `error.request` is an instance of XMLHttpRequest
|
|
|
+ NotificationStore.notify('Request failed, there might be a problem with the connection to the server.', 'error')
|
|
|
+ console.log(`%cError No Response: ${axiosOptions.url}`, 'color: #D0021B')
|
|
|
+ reject({})
|
|
|
} else {
|
|
|
- console.log(`%cError Response: ${options.url}`, 'color: #D0021B', result.data) // eslint-disable-line no-console
|
|
|
+ reject({})
|
|
|
|
|
|
- let loginUrl = '#/'
|
|
|
-
|
|
|
- if (result.data && result.data.error && result.data.error.message &&
|
|
|
- (result.status !== 401 || window.location.hash !== loginUrl)) {
|
|
|
- // $FlowIgnore
|
|
|
- NotificationStore.notify(result.data.error.message, 'error')
|
|
|
+ if (error.constructor.name === 'Cancel') {
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- if (result.status === 401 && window.location.hash !== loginUrl) {
|
|
|
- this.resetHeaders()
|
|
|
- window.location.href = `/${loginUrl}`
|
|
|
- }
|
|
|
- reject({ status: request.status })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- request.onerror = (result) => {
|
|
|
- let loginUrl = '#/'
|
|
|
- if (window.location.hash !== loginUrl) {
|
|
|
- NotificationStore.notify(`Request failed, there might be a problem with the
|
|
|
- connection to the server.`, 'error')
|
|
|
+ // Something happened in setting up the request that triggered an Error
|
|
|
+ NotificationStore.notify('Request failed, there might be a problem with the connection to the server.', 'error')
|
|
|
+ console.log(`%cError Something happened in setting up the request: ${axiosOptions.url}`, 'color: #D0021B')
|
|
|
}
|
|
|
-
|
|
|
- // $FlowIgnore
|
|
|
- console.log('%cError Response: ', 'color: #D0021B', result.data) // eslint-disable-line no-console
|
|
|
- reject({ status: 500, data: 'Connection error' })
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- resetHeaders() {
|
|
|
- this.defaultHeaders = {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static processHeaders(rawHeaders: string) {
|
|
|
- let headers = {}
|
|
|
- let lines = rawHeaders.split('\n')
|
|
|
- lines.forEach((line) => {
|
|
|
- let comps = line.split(':')
|
|
|
- if (comps[0].length) {
|
|
|
- headers[comps[0]] = comps[1].trim()
|
|
|
- }
|
|
|
+ })
|
|
|
})
|
|
|
- return headers
|
|
|
}
|
|
|
|
|
|
setDefaultHeader(name: string, value: ?string) {
|
|
|
- if (value == null) {
|
|
|
- delete this.defaultHeaders[name]
|
|
|
- } else {
|
|
|
- this.defaultHeaders[name] = value
|
|
|
- }
|
|
|
+ axios.defaults.headers.common[name] = value
|
|
|
}
|
|
|
}
|
|
|
|