AxiosStrategy.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import axios, { AxiosRequestConfig } from "axios"
  2. import { v4 } from "uuid"
  3. import { pipe } from "fp-ts/function"
  4. import * as TE from "fp-ts/TaskEither"
  5. import cloneDeep from "lodash/cloneDeep"
  6. import { NetworkResponse, NetworkStrategy } from "../network"
  7. import { decodeB64StringToArrayBuffer } from "../utils/b64"
  8. import { settingsStore } from "~/newstore/settings"
  9. let cancelSource = axios.CancelToken.source()
  10. type ProxyHeaders = {
  11. "multipart-part-key"?: string
  12. }
  13. type ProxyPayloadType = FormData | (AxiosRequestConfig & { wantsBinary: true })
  14. export const cancelRunningAxiosRequest = () => {
  15. cancelSource.cancel()
  16. // Create a new cancel token
  17. cancelSource = axios.CancelToken.source()
  18. }
  19. const getProxyPayload = (
  20. req: AxiosRequestConfig,
  21. multipartKey: string | null
  22. ) => {
  23. let payload: ProxyPayloadType = {
  24. ...req,
  25. wantsBinary: true,
  26. }
  27. if (payload.data instanceof FormData) {
  28. const formData = payload.data
  29. payload.data = ""
  30. formData.append(multipartKey!, JSON.stringify(payload))
  31. payload = formData
  32. }
  33. return payload
  34. }
  35. const preProcessRequest = (req: AxiosRequestConfig): AxiosRequestConfig => {
  36. const reqClone = cloneDeep(req)
  37. // If the parameters are URLSearchParams, inject them to URL instead
  38. // This prevents issues of marshalling the URLSearchParams to the proxy
  39. if (reqClone.params instanceof URLSearchParams) {
  40. try {
  41. const url = new URL(reqClone.url ?? "")
  42. for (const [key, value] of reqClone.params.entries()) {
  43. url.searchParams.append(key, value)
  44. }
  45. reqClone.url = url.toString()
  46. } catch (e) {}
  47. reqClone.params = {}
  48. }
  49. return reqClone
  50. }
  51. const axiosWithProxy: NetworkStrategy = (req) =>
  52. pipe(
  53. TE.Do,
  54. TE.bind("processedReq", () => TE.of(preProcessRequest(req))),
  55. // If the request has FormData, the proxy needs a key
  56. TE.bind("multipartKey", ({ processedReq }) =>
  57. TE.of(processedReq.data instanceof FormData ? v4() : null)
  58. ),
  59. // Build headers to send
  60. TE.bind("headers", ({ processedReq, multipartKey }) =>
  61. TE.of(
  62. processedReq.data instanceof FormData
  63. ? <ProxyHeaders>{
  64. "multipart-part-key": `proxyRequestData-${multipartKey}`,
  65. }
  66. : <ProxyHeaders>{}
  67. )
  68. ),
  69. // Create payload
  70. TE.bind("payload", ({ processedReq, multipartKey }) =>
  71. TE.of(getProxyPayload(processedReq, multipartKey))
  72. ),
  73. // Run the proxy request
  74. TE.chain(({ payload, headers }) =>
  75. TE.tryCatch(
  76. () =>
  77. axios.post(
  78. settingsStore.value.PROXY_URL || "https://proxy.hoppscotch.io",
  79. payload,
  80. {
  81. headers,
  82. cancelToken: cancelSource.token,
  83. }
  84. ),
  85. (reason) =>
  86. axios.isCancel(reason)
  87. ? "cancellation" // Convert cancellation errors into cancellation strings
  88. : reason
  89. )
  90. ),
  91. // Check success predicate
  92. TE.chain(
  93. TE.fromPredicate(
  94. ({ data }) => data.success,
  95. ({ data }) => data.data.message || "Proxy Error"
  96. )
  97. ),
  98. // Process Base64
  99. TE.chain(({ data }) => {
  100. if (data.isBinary) {
  101. data.data = decodeB64StringToArrayBuffer(data.data)
  102. }
  103. return TE.of(data)
  104. })
  105. )
  106. const axiosWithoutProxy: NetworkStrategy = (req) =>
  107. pipe(
  108. TE.tryCatch(
  109. () =>
  110. axios({
  111. ...req,
  112. cancelToken: (cancelSource && cancelSource.token) || "",
  113. responseType: "arraybuffer",
  114. }),
  115. (e) => (axios.isCancel(e) ? "cancellation" : (e as any))
  116. ),
  117. TE.orElse((e) =>
  118. e !== "cancellation" && e.response
  119. ? TE.right(e.response as NetworkResponse)
  120. : TE.left(e)
  121. )
  122. )
  123. const axiosStrategy: NetworkStrategy = (req) =>
  124. pipe(
  125. req,
  126. settingsStore.value.PROXY_ENABLED ? axiosWithProxy : axiosWithoutProxy
  127. )
  128. export default axiosStrategy