fix(common): apply platform default proxy URL on load and reset (#6142)
This commit is contained in:
parent
84f774265b
commit
0cdf8de02d
3 changed files with 109 additions and 52 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
/**
|
||||
* 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: settingsStore.value.PROXY_URL ?? "https://proxy.hoppscotch.io",
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue