axios.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // eslint-disable-next-line no-restricted-imports
  2. import axios from 'axios';
  3. import { formatISO } from 'date-fns';
  4. import qs from 'qs';
  5. // eslint-disable-next-line no-restricted-imports
  6. export * from 'axios';
  7. const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(Z|[+-]\d{2}:\d{2})$/;
  8. export type DateConvertible = string | number | boolean | Date | null | undefined | DateConvertible[] | { [key: string]: DateConvertible };
  9. /**
  10. * Converts string to dates recursively.
  11. *
  12. * @param data - Data to be transformed to Date if applicable.
  13. * @param seen - Set containing data that has been through the function before.
  14. * @returns - Data containing transformed Dates.
  15. */
  16. function convertStringsToDatesRecursive(data: DateConvertible, seen: Set<unknown>): DateConvertible {
  17. if (typeof data !== 'object' || data === null) {
  18. if (typeof data === 'string' && isoDateRegex.test(data)) {
  19. return new Date(data);
  20. }
  21. return data;
  22. }
  23. // Check for circular reference
  24. if (seen.has(data)) {
  25. return data;
  26. }
  27. seen.add(data);
  28. if (Array.isArray(data)) {
  29. return data.map(item => convertStringsToDatesRecursive(item, seen));
  30. }
  31. const newData: Record<string, DateConvertible> = {};
  32. for (const key of Object.keys(data as object)) {
  33. const value = (data as Record<string, DateConvertible>)[key];
  34. if (typeof value === 'string' && isoDateRegex.test(value)) {
  35. newData[key] = new Date(value);
  36. }
  37. else if (typeof value === 'object' && value !== null) {
  38. newData[key] = convertStringsToDatesRecursive(value, seen);
  39. }
  40. else {
  41. newData[key] = value;
  42. }
  43. }
  44. return newData;
  45. }
  46. // Function overloads for better type inference
  47. export function convertStringsToDates(data: string): string | Date;
  48. export function convertStringsToDates<T extends DateConvertible>(data: T): DateConvertible;
  49. export function convertStringsToDates<T extends DateConvertible[]>(data: T): DateConvertible[];
  50. export function convertStringsToDates<T extends Record<string, DateConvertible>>(data: T): Record<string, DateConvertible>;
  51. export function convertStringsToDates(data: DateConvertible): DateConvertible {
  52. return convertStringsToDatesRecursive(data, new Set());
  53. }
  54. // Determine the base array of transformers
  55. let baseTransformers = axios.defaults.transformResponse;
  56. if (baseTransformers == null) {
  57. baseTransformers = [];
  58. }
  59. else if (!Array.isArray(baseTransformers)) {
  60. // If it's a single transformer function, wrap it in an array
  61. baseTransformers = [baseTransformers];
  62. }
  63. const customAxios = axios.create({
  64. headers: {
  65. 'X-Requested-With': 'XMLHttpRequest',
  66. 'Content-Type': 'application/json',
  67. },
  68. transformResponse: baseTransformers.concat(
  69. (data) => {
  70. return convertStringsToDates(data);
  71. },
  72. ),
  73. });
  74. // serialize Date config: https://github.com/axios/axios/issues/1548#issuecomment-548306666
  75. customAxios.interceptors.request.use((config) => {
  76. config.paramsSerializer = params => qs.stringify(params, {
  77. serializeDate: (date: Date) => {
  78. return formatISO(date, { representation: 'complete' });
  79. },
  80. });
  81. return config;
  82. });
  83. export default customAxios;