network.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { AxiosResponse, AxiosRequestConfig } from "axios"
  2. import { BehaviorSubject, Observable } from "rxjs"
  3. import cloneDeep from "lodash/cloneDeep"
  4. import * as T from "fp-ts/Task"
  5. import * as TE from "fp-ts/TaskEither"
  6. import { pipe } from "fp-ts/function"
  7. import AxiosStrategy, {
  8. cancelRunningAxiosRequest,
  9. } from "./strategies/AxiosStrategy"
  10. import ExtensionStrategy, {
  11. cancelRunningExtensionRequest,
  12. hasExtensionInstalled,
  13. } from "./strategies/ExtensionStrategy"
  14. import { HoppRESTResponse } from "./types/HoppRESTResponse"
  15. import { EffectiveHoppRESTRequest } from "./utils/EffectiveURL"
  16. import { settingsStore } from "~/newstore/settings"
  17. export type NetworkResponse = AxiosResponse<any> & {
  18. config?: {
  19. timeData?: {
  20. startTime: number
  21. endTime: number
  22. }
  23. }
  24. }
  25. export type NetworkStrategy = (
  26. req: AxiosRequestConfig
  27. ) => TE.TaskEither<any, NetworkResponse>
  28. export const cancelRunningRequest = () => {
  29. if (isExtensionsAllowed() && hasExtensionInstalled()) {
  30. cancelRunningExtensionRequest()
  31. } else {
  32. cancelRunningAxiosRequest()
  33. }
  34. }
  35. const isExtensionsAllowed = () => settingsStore.value.EXTENSIONS_ENABLED
  36. const runAppropriateStrategy = (req: AxiosRequestConfig) => {
  37. if (isExtensionsAllowed() && hasExtensionInstalled()) {
  38. return ExtensionStrategy(req)
  39. }
  40. return AxiosStrategy(req)
  41. }
  42. /**
  43. * Returns an identifier for how a request will be ran
  44. * if the system is asked to fire a request
  45. *
  46. */
  47. export function getCurrentStrategyID() {
  48. if (isExtensionsAllowed() && hasExtensionInstalled()) {
  49. return "extension" as const
  50. } else if (settingsStore.value.PROXY_ENABLED) {
  51. return "proxy" as const
  52. } else {
  53. return "normal" as const
  54. }
  55. }
  56. export const sendNetworkRequest = (req: any) =>
  57. pipe(
  58. runAppropriateStrategy(req),
  59. TE.getOrElse((e) => {
  60. throw e
  61. })
  62. )()
  63. const processResponse = (
  64. res: NetworkResponse,
  65. req: EffectiveHoppRESTRequest,
  66. backupTimeStart: number,
  67. backupTimeEnd: number,
  68. successState: HoppRESTResponse["type"]
  69. ) =>
  70. pipe(
  71. TE.Do,
  72. // Calculate the content length
  73. TE.bind("contentLength", () =>
  74. TE.of(
  75. res.headers["content-length"]
  76. ? parseInt(res.headers["content-length"])
  77. : (res.data as ArrayBuffer).byteLength
  78. )
  79. ),
  80. // Building the final response object
  81. TE.map(
  82. ({ contentLength }) =>
  83. <HoppRESTResponse>{
  84. type: successState,
  85. statusCode: res.status,
  86. body: res.data,
  87. headers: Object.keys(res.headers).map((x) => ({
  88. key: x,
  89. value: res.headers[x],
  90. })),
  91. meta: {
  92. responseSize: contentLength,
  93. responseDuration: backupTimeEnd - backupTimeStart,
  94. },
  95. req,
  96. }
  97. )
  98. )
  99. export function createRESTNetworkRequestStream(
  100. request: EffectiveHoppRESTRequest
  101. ): Observable<HoppRESTResponse> {
  102. const response = new BehaviorSubject<HoppRESTResponse>({
  103. type: "loading",
  104. req: request,
  105. })
  106. pipe(
  107. TE.Do,
  108. // Get a deep clone of the request
  109. TE.bind("req", () => TE.of(cloneDeep(request))),
  110. // Assembling headers object
  111. TE.bind("headers", ({ req }) =>
  112. TE.of(
  113. req.effectiveFinalHeaders.reduce((acc, { key, value }) => {
  114. return Object.assign(acc, { [key]: value })
  115. }, {})
  116. )
  117. ),
  118. // Assembling params object
  119. TE.bind("params", ({ req }) => {
  120. const params = new URLSearchParams()
  121. req.effectiveFinalParams.forEach((x) => {
  122. params.append(x.key, x.value)
  123. })
  124. return TE.of(params)
  125. }),
  126. // Keeping the backup start time
  127. TE.bind("backupTimeStart", () => TE.of(Date.now())),
  128. // Running the request and getting the response
  129. TE.bind("res", ({ req, headers, params }) =>
  130. runAppropriateStrategy({
  131. method: req.method as any,
  132. url: req.effectiveFinalURL.trim(),
  133. headers,
  134. params,
  135. data: req.effectiveFinalBody,
  136. })
  137. ),
  138. // Getting the backup end time
  139. TE.bind("backupTimeEnd", () => TE.of(Date.now())),
  140. // Assemble the final response object
  141. TE.chainW(({ req, res, backupTimeEnd, backupTimeStart }) =>
  142. processResponse(res, req, backupTimeStart, backupTimeEnd, "success")
  143. ),
  144. // Writing success state to the stream
  145. TE.chain((res) => {
  146. response.next(res)
  147. response.complete()
  148. return TE.of(res)
  149. }),
  150. // Package the error type
  151. TE.getOrElseW((e) => {
  152. const obj: HoppRESTResponse = {
  153. type: "network_fail",
  154. error: e,
  155. req: request,
  156. }
  157. response.next(obj)
  158. response.complete()
  159. return T.of(obj)
  160. })
  161. )()
  162. return response
  163. }