From 0cdf8de02db571afbe4d07c4aa4f5b94dc1ba7c4 Mon Sep 17 00:00:00 2001
From: Nivedin <53208152+nivedin@users.noreply.github.com>
Date: Thu, 23 Apr 2026 18:25:26 +0530
Subject: [PATCH] fix(common): apply platform default proxy URL on load and
reset (#6142)
---
.../src/components/settings/Proxy.vue | 58 +++++++-----
.../src/newstore/settings.ts | 14 ++-
.../std/kernel-interceptors/proxy/store.ts | 89 +++++++++++++++----
3 files changed, 109 insertions(+), 52 deletions(-)
diff --git a/packages/hoppscotch-common/src/components/settings/Proxy.vue b/packages/hoppscotch-common/src/components/settings/Proxy.vue
index 6c136ea5..5a2f92f1 100644
--- a/packages/hoppscotch-common/src/components/settings/Proxy.vue
+++ b/packages/hoppscotch-common/src/components/settings/Proxy.vue
@@ -31,20 +31,17 @@
diff --git a/packages/hoppscotch-common/src/newstore/settings.ts b/packages/hoppscotch-common/src/newstore/settings.ts
index bdfa4458..cb05f924 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 { DEFAULT_HOPP_PROXY_URL, getDefaultProxyUrl } from "~/helpers/proxyUrl"
+import { DEFAULT_HOPP_PROXY_URL } from "~/helpers/proxyUrl"
export const HoppBgColors = ["system", "light", "dark", "black"] as const
@@ -87,12 +87,6 @@ export type SettingsDef = {
ENABLE_EXPERIMENTAL_DOCUMENTATION: boolean
}
-let defaultProxyURL = DEFAULT_HOPP_PROXY_URL
-
-getDefaultProxyUrl().then((url) => {
- defaultProxyURL = url
-})
-
export const getDefaultSettings = (): SettingsDef => {
return {
syncCollections: true,
@@ -122,8 +116,10 @@ export const getDefaultSettings = (): SettingsDef => {
// Set empty because interceptor module will set the default value
CURRENT_KERNEL_INTERCEPTOR_ID: "",
- // TODO: Interceptor related settings should move under the interceptor systems
- PROXY_URL: defaultProxyURL,
+ // Static fallback, the actual platform-aware proxy URL is managed by
+ // `KernelInterceptorProxyStore` which resolves it at runtime via
+ // `getDefaultProxyUrl()` (after the platform is initialised).
+ PROXY_URL: DEFAULT_HOPP_PROXY_URL,
URL_EXCLUDES: {
auth: true,
httpUser: true,
diff --git a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts
index f70308a2..ffe62887 100644
--- a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts
+++ b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts
@@ -1,12 +1,13 @@
+import { ref, readonly, toRaw, type Ref, type DeepReadonly } from "vue"
import { Service } from "dioc"
import { Store } from "~/kernel/store"
-import { settingsStore } from "~/newstore/settings"
+import { getDefaultProxyUrl, DEFAULT_HOPP_PROXY_URL } from "~/helpers/proxyUrl"
import * as E from "fp-ts/Either"
const STORE_NAMESPACE = "interceptors.proxy.v1"
const SETTINGS_KEY = "settings"
-type ProxySettings = {
+export type ProxySettings = {
version: "v1"
proxyUrl: string
accessToken: string
@@ -18,16 +19,41 @@ interface StoredData {
lastUpdated: string
}
-const DEFAULT_SETTINGS: ProxySettings = {
- version: "v1",
- proxyUrl: settingsStore.value.PROXY_URL ?? "https://proxy.hoppscotch.io",
- accessToken: import.meta.env.VITE_PROXYSCOTCH_ACCESS_TOKEN ?? "",
+/**
+ * Build fresh default settings.
+ * Called as a function (not a static const) so that `getDefaultProxyUrl()`
+ * can resolve against the current platform, which isn't available at
+ * module-load time.
+ */
+async function buildDefaultSettings(): Promise {
+ return {
+ version: "v1",
+ proxyUrl: await getDefaultProxyUrl(),
+ accessToken: import.meta.env.VITE_PROXYSCOTCH_ACCESS_TOKEN ?? "",
+ }
}
+/**
+ * Reactive proxy settings store.
+ *
+ * Exposes `settings$` as a readonly ref, any component or service that reads
+ * `settings$.value` will automatically re-render when settings change.
+ */
export class KernelInterceptorProxyStore extends Service {
public static readonly ID = "KERNEL_PROXY_INTERCEPTOR_STORE"
- private settings: ProxySettings = { ...DEFAULT_SETTINGS }
+ private readonly _settings = ref({
+ version: "v1",
+ proxyUrl: DEFAULT_HOPP_PROXY_URL,
+ accessToken: import.meta.env.VITE_PROXYSCOTCH_ACCESS_TOKEN ?? "",
+ })
+
+ /**
+ * Reactive, read-only view of the current proxy settings.
+ */
+ public readonly settings$: DeepReadonly[> = readonly(
+ this._settings
+ )
async onServiceInit(): Promise {
const initResult = await Store.init()
@@ -42,7 +68,14 @@ export class KernelInterceptorProxyStore extends Service {
watcher.on("change", async ({ value }) => {
if (value) {
const storedData = value as StoredData
- this.settings = storedData.settings
+ this._settings.value = {
+ ...this._settings.value,
+ // Only sync user-configurable fields from external changes.
+ // Fallback to current value if persisted data is missing the field
+ // (e.g. older schema). accessToken stays env-derived.
+ proxyUrl:
+ storedData.settings?.proxyUrl ?? this._settings.value.proxyUrl,
+ }
}
})
}
@@ -53,21 +86,29 @@ export class KernelInterceptorProxyStore extends Service {
SETTINGS_KEY
)
+ const defaults = await buildDefaultSettings()
+
if (E.isRight(loadResult) && loadResult.right) {
const storedData = loadResult.right
- this.settings = {
- ...DEFAULT_SETTINGS,
- ...storedData.settings,
+ this._settings.value = {
+ ...defaults,
+ // Only restore user-configurable fields from storage.
+ // accessToken is env-derived (VITE_PROXYSCOTCH_ACCESS_TOKEN) and
+ // must always reflect the current deployment, not a stale persisted value.
+ proxyUrl: storedData.settings?.proxyUrl ?? defaults.proxyUrl,
}
} else {
+ this._settings.value = { ...defaults }
await this.persistSettings()
}
}
private async persistSettings(): Promise {
+ const rawSettings = toRaw(this._settings.value)
+
const storedData: StoredData = {
version: "v1",
- settings: this.settings,
+ settings: { ...rawSettings },
lastUpdated: new Date().toISOString(),
}
@@ -82,21 +123,31 @@ export class KernelInterceptorProxyStore extends Service {
}
}
- public async updateSettings(settings: Partial): Promise {
- this.settings = {
- ...this.settings,
- ...settings,
+ /**
+ * Update user-configurable proxy settings.
+ * Only `proxyUrl` is user-configurable, `accessToken` and `version` are
+ * derived at runtime and cannot be overwritten by callers.
+ */
+ public async updateSettings(
+ patch: Pick
+ ): Promise {
+ this._settings.value = {
+ ...this._settings.value,
+ proxyUrl: patch.proxyUrl,
}
-
await this.persistSettings()
}
+ /**
+ * @deprecated Use `settings$` for reactive access. This exists only for
+ * non-reactive contexts (e.g. inside `execute()` in the interceptor service).
+ */
public getSettings(): ProxySettings {
- return { ...this.settings }
+ return { ...this._settings.value }
}
public async resetSettings(): Promise {
- this.settings = { ...DEFAULT_SETTINGS }
+ this._settings.value = await buildDefaultSettings()
await this.persistSettings()
}
}
]