feat(app): add configured web servers
This commit is contained in:
parent
4caec80e67
commit
07cd7c0f93
8 changed files with 157 additions and 7 deletions
|
|
@ -255,6 +255,7 @@ declare global {
|
|||
interface Window {
|
||||
__OPENCODE__?: {
|
||||
deepLinks?: string[]
|
||||
servers?: unknown
|
||||
}
|
||||
api?: {
|
||||
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { dict as en } from "@/i18n/en"
|
|||
import { dict as zh } from "@/i18n/zh"
|
||||
import { handleNotificationClick } from "@/utils/notification-click"
|
||||
import { authFromToken } from "@/utils/server"
|
||||
import { parseStartupServers } from "@/utils/startup-servers"
|
||||
import pkg from "../package.json"
|
||||
import { ServerConnection } from "./context/server"
|
||||
|
||||
|
|
@ -112,6 +113,14 @@ const getDefaultUrl = () => {
|
|||
return getCurrentUrl()
|
||||
}
|
||||
|
||||
const remoteServersMeta = () => document.querySelector<HTMLMetaElement>('meta[name="opencode-remote-servers"]')?.content
|
||||
|
||||
const getStartupServers = () => [
|
||||
...parseStartupServers(import.meta.env.VITE_OPENCODE_REMOTE_SERVERS),
|
||||
...parseStartupServers(remoteServersMeta()),
|
||||
...parseStartupServers(window.__OPENCODE__?.servers),
|
||||
]
|
||||
|
||||
const clearAuthToken = () => {
|
||||
const params = new URLSearchParams(location.search)
|
||||
if (!params.has("auth_token")) return
|
||||
|
|
@ -171,7 +180,7 @@ if (root instanceof HTMLElement) {
|
|||
<AppInterface
|
||||
defaultServer={ServerConnection.Key.make(getDefaultUrl())}
|
||||
canonicalLocalServer={ServerConnection.key(server)}
|
||||
servers={[server]}
|
||||
servers={[...getStartupServers(), server]}
|
||||
disableHealthCheck
|
||||
/>
|
||||
</AppBaseProviders>
|
||||
|
|
|
|||
1
packages/app/src/env.d.ts
vendored
1
packages/app/src/env.d.ts
vendored
|
|
@ -2,6 +2,7 @@ interface ImportMetaEnv {
|
|||
readonly VITE_OPENCODE_SERVER_HOST: string
|
||||
readonly VITE_OPENCODE_SERVER_PORT: string
|
||||
readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod"
|
||||
readonly VITE_OPENCODE_REMOTE_SERVERS?: string
|
||||
|
||||
readonly VITE_SENTRY_DSN?: string
|
||||
readonly VITE_SENTRY_ENVIRONMENT?: string
|
||||
|
|
|
|||
49
packages/app/src/utils/startup-servers.test.ts
Normal file
49
packages/app/src/utils/startup-servers.test.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { parseStartupServers } from "./startup-servers"
|
||||
|
||||
describe("parseStartupServers", () => {
|
||||
test("parses comma separated startup servers", () => {
|
||||
expect(parseStartupServers("Dax=http://romulus.example.test:4096, apollo.example.test:4096")).toEqual([
|
||||
{
|
||||
type: "http",
|
||||
displayName: "Dax",
|
||||
http: { url: "http://romulus.example.test:4096" },
|
||||
},
|
||||
{
|
||||
type: "http",
|
||||
http: { url: "http://apollo.example.test:4096" },
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("parses JSON startup servers with credentials", () => {
|
||||
expect(
|
||||
parseStartupServers(
|
||||
JSON.stringify([
|
||||
{
|
||||
displayName: "Kit box",
|
||||
url: "kit.example.test:4096/",
|
||||
username: "opencode",
|
||||
password: "secret",
|
||||
},
|
||||
]),
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
type: "http",
|
||||
displayName: "Kit box",
|
||||
http: { url: "http://kit.example.test:4096", username: "opencode", password: "secret" },
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("parses JSON name to URL maps", () => {
|
||||
expect(parseStartupServers('{"Dax":"http://romulus.example.test:4096"}')).toEqual([
|
||||
{
|
||||
type: "http",
|
||||
displayName: "Dax",
|
||||
http: { url: "http://romulus.example.test:4096" },
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
62
packages/app/src/utils/startup-servers.ts
Normal file
62
packages/app/src/utils/startup-servers.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { normalizeServerUrl, ServerConnection } from "@/context/server"
|
||||
|
||||
export type StartupServerConfig =
|
||||
| string
|
||||
| {
|
||||
url?: unknown
|
||||
name?: unknown
|
||||
displayName?: unknown
|
||||
username?: unknown
|
||||
password?: unknown
|
||||
}
|
||||
|
||||
export function parseStartupServers(input: unknown): ServerConnection.Http[] {
|
||||
const parsed = typeof input === "string" ? parseServerString(input) : input
|
||||
if (Array.isArray(parsed)) return parsed.flatMap((value) => serverFromConfig(value))
|
||||
if (!isRecord(parsed)) return []
|
||||
return Object.entries(parsed).flatMap(([name, value]) =>
|
||||
typeof value === "string" ? serverFromConfig({ name, url: value }) : serverFromConfig(value),
|
||||
)
|
||||
}
|
||||
|
||||
function parseServerString(input: string): unknown {
|
||||
const trimmed = input.trim()
|
||||
if (!trimmed) return []
|
||||
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
||||
try {
|
||||
return JSON.parse(trimmed)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
return trimmed.split(",").map((item) => {
|
||||
const value = item.trim()
|
||||
const index = value.indexOf("=")
|
||||
if (index <= 0) return value
|
||||
return { name: value.slice(0, index).trim(), url: value.slice(index + 1).trim() }
|
||||
})
|
||||
}
|
||||
|
||||
function serverFromConfig(input: unknown): ServerConnection.Http[] {
|
||||
const url = normalizeServerUrl(typeof input === "string" ? input : (isRecord(input) && stringValue(input.url)) || "")
|
||||
if (!url) return []
|
||||
|
||||
const conn: ServerConnection.Http = { type: "http", http: { url } }
|
||||
if (isRecord(input)) {
|
||||
const displayName = stringValue(input.displayName) ?? stringValue(input.name)
|
||||
if (displayName) conn.displayName = displayName
|
||||
const username = stringValue(input.username)
|
||||
if (username) conn.http.username = username
|
||||
const password = stringValue(input.password)
|
||||
if (password) conn.http.password = password
|
||||
}
|
||||
return [conn]
|
||||
}
|
||||
|
||||
function stringValue(value: unknown) {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue