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
This commit is contained in:
weishu
2025-12-25 17:42:30 +08:00
parent 73f5a51548
commit 0be023b23b
9 changed files with 318 additions and 35 deletions
+16 -2
View File
@@ -11,21 +11,35 @@ import type {
} from '@/types/api'
type ApiClientOptions = {
baseUrl?: string
getToken?: () => string | null
onUnauthorized?: () => Promise<string | null>
}
export class ApiClient {
private token: string
private readonly baseUrl: string | null
private readonly getToken: (() => string | null) | null
private readonly onUnauthorized: (() => Promise<string | null>) | null
constructor(token: string, options?: ApiClientOptions) {
this.token = token
this.baseUrl = options?.baseUrl ?? null
this.getToken = options?.getToken ?? null
this.onUnauthorized = options?.onUnauthorized ?? null
}
private buildUrl(path: string): string {
if (!this.baseUrl) {
return path
}
try {
return new URL(path, this.baseUrl).toString()
} catch {
return path
}
}
private async request<T>(
path: string,
init?: RequestInit,
@@ -44,7 +58,7 @@ export class ApiClient {
headers.set('content-type', 'application/json')
}
const res = await fetch(path, {
const res = await fetch(this.buildUrl(path), {
...init,
headers
})
@@ -69,7 +83,7 @@ export class ApiClient {
}
async authenticate(auth: { initData: string } | { accessToken: string }): Promise<AuthResponse> {
const res = await fetch('/api/auth', {
const res = await fetch(this.buildUrl('/api/auth'), {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(auth)