refactor: move from network strategies to generic interceptor service (#3242)
This commit is contained in:
@@ -1,189 +1,107 @@
|
||||
import { AxiosResponse, AxiosRequestConfig } from "axios"
|
||||
import { AxiosRequestConfig } from "axios"
|
||||
import { BehaviorSubject, Observable } from "rxjs"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import * as T from "fp-ts/Task"
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import AxiosStrategy, {
|
||||
cancelRunningAxiosRequest,
|
||||
} from "./strategies/AxiosStrategy"
|
||||
import ExtensionStrategy, {
|
||||
cancelRunningExtensionRequest,
|
||||
hasExtensionInstalled,
|
||||
} from "./strategies/ExtensionStrategy"
|
||||
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
||||
import { EffectiveHoppRESTRequest } from "./utils/EffectiveURL"
|
||||
import { settingsStore } from "~/newstore/settings"
|
||||
|
||||
export type NetworkResponse = AxiosResponse<any> & {
|
||||
config?: {
|
||||
timeData?: {
|
||||
startTime: number
|
||||
endTime: number
|
||||
}
|
||||
}
|
||||
}
|
||||
import { getService } from "~/modules/dioc"
|
||||
import {
|
||||
InterceptorService,
|
||||
NetworkResponse,
|
||||
} from "~/services/interceptor.service"
|
||||
|
||||
export type NetworkStrategy = (
|
||||
req: AxiosRequestConfig
|
||||
) => TE.TaskEither<any, NetworkResponse>
|
||||
|
||||
export const cancelRunningRequest = () => {
|
||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
||||
cancelRunningExtensionRequest()
|
||||
} else {
|
||||
cancelRunningAxiosRequest()
|
||||
}
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
const isExtensionsAllowed = () => settingsStore.value.EXTENSIONS_ENABLED
|
||||
|
||||
const runAppropriateStrategy = (req: AxiosRequestConfig) => {
|
||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
||||
return ExtensionStrategy(req)
|
||||
}
|
||||
|
||||
return AxiosStrategy(req)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an identifier for how a request will be ran
|
||||
* if the system is asked to fire a request
|
||||
*
|
||||
*/
|
||||
export function getCurrentStrategyID() {
|
||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
||||
return "extension" as const
|
||||
} else if (settingsStore.value.PROXY_ENABLED) {
|
||||
return "proxy" as const
|
||||
} else {
|
||||
return "normal" as const
|
||||
}
|
||||
}
|
||||
|
||||
export const sendNetworkRequest = (req: any) =>
|
||||
pipe(
|
||||
runAppropriateStrategy(req),
|
||||
TE.getOrElse((e) => {
|
||||
throw e
|
||||
})
|
||||
)()
|
||||
|
||||
const processResponse = (
|
||||
function processResponse(
|
||||
res: NetworkResponse,
|
||||
req: EffectiveHoppRESTRequest,
|
||||
backupTimeStart: number,
|
||||
backupTimeEnd: number,
|
||||
successState: HoppRESTResponse["type"]
|
||||
) =>
|
||||
pipe(
|
||||
TE.Do,
|
||||
) {
|
||||
const contentLength = res.headers["content-length"]
|
||||
? parseInt(res.headers["content-length"])
|
||||
: (res.data as ArrayBuffer).byteLength
|
||||
|
||||
// Calculate the content length
|
||||
TE.bind("contentLength", () =>
|
||||
TE.of(
|
||||
res.headers["content-length"]
|
||||
? parseInt(res.headers["content-length"])
|
||||
: (res.data as ArrayBuffer).byteLength
|
||||
)
|
||||
),
|
||||
|
||||
// Building the final response object
|
||||
TE.map(
|
||||
({ contentLength }) =>
|
||||
<HoppRESTResponse>{
|
||||
type: successState,
|
||||
statusCode: res.status,
|
||||
body: res.data,
|
||||
headers: Object.keys(res.headers).map((x) => ({
|
||||
key: x,
|
||||
value: res.headers[x],
|
||||
})),
|
||||
meta: {
|
||||
responseSize: contentLength,
|
||||
responseDuration: backupTimeEnd - backupTimeStart,
|
||||
},
|
||||
req,
|
||||
}
|
||||
)
|
||||
)
|
||||
return <HoppRESTResponse>{
|
||||
type: successState,
|
||||
statusCode: res.status,
|
||||
body: res.data,
|
||||
headers: Object.keys(res.headers).map((x) => ({
|
||||
key: x,
|
||||
value: res.headers[x],
|
||||
})),
|
||||
meta: {
|
||||
responseSize: contentLength,
|
||||
responseDuration: backupTimeEnd - backupTimeStart,
|
||||
},
|
||||
req,
|
||||
}
|
||||
}
|
||||
|
||||
export function createRESTNetworkRequestStream(
|
||||
request: EffectiveHoppRESTRequest
|
||||
): Observable<HoppRESTResponse> {
|
||||
): [Observable<HoppRESTResponse>, () => void] {
|
||||
const response = new BehaviorSubject<HoppRESTResponse>({
|
||||
type: "loading",
|
||||
req: request,
|
||||
})
|
||||
|
||||
pipe(
|
||||
TE.Do,
|
||||
const req = cloneDeep(request)
|
||||
|
||||
// Get a deep clone of the request
|
||||
TE.bind("req", () => TE.of(cloneDeep(request))),
|
||||
const headers = req.effectiveFinalHeaders.reduce((acc, { key, value }) => {
|
||||
return Object.assign(acc, { [key]: value })
|
||||
}, {})
|
||||
|
||||
// Assembling headers object
|
||||
TE.bind("headers", ({ req }) =>
|
||||
TE.of(
|
||||
req.effectiveFinalHeaders.reduce((acc, { key, value }) => {
|
||||
return Object.assign(acc, { [key]: value })
|
||||
}, {})
|
||||
const params = new URLSearchParams()
|
||||
for (const param of req.effectiveFinalParams) {
|
||||
params.append(param.key, param.value)
|
||||
}
|
||||
|
||||
const backupTimeStart = Date.now()
|
||||
|
||||
const service = getService(InterceptorService)
|
||||
|
||||
const res = service.runRequest({
|
||||
method: req.method as any,
|
||||
url: req.effectiveFinalURL.trim(),
|
||||
headers,
|
||||
params,
|
||||
data: req.effectiveFinalBody,
|
||||
})
|
||||
|
||||
res.response.then((res) => {
|
||||
const backupTimeEnd = Date.now()
|
||||
|
||||
if (E.isRight(res)) {
|
||||
const processedRes = processResponse(
|
||||
res.right,
|
||||
req,
|
||||
backupTimeStart,
|
||||
backupTimeEnd,
|
||||
"success"
|
||||
)
|
||||
),
|
||||
|
||||
// Assembling params object
|
||||
TE.bind("params", ({ req }) => {
|
||||
const params = new URLSearchParams()
|
||||
req.effectiveFinalParams.forEach((x) => {
|
||||
params.append(x.key, x.value)
|
||||
})
|
||||
return TE.of(params)
|
||||
}),
|
||||
|
||||
// Keeping the backup start time
|
||||
TE.bind("backupTimeStart", () => TE.of(Date.now())),
|
||||
|
||||
// Running the request and getting the response
|
||||
TE.bind("res", ({ req, headers, params }) =>
|
||||
runAppropriateStrategy({
|
||||
method: req.method as any,
|
||||
url: req.effectiveFinalURL.trim(),
|
||||
headers,
|
||||
params,
|
||||
data: req.effectiveFinalBody,
|
||||
})
|
||||
),
|
||||
|
||||
// Getting the backup end time
|
||||
TE.bind("backupTimeEnd", () => TE.of(Date.now())),
|
||||
|
||||
// Assemble the final response object
|
||||
TE.chainW(({ req, res, backupTimeEnd, backupTimeStart }) =>
|
||||
processResponse(res, req, backupTimeStart, backupTimeEnd, "success")
|
||||
),
|
||||
|
||||
// Writing success state to the stream
|
||||
TE.chain((res) => {
|
||||
response.next(res)
|
||||
response.next(processedRes)
|
||||
response.complete()
|
||||
|
||||
return TE.of(res)
|
||||
}),
|
||||
return
|
||||
}
|
||||
|
||||
// Package the error type
|
||||
TE.getOrElseW((e) => {
|
||||
const obj: HoppRESTResponse = {
|
||||
type: "network_fail",
|
||||
error: e,
|
||||
req: request,
|
||||
}
|
||||
|
||||
response.next(obj)
|
||||
response.complete()
|
||||
|
||||
return T.of(obj)
|
||||
response.next({
|
||||
type: "network_fail",
|
||||
req,
|
||||
error: res.left,
|
||||
})
|
||||
)()
|
||||
response.complete()
|
||||
})
|
||||
|
||||
return response
|
||||
return [response, () => res.cancel()]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user