server.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { app } from "electron"
  2. import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants"
  3. import { getUserShell, loadShellEnv } from "./shell-env"
  4. import { getStore } from "./store"
  5. export type WslConfig = { enabled: boolean }
  6. export type HealthCheck = { wait: Promise<void> }
  7. export function getDefaultServerUrl(): string | null {
  8. const value = getStore().get(DEFAULT_SERVER_URL_KEY)
  9. return typeof value === "string" ? value : null
  10. }
  11. export function setDefaultServerUrl(url: string | null) {
  12. if (url) {
  13. getStore().set(DEFAULT_SERVER_URL_KEY, url)
  14. return
  15. }
  16. getStore().delete(DEFAULT_SERVER_URL_KEY)
  17. }
  18. export function getWslConfig(): WslConfig {
  19. const value = getStore().get(WSL_ENABLED_KEY)
  20. return { enabled: typeof value === "boolean" ? value : false }
  21. }
  22. export function setWslConfig(config: WslConfig) {
  23. getStore().set(WSL_ENABLED_KEY, config.enabled)
  24. }
  25. export async function spawnLocalServer(hostname: string, port: number, password: string) {
  26. prepareServerEnv(password)
  27. const { Log, Server } = await import("virtual:opencode-server")
  28. await Log.init({ level: "WARN" })
  29. const listener = await Server.listen({
  30. port,
  31. hostname,
  32. username: "opencode",
  33. password,
  34. cors: ["oc://renderer"],
  35. })
  36. const wait = (async () => {
  37. const url = `http://${hostname}:${port}`
  38. const ready = async () => {
  39. while (true) {
  40. await new Promise((resolve) => setTimeout(resolve, 100))
  41. if (await checkHealth(url, password)) return
  42. }
  43. }
  44. await ready()
  45. })()
  46. return { listener, health: { wait } }
  47. }
  48. function prepareServerEnv(password: string) {
  49. const shell = process.platform === "win32" ? null : getUserShell()
  50. const shellEnv = shell ? (loadShellEnv(shell) ?? {}) : {}
  51. const env = {
  52. ...process.env,
  53. ...shellEnv,
  54. OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true",
  55. OPENCODE_EXPERIMENTAL_FILEWATCHER: "true",
  56. OPENCODE_CLIENT: "desktop",
  57. OPENCODE_SERVER_USERNAME: "opencode",
  58. OPENCODE_SERVER_PASSWORD: password,
  59. XDG_STATE_HOME: app.getPath("userData"),
  60. }
  61. Object.assign(process.env, env)
  62. }
  63. export async function checkHealth(url: string, password?: string | null): Promise<boolean> {
  64. let healthUrl: URL
  65. try {
  66. healthUrl = new URL("/global/health", url)
  67. } catch {
  68. return false
  69. }
  70. const headers = new Headers()
  71. if (password) {
  72. const auth = Buffer.from(`opencode:${password}`).toString("base64")
  73. headers.set("authorization", `Basic ${auth}`)
  74. }
  75. try {
  76. const res = await fetch(healthUrl, {
  77. method: "GET",
  78. headers,
  79. signal: AbortSignal.timeout(3000),
  80. })
  81. return res.ok
  82. } catch {
  83. return false
  84. }
  85. }