HTTPService.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import { addLogLine } from "@ente/shared/logging";
  2. import { logError } from "@ente/shared/sentry";
  3. import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
  4. import { ApiError, CustomError, isApiErrorResponse } from "../error";
  5. interface IHTTPHeaders {
  6. [headerKey: string]: any;
  7. }
  8. interface IQueryPrams {
  9. [paramName: string]: any;
  10. }
  11. /**
  12. * Service to manage all HTTP calls.
  13. */
  14. class HTTPService {
  15. constructor() {
  16. axios.interceptors.response.use(
  17. (response) => Promise.resolve(response),
  18. (error) => {
  19. const config = error.config as AxiosRequestConfig;
  20. if (error.response) {
  21. const response = error.response as AxiosResponse;
  22. let apiError: ApiError;
  23. // The request was made and the server responded with a status code
  24. // that falls out of the range of 2xx
  25. if (isApiErrorResponse(response.data)) {
  26. const responseData = response.data;
  27. logError(error, "HTTP Service Error", {
  28. url: config.url,
  29. method: config.method,
  30. xRequestId: response.headers["x-request-id"],
  31. httpStatus: response.status,
  32. errMessage: responseData.message,
  33. errCode: responseData.code,
  34. });
  35. apiError = new ApiError(
  36. responseData.message,
  37. responseData.code,
  38. response.status,
  39. );
  40. } else {
  41. if (response.status >= 400 && response.status < 500) {
  42. apiError = new ApiError(
  43. CustomError.CLIENT_ERROR,
  44. "",
  45. response.status,
  46. );
  47. } else {
  48. apiError = new ApiError(
  49. CustomError.ServerError,
  50. "",
  51. response.status,
  52. );
  53. }
  54. }
  55. logError(apiError, "HTTP Service Error", {
  56. url: config.url,
  57. method: config.method,
  58. cfRay: response.headers["cf-ray"],
  59. xRequestId: response.headers["x-request-id"],
  60. httpStatus: response.status,
  61. });
  62. throw apiError;
  63. } else if (error.request) {
  64. // The request was made but no response was received
  65. // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
  66. // http.ClientRequest in node.js
  67. addLogLine(
  68. "request failed- no response",
  69. `url: ${config.url}`,
  70. `method: ${config.method}`,
  71. );
  72. return Promise.reject(error);
  73. } else {
  74. // Something happened in setting up the request that triggered an Error
  75. addLogLine(
  76. "request failed- axios error",
  77. `url: ${config.url}`,
  78. `method: ${config.method}`,
  79. );
  80. return Promise.reject(error);
  81. }
  82. },
  83. );
  84. }
  85. /**
  86. * header object to be append to all api calls.
  87. */
  88. private headers: IHTTPHeaders = {
  89. "content-type": "application/json",
  90. };
  91. /**
  92. * Sets the headers to the given object.
  93. */
  94. public setHeaders(headers: IHTTPHeaders) {
  95. this.headers = {
  96. ...this.headers,
  97. ...headers,
  98. };
  99. }
  100. /**
  101. * Adds a header to list of headers.
  102. */
  103. public appendHeader(key: string, value: string) {
  104. this.headers = {
  105. ...this.headers,
  106. [key]: value,
  107. };
  108. }
  109. /**
  110. * Removes the given header.
  111. */
  112. public removeHeader(key: string) {
  113. this.headers[key] = undefined;
  114. }
  115. /**
  116. * Returns axios interceptors.
  117. */
  118. // eslint-disable-next-line class-methods-use-this
  119. public getInterceptors() {
  120. return axios.interceptors;
  121. }
  122. /**
  123. * Generic HTTP request.
  124. * This is done so that developer can use any functionality
  125. * provided by axios. Here, only the set headers are spread
  126. * over what was sent in config.
  127. */
  128. public async request(config: AxiosRequestConfig, customConfig?: any) {
  129. // eslint-disable-next-line no-param-reassign
  130. config.headers = {
  131. ...this.headers,
  132. ...config.headers,
  133. };
  134. if (customConfig?.cancel) {
  135. config.cancelToken = new axios.CancelToken(
  136. (c) => (customConfig.cancel.exec = c),
  137. );
  138. }
  139. return await axios({ ...config, ...customConfig });
  140. }
  141. /**
  142. * Get request.
  143. */
  144. public get(
  145. url: string,
  146. params?: IQueryPrams,
  147. headers?: IHTTPHeaders,
  148. customConfig?: any,
  149. ) {
  150. return this.request(
  151. {
  152. headers,
  153. method: "GET",
  154. params,
  155. url,
  156. },
  157. customConfig,
  158. );
  159. }
  160. /**
  161. * Post request
  162. */
  163. public post(
  164. url: string,
  165. data?: any,
  166. params?: IQueryPrams,
  167. headers?: IHTTPHeaders,
  168. customConfig?: any,
  169. ) {
  170. return this.request(
  171. {
  172. data,
  173. headers,
  174. method: "POST",
  175. params,
  176. url,
  177. },
  178. customConfig,
  179. );
  180. }
  181. /**
  182. * Patch request
  183. */
  184. public patch(
  185. url: string,
  186. data?: any,
  187. params?: IQueryPrams,
  188. headers?: IHTTPHeaders,
  189. customConfig?: any,
  190. ) {
  191. return this.request(
  192. {
  193. data,
  194. headers,
  195. method: "PATCH",
  196. params,
  197. url,
  198. },
  199. customConfig,
  200. );
  201. }
  202. /**
  203. * Put request
  204. */
  205. public put(
  206. url: string,
  207. data: any,
  208. params?: IQueryPrams,
  209. headers?: IHTTPHeaders,
  210. customConfig?: any,
  211. ) {
  212. return this.request(
  213. {
  214. data,
  215. headers,
  216. method: "PUT",
  217. params,
  218. url,
  219. },
  220. customConfig,
  221. );
  222. }
  223. /**
  224. * Delete request
  225. */
  226. public delete(
  227. url: string,
  228. data: any,
  229. params?: IQueryPrams,
  230. headers?: IHTTPHeaders,
  231. customConfig?: any,
  232. ) {
  233. return this.request(
  234. {
  235. data,
  236. headers,
  237. method: "DELETE",
  238. params,
  239. url,
  240. },
  241. customConfig,
  242. );
  243. }
  244. }
  245. // Creates a Singleton Service.
  246. // This will help me maintain common headers / functionality
  247. // at a central place.
  248. export default new HTTPService();