feat: add the ability to configure query params encoding for requests (#4412)
Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
@@ -62,7 +62,9 @@
|
|||||||
"turn_on": "Turn on",
|
"turn_on": "Turn on",
|
||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"verify": "Verify",
|
"verify": "Verify",
|
||||||
"yes": "Yes"
|
"yes": "Yes",
|
||||||
|
"enable": "Enable",
|
||||||
|
"disable": "Disable"
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"new": "Add new",
|
"new": "Add new",
|
||||||
@@ -714,12 +716,16 @@
|
|||||||
"account_email_description": "Your primary email address.",
|
"account_email_description": "Your primary email address.",
|
||||||
"account_name_description": "This is your display name.",
|
"account_name_description": "This is your display name.",
|
||||||
"additional": "Additional Settings",
|
"additional": "Additional Settings",
|
||||||
|
"auto_encode_mode": "Auto",
|
||||||
|
"auto_encode_mode_tooltip": "Encode the parameters in the request only if some special characters are present",
|
||||||
"background": "Background",
|
"background": "Background",
|
||||||
"black_mode": "Black",
|
"black_mode": "Black",
|
||||||
"choose_language": "Choose language",
|
"choose_language": "Choose language",
|
||||||
"dark_mode": "Dark",
|
"dark_mode": "Dark",
|
||||||
"delete_account": "Delete account",
|
"delete_account": "Delete account",
|
||||||
"delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.",
|
"delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.",
|
||||||
|
"disable_encode_mode_tooltip": "Never encode the parameters in the request",
|
||||||
|
"enable_encode_mode_tooltip": "Always encode the parameters in the request",
|
||||||
"expand_navigation": "Expand navigation",
|
"expand_navigation": "Expand navigation",
|
||||||
"experiments": "Experiments",
|
"experiments": "Experiments",
|
||||||
"experiments_notice": "This is a collection of experiments we're working on that might turn out to be useful, fun, both, or neither. They're not final and may not be stable, so if something overly weird happens, don't panic. Just turn the dang thing off. Jokes aside, ",
|
"experiments_notice": "This is a collection of experiments we're working on that might turn out to be useful, fun, both, or neither. They're not final and may not be stable, so if something overly weird happens, don't panic. Just turn the dang thing off. Jokes aside, ",
|
||||||
@@ -728,11 +734,15 @@
|
|||||||
"extensions": "Browser extension",
|
"extensions": "Browser extension",
|
||||||
"extensions_use_toggle": "Use the browser extension to send requests (if present)",
|
"extensions_use_toggle": "Use the browser extension to send requests (if present)",
|
||||||
"follow": "Follow us",
|
"follow": "Follow us",
|
||||||
|
"general": "General",
|
||||||
|
"general_description": " General settings used in the application",
|
||||||
"interceptor": "Interceptor",
|
"interceptor": "Interceptor",
|
||||||
"interceptor_description": "Middleware between application and APIs.",
|
"interceptor_description": "Middleware between application and APIs.",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"light_mode": "Light",
|
"light_mode": "Light",
|
||||||
"official_proxy_hosting": "Official Proxy is hosted by Hoppscotch.",
|
"official_proxy_hosting": "Official Proxy is hosted by Hoppscotch.",
|
||||||
|
"query_parameters_encoding": "Query Parameters Encoding",
|
||||||
|
"query_parameters_encoding_description": "Configure encoding for query parameters in requests",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"profile_description": "Update your profile details",
|
"profile_description": "Update your profile details",
|
||||||
"profile_email": "Email address",
|
"profile_email": "Email address",
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ declare module 'vue' {
|
|||||||
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
||||||
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
|
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
|
||||||
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
|
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
|
||||||
|
SmartEncodingPicker: typeof import('./components/smart/EncodingPicker.vue')['default']
|
||||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
||||||
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
||||||
TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
|
TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div
|
||||||
|
v-for="(mode, index) of modes"
|
||||||
|
:key="`mode-${index}`"
|
||||||
|
class="flex w-fit"
|
||||||
|
>
|
||||||
|
<HoppSmartRadio
|
||||||
|
v-tippy="{ theme: 'tooltip', maxWidth: 500 }"
|
||||||
|
:value="mode"
|
||||||
|
:label="t(getEncodingModeName(mode))"
|
||||||
|
:title="t(getEncodingModeTooltip(mode))"
|
||||||
|
:selected="mode === activeMode"
|
||||||
|
:class="'!px-0 hover:bg-transparent'"
|
||||||
|
@change="changeMode(mode)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { EncodeModes, EncodeMode, applySetting } from "~/newstore/settings"
|
||||||
|
import { useSetting } from "@composables/settings"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const modes = EncodeModes
|
||||||
|
const activeMode = useSetting("ENCODE_MODE")
|
||||||
|
|
||||||
|
const changeMode = (mode: EncodeMode) => {
|
||||||
|
applySetting("ENCODE_MODE", mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEncodingModeName = (mode: string) => {
|
||||||
|
switch (mode) {
|
||||||
|
case "enable":
|
||||||
|
return "action.enable"
|
||||||
|
case "disable":
|
||||||
|
return "action.disable"
|
||||||
|
case "auto":
|
||||||
|
return "settings.auto_encode_mode"
|
||||||
|
default:
|
||||||
|
return "settings.encode_mode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEncodingModeTooltip = (mode: string) => {
|
||||||
|
switch (mode) {
|
||||||
|
case "enable":
|
||||||
|
return "settings.enable_encode_mode_tooltip"
|
||||||
|
case "disable":
|
||||||
|
return "settings.disable_encode_mode_tooltip"
|
||||||
|
case "auto":
|
||||||
|
return "settings.auto_encode_mode_tooltip"
|
||||||
|
default:
|
||||||
|
return "settings.enable_encode_mode_tooltip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -20,6 +20,10 @@ export const HoppAccentColors = [
|
|||||||
"pink",
|
"pink",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
export const EncodeModes = ["enable", "disable", "auto"] as const
|
||||||
|
|
||||||
|
export type EncodeMode = (typeof EncodeModes)[number]
|
||||||
|
|
||||||
export type HoppAccentColor = (typeof HoppAccentColors)[number]
|
export type HoppAccentColor = (typeof HoppAccentColors)[number]
|
||||||
|
|
||||||
export type SettingsDef = {
|
export type SettingsDef = {
|
||||||
@@ -59,6 +63,7 @@ export type SettingsDef = {
|
|||||||
}
|
}
|
||||||
THEME_COLOR: HoppAccentColor
|
THEME_COLOR: HoppAccentColor
|
||||||
BG_COLOR: HoppBgColor
|
BG_COLOR: HoppBgColor
|
||||||
|
ENCODE_MODE: EncodeMode
|
||||||
TELEMETRY_ENABLED: boolean
|
TELEMETRY_ENABLED: boolean
|
||||||
EXPAND_NAVIGATION: boolean
|
EXPAND_NAVIGATION: boolean
|
||||||
SIDEBAR: boolean
|
SIDEBAR: boolean
|
||||||
@@ -107,6 +112,7 @@ export const getDefaultSettings = (): SettingsDef => ({
|
|||||||
},
|
},
|
||||||
THEME_COLOR: "indigo",
|
THEME_COLOR: "indigo",
|
||||||
BG_COLOR: "system",
|
BG_COLOR: "system",
|
||||||
|
ENCODE_MODE: "enable",
|
||||||
TELEMETRY_ENABLED: true,
|
TELEMETRY_ENABLED: true,
|
||||||
EXPAND_NAVIGATION: false,
|
EXPAND_NAVIGATION: false,
|
||||||
SIDEBAR: true,
|
SIDEBAR: true,
|
||||||
|
|||||||
@@ -4,38 +4,13 @@
|
|||||||
<div class="md:grid md:grid-cols-3 md:gap-4">
|
<div class="md:grid md:grid-cols-3 md:gap-4">
|
||||||
<div class="p-8 md:col-span-1">
|
<div class="p-8 md:col-span-1">
|
||||||
<h3 class="heading">
|
<h3 class="heading">
|
||||||
{{ t("settings.theme") }}
|
{{ t("settings.general") }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="my-1 text-secondaryLight">
|
<p class="my-1 text-secondaryLight">
|
||||||
{{ t("settings.theme_description") }}
|
{{ t("settings.general_description") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-8 p-8 md:col-span-2">
|
<div class="space-y-8 p-8 md:col-span-2">
|
||||||
<section>
|
|
||||||
<h4 class="font-semibold text-secondaryDark">
|
|
||||||
{{ t("settings.background") }}
|
|
||||||
</h4>
|
|
||||||
<div class="my-1 text-secondaryLight">
|
|
||||||
{{ t(getColorModeName(colorMode.preference)) }}
|
|
||||||
<span v-if="colorMode.preference === 'system'">
|
|
||||||
({{ t(getColorModeName(colorMode.value)) }})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
|
||||||
<SmartColorModePicker />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h4 class="font-semibold text-secondaryDark">
|
|
||||||
{{ t("settings.accent_color") }}
|
|
||||||
</h4>
|
|
||||||
<div class="my-1 text-secondaryLight">
|
|
||||||
{{ ACCENT_COLOR.charAt(0).toUpperCase() + ACCENT_COLOR.slice(1) }}
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
|
||||||
<SmartAccentModePicker />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section>
|
<section>
|
||||||
<h4 class="font-semibold text-secondaryDark">
|
<h4 class="font-semibold text-secondaryDark">
|
||||||
{{ t("settings.language") }}
|
{{ t("settings.language") }}
|
||||||
@@ -44,6 +19,19 @@
|
|||||||
<SmartChangeLanguage />
|
<SmartChangeLanguage />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h4 class="font-semibold text-secondaryDark">
|
||||||
|
{{ t("settings.query_parameters_encoding") }}
|
||||||
|
</h4>
|
||||||
|
<div class="my-1 text-secondaryLight">
|
||||||
|
{{ t("settings.query_parameters_encoding_description") }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<SmartEncodingPicker />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h4 class="font-semibold text-secondaryDark">
|
<h4 class="font-semibold text-secondaryDark">
|
||||||
{{ t("settings.experiments") }}
|
{{ t("settings.experiments") }}
|
||||||
@@ -96,6 +84,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="md:grid md:grid-cols-3 md:gap-4">
|
||||||
|
<div class="p-8 md:col-span-1">
|
||||||
|
<h3 class="heading">
|
||||||
|
{{ t("settings.theme") }}
|
||||||
|
</h3>
|
||||||
|
<p class="my-1 text-secondaryLight">
|
||||||
|
{{ t("settings.theme_description") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-8 p-8 md:col-span-2">
|
||||||
|
<section>
|
||||||
|
<h4 class="font-semibold text-secondaryDark">
|
||||||
|
{{ t("settings.background") }}
|
||||||
|
</h4>
|
||||||
|
<div class="my-1 text-secondaryLight">
|
||||||
|
{{ t(getColorModeName(colorMode.preference)) }}
|
||||||
|
<span v-if="colorMode.preference === 'system'">
|
||||||
|
({{ t(getColorModeName(colorMode.value)) }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<SmartColorModePicker />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h4 class="font-semibold text-secondaryDark">
|
||||||
|
{{ t("settings.accent_color") }}
|
||||||
|
</h4>
|
||||||
|
<div class="my-1 text-secondaryLight">
|
||||||
|
{{ ACCENT_COLOR.charAt(0).toUpperCase() + ACCENT_COLOR.slice(1) }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<SmartAccentModePicker />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="md:grid md:grid-cols-3 md:gap-4">
|
<div class="md:grid md:grid-cols-3 md:gap-4">
|
||||||
<div class="p-8 md:col-span-1">
|
<div class="p-8 md:col-span-1">
|
||||||
<h3 class="heading">
|
<h3 class="heading">
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
RequestRunResult,
|
RequestRunResult,
|
||||||
} from "~/services/interceptor.service"
|
} from "~/services/interceptor.service"
|
||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
import { cloneDeep } from "lodash-es"
|
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { ref, watch } from "vue"
|
import { ref, watch } from "vue"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
@@ -24,6 +23,7 @@ import { UIExtensionService } from "~/services/ui-extension.service"
|
|||||||
import { x25519 } from "@noble/curves/ed25519"
|
import { x25519 } from "@noble/curves/ed25519"
|
||||||
import { base16 } from "@scure/base"
|
import { base16 } from "@scure/base"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
import { preProcessRequest } from "../helpers"
|
||||||
|
|
||||||
type KeyValuePair = {
|
type KeyValuePair = {
|
||||||
key: string
|
key: string
|
||||||
@@ -98,33 +98,6 @@ type RunRequestResponse = {
|
|||||||
// and the axios present in this package
|
// and the axios present in this package
|
||||||
type AxiosRequestConfig = Parameters<Interceptor["runRequest"]>[0]
|
type AxiosRequestConfig = Parameters<Interceptor["runRequest"]>[0]
|
||||||
|
|
||||||
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 processBody(
|
async function processBody(
|
||||||
axiosReq: AxiosRequestConfig
|
axiosReq: AxiosRequestConfig
|
||||||
): Promise<BodyDef | null> {
|
): Promise<BodyDef | null> {
|
||||||
|
|||||||
@@ -6,34 +6,7 @@ import {
|
|||||||
RequestRunResult,
|
RequestRunResult,
|
||||||
} from "../../../services/interceptor.service"
|
} from "../../../services/interceptor.service"
|
||||||
import axios, { AxiosRequestConfig, CancelToken } from "axios"
|
import axios, { AxiosRequestConfig, CancelToken } from "axios"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { preProcessRequest } from "./helpers"
|
||||||
|
|
||||||
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(
|
async function runRequest(
|
||||||
req: AxiosRequestConfig,
|
req: AxiosRequestConfig,
|
||||||
@@ -41,11 +14,9 @@ async function runRequest(
|
|||||||
): RequestRunResult["response"] {
|
): RequestRunResult["response"] {
|
||||||
const timeStart = Date.now()
|
const timeStart = Date.now()
|
||||||
|
|
||||||
const processedReq = preProcessRequest(req)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
...processedReq,
|
...req,
|
||||||
cancelToken,
|
cancelToken,
|
||||||
responseType: "arraybuffer",
|
responseType: "arraybuffer",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import {
|
|||||||
InterceptorError,
|
InterceptorError,
|
||||||
RequestRunResult,
|
RequestRunResult,
|
||||||
} from "~/services/interceptor.service"
|
} from "~/services/interceptor.service"
|
||||||
import { cloneDeep } from "lodash-es"
|
|
||||||
import { computed, readonly, ref } from "vue"
|
import { computed, readonly, ref } from "vue"
|
||||||
import { browserIsChrome, browserIsFirefox } from "~/helpers/utils/userAgent"
|
import { browserIsChrome, browserIsFirefox } from "~/helpers/utils/userAgent"
|
||||||
import SettingsExtension from "~/components/settings/Extension.vue"
|
import SettingsExtension from "~/components/settings/Extension.vue"
|
||||||
import InterceptorsExtensionSubtitle from "~/components/interceptors/ExtensionSubtitle.vue"
|
import InterceptorsExtensionSubtitle from "~/components/interceptors/ExtensionSubtitle.vue"
|
||||||
import InterceptorsErrorPlaceholder from "~/components/interceptors/ErrorPlaceholder.vue"
|
import InterceptorsErrorPlaceholder from "~/components/interceptors/ErrorPlaceholder.vue"
|
||||||
import { until } from "@vueuse/core"
|
import { until } from "@vueuse/core"
|
||||||
|
import { preProcessRequest } from "./helpers"
|
||||||
|
|
||||||
export const defineSubscribableObject = <T extends object>(obj: T) => {
|
export const defineSubscribableObject = <T extends object>(obj: T) => {
|
||||||
const proxyObject = {
|
const proxyObject = {
|
||||||
@@ -55,31 +55,6 @@ export const cancelRunningExtensionRequest = () => {
|
|||||||
window.__POSTWOMAN_EXTENSION_HOOK__?.cancelRequest()
|
window.__POSTWOMAN_EXTENSION_HOOK__?.cancelRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExtensionStatus = "available" | "unknown-origin" | "waiting"
|
export type ExtensionStatus = "available" | "unknown-origin" | "waiting"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { AxiosRequestConfig } from "axios"
|
||||||
|
import { cloneDeep } from "lodash-es"
|
||||||
|
import { useSetting } from "~/composables/settings"
|
||||||
|
|
||||||
|
// Helper function to check if a string is already encoded
|
||||||
|
const isEncoded = (value: string) => {
|
||||||
|
try {
|
||||||
|
return value !== decodeURIComponent(value)
|
||||||
|
} catch (e) {
|
||||||
|
return false // in case of malformed URI sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const preProcessRequest = (
|
||||||
|
req: AxiosRequestConfig
|
||||||
|
): AxiosRequestConfig => {
|
||||||
|
const reqClone = cloneDeep(req)
|
||||||
|
const encodeMode = useSetting("ENCODE_MODE")
|
||||||
|
|
||||||
|
// 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()) {
|
||||||
|
let finalValue = value
|
||||||
|
if (
|
||||||
|
encodeMode.value === "enable" ||
|
||||||
|
(encodeMode.value === "auto" &&
|
||||||
|
/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/.test(value))
|
||||||
|
) {
|
||||||
|
// Check if the value is already encoded (e.g., contains % symbols)
|
||||||
|
if (!isEncoded(value)) {
|
||||||
|
finalValue = encodeURIComponent(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the parameter with the final value
|
||||||
|
url.searchParams.append(key, finalValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode the URL to prevent double encoding
|
||||||
|
reqClone.url = decodeURIComponent(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
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Interceptor, RequestRunResult } from "~/services/interceptor.service"
|
import { Interceptor, RequestRunResult } from "~/services/interceptor.service"
|
||||||
import { AxiosRequestConfig, CancelToken } from "axios"
|
import { AxiosRequestConfig, CancelToken } from "axios"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { preProcessRequest } from "./browser"
|
import { preProcessRequest } from "./helpers"
|
||||||
import { v4 } from "uuid"
|
import { v4 } from "uuid"
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import { settingsStore } from "~/newstore/settings"
|
import { settingsStore } from "~/newstore/settings"
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ const ThemeColorSchema = z.enum([
|
|||||||
|
|
||||||
const BgColorSchema = z.enum(["system", "light", "dark", "black"])
|
const BgColorSchema = z.enum(["system", "light", "dark", "black"])
|
||||||
|
|
||||||
|
const EncodeMode = z.enum(["enable", "disable", "auto"])
|
||||||
|
|
||||||
const SettingsDefSchema = z.object({
|
const SettingsDefSchema = z.object({
|
||||||
syncCollections: z.boolean(),
|
syncCollections: z.boolean(),
|
||||||
syncHistory: z.boolean(),
|
syncHistory: z.boolean(),
|
||||||
@@ -41,6 +43,7 @@ const SettingsDefSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
THEME_COLOR: ThemeColorSchema,
|
THEME_COLOR: ThemeColorSchema,
|
||||||
BG_COLOR: BgColorSchema,
|
BG_COLOR: BgColorSchema,
|
||||||
|
ENCODE_MODE: EncodeMode.catch("enable"),
|
||||||
TELEMETRY_ENABLED: z.boolean(),
|
TELEMETRY_ENABLED: z.boolean(),
|
||||||
EXPAND_NAVIGATION: z.boolean(),
|
EXPAND_NAVIGATION: z.boolean(),
|
||||||
SIDEBAR: z.boolean(),
|
SIDEBAR: z.boolean(),
|
||||||
|
|||||||
Reference in New Issue
Block a user