Files
hoppscotch/packages/hoppscotch-selfhost-desktop/src/platform/interceptors/native.ts
Andrew Bastin be57255bf7 refactor: update to dioc v3 (#4009)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-04-29 19:06:18 +05:30

166 lines
4.0 KiB
TypeScript

import * as E from "fp-ts/Either"
import {
Interceptor,
InterceptorError,
RequestRunResult,
} from "@hoppscotch/common/services/interceptor.service"
import { CookieJarService } from "@hoppscotch/common/services/cookie-jar.service"
import axios, { AxiosRequestConfig, CancelToken } from "axios"
import { cloneDeep } from "lodash-es"
import { Body, HttpVerb, ResponseType, getClient } from "@tauri-apps/api/http"
import { Service } from "dioc"
export const preProcessRequest = (
req: AxiosRequestConfig
): AxiosRequestConfig => {
const reqClone = cloneDeep(req)
// If the parameters are URLSearchParams, inject them to URL instead
// This prevents issues of marshalling the URLSearchParams to the proxy
if (reqClone.params instanceof URLSearchParams) {
try {
const url = new URL(reqClone.url ?? "")
for (const [key, value] of reqClone.params.entries()) {
url.searchParams.append(key, value)
}
reqClone.url = url.toString()
} catch (e) {
// making this a non-empty block, so we can make the linter happy.
// we should probably use, allowEmptyCatch, or take the time to do something with the caught errors :)
}
reqClone.params = {}
}
return reqClone
}
async function runRequest(
req: AxiosRequestConfig,
cancelled: () => boolean
): RequestRunResult["response"] {
const timeStart = Date.now()
const processedReq = preProcessRequest(req)
try {
const client = await getClient()
if (cancelled()) {
client.drop()
return E.left("cancellation")
}
let body = Body.text(processedReq.data ?? "")
if (processedReq.data instanceof FormData) {
let body_data = {}
for (const entry of processedReq.data.entries()) {
const [name, value] = entry
if (value instanceof File) {
let file_data = await value.arrayBuffer()
body_data[name] = {
file: new Uint8Array(file_data),
fileName: value.name,
}
}
}
body = Body.form(body_data)
}
const res = await client.request({
method: processedReq.method as HttpVerb,
url: processedReq.url ?? "",
responseType: ResponseType.Binary,
headers: processedReq.headers,
body: body,
})
if (cancelled()) {
client.drop()
return E.left("cancellation")
}
res.data = new Uint8Array(res.data as number[]).buffer
const timeEnd = Date.now()
return E.right({
...res,
config: {
timeData: {
startTime: timeStart,
endTime: timeEnd,
},
},
})
} catch (e) {
const timeEnd = Date.now()
if (axios.isAxiosError(e) && e.response) {
return E.right({
...e.response,
config: {
timeData: {
startTime: timeStart,
endTime: timeEnd,
},
},
})
} else if (axios.isCancel(e)) {
return E.left("cancellation")
} else {
return E.left(<InterceptorError>{
humanMessage: {
heading: (t) => t("error.network_fail"),
description: (t) => t("helpers.network_fail"),
},
error: e,
})
}
}
}
export class NativeInterceptorService extends Service implements Interceptor {
public static readonly ID = "NATIVE_INTERCEPTOR_SERVICE"
public interceptorID = "native" // TODO: i18n this
public name = () => "Native"
public selectable = { type: "selectable" as const }
public supportsCookies = true
public cookieJarService = this.bind(CookieJarService)
public runRequest(req: any) {
const processedReq = preProcessRequest(req)
const relevantCookies = this.cookieJarService.getCookiesForURL(
new URL(processedReq.url!)
)
processedReq.headers["Cookie"] = relevantCookies
.map((cookie) => `${cookie.name!}=${cookie.value!}`)
.join(";")
let cancelled = false
const checkCancelled = () => {
return cancelled
}
return {
cancel: () => {
cancelled = true
},
response: runRequest(processedReq, checkCancelled),
}
}
}