From a243f9ec5644cc2c4f2df048b84453f07206d63c Mon Sep 17 00:00:00 2001 From: Nikolai Giman Date: Fri, 21 Aug 2026 10:06:54 +0200 Subject: [PATCH] fix: normalize server url --- .../features/auth/ServerSelector.vue | 20 ++++++++----------- web/src/composables/useAdditionalServer.ts | 13 +++++++----- web/src/helpers/serverUrl.ts | 12 +++++++++++ web/src/plugins/axios.ts | 4 +++- 4 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 web/src/helpers/serverUrl.ts diff --git a/web/src/components/features/auth/ServerSelector.vue b/web/src/components/features/auth/ServerSelector.vue index dffd53e..8f14d35 100644 --- a/web/src/components/features/auth/ServerSelector.vue +++ b/web/src/components/features/auth/ServerSelector.vue @@ -68,6 +68,10 @@ placeholder="https://api.example.com" class="w-full" autofocus + autocapitalize="none" + autocorrect="off" + spellcheck="false" + inputmode="url" @keyup.enter="handleAddServer" /> @@ -104,6 +108,7 @@ import { computed, onMounted, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useBreakpoints, breakpointsTailwind } from '@vueuse/core' import { useAdditionalServer } from '@/composables/useAdditionalServer' +import { normalizeServerUrl } from '@/helpers/serverUrl' const { t } = useI18n() const bp = useBreakpoints({ ...breakpointsTailwind, fullscreenModalMax: 1366 }) @@ -153,15 +158,7 @@ const serverOptions = computed(() => { return options }) -const isValidUrl = computed(() => { - if (!newServerUrl.value) return false - try { - const url = new URL(newServerUrl.value) - return url.protocol === 'http:' || url.protocol === 'https:' - } catch { - return false - } -}) +const isValidUrl = computed(() => normalizeServerUrl(newServerUrl.value) !== null) watch(selectedServer, (newServer) => { if (newServer && newServer !== mainServer.value) { @@ -171,9 +168,8 @@ watch(selectedServer, (newServer) => { }) function handleAddServer() { - if (!isValidUrl.value) return - - const url = newServerUrl.value.trim().replace(/\/$/, '') + const url = normalizeServerUrl(newServerUrl.value) + if (!url) return if (!allServers.value.includes(url) && url !== systemServer.value) { addServerFn.value(url) diff --git a/web/src/composables/useAdditionalServer.ts b/web/src/composables/useAdditionalServer.ts index 29ea869..2c9b7bc 100644 --- a/web/src/composables/useAdditionalServer.ts +++ b/web/src/composables/useAdditionalServer.ts @@ -4,6 +4,7 @@ import $api from '@/helpers/axios' import { $ls, $tvApi } from '@/plugins/axios' import { additionalUrlStore } from '@/stores/additional-url.store' import { getConfiguredApiUrl } from '@/helpers/serverConfig' +import { normalizeServerUrl } from '@/helpers/serverUrl' export const LS_KEY_ADDITIONAL_SERVERS = 'additionalServers' export const LS_KEY_MAIN_SERVER = 'mainServer' @@ -32,14 +33,16 @@ export const useAdditionalServer = async () => { (process.env.NODE_ENV !== 'production' ? 'http://localhost:1401' : 'https://api.taskview.tech') const setMainServer = (server: string) => { - mainServer.value = server - $ls.setValue(LS_KEY_MAIN_SERVER, server) - $api.defaults.baseURL = server - $tvApi?.setBaseUrl(server) + const normalized = normalizeServerUrl(server) ?? server + mainServer.value = normalized + $ls.setValue(LS_KEY_MAIN_SERVER, normalized) + $api.defaults.baseURL = normalized + $tvApi?.setBaseUrl(normalized) } const addServer = (server: string) => { - allServers.value.push(server) + const normalized = normalizeServerUrl(server) ?? server + allServers.value.push(normalized) $ls.setValue(LS_KEY_ADDITIONAL_SERVERS, allServers.value) } diff --git a/web/src/helpers/serverUrl.ts b/web/src/helpers/serverUrl.ts new file mode 100644 index 0000000..29fa5f1 --- /dev/null +++ b/web/src/helpers/serverUrl.ts @@ -0,0 +1,12 @@ +export function normalizeServerUrl(raw: string): string | null { + const trimmed = raw.trim() + if (!trimmed) return null + try { + const url = new URL(trimmed) + if (url.protocol !== 'http:' && url.protocol !== 'https:') return null + const path = url.pathname.replace(/\/+$/, '') + return `${url.protocol}//${url.host}${path}` + } catch { + return null + } +} diff --git a/web/src/plugins/axios.ts b/web/src/plugins/axios.ts index 9d72253..ed58252 100644 --- a/web/src/plugins/axios.ts +++ b/web/src/plugins/axios.ts @@ -5,6 +5,7 @@ import $api from '@/helpers/axios' import LocalStorage from '@/helpers/LocalStorage' import { LS_KEY_MAIN_SERVER } from '@/composables/useAdditionalServer' import { getConfiguredApiUrl } from '@/helpers/serverConfig' +import { normalizeServerUrl } from '@/helpers/serverUrl' let $ls: LocalStorage const $tvApi: TvApi = new TvApi($api) @@ -21,7 +22,8 @@ const api = { $ls = app.config.globalProperties.$ls // A deploy-time API URL (config.js) always wins over a server saved in local storage - const savedServer = getConfiguredApiUrl() ? null : await $ls.getValue(LS_KEY_MAIN_SERVER) + const savedRaw = getConfiguredApiUrl() ? null : await $ls.getValue(LS_KEY_MAIN_SERVER) + const savedServer = savedRaw ? (normalizeServerUrl(savedRaw) ?? savedRaw) : null if (savedServer) { $api.defaults.baseURL = savedServer $api.defaults.headers.common['ngrok-skip-browser-warning'] = '1'