network.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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", () => {
  110. debugger
  111. return TE.of(cloneDeep(request))
  112. }),
  113. // Assembling headers object
  114. TE.bind("headers", ({ req }) => {
  115. debugger
  116. return TE.of(
  117. req.effectiveFinalHeaders.reduce((acc, { key, value }) => {
  118. return Object.assign(acc, { [key]: value })
  119. }, {})
  120. )
  121. }),
  122. // Assembling params object
  123. TE.bind("params", ({ req }) => {
  124. debugger
  125. return TE.of(
  126. req.effectiveFinalParams.reduce((acc, { key, value }) => {
  127. return Object.assign(acc, { [key]: value })
  128. }, {})
  129. )
  130. }),
  131. // Keeping the backup start time
  132. TE.bind("backupTimeStart", () => {
  133. debugger
  134. return TE.of(Date.now())
  135. }),
  136. // Running the request and getting the response
  137. TE.bind("res", ({ req, headers, params }) => {
  138. debugger
  139. return runAppropriateStrategy({
  140. method: req.method as any,
  141. url: req.effectiveFinalURL,
  142. headers,
  143. params,
  144. data: req.effectiveFinalBody,
  145. })
  146. }),
  147. // Getting the backup end time
  148. TE.bind("backupTimeEnd", () => {
  149. debugger
  150. return TE.of(Date.now())
  151. }),
  152. // Assemble the final response object
  153. TE.chainW(({ req, res, backupTimeEnd, backupTimeStart }) => {
  154. debugger
  155. return processResponse(
  156. res,
  157. req,
  158. backupTimeStart,
  159. backupTimeEnd,
  160. "success"
  161. )
  162. }),
  163. // Writing success state to the stream
  164. TE.chain((res) => {
  165. debugger
  166. response.next(res)
  167. response.complete()
  168. return TE.of(res)
  169. }),
  170. // Package the error type
  171. TE.getOrElseW((e) => {
  172. debugger
  173. const obj: HoppRESTResponse = {
  174. type: "network_fail",
  175. error: e,
  176. req: request,
  177. }
  178. response.next(obj)
  179. response.complete()
  180. return T.of(obj)
  181. })
  182. )()
  183. return response
  184. }