diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json
index 27ed5ba67..6315e5d2b 100644
--- a/packages/hoppscotch-common/locales/en.json
+++ b/packages/hoppscotch-common/locales/en.json
@@ -249,6 +249,7 @@
"no_duration": "No duration",
"no_results_found": "No matches found",
"page_not_found": "This page could not be found",
+ "proxy_error": "Proxy error",
"script_fail": "Could not execute pre-request script",
"something_went_wrong": "Something went wrong",
"test_script_fail": "Could not execute post-request script"
diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts
index ba0a9fb7d..13997d2af 100644
--- a/packages/hoppscotch-common/src/components.d.ts
+++ b/packages/hoppscotch-common/src/components.d.ts
@@ -92,6 +92,7 @@ declare module '@vue/runtime-core' {
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
+ HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio']
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
@@ -160,6 +161,8 @@ declare module '@vue/runtime-core' {
RealtimeLog: typeof import('./components/realtime/Log.vue')['default']
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
+ SettingsExtension: typeof import('./components/settings/Extension.vue')['default']
+ SettingsProxy: typeof import('./components/settings/Proxy.vue')['default']
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
SmartAnchor: typeof import('./../../hoppscotch-ui/src/components/smart/Anchor.vue')['default']
SmartAutoComplete: typeof import('./../../hoppscotch-ui/src/components/smart/AutoComplete.vue')['default']
diff --git a/packages/hoppscotch-common/src/components/app/Interceptor.vue b/packages/hoppscotch-common/src/components/app/Interceptor.vue
index f54dbb110..427770310 100644
--- a/packages/hoppscotch-common/src/components/app/Interceptor.vue
+++ b/packages/hoppscotch-common/src/components/app/Interceptor.vue
@@ -8,91 +8,41 @@
{{ t("settings.interceptor_description") }}
-
-
diff --git a/packages/hoppscotch-common/src/components/graphql/Request.vue b/packages/hoppscotch-common/src/components/graphql/Request.vue
index afaefa79c..9f303b05a 100644
--- a/packages/hoppscotch-common/src/components/graphql/Request.vue
+++ b/packages/hoppscotch-common/src/components/graphql/Request.vue
@@ -29,7 +29,6 @@
diff --git a/packages/hoppscotch-common/src/components/settings/Extension.vue b/packages/hoppscotch-common/src/components/settings/Extension.vue
new file mode 100644
index 000000000..1ccd80baa
--- /dev/null
+++ b/packages/hoppscotch-common/src/components/settings/Extension.vue
@@ -0,0 +1,88 @@
+
+
+
+ {{
+ `${t("settings.extension_version")}: v${extensionVersion.major}.${
+ extensionVersion.minor
+ }`
+ }}
+
+
+ {{ t("settings.extension_version") }}:
+ {{ t("settings.extension_ver_not_reported") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("settings.extensions_use_toggle") }}
+
+
+
+
+
+
diff --git a/packages/hoppscotch-common/src/components/settings/Proxy.vue b/packages/hoppscotch-common/src/components/settings/Proxy.vue
new file mode 100644
index 000000000..f5ff15ff8
--- /dev/null
+++ b/packages/hoppscotch-common/src/components/settings/Proxy.vue
@@ -0,0 +1,94 @@
+
+
+ {{ `${t("settings.official_proxy_hosting")} ${t("settings.read_the")}` }}
+ .
+
+
+
+
+ {{ t("settings.proxy_use_toggle") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/hoppscotch-common/src/helpers/GQLConnection.ts b/packages/hoppscotch-common/src/helpers/GQLConnection.ts
index 15d17b5cd..3af67815e 100644
--- a/packages/hoppscotch-common/src/helpers/GQLConnection.ts
+++ b/packages/hoppscotch-common/src/helpers/GQLConnection.ts
@@ -1,3 +1,4 @@
+import * as E from "fp-ts/Either"
import { BehaviorSubject } from "rxjs"
import {
getIntrospectionQuery,
@@ -11,7 +12,8 @@ import {
} from "graphql"
import { distinctUntilChanged, map } from "rxjs/operators"
import { GQLHeader, HoppGQLAuth } from "@hoppscotch/data"
-import { sendNetworkRequest } from "./network"
+import { getService } from "~/modules/dioc"
+import { InterceptorService } from "~/services/interceptor.service"
const GQL_SCHEMA_POLL_INTERVAL = 7000
@@ -181,7 +183,7 @@ export class GQLConnection {
headers.forEach((x) => (finalHeaders[x.key] = x.value))
const reqOptions = {
- method: "POST",
+ method: "POST" as const,
url,
headers: {
...finalHeaders,
@@ -190,11 +192,20 @@ export class GQLConnection {
data: introspectionQuery,
}
- const data = await sendNetworkRequest(reqOptions)
+ const interceptorService = getService(InterceptorService)
+
+ const res = await interceptorService.runRequest(reqOptions).response
+
+ if (E.isLeft(res)) {
+ console.error(res.left)
+ throw new Error(res.left.toString())
+ }
+
+ const data = res.right
// HACK : Temporary trailing null character issue from the extension fix
const response = new TextDecoder("utf-8")
- .decode(data.data)
+ .decode(data.data as any)
.replace(/\0+$/, "")
const introspectResponse = JSON.parse(response)
@@ -245,7 +256,7 @@ export class GQLConnection {
.forEach(({ key, value }) => (finalHeaders[key] = value))
const reqOptions = {
- method: "POST",
+ method: "POST" as const,
url,
headers: {
...finalHeaders,
@@ -260,11 +271,19 @@ export class GQLConnection {
},
}
- const res = await sendNetworkRequest(reqOptions)
+ const interceptorService = getService(InterceptorService)
+ const result = await interceptorService.runRequest(reqOptions).response
+
+ if (E.isLeft(result)) {
+ console.error(result.left)
+ throw new Error(result.left.toString())
+ }
+
+ const res = result.right
// HACK: Temporary trailing null character issue from the extension fix
const responseText = new TextDecoder("utf-8")
- .decode(res.data)
+ .decode(res.data as any)
.replace(/\0+$/, "")
return responseText
diff --git a/packages/hoppscotch-common/src/helpers/RequestRunner.ts b/packages/hoppscotch-common/src/helpers/RequestRunner.ts
index a79a95907..98666efc0 100644
--- a/packages/hoppscotch-common/src/helpers/RequestRunner.ts
+++ b/packages/hoppscotch-common/src/helpers/RequestRunner.ts
@@ -1,6 +1,5 @@
import { Observable, Subject } from "rxjs"
import { filter } from "rxjs/operators"
-import * as TE from "fp-ts/lib/TaskEither"
import { flow, pipe } from "fp-ts/function"
import * as O from "fp-ts/Option"
import * as A from "fp-ts/Array"
@@ -10,7 +9,7 @@ import {
runTestScript,
TestDescriptor,
} from "@hoppscotch/js-sandbox"
-import { isRight } from "fp-ts/Either"
+import * as E from "fp-ts/Either"
import { cloneDeep } from "lodash-es"
import {
getCombinedEnvVariables,
@@ -69,106 +68,130 @@ export const executedResponses$ = new Subject<
HoppRESTResponse & { type: "success" | "fail " }
>()
-export const runRESTRequest$ = (
+export function runRESTRequest$(
tab: Ref
-): TE.TaskEither> =>
- pipe(
- getFinalEnvsFromPreRequest(
- tab.value.document.request.preRequestScript,
- getCombinedEnvVariables()
- ),
- TE.chain((envs) => {
- const effectiveRequest = getEffectiveRESTRequest(
- tab.value.document.request,
- {
- name: "Env",
- variables: combineEnvVariables(envs),
- }
- )
+): [
+ () => void,
+ Promise<
+ | E.Left<"script_fail" | "cancellation">
+ | E.Right>
+ >
+] {
+ let cancelCalled = false
+ let cancelFunc: (() => void) | null = null
- const stream = createRESTNetworkRequestStream(effectiveRequest)
+ const cancel = () => {
+ cancelCalled = true
+ cancelFunc?.()
+ }
- // Run Test Script when request ran successfully
- const subscription = stream
- .pipe(filter((res) => res.type === "success" || res.type === "fail"))
- .subscribe(async (res) => {
- if (res.type === "success" || res.type === "fail") {
- executedResponses$.next(
- // @ts-expect-error Typescript can't figure out this inference for some reason
- res
- )
+ const res = getFinalEnvsFromPreRequest(
+ tab.value.document.request.preRequestScript,
+ getCombinedEnvVariables()
+ )().then((envs) => {
+ if (cancelCalled) return E.left("cancellation" as const)
- const runResult = await runTestScript(res.req.testScript, envs, {
+ if (E.isLeft(envs)) {
+ console.error(envs.left)
+ return E.left("script_fail" as const)
+ }
+
+ const effectiveRequest = getEffectiveRESTRequest(
+ tab.value.document.request,
+ {
+ name: "Env",
+ variables: combineEnvVariables(envs.right),
+ }
+ )
+
+ const [stream, cancelRun] = createRESTNetworkRequestStream(effectiveRequest)
+ cancelFunc = cancelRun
+
+ const subscription = stream
+ .pipe(filter((res) => res.type === "success" || res.type === "fail"))
+ .subscribe(async (res) => {
+ if (res.type === "success" || res.type === "fail") {
+ executedResponses$.next(
+ // @ts-expect-error Typescript can't figure out this inference for some reason
+ res
+ )
+
+ const runResult = await runTestScript(
+ res.req.testScript,
+ envs.right,
+ {
status: res.statusCode,
body: getTestableBody(res),
headers: res.headers,
- })()
-
- if (isRight(runResult)) {
- tab.value.testResults = translateToSandboxTestResults(
- runResult.right
- )
-
- setGlobalEnvVariables(runResult.right.envs.global)
-
- if (
- environmentsStore.value.selectedEnvironmentIndex.type ===
- "MY_ENV"
- ) {
- const env = getEnvironment({
- type: "MY_ENV",
- index: environmentsStore.value.selectedEnvironmentIndex.index,
- })
- updateEnvironment(
- environmentsStore.value.selectedEnvironmentIndex.index,
- {
- ...env,
- variables: runResult.right.envs.selected,
- }
- )
- } else if (
- environmentsStore.value.selectedEnvironmentIndex.type ===
- "TEAM_ENV"
- ) {
- const env = getEnvironment({
- type: "TEAM_ENV",
- })
- pipe(
- updateTeamEnvironment(
- JSON.stringify(runResult.right.envs.selected),
- environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
- env.name
- )
- )()
- }
- } else {
- tab.value.testResults = {
- description: "",
- expectResults: [],
- tests: [],
- envDiff: {
- global: {
- additions: [],
- deletions: [],
- updations: [],
- },
- selected: {
- additions: [],
- deletions: [],
- updations: [],
- },
- },
- scriptError: true,
- }
}
+ )()
- subscription.unsubscribe()
+ if (E.isRight(runResult)) {
+ tab.value.testResults = translateToSandboxTestResults(
+ runResult.right
+ )
+
+ setGlobalEnvVariables(runResult.right.envs.global)
+
+ if (
+ environmentsStore.value.selectedEnvironmentIndex.type === "MY_ENV"
+ ) {
+ const env = getEnvironment({
+ type: "MY_ENV",
+ index: environmentsStore.value.selectedEnvironmentIndex.index,
+ })
+ updateEnvironment(
+ environmentsStore.value.selectedEnvironmentIndex.index,
+ {
+ ...env,
+ variables: runResult.right.envs.selected,
+ }
+ )
+ } else if (
+ environmentsStore.value.selectedEnvironmentIndex.type ===
+ "TEAM_ENV"
+ ) {
+ const env = getEnvironment({
+ type: "TEAM_ENV",
+ })
+ pipe(
+ updateTeamEnvironment(
+ JSON.stringify(runResult.right.envs.selected),
+ environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
+ env.name
+ )
+ )()
+ }
+ } else {
+ tab.value.testResults = {
+ description: "",
+ expectResults: [],
+ tests: [],
+ envDiff: {
+ global: {
+ additions: [],
+ deletions: [],
+ updations: [],
+ },
+ selected: {
+ additions: [],
+ deletions: [],
+ updations: [],
+ },
+ },
+ scriptError: true,
+ }
}
- })
- return TE.right(stream)
- })
- )
+ subscription.unsubscribe()
+ }
+ })
+
+ return E.right(stream)
+ })
+
+ return [cancel, res]
+}
const getAddedEnvVariables = (
current: Environment["variables"],
diff --git a/packages/hoppscotch-common/src/helpers/network.ts b/packages/hoppscotch-common/src/helpers/network.ts
index 4e2f2522f..33d2f8d2f 100644
--- a/packages/hoppscotch-common/src/helpers/network.ts
+++ b/packages/hoppscotch-common/src/helpers/network.ts
@@ -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 & {
- 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
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 }) =>
- {
- 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 {
+ 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 {
+): [Observable, () => void] {
const response = new BehaviorSubject({
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()]
}
diff --git a/packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts b/packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts
deleted file mode 100644
index 137bc6592..000000000
--- a/packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import axios, { AxiosRequestConfig } from "axios"
-import { v4 } from "uuid"
-import { pipe } from "fp-ts/function"
-import * as TE from "fp-ts/TaskEither"
-import { cloneDeep } from "lodash-es"
-import { NetworkResponse, NetworkStrategy } from "../network"
-import { decodeB64StringToArrayBuffer } from "../utils/b64"
-import { settingsStore } from "~/newstore/settings"
-
-let cancelSource = axios.CancelToken.source()
-
-type ProxyHeaders = {
- "multipart-part-key"?: string
-}
-
-type ProxyPayloadType = FormData | (AxiosRequestConfig & { wantsBinary: true })
-
-export const cancelRunningAxiosRequest = () => {
- cancelSource.cancel()
-
- // Create a new cancel token
- cancelSource = axios.CancelToken.source()
-}
-
-const getProxyPayload = (
- req: AxiosRequestConfig,
- multipartKey: string | null
-) => {
- let payload: ProxyPayloadType = {
- ...req,
- wantsBinary: true,
- accessToken: import.meta.env.VITE_PROXYSCOTCH_ACCESS_TOKEN ?? "",
- }
-
- if (payload.data instanceof FormData) {
- const formData = payload.data
- payload.data = ""
- formData.append(multipartKey!, JSON.stringify(payload))
- payload = formData
- }
-
- return payload
-}
-
-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
-}
-
-const axiosWithProxy: NetworkStrategy = (req) =>
- pipe(
- TE.Do,
-
- TE.bind("processedReq", () => TE.of(preProcessRequest(req))),
-
- // If the request has FormData, the proxy needs a key
- TE.bind("multipartKey", ({ processedReq }) =>
- TE.of(
- processedReq.data instanceof FormData
- ? `proxyRequestData-${v4()}`
- : null
- )
- ),
-
- // Build headers to send
- TE.bind("headers", ({ processedReq, multipartKey }) =>
- TE.of(
- processedReq.data instanceof FormData
- ? {
- "multipart-part-key": multipartKey,
- }
- : {}
- )
- ),
-
- // Create payload
- TE.bind("payload", ({ processedReq, multipartKey }) =>
- TE.of(getProxyPayload(processedReq, multipartKey))
- ),
-
- // Run the proxy request
- TE.chain(({ payload, headers }) =>
- TE.tryCatch(
- () =>
- axios.post(
- settingsStore.value.PROXY_URL || "https://proxy.hoppscotch.io",
- payload,
- {
- headers,
- cancelToken: cancelSource.token,
- }
- ),
- (reason) =>
- axios.isCancel(reason)
- ? "cancellation" // Convert cancellation errors into cancellation strings
- : reason
- )
- ),
-
- // Check success predicate
- TE.chain(
- TE.fromPredicate(
- ({ data }) => data.success,
- ({ data }) => data.data.message || "Proxy Error"
- )
- ),
-
- // Process Base64
- TE.chain(({ data }) => {
- if (data.isBinary) {
- data.data = decodeB64StringToArrayBuffer(data.data)
- }
-
- return TE.of(data)
- })
- )
-
-const axiosWithoutProxy: NetworkStrategy = (req) =>
- pipe(
- TE.tryCatch(
- () =>
- axios({
- ...req,
- cancelToken: (cancelSource && cancelSource.token) || "",
- responseType: "arraybuffer",
- }),
- (e) => (axios.isCancel(e) ? "cancellation" : (e as any))
- ),
-
- TE.orElse((e) =>
- e !== "cancellation" && e.response
- ? TE.right(e.response as NetworkResponse)
- : TE.left(e)
- )
- )
-
-const axiosStrategy: NetworkStrategy = (req) =>
- pipe(
- req,
- settingsStore.value.PROXY_ENABLED ? axiosWithProxy : axiosWithoutProxy
- )
-
-export default axiosStrategy
diff --git a/packages/hoppscotch-common/src/helpers/strategies/ExtensionStrategy.ts b/packages/hoppscotch-common/src/helpers/strategies/ExtensionStrategy.ts
deleted file mode 100644
index 5bb0b69df..000000000
--- a/packages/hoppscotch-common/src/helpers/strategies/ExtensionStrategy.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import * as TE from "fp-ts/TaskEither"
-import * as O from "fp-ts/Option"
-import { pipe } from "fp-ts/function"
-import { AxiosRequestConfig } from "axios"
-import { cloneDeep } from "lodash-es"
-import { NetworkResponse, NetworkStrategy } from "../network"
-import { browserIsChrome, browserIsFirefox } from "../utils/userAgent"
-
-export const hasExtensionInstalled = () =>
- typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined"
-
-export const hasChromeExtensionInstalled = () =>
- hasExtensionInstalled() && browserIsChrome()
-
-export const hasFirefoxExtensionInstalled = () =>
- hasExtensionInstalled() && browserIsFirefox()
-
-export const cancelRunningExtensionRequest = () => {
- window.__POSTWOMAN_EXTENSION_HOOK__?.cancelRequest()
-}
-
-export const defineSubscribableObject = (obj: T) => {
- const proxyObject = {
- ...obj,
- _subscribers: {} as {
- // eslint-disable-next-line no-unused-vars
- [key in keyof T]?: ((...args: any[]) => any)[]
- },
- subscribe(prop: keyof T, func: (...args: any[]) => any): void {
- if (Array.isArray(this._subscribers[prop])) {
- this._subscribers[prop]?.push(func)
- } else {
- this._subscribers[prop] = [func]
- }
- },
- }
-
- type SubscribableProxyObject = typeof proxyObject
-
- return new Proxy(proxyObject, {
- set(obj, prop, newVal) {
- obj[prop as keyof SubscribableProxyObject] = newVal
-
- const currentSubscribers = obj._subscribers[prop as keyof T]
-
- if (Array.isArray(currentSubscribers)) {
- for (const subscriber of currentSubscribers) {
- subscriber(newVal)
- }
- }
-
- return true
- },
- })
-}
-
-const preProcessRequest = (req: AxiosRequestConfig): AxiosRequestConfig => {
- const reqClone = cloneDeep(req)
-
- // If the parameters are URLSearchParams, inject them to URL instead
- // This prevents marshalling issues with structured cloning of URLSearchParams
- 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
-}
-
-const extensionStrategy: NetworkStrategy = (req) =>
- pipe(
- TE.Do,
-
- TE.bind("processedReq", () => TE.of(preProcessRequest(req))),
-
- // Storeing backup timing data in case the extension does not have that info
- TE.bind("backupTimeDataStart", () => TE.of(new Date().getTime())),
-
- // Run the request
- TE.bind("response", ({ processedReq }) =>
- pipe(
- window.__POSTWOMAN_EXTENSION_HOOK__,
- O.fromNullable,
- TE.fromOption(() => "NO_PW_EXT_HOOK" as const),
- TE.chain((extensionHook) =>
- TE.tryCatch(
- () =>
- extensionHook.sendRequest({
- ...processedReq,
- wantsBinary: true,
- }),
- (err) => err as any
- )
- )
- )
- ),
-
- // Inject backup time data if not present
- TE.map(({ backupTimeDataStart, response }) => ({
- ...response,
- config: {
- timeData: {
- startTime: backupTimeDataStart,
- endTime: new Date().getTime(),
- },
- ...response.config,
- },
- })),
- TE.orElse((e) =>
- e !== "cancellation" && e.response
- ? TE.right(e.response as NetworkResponse)
- : TE.left(e)
- )
- )
-
-export default extensionStrategy
diff --git a/packages/hoppscotch-common/src/helpers/strategies/__tests__/AxiosStrategy-NoProxy.spec.js b/packages/hoppscotch-common/src/helpers/strategies/__tests__/AxiosStrategy-NoProxy.spec.js
deleted file mode 100644
index 6005eaf13..000000000
--- a/packages/hoppscotch-common/src/helpers/strategies/__tests__/AxiosStrategy-NoProxy.spec.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import { vi, describe, expect, test } from "vitest"
-import axios from "axios"
-import axiosStrategy from "../AxiosStrategy"
-
-vi.mock("axios")
-vi.mock("~/newstore/settings", () => {
- return {
- __esModule: true,
- settingsStore: {
- value: {
- PROXY_ENABLED: false,
- },
- },
- }
-})
-
-axios.CancelToken.source.mockReturnValue({ token: "test" })
-axios.mockResolvedValue({})
-
-describe("axiosStrategy", () => {
- describe("No-Proxy Requests", () => {
- test("sends request to the actual sender if proxy disabled", async () => {
- await axiosStrategy({ url: "test" })()
-
- expect(axios).toBeCalledWith(
- expect.objectContaining({
- url: "test",
- })
- )
- })
-
- test("asks axios to return data as arraybuffer", async () => {
- await axiosStrategy({ url: "test" })()
-
- expect(axios).toBeCalledWith(
- expect.objectContaining({
- responseType: "arraybuffer",
- })
- )
- })
-
- test("resolves successful requests", async () => {
- expect(await axiosStrategy({})()).toBeRight()
- })
-
- test("rejects cancel errors with text 'cancellation'", async () => {
- axios.isCancel.mockReturnValueOnce(true)
- axios.mockRejectedValue("err")
-
- expect(await axiosStrategy({})()).toEqualLeft("cancellation")
- })
-
- test("rejects non-cancellation errors as-is", async () => {
- axios.isCancel.mockReturnValueOnce(false)
- axios.mockRejectedValue("err")
-
- expect(await axiosStrategy({})()).toEqualLeft("err")
- })
-
- test("non-cancellation errors that have response data are right", async () => {
- const errorResponse = { error: "errr" }
- axios.isCancel.mockReturnValueOnce(false)
- axios.mockRejectedValue({
- response: {
- data: Buffer.from(JSON.stringify(errorResponse), "utf8").toString(
- "base64"
- ),
- },
- })
-
- expect(await axiosStrategy({})()).toSubsetEqualRight({
- data: "eyJlcnJvciI6ImVycnIifQ==",
- })
- })
- })
-})
diff --git a/packages/hoppscotch-common/src/helpers/strategies/__tests__/AxiosStrategy-Proxy.spec.js b/packages/hoppscotch-common/src/helpers/strategies/__tests__/AxiosStrategy-Proxy.spec.js
deleted file mode 100644
index 314cac603..000000000
--- a/packages/hoppscotch-common/src/helpers/strategies/__tests__/AxiosStrategy-Proxy.spec.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import { describe, test, expect, vi } from "vitest"
-import axios from "axios"
-import axiosStrategy from "../AxiosStrategy"
-
-vi.mock("../../utils/b64", () => ({
- __esModule: true,
- decodeB64StringToArrayBuffer: vi.fn((data) => `${data}-converted`),
-}))
-vi.mock("~/newstore/settings", () => {
- return {
- __esModule: true,
- settingsStore: {
- value: {
- PROXY_ENABLED: true,
- PROXY_URL: "test",
- },
- },
- }
-})
-
-describe("axiosStrategy", () => {
- describe("Proxy Requests", () => {
- test("sends POST request to proxy if proxy is enabled", async () => {
- let passedURL
-
- vi.spyOn(axios, "post").mockImplementation((url) => {
- passedURL = url
- return Promise.resolve({ data: { success: true, isBinary: false } })
- })
-
- await axiosStrategy({})()
-
- expect(passedURL).toEqual("test")
- })
-
- test("passes request fields to axios properly", async () => {
- const reqFields = {
- testA: "testA",
- testB: "testB",
- testC: "testC",
- }
-
- let passedFields
-
- vi.spyOn(axios, "post").mockImplementation((_url, req) => {
- passedFields = req
- return Promise.resolve({ data: { success: true, isBinary: false } })
- })
-
- await axiosStrategy(reqFields)()
-
- expect(passedFields).toMatchObject(reqFields)
- })
-
- test("passes wantsBinary field", async () => {
- let passedFields
-
- vi.spyOn(axios, "post").mockImplementation((_url, req) => {
- passedFields = req
- return Promise.resolve({ data: { success: true, isBinary: false } })
- })
-
- await axiosStrategy({})()
-
- expect(passedFields).toHaveProperty("wantsBinary")
- })
-
- test("checks for proxy response success field and throws error message for non-success", async () => {
- vi.spyOn(axios, "post").mockResolvedValue({
- data: {
- success: false,
- data: {
- message: "test message",
- },
- },
- })
-
- expect(await axiosStrategy({})()).toEqualLeft("test message")
- })
-
- test("checks for proxy response success field and throws error 'Proxy Error' for non-success", async () => {
- vi.spyOn(axios, "post").mockResolvedValue({
- data: {
- success: false,
- data: {},
- },
- })
-
- expect(await axiosStrategy({})()).toBeLeft("Proxy Error")
- })
-
- test("checks for proxy response success and doesn't left for success", async () => {
- vi.spyOn(axios, "post").mockResolvedValue({
- data: {
- success: true,
- data: {},
- },
- })
-
- expect(await axiosStrategy({})()).toBeRight()
- })
-
- test("checks isBinary response field and right with the converted value if so", async () => {
- vi.spyOn(axios, "post").mockResolvedValue({
- data: {
- success: true,
- isBinary: true,
- data: "testdata",
- },
- })
-
- expect(await axiosStrategy({})()).toSubsetEqualRight({
- data: "testdata-converted",
- })
- })
-
- test("checks isBinary response field and right with the actual value if not so", async () => {
- vi.spyOn(axios, "post").mockResolvedValue({
- data: {
- success: true,
- isBinary: false,
- data: "testdata",
- },
- })
-
- expect(await axiosStrategy({})()).toSubsetEqualRight({
- data: "testdata",
- })
- })
-
- test("cancel errors are returned a left with the string 'cancellation'", async () => {
- vi.spyOn(axios, "post").mockRejectedValue("errr")
- vi.spyOn(axios, "isCancel").mockReturnValueOnce(true)
-
- expect(await axiosStrategy({})()).toEqualLeft("cancellation")
- })
-
- test("non-cancellation errors return a left", async () => {
- vi.spyOn(axios, "post").mockRejectedValue("errr")
- vi.spyOn(axios, "isCancel").mockReturnValueOnce(false)
-
- expect(await axiosStrategy({})()).toEqualLeft("errr")
- })
- })
-})
diff --git a/packages/hoppscotch-common/src/helpers/strategies/__tests__/ExtensionStrategy-NoProxy.spec.js b/packages/hoppscotch-common/src/helpers/strategies/__tests__/ExtensionStrategy-NoProxy.spec.js
deleted file mode 100644
index 99d977373..000000000
--- a/packages/hoppscotch-common/src/helpers/strategies/__tests__/ExtensionStrategy-NoProxy.spec.js
+++ /dev/null
@@ -1,212 +0,0 @@
-import { vi, describe, expect, test, beforeEach } from "vitest"
-import extensionStrategy, {
- hasExtensionInstalled,
- hasChromeExtensionInstalled,
- hasFirefoxExtensionInstalled,
- cancelRunningExtensionRequest,
-} from "../ExtensionStrategy"
-
-vi.mock("../../utils/b64", () => ({
- __esModule: true,
- decodeB64StringToArrayBuffer: vi.fn((data) => `${data}-converted`),
-}))
-
-vi.mock("~/newstore/settings", () => {
- return {
- __esModule: true,
- settingsStore: {
- value: {
- EXTENSIONS_ENABLED: true,
- PROXY_ENABLED: false,
- },
- },
- }
-})
-
-describe("hasExtensionInstalled", () => {
- test("returns true if extension is present and hooked", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {}
-
- expect(hasExtensionInstalled()).toEqual(true)
- })
-
- test("returns false if extension not present or not hooked", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
-
- expect(hasExtensionInstalled()).toEqual(false)
- })
-})
-
-describe("hasChromeExtensionInstalled", () => {
- test("returns true if extension is hooked and browser is chrome", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {}
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
- vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
-
- expect(hasChromeExtensionInstalled()).toEqual(true)
- })
-
- test("returns false if extension is hooked and browser is not chrome", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {}
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
- vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
-
- expect(hasChromeExtensionInstalled()).toEqual(false)
- })
-
- test("returns false if extension not installed and browser is chrome", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
- vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
-
- expect(hasChromeExtensionInstalled()).toEqual(false)
- })
-
- test("returns false if extension not installed and browser is not chrome", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
- vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
-
- expect(hasChromeExtensionInstalled()).toEqual(false)
- })
-})
-
-describe("hasFirefoxExtensionInstalled", () => {
- test("returns true if extension is hooked and browser is firefox", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {}
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
-
- expect(hasFirefoxExtensionInstalled()).toEqual(true)
- })
-
- test("returns false if extension is hooked and browser is not firefox", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {}
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
-
- expect(hasFirefoxExtensionInstalled()).toEqual(false)
- })
-
- test("returns false if extension not installed and browser is firefox", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
-
- expect(hasFirefoxExtensionInstalled()).toEqual(false)
- })
-
- test("returns false if extension not installed and browser is not firefox", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
- vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
-
- expect(hasFirefoxExtensionInstalled()).toEqual(false)
- })
-})
-
-describe("cancelRunningExtensionRequest", () => {
- const cancelFunc = vi.fn()
-
- beforeEach(() => {
- cancelFunc.mockClear()
- })
-
- test("cancels request if extension installed and function present in hook", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {
- cancelRequest: cancelFunc,
- }
-
- cancelRunningExtensionRequest()
- expect(cancelFunc).toHaveBeenCalledTimes(1)
- })
-
- test("does not cancel request if extension not installed", () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
-
- cancelRunningExtensionRequest()
- expect(cancelFunc).not.toHaveBeenCalled()
- })
-})
-
-describe("extensionStrategy", () => {
- const sendReqFunc = vi.fn()
-
- beforeEach(() => {
- sendReqFunc.mockClear()
- })
-
- describe("Non-Proxy Requests", () => {
- test("ask extension to send request", async () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {
- sendRequest: sendReqFunc,
- }
-
- sendReqFunc.mockResolvedValue({
- data: '{"success":true,"data":""}',
- })
-
- await extensionStrategy({})()
-
- expect(sendReqFunc).toHaveBeenCalledTimes(1)
- })
-
- test("sends request to the actual sender if proxy disabled", async () => {
- let passedUrl
-
- global.__POSTWOMAN_EXTENSION_HOOK__ = {
- sendRequest: sendReqFunc,
- }
-
- sendReqFunc.mockImplementation(({ url }) => {
- passedUrl = url
-
- return Promise.resolve({
- data: '{"success":true,"data":""}',
- })
- })
-
- await extensionStrategy({ url: "test" })()
-
- expect(passedUrl).toEqual("test")
- })
-
- test("asks extension to get binary data", async () => {
- let passedFields
-
- global.__POSTWOMAN_EXTENSION_HOOK__ = {
- sendRequest: sendReqFunc,
- }
-
- sendReqFunc.mockImplementation((fields) => {
- passedFields = fields
-
- return Promise.resolve({
- data: '{"success":true,"data":""}',
- })
- })
-
- await extensionStrategy({})()
-
- expect(passedFields).toHaveProperty("wantsBinary")
- })
-
- test("rights successful requests", async () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {
- sendRequest: sendReqFunc,
- }
-
- sendReqFunc.mockResolvedValue({
- data: '{"success":true,"data":""}',
- })
-
- expect(await extensionStrategy({})()).toBeRight()
- })
-
- test("rejects errors as-is", async () => {
- global.__POSTWOMAN_EXTENSION_HOOK__ = {
- sendRequest: sendReqFunc,
- }
-
- sendReqFunc.mockRejectedValue("err")
-
- expect(await extensionStrategy({})()).toEqualLeft("err")
- })
- })
-})
diff --git a/packages/hoppscotch-common/src/helpers/types/HoppRESTResponse.ts b/packages/hoppscotch-common/src/helpers/types/HoppRESTResponse.ts
index b43f6f6fd..bb5141d4c 100644
--- a/packages/hoppscotch-common/src/helpers/types/HoppRESTResponse.ts
+++ b/packages/hoppscotch-common/src/helpers/types/HoppRESTResponse.ts
@@ -19,7 +19,7 @@ export type HoppRESTResponse =
}
| {
type: "network_fail"
- error: Error
+ error: unknown
req: HoppRESTRequest
}
diff --git a/packages/hoppscotch-common/src/modules/hoppExtension.ts b/packages/hoppscotch-common/src/modules/hoppExtension.ts
deleted file mode 100644
index b4e114472..000000000
--- a/packages/hoppscotch-common/src/modules/hoppExtension.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { HoppModule } from "."
-import {
- changeExtensionStatus,
- ExtensionStatus,
-} from "~/newstore/HoppExtension"
-import { ref } from "vue"
-import { defineSubscribableObject } from "~/helpers/strategies/ExtensionStrategy"
-
-/* Module defining the hooking mechanism between Hoppscotch and the Hoppscotch Browser Extension */
-
-export default {
- onVueAppInit() {
- const extensionPollIntervalId = ref>()
-
- if (window.__HOPP_EXTENSION_STATUS_PROXY__) {
- changeExtensionStatus(window.__HOPP_EXTENSION_STATUS_PROXY__.status)
-
- window.__HOPP_EXTENSION_STATUS_PROXY__.subscribe(
- "status",
- (status: ExtensionStatus) => changeExtensionStatus(status)
- )
- } else {
- const statusProxy = defineSubscribableObject({
- status: "waiting" as ExtensionStatus,
- })
-
- window.__HOPP_EXTENSION_STATUS_PROXY__ = statusProxy
- statusProxy.subscribe("status", (status: ExtensionStatus) =>
- changeExtensionStatus(status)
- )
-
- /**
- * Keeping identifying extension backward compatible
- * We are assuming the default version is 0.24 or later. So if the extension exists, its identified immediately,
- * then we use a poll to find the version, this will get the version for 0.24 and any other version
- * of the extension, but will have a slight lag.
- * 0.24 users will get the benefits of 0.24, while the extension won't break for the old users
- */
- extensionPollIntervalId.value = setInterval(() => {
- if (typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined") {
- if (extensionPollIntervalId.value)
- clearInterval(extensionPollIntervalId.value)
-
- const version = window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
-
- // When the version is not 0.24 or higher, the extension wont do this. so we have to do it manually
- if (
- version.major === 0 &&
- version.minor <= 23 &&
- window.__HOPP_EXTENSION_STATUS_PROXY__
- ) {
- window.__HOPP_EXTENSION_STATUS_PROXY__.status = "available"
- }
- }
- }, 2000)
- }
- },
-}
diff --git a/packages/hoppscotch-common/src/modules/interceptors.ts b/packages/hoppscotch-common/src/modules/interceptors.ts
new file mode 100644
index 000000000..43d71a322
--- /dev/null
+++ b/packages/hoppscotch-common/src/modules/interceptors.ts
@@ -0,0 +1,46 @@
+import { InterceptorService } from "~/services/interceptor.service"
+import { HoppModule } from "."
+import { getService } from "./dioc"
+import { platform } from "~/platform"
+import { watch } from "vue"
+import { applySetting } from "~/newstore/settings"
+import { useSettingStatic } from "~/composables/settings"
+
+export default {
+ onVueAppInit() {
+ const interceptorService = getService(InterceptorService)
+
+ for (const interceptorDef of platform.interceptors.interceptors) {
+ if (interceptorDef.type === "standalone") {
+ interceptorService.registerInterceptor(interceptorDef.interceptor)
+ } else {
+ const service = getService(interceptorDef.service)
+
+ interceptorService.registerInterceptor(service)
+ }
+ }
+
+ interceptorService.currentInterceptorID.value =
+ platform.interceptors.default
+
+ watch(interceptorService.currentInterceptorID, (id) => {
+ applySetting(
+ "CURRENT_INTERCEPTOR_ID",
+ id ?? platform.interceptors.default
+ )
+ })
+
+ const [setting] = useSettingStatic("CURRENT_INTERCEPTOR_ID")
+
+ watch(
+ setting,
+ () => {
+ interceptorService.currentInterceptorID.value =
+ setting.value ?? platform.interceptors.default
+ },
+ {
+ immediate: true,
+ }
+ )
+ },
+}
diff --git a/packages/hoppscotch-common/src/newstore/localpersistence.ts b/packages/hoppscotch-common/src/newstore/localpersistence.ts
index c67776b4d..b7b94c3e4 100644
--- a/packages/hoppscotch-common/src/newstore/localpersistence.ts
+++ b/packages/hoppscotch-common/src/newstore/localpersistence.ts
@@ -9,10 +9,11 @@ import {
import {
settingsStore,
bulkApplySettings,
- defaultSettings,
+ getDefaultSettings,
applySetting,
HoppAccentColor,
HoppBgColor,
+ performSettingsDataMigrations,
} from "./settings"
import {
restHistoryStore,
@@ -80,7 +81,7 @@ function checkAndMigrateOldSettings() {
const { postwoman } = vuexData
if (!isEmpty(postwoman?.settings)) {
- const settingsData = assign(clone(defaultSettings), postwoman.settings)
+ const settingsData = assign(clone(getDefaultSettings()), postwoman.settings)
window.localStorage.setItem("settings", JSON.stringify(settingsData))
@@ -150,8 +151,12 @@ function setupSettingsPersistence() {
window.localStorage.getItem("settings") || "{}"
)
- if (settingsData) {
- bulkApplySettings(settingsData)
+ const updatedSettings = settingsData
+ ? performSettingsDataMigrations(settingsData)
+ : settingsData
+
+ if (updatedSettings) {
+ bulkApplySettings(updatedSettings)
}
settingsStore.subject$.subscribe((settings) => {
diff --git a/packages/hoppscotch-common/src/newstore/settings.ts b/packages/hoppscotch-common/src/newstore/settings.ts
index cc4ffa43f..c23d57b75 100644
--- a/packages/hoppscotch-common/src/newstore/settings.ts
+++ b/packages/hoppscotch-common/src/newstore/settings.ts
@@ -1,5 +1,5 @@
import { pluck, distinctUntilChanged } from "rxjs/operators"
-import { has } from "lodash-es"
+import { cloneDeep, defaultsDeep, has } from "lodash-es"
import { Observable } from "rxjs"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
@@ -32,9 +32,10 @@ export type SettingsDef = {
syncHistory: boolean
syncEnvironments: boolean
- PROXY_ENABLED: boolean
PROXY_URL: string
- EXTENSIONS_ENABLED: boolean
+
+ CURRENT_INTERCEPTOR_ID: string
+
URL_EXCLUDES: {
auth: boolean
httpUser: boolean
@@ -53,14 +54,15 @@ export type SettingsDef = {
COLUMN_LAYOUT: boolean
}
-export const defaultSettings: SettingsDef = {
+export const getDefaultSettings = (): SettingsDef => ({
syncCollections: true,
syncHistory: true,
syncEnvironments: true,
- PROXY_ENABLED: false,
+ CURRENT_INTERCEPTOR_ID: "browser", // TODO: Allow the platform definition to take this place
+
+ // TODO: Interceptor related settings should move under the interceptor systems
PROXY_URL: "https://proxy.hoppscotch.io/",
- EXTENSIONS_ENABLED: false,
URL_EXCLUDES: {
auth: true,
httpUser: true,
@@ -77,7 +79,7 @@ export const defaultSettings: SettingsDef = {
ZEN_MODE: false,
FONT_SIZE: "small",
COLUMN_LAYOUT: true,
-}
+})
type ApplySettingPayload = {
[K in keyof SettingsDef]: {
@@ -86,8 +88,6 @@ type ApplySettingPayload = {
}
}[keyof SettingsDef]
-const validKeys = Object.keys(defaultSettings)
-
const dispatchers = defineDispatchers({
bulkApplySettings(_currentState: SettingsDef, payload: Partial) {
return payload
@@ -112,13 +112,6 @@ const dispatchers = defineDispatchers({
_currentState: SettingsDef,
{ settingKey, value }: ApplySettingPayload
) {
- if (!validKeys.includes(settingKey)) {
- // console.log(
- // `Ignoring non-existent setting key '${settingKey}' assignment`
- // )
- return {}
- }
-
const result: Partial = {
[settingKey]: value,
}
@@ -127,7 +120,10 @@ const dispatchers = defineDispatchers({
},
})
-export const settingsStore = new DispatchingStore(defaultSettings, dispatchers)
+export const settingsStore = new DispatchingStore(
+ getDefaultSettings(),
+ dispatchers
+)
/**
* An observable value to make avail all the state information at once
@@ -156,16 +152,39 @@ export function toggleSetting(settingKey: KeysMatching) {
})
}
-export function applySetting(
- settingKey: K["settingKey"],
- value: K["value"]
+export function applySetting(
+ settingKey: K,
+ value: SettingsDef[K]
) {
settingsStore.dispatch({
dispatcher: "applySetting",
- // @ts-expect-error TS is not able to understand the type semantics here
payload: {
+ // @ts-expect-error TS is not able to understand the type semantics here
settingKey,
+ // @ts-expect-error TS is not able to understand the type semantics here
value,
},
})
}
+
+export function performSettingsDataMigrations(data: any): SettingsDef {
+ const source = cloneDeep(data)
+
+ if (source["EXTENSIONS_ENABLED"]) {
+ const result = JSON.parse(source["EXTENSIONS_ENABLED"])
+
+ if (result) source["CURRENT_INTERCEPTOR_ID"] = "extension"
+ delete source["EXTENSIONS_ENABLED"]
+ }
+
+ if (source["PROXY_ENABLED"]) {
+ const result = JSON.parse(source["PROXY_ENABLED"])
+
+ if (result) source["CURRENT_INTERCEPTOR_ID"] = "proxy"
+ delete source["PROXY_ENABLED"]
+ }
+
+ const final = defaultsDeep(source, getDefaultSettings())
+
+ return final
+}
diff --git a/packages/hoppscotch-common/src/pages/settings.vue b/packages/hoppscotch-common/src/pages/settings.vue
index e4a0a991a..eac82068c 100644
--- a/packages/hoppscotch-common/src/pages/settings.vue
+++ b/packages/hoppscotch-common/src/pages/settings.vue
@@ -113,108 +113,11 @@
-
+
- {{ t("settings.extensions") }}
+ {{ settings.entryTitle(t) }}
-
-
- {{
- `${t("settings.extension_version")}: v${
- extensionVersion.major
- }.${extensionVersion.minor}`
- }}
-
-
- {{ t("settings.extension_version") }}:
- {{ t("settings.extension_ver_not_reported") }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t("settings.extensions_use_toggle") }}
-
-
-
-
-
-
- {{ t("settings.proxy") }}
-
-
- {{
- `${t("settings.official_proxy_hosting")} ${t(
- "settings.read_the"
- )}`
- }}
- .
-
-
-
-
- {{ t("settings.proxy_use_toggle") }}
-
-
-
-
-
-
-
-
-
-
-
+
@@ -236,62 +139,47 @@