/* Copyright (C) 2017 Cloudbase Solutions SRL This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import axios, { AxiosResponse, AxiosRequestConfig } from "axios"; import cookie from "js-cookie"; import cacher from "./Cacher"; import logger from "./ApiLogger"; import ApiCallerHandlers from "./ApiCallerHandlers"; type Cancelable = { requestId: string; cancel: () => void; }; export type RequestOptions = { url: string; method?: AxiosRequestConfig["method"]; cancelId?: string; headers?: { [prop: string]: string }; data?: any; responseType?: | "arraybuffer" | "blob" | "document" | "json" | "text" | "stream"; quietError?: boolean | null; skipLog?: boolean | null; cache?: boolean | null; cacheFor?: number | null; timeout?: number; }; let cancelables: Cancelable[] = []; const CancelToken = axios.CancelToken; const addCancelable = (cancelable: Cancelable) => { cancelables.unshift(cancelable); if (cancelables.length > 100) { cancelables.pop(); } }; class ApiCaller { constructor() { axios.defaults.headers.common["Content-Type"] = "application/json"; } get projectId(): string { return cookie.get("projectId") || "undefined"; } removeFromCache(url: string) { cacher.remove(url); } cancelRequests(cancelRequestId: string) { const filteredCancelables = cancelables.filter( r => r.requestId === cancelRequestId ); filteredCancelables.forEach(c => { c.cancel(); }); cancelables = cancelables.filter(r => r.requestId !== cancelRequestId); } get(url: string): Promise { return this.send({ url }); } async send(options: RequestOptions): Promise> { const cachedData = options.cache ? cacher.load({ key: options.url, maxAge: options.cacheFor }) : null; if (cachedData) { const response: any = { data: cachedData }; return response; } const axiosOptions: AxiosRequestConfig = { url: options.url, method: options.method || "GET", headers: options.headers || {}, data: options.data || null, responseType: options.responseType || "json", timeout: options.timeout, }; if (options.cancelId) { let cancel = () => {}; axiosOptions.cancelToken = new CancelToken(c => { cancel = c; }); addCancelable({ requestId: options.cancelId, cancel }); } if (!options.skipLog) { logger.log({ url: axiosOptions.url, method: axiosOptions.method || "GET", type: "REQUEST", }); } const apiCallerHandlers = new ApiCallerHandlers(options, axiosOptions); try { const response = await axios(axiosOptions); if (!options.skipLog) { console.log( `%cResponse ${axiosOptions.url}`, "color: #0044CA", response.data ); logger.log({ url: axiosOptions.url, method: axiosOptions.method || "GET", type: "RESPONSE", requestStatus: 200, }); } if (options.cache) { cacher.save({ key: options.url, data: response.data }); } return response; } catch (err) { const error: any = err; if (error.response) { throw apiCallerHandlers.handleErrorResponse(error); } else if (error.request) { // eslint-disable-next-line @typescript-eslint/no-throw-literal throw apiCallerHandlers.handleErrorRequest(error); } else { throw apiCallerHandlers.handleRequestCancel(error); } } } setDefaultHeader(name: string, value: string | null) { axios.defaults.headers.common[name] = value; } } export default new ApiCaller();