Files
hapi/web/src/hooks/useServerUrl.ts
T
weishu 0be023b23b feat: add configurable server URL for standalone web hosting
Adds support for hosting the web UI separately from the hapi server on static hosts (GitHub Pages, Cloudflare Pages). Users can now set a custom server origin via a dialog on the login screen, with the ability to return to same-origin behavior.

Changes include:
- New useServerUrl hook for managing server URL configuration and storage
- Updated API client to support baseUrl parameter for all requests
- Enhanced login UI with server picker dialog (top-right button)
- Auth system now keys tokens per baseUrl to support multiple servers
- SSE connection updated to use configured baseUrl
- Documentation updates for standalone hosting setup
2025-12-25 17:42:30 +08:00

95 lines
2.5 KiB
TypeScript

import { useCallback, useMemo, useState } from 'react'
const SERVER_URL_KEY = 'hapi_server_url'
export type ServerUrlResult =
| { ok: true; value: string }
| { ok: false; error: string }
export function normalizeServerUrl(input: string): ServerUrlResult {
const trimmed = input.trim()
if (!trimmed) {
return { ok: false, error: 'Enter a server URL like https://example.com' }
}
let parsed: URL
try {
parsed = new URL(trimmed)
} catch {
return { ok: false, error: 'Enter a valid URL including http:// or https://' }
}
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return { ok: false, error: 'Server URL must start with http:// or https://' }
}
return { ok: true, value: parsed.origin }
}
function readStoredServerUrl(): string | null {
try {
const stored = localStorage.getItem(SERVER_URL_KEY)
if (!stored) {
return null
}
const normalized = normalizeServerUrl(stored)
if (!normalized.ok) {
localStorage.removeItem(SERVER_URL_KEY)
return null
}
return normalized.value
} catch {
return null
}
}
function writeStoredServerUrl(value: string): void {
try {
localStorage.setItem(SERVER_URL_KEY, value)
} catch {
// Ignore storage errors
}
}
function clearStoredServerUrl(): void {
try {
localStorage.removeItem(SERVER_URL_KEY)
} catch {
// Ignore storage errors
}
}
export function useServerUrl(): {
serverUrl: string | null
baseUrl: string
setServerUrl: (input: string) => ServerUrlResult
clearServerUrl: () => void
} {
const [serverUrl, setServerUrlState] = useState<string | null>(() => readStoredServerUrl())
const fallbackOrigin = typeof window !== 'undefined' ? window.location.origin : ''
const baseUrl = useMemo(() => serverUrl ?? fallbackOrigin, [serverUrl, fallbackOrigin])
const setServerUrl = useCallback((input: string): ServerUrlResult => {
const normalized = normalizeServerUrl(input)
if (!normalized.ok) {
return normalized
}
writeStoredServerUrl(normalized.value)
setServerUrlState(normalized.value)
return normalized
}, [])
const clearServerUrl = useCallback(() => {
clearStoredServerUrl()
setServerUrlState(null)
}, [])
return {
serverUrl,
baseUrl,
setServerUrl,
clearServerUrl
}
}