fix(common): apply platform default proxy URL on load and reset (#6142)

This commit is contained in:
Nivedin 2026-04-23 18:25:26 +05:30 committed by GitHub
parent 84f774265b
commit 0cdf8de02d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 109 additions and 52 deletions

View file

@ -31,20 +31,17 @@
</template>
<script setup lang="ts">
import { ref, onMounted, computed, watch } from "vue"
import { ref, computed, watch } from "vue"
import { refAutoReset } from "@vueuse/core"
import { useService } from "dioc/vue"
import { useI18n } from "~/composables/i18n"
import { useToast } from "~/composables/toast"
import { useReadonlyStream } from "~/composables/stream"
import { getDefaultProxyUrl } from "~/helpers/proxyUrl"
import { platform } from "~/platform"
import { KernelInterceptorProxyStore } from "~/platform/std/kernel-interceptors/proxy/store"
import { ProxyKernelInterceptorService } from "~/platform/std/kernel-interceptors/proxy/index"
import { KernelInterceptorService } from "~/services/kernel-interceptor.service"
import IconRotateCCW from "~icons/lucide/rotate-ccw"
@ -57,26 +54,39 @@ const store = useService(KernelInterceptorProxyStore)
const interceptorService = useService(KernelInterceptorService)
const proxyInterceptorService = useService(ProxyKernelInterceptorService)
const proxyUrl = ref("")
// Local editable copy, synced from the reactive store
const proxyUrl = ref(store.settings$.value.proxyUrl)
// When the store's settings change (e.g. async init resolves, or external
// tab updates via the Store watcher), keep the local input in sync
// but only if the user hasn't actively edited it to something different.
watch(
() => store.settings$.value.proxyUrl,
(storeUrl, prevStoreUrl) => {
// Don't overwrite user edits, only sync when local still matches
// the previous store value (i.e. user hasn't typed anything new)
if (proxyUrl.value === "" || proxyUrl.value === prevStoreUrl) {
proxyUrl.value = storeUrl
}
},
{ immediate: true }
)
const currentUser = useReadonlyStream(
platform.auth.getCurrentUserStream(),
platform.auth.getCurrentUser()
)
async function updateProxyUrl() {
await store.updateSettings({ proxyUrl: proxyUrl.value })
toast.success(t("state.saved"))
}
watch(
() => currentUser.value,
async () => {
if (!currentUser.value) {
proxyUrl.value = await getDefaultProxyUrl()
}
// Reset proxy settings to platform defaults when user logs out.
// Force-sync the local ref after reset the settings$ watch has a guard
// that skips sync when the user has unsaved local edits, but logout should
// unconditionally reset the input.
watch(currentUser, async (user) => {
if (!user) {
await store.resetSettings()
proxyUrl.value = store.settings$.value.proxyUrl
}
)
})
const enabled = computed(
() => interceptorService.getCurrentId() === proxyInterceptorService.id
@ -87,16 +97,16 @@ const clearIcon = refAutoReset<typeof IconRotateCCW | typeof IconCheck>(
1000
)
async function updateProxyUrl() {
await store.updateSettings({ proxyUrl: proxyUrl.value })
toast.success(t("state.saved"))
}
async function resetSettings() {
await store.resetSettings()
const settings = store.getSettings()
proxyUrl.value = settings.proxyUrl
// Store is reactive settings$ already updated, just sync local ref
proxyUrl.value = store.settings$.value.proxyUrl
clearIcon.value = IconCheck
toast.success(t("state.cleared"))
}
onMounted(async () => {
const settings = store.getSettings()
proxyUrl.value = settings.proxyUrl
})
</script>

View file

@ -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,

View file

@ -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<ProxySettings> {
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<ProxySettings>({
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<Ref<ProxySettings>> = readonly(
this._settings
)
async onServiceInit(): Promise<void> {
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<void> {
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<ProxySettings>): Promise<void> {
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<ProxySettings, "proxyUrl">
): Promise<void> {
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<void> {
this.settings = { ...DEFAULT_SETTINGS }
this._settings.value = await buildDefaultSettings()
await this.persistSettings()
}
}