diff --git a/packages/hoppscotch-common/src/components/settings/Proxy.vue b/packages/hoppscotch-common/src/components/settings/Proxy.vue index f1ee95bf..a0de5a4c 100644 --- a/packages/hoppscotch-common/src/components/settings/Proxy.vue +++ b/packages/hoppscotch-common/src/components/settings/Proxy.vue @@ -56,12 +56,14 @@ const currentUser = useReadonlyStream( platform.auth.getCurrentUser() ) -watch(currentUser, async () => { - if (!currentUser.value) { - PROXY_URL.value = await getDefaultProxyUrl() +watch( + () => currentUser.value, + async () => { + if (!currentUser.value) { + PROXY_URL.value = await getDefaultProxyUrl() + } } -}) - +) const proxyEnabled = computed( () => interceptorService.currentInterceptorID.value === 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 18bf22f3..00000000 --- a/packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts +++ /dev/null @@ -1,166 +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" -import { getDefaultProxyUrl } from "../proxyUrl" - -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 defaultProxyURL = await getDefaultProxyUrl() - -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 || defaultProxyURL, - 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/newstore/settings.ts b/packages/hoppscotch-common/src/newstore/settings.ts index f45c7687..3028af6c 100644 --- a/packages/hoppscotch-common/src/newstore/settings.ts +++ b/packages/hoppscotch-common/src/newstore/settings.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs" import { distinctUntilChanged, pluck } from "rxjs/operators" import type { KeysMatching } from "~/types/ts-utils" import DispatchingStore, { defineDispatchers } from "./DispatchingStore" -import { getDefaultProxyUrl } from "~/helpers/proxyUrl" +import { DEFAULT_HOPP_PROXY_URL, getDefaultProxyUrl } from "~/helpers/proxyUrl" export const HoppBgColors = ["system", "light", "dark", "black"] as const @@ -83,60 +83,66 @@ export type SettingsDef = { CUSTOM_NAMING_STYLE: string } -const defaultProxyURL = await getDefaultProxyUrl() +let defaultProxyURL = DEFAULT_HOPP_PROXY_URL -export const getDefaultSettings = (): SettingsDef => ({ - syncCollections: true, - syncHistory: true, - syncEnvironments: true, - - WRAP_LINES: { - httpRequestBody: true, - httpResponseBody: true, - httpHeaders: true, - httpParams: true, - httpUrlEncoded: true, - httpPreRequest: true, - httpTest: true, - httpRequestVariables: true, - graphqlQuery: true, - graphqlResponseBody: true, - graphqlHeaders: false, - graphqlVariables: false, - graphqlSchema: true, - importCurl: true, - codeGen: true, - cookie: true, - multipartFormdata: true, - }, - - // Set empty because interceptor module will set the default value - CURRENT_INTERCEPTOR_ID: "", - - // TODO: Interceptor related settings should move under the interceptor systems - PROXY_URL: defaultProxyURL, - URL_EXCLUDES: { - auth: true, - httpUser: true, - httpPassword: true, - bearerToken: true, - oauth2Token: true, - }, - THEME_COLOR: "indigo", - BG_COLOR: "system", - ENCODE_MODE: "enable", - TELEMETRY_ENABLED: true, - EXPAND_NAVIGATION: false, - SIDEBAR: true, - SIDEBAR_ON_LEFT: false, - COLUMN_LAYOUT: true, - - HAS_OPENED_SPOTLIGHT: false, - ENABLE_AI_EXPERIMENTS: true, - AI_REQUEST_NAMING_STYLE: "DESCRIPTIVE_WITH_SPACES", - CUSTOM_NAMING_STYLE: "", +getDefaultProxyUrl().then((url) => { + defaultProxyURL = url }) +export const getDefaultSettings = (): SettingsDef => { + return { + syncCollections: true, + syncHistory: true, + syncEnvironments: true, + + WRAP_LINES: { + httpRequestBody: true, + httpResponseBody: true, + httpHeaders: true, + httpParams: true, + httpUrlEncoded: true, + httpPreRequest: true, + httpTest: true, + httpRequestVariables: true, + graphqlQuery: true, + graphqlResponseBody: true, + graphqlHeaders: false, + graphqlVariables: false, + graphqlSchema: true, + importCurl: true, + codeGen: true, + cookie: true, + multipartFormdata: true, + }, + + // Set empty because interceptor module will set the default value + CURRENT_INTERCEPTOR_ID: "", + + // TODO: Interceptor related settings should move under the interceptor systems + PROXY_URL: defaultProxyURL, + URL_EXCLUDES: { + auth: true, + httpUser: true, + httpPassword: true, + bearerToken: true, + oauth2Token: true, + }, + THEME_COLOR: "indigo", + BG_COLOR: "system", + ENCODE_MODE: "enable", + TELEMETRY_ENABLED: true, + EXPAND_NAVIGATION: false, + SIDEBAR: true, + SIDEBAR_ON_LEFT: false, + COLUMN_LAYOUT: true, + + HAS_OPENED_SPOTLIGHT: false, + ENABLE_AI_EXPERIMENTS: true, + AI_REQUEST_NAMING_STYLE: "DESCRIPTIVE_WITH_SPACES", + CUSTOM_NAMING_STYLE: "", + } +} + type ApplySettingPayload = { [K in keyof SettingsDef]: { settingKey: K diff --git a/packages/hoppscotch-common/src/pages/r/_id.vue b/packages/hoppscotch-common/src/pages/r/_id.vue index aae2f795..dae9570d 100644 --- a/packages/hoppscotch-common/src/pages/r/_id.vue +++ b/packages/hoppscotch-common/src/pages/r/_id.vue @@ -139,7 +139,6 @@ const addRequestToTab = () => { type: "request", request: safelyExtractRESTRequest(request, getDefaultRESTRequest()), isDirty: false, - type: "request", }) router.push({ path: "/" }) diff --git a/packages/hoppscotch-common/src/pages/settings.vue b/packages/hoppscotch-common/src/pages/settings.vue index 933322e7..5b88e71e 100644 --- a/packages/hoppscotch-common/src/pages/settings.vue +++ b/packages/hoppscotch-common/src/pages/settings.vue @@ -248,8 +248,8 @@