feat(desktop): spawn local server with password (#8139)
This commit is contained in:
@@ -9,11 +9,13 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
name: "GlobalSDK",
|
name: "GlobalSDK",
|
||||||
init: () => {
|
init: () => {
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
|
const platform = usePlatform()
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
|
|
||||||
const eventSdk = createOpencodeClient({
|
const eventSdk = createOpencodeClient({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
|
fetch: platform.fetch,
|
||||||
})
|
})
|
||||||
const emitter = createGlobalEmitter<{
|
const emitter = createGlobalEmitter<{
|
||||||
[key: string]: Event
|
[key: string]: Event
|
||||||
@@ -93,7 +95,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
stop()
|
stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
const platform = usePlatform()
|
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { ErrorPage, type InitError } from "../pages/error"
|
|||||||
import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
|
import { usePlatform } from "./platform"
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
status: "loading" | "partial" | "complete"
|
status: "loading" | "partial" | "complete"
|
||||||
@@ -64,6 +65,7 @@ type State = {
|
|||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
|
const platform = usePlatform()
|
||||||
const [globalStore, setGlobalStore] = createStore<{
|
const [globalStore, setGlobalStore] = createStore<{
|
||||||
ready: boolean
|
ready: boolean
|
||||||
error?: InitError
|
error?: InitError
|
||||||
@@ -139,6 +141,7 @@ function createGlobalSync() {
|
|||||||
const [store, setStore] = child(directory)
|
const [store, setStore] = child(directory)
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
|
fetch: platform.fetch,
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
@@ -396,6 +399,7 @@ function createGlobalSync() {
|
|||||||
case "lsp.updated": {
|
case "lsp.updated": {
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
|
fetch: platform.fetch,
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
|
||||||
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils"
|
||||||
|
|
||||||
const RUST_TARGET = Bun.env.TAURI_ENV_TARGET_TRIPLE
|
const RUST_TARGET = Bun.env.TAURI_ENV_TARGET_TRIPLE
|
||||||
|
|
||||||
const sidecarConfig = getCurrentSidecar(RUST_TARGET)
|
const sidecarConfig = getCurrentSidecar(RUST_TARGET)
|
||||||
|
|
||||||
const binaryPath = `../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode${process.platform === "win32" ? ".exe" : ""}`
|
const binaryPath = windowsify(`../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode`)
|
||||||
|
|
||||||
await $`cd ../opencode && bun run build --single`
|
await $`cd ../opencode && bun run build --single`
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
|
||||||
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils"
|
||||||
|
|
||||||
const sidecarConfig = getCurrentSidecar()
|
const sidecarConfig = getCurrentSidecar()
|
||||||
|
|
||||||
@@ -10,6 +10,4 @@ const dir = "src-tauri/target/opencode-binaries"
|
|||||||
await $`mkdir -p ${dir}`
|
await $`mkdir -p ${dir}`
|
||||||
await $`gh run download ${Bun.env.GITHUB_RUN_ID} -n opencode-cli`.cwd(dir)
|
await $`gh run download ${Bun.env.GITHUB_RUN_ID} -n opencode-cli`.cwd(dir)
|
||||||
|
|
||||||
await copyBinaryToSidecarFolder(
|
await copyBinaryToSidecarFolder(windowsify(`${dir}/${sidecarConfig.ocBinary}/bin/opencode`))
|
||||||
`${dir}/${sidecarConfig.ocBinary}/bin/opencode${process.platform === "win32" ? ".exe" : ""}`,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -41,8 +41,13 @@ export function getCurrentSidecar(target = RUST_TARGET) {
|
|||||||
|
|
||||||
export async function copyBinaryToSidecarFolder(source: string, target = RUST_TARGET) {
|
export async function copyBinaryToSidecarFolder(source: string, target = RUST_TARGET) {
|
||||||
await $`mkdir -p src-tauri/sidecars`
|
await $`mkdir -p src-tauri/sidecars`
|
||||||
const dest = `src-tauri/sidecars/opencode-cli-${target}${process.platform === "win32" ? ".exe" : ""}`
|
const dest = windowsify(`src-tauri/sidecars/opencode-cli-${target}`)
|
||||||
await $`cp ${source} ${dest}`
|
await $`cp ${source} ${dest}`
|
||||||
|
|
||||||
console.log(`Copied ${source} to ${dest}`)
|
console.log(`Copied ${source} to ${dest}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function windowsify(path: string) {
|
||||||
|
if (path.endsWith(".exe")) return path
|
||||||
|
return `${path}${process.platform === "win32" ? ".exe" : ""}`
|
||||||
|
}
|
||||||
|
|||||||
7
packages/desktop/src-tauri/Cargo.lock
generated
7
packages/desktop/src-tauri/Cargo.lock
generated
@@ -2814,6 +2814,7 @@ dependencies = [
|
|||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"uuid",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5364,13 +5365,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.18.1"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde_core",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ tauri-plugin-os = "2"
|
|||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
semver = "1.0.27"
|
semver = "1.0.27"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||||
|
uuid = { version = "1.19.0", features = ["v4"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
gtk = "0.18.2"
|
gtk = "0.18.2"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod window_customizer;
|
|||||||
|
|
||||||
use cli::{install_cli, sync_cli};
|
use cli::{install_cli, sync_cli};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use futures::future;
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
@@ -13,22 +14,29 @@ use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewUrl, Webvie
|
|||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
||||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_store::StoreExt;
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use crate::window_customizer::PinchZoomDisablePlugin;
|
use crate::window_customizer::PinchZoomDisablePlugin;
|
||||||
|
|
||||||
const SETTINGS_STORE: &str = "opencode.settings.dat";
|
const SETTINGS_STORE: &str = "opencode.settings.dat";
|
||||||
const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
|
const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize)]
|
||||||
|
struct ServerReadyData {
|
||||||
|
url: String,
|
||||||
|
password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ServerState {
|
struct ServerState {
|
||||||
child: Arc<Mutex<Option<CommandChild>>>,
|
child: Arc<Mutex<Option<CommandChild>>>,
|
||||||
status: futures::future::Shared<tokio::sync::oneshot::Receiver<Result<String, String>>>,
|
status: future::Shared<oneshot::Receiver<Result<ServerReadyData, String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerState {
|
impl ServerState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
child: Option<CommandChild>,
|
child: Option<CommandChild>,
|
||||||
status: tokio::sync::oneshot::Receiver<Result<String, String>>,
|
status: oneshot::Receiver<Result<ServerReadyData, String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
child: Arc::new(Mutex::new(child)),
|
child: Arc::new(Mutex::new(child)),
|
||||||
@@ -80,7 +88,7 @@ async fn get_logs(app: AppHandle) -> Result<String, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<String, String> {
|
async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerReadyData, String> {
|
||||||
state
|
state
|
||||||
.status
|
.status
|
||||||
.clone()
|
.clone()
|
||||||
@@ -137,13 +145,14 @@ fn get_sidecar_port() -> u32 {
|
|||||||
}) as u32
|
}) as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild {
|
fn spawn_sidecar(app: &AppHandle, port: u32, password: &str) -> CommandChild {
|
||||||
let log_state = app.state::<LogState>();
|
let log_state = app.state::<LogState>();
|
||||||
let log_state_clone = log_state.inner().clone();
|
let log_state_clone = log_state.inner().clone();
|
||||||
|
|
||||||
println!("spawning sidecar on port {port}");
|
println!("spawning sidecar on port {port}");
|
||||||
|
|
||||||
let (mut rx, child) = cli::create_command(app, format!("serve --port {port}").as_str())
|
let (mut rx, child) = cli::create_command(app, format!("serve --port {port}").as_str())
|
||||||
|
.env("OPENCODE_SERVER_PASSWORD", password)
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to spawn opencode");
|
.expect("Failed to spawn opencode");
|
||||||
|
|
||||||
@@ -184,7 +193,7 @@ fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild {
|
|||||||
child
|
child
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_server_health(url: &str) -> bool {
|
async fn check_server_health(url: &str, password: Option<&str>) -> bool {
|
||||||
let health_url = format!("{}/health", url.trim_end_matches('/'));
|
let health_url = format!("{}/health", url.trim_end_matches('/'));
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
.timeout(Duration::from_secs(3))
|
.timeout(Duration::from_secs(3))
|
||||||
@@ -194,9 +203,13 @@ async fn check_server_health(url: &str) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
client
|
let mut req = client.get(&health_url);
|
||||||
.get(&health_url)
|
|
||||||
.send()
|
if let Some(password) = password {
|
||||||
|
req = req.basic_auth("opencode", Some(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
req.send()
|
||||||
.await
|
.await
|
||||||
.map(|r| r.status().is_success())
|
.map(|r| r.status().is_success())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
@@ -267,7 +280,7 @@ pub fn run() {
|
|||||||
|
|
||||||
window_builder.build().expect("Failed to create window");
|
window_builder.build().expect("Failed to create window");
|
||||||
|
|
||||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
app.manage(ServerState::new(None, rx));
|
app.manage(ServerState::new(None, rx));
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -344,12 +357,18 @@ fn get_server_url_from_config(config: &cli::Config) -> Option<String> {
|
|||||||
async fn setup_server_connection(
|
async fn setup_server_connection(
|
||||||
app: &AppHandle,
|
app: &AppHandle,
|
||||||
custom_url: Option<String>,
|
custom_url: Option<String>,
|
||||||
) -> Result<(Option<CommandChild>, String), String> {
|
) -> Result<(Option<CommandChild>, ServerReadyData), String> {
|
||||||
if let Some(url) = custom_url {
|
if let Some(url) = custom_url {
|
||||||
loop {
|
loop {
|
||||||
if check_server_health(&url).await {
|
if check_server_health(&url, None).await {
|
||||||
println!("Connected to custom server: {}", url);
|
println!("Connected to custom server: {}", url);
|
||||||
return Ok((None, url.clone()));
|
return Ok((
|
||||||
|
None,
|
||||||
|
ServerReadyData {
|
||||||
|
url: url.clone(),
|
||||||
|
password: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const RETRY: &str = "Retry";
|
const RETRY: &str = "Retry";
|
||||||
@@ -374,19 +393,36 @@ async fn setup_server_connection(
|
|||||||
let local_port = get_sidecar_port();
|
let local_port = get_sidecar_port();
|
||||||
let local_url = format!("http://127.0.0.1:{local_port}");
|
let local_url = format!("http://127.0.0.1:{local_port}");
|
||||||
|
|
||||||
if !check_server_health(&local_url).await {
|
if !check_server_health(&local_url, None).await {
|
||||||
match spawn_local_server(app, local_port).await {
|
let password = uuid::Uuid::new_v4().to_string();
|
||||||
Ok(child) => Ok(Some(child)),
|
|
||||||
|
match spawn_local_server(app, local_port, &password).await {
|
||||||
|
Ok(child) => Ok((
|
||||||
|
Some(child),
|
||||||
|
ServerReadyData {
|
||||||
|
url: local_url,
|
||||||
|
password: Some(password),
|
||||||
|
},
|
||||||
|
)),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok((
|
||||||
|
None,
|
||||||
|
ServerReadyData {
|
||||||
|
url: local_url,
|
||||||
|
password: None,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
.map(|child| (child, local_url))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_local_server(app: &AppHandle, port: u32) -> Result<CommandChild, String> {
|
async fn spawn_local_server(
|
||||||
let child = spawn_sidecar(app, port);
|
app: &AppHandle,
|
||||||
|
port: u32,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<CommandChild, String> {
|
||||||
|
let child = spawn_sidecar(app, port, password);
|
||||||
let url = format!("http://127.0.0.1:{port}");
|
let url = format!("http://127.0.0.1:{port}");
|
||||||
|
|
||||||
let timestamp = Instant::now();
|
let timestamp = Instant::now();
|
||||||
@@ -400,7 +436,7 @@ async fn spawn_local_server(app: &AppHandle, port: u32) -> Result<CommandChild,
|
|||||||
|
|
||||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
|
||||||
if check_server_health(&url).await {
|
if check_server_health(&url, Some(password)).await {
|
||||||
println!("Server ready after {:?}", timestamp.elapsed());
|
println!("Server ready after {:?}", timestamp.elapsed());
|
||||||
break Ok(child);
|
break Ok(child);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ import { AsyncStorage } from "@solid-primitives/storage"
|
|||||||
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
|
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
|
||||||
import { Store } from "@tauri-apps/plugin-store"
|
import { Store } from "@tauri-apps/plugin-store"
|
||||||
import { Logo } from "@opencode-ai/ui/logo"
|
import { Logo } from "@opencode-ai/ui/logo"
|
||||||
import { Accessor, JSX, createResource } from "solid-js"
|
import { createSignal, Show, Accessor, JSX, createResource } from "solid-js"
|
||||||
|
|
||||||
import { UPDATER_ENABLED } from "./updater"
|
import { UPDATER_ENABLED } from "./updater"
|
||||||
import { createMenu } from "./menu"
|
import { createMenu } from "./menu"
|
||||||
import pkg from "../package.json"
|
import pkg from "../package.json"
|
||||||
import { Show } from "solid-js"
|
|
||||||
|
|
||||||
const root = document.getElementById("root")
|
const root = document.getElementById("root")
|
||||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||||
@@ -29,7 +28,7 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
|
|
||||||
let update: Update | null = null
|
let update: Update | null = null
|
||||||
|
|
||||||
const platform: Platform = {
|
const createPlatform = (password: Accessor<string | null>): Platform => ({
|
||||||
platform: "desktop",
|
platform: "desktop",
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
|
|
||||||
@@ -256,7 +255,25 @@ const platform: Platform = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
fetch: tauriFetch,
|
fetch: (input, init) => {
|
||||||
|
const pw = password()
|
||||||
|
|
||||||
|
const addHeader = (headers: Headers, password: string) => {
|
||||||
|
headers.append("Authorization", `Basic ${btoa(`opencode:${password}`)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input instanceof Request) {
|
||||||
|
if (pw) addHeader(input.headers, pw)
|
||||||
|
return tauriFetch(input)
|
||||||
|
} else {
|
||||||
|
const headers = new Headers(init?.headers)
|
||||||
|
if (pw) addHeader(headers, pw)
|
||||||
|
return tauriFetch(input, {
|
||||||
|
...(init as any),
|
||||||
|
headers: headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getDefaultServerUrl: async () => {
|
getDefaultServerUrl: async () => {
|
||||||
const result = await invoke<string | null>("get_default_server_url").catch(() => null)
|
const result = await invoke<string | null>("get_default_server_url").catch(() => null)
|
||||||
@@ -266,7 +283,7 @@ const platform: Platform = {
|
|||||||
setDefaultServerUrl: async (url: string | null) => {
|
setDefaultServerUrl: async (url: string | null) => {
|
||||||
await invoke("set_default_server_url", { url })
|
await invoke("set_default_server_url", { url })
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
createMenu()
|
createMenu()
|
||||||
|
|
||||||
@@ -276,26 +293,37 @@ root?.addEventListener("mousewheel", (e) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
render(() => {
|
render(() => {
|
||||||
|
const [serverPassword, setServerPassword] = createSignal<string | null>(null)
|
||||||
|
const platform = createPlatform(() => serverPassword())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlatformProvider value={platform}>
|
<PlatformProvider value={platform}>
|
||||||
{ostype() === "macos" && (
|
|
||||||
<div class="mx-px bg-background-base border-b border-border-weak-base h-8" data-tauri-drag-region />
|
|
||||||
)}
|
|
||||||
<AppBaseProviders>
|
<AppBaseProviders>
|
||||||
<ServerGate>{(serverUrl) => <AppInterface defaultUrl={serverUrl()} />}</ServerGate>
|
{ostype() === "macos" && (
|
||||||
|
<div class="mx-px bg-background-base border-b border-border-weak-base h-8" data-tauri-drag-region />
|
||||||
|
)}
|
||||||
|
<ServerGate>
|
||||||
|
{(data) => {
|
||||||
|
setServerPassword(data().password)
|
||||||
|
|
||||||
|
return <AppInterface defaultUrl={data().url} />
|
||||||
|
}}
|
||||||
|
</ServerGate>
|
||||||
</AppBaseProviders>
|
</AppBaseProviders>
|
||||||
</PlatformProvider>
|
</PlatformProvider>
|
||||||
)
|
)
|
||||||
}, root!)
|
}, root!)
|
||||||
|
|
||||||
|
type ServerReadyData = { url: string; password: string | null }
|
||||||
|
|
||||||
// Gate component that waits for the server to be ready
|
// Gate component that waits for the server to be ready
|
||||||
function ServerGate(props: { children: (url: Accessor<string>) => JSX.Element }) {
|
function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.Element }) {
|
||||||
const [serverUrl] = createResource<string>(() => invoke("ensure_server_ready"))
|
const [serverData] = createResource<ServerReadyData>(() => invoke("ensure_server_ready"))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Not using suspense as not all components are compatible with it (undefined refs)
|
// Not using suspense as not all components are compatible with it (undefined refs)
|
||||||
<Show
|
<Show
|
||||||
when={serverUrl.state !== "pending" && serverUrl()}
|
when={serverData.state !== "pending" && serverData()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="h-screen w-screen flex flex-col items-center justify-center bg-background-base">
|
<div class="h-screen w-screen flex flex-col items-center justify-center bg-background-base">
|
||||||
<Logo class="w-xl opacity-12 animate-pulse" />
|
<Logo class="w-xl opacity-12 animate-pulse" />
|
||||||
@@ -303,7 +331,7 @@ function ServerGate(props: { children: (url: Accessor<string>) => JSX.Element })
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(serverUrl) => props.children(serverUrl)}
|
{(data) => props.children(data)}
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user