mirror of
https://github.com/getpaseo/paseo.git
synced 2026-08-15 04:42:45 +00:00
Reshape desktop bootstrap into independent services
HostRuntime self-boots via boot() and emits state through existing subscribeAll plumbing. DaemonStartService runs in parallel: on success it upserts the connection, which HostRuntime's syncHosts already wires into a controller; on failure it emits an error. The bootstrap no longer awaits daemon-start before letting saved-host controllers connect. index.tsx resolves the redirect from useEarliestOnlineHostServerId + useDaemonStartLastError subscriptions, not from a procedural promise. The splash error is derived state: splashError = !anyOnlineHostServerId ? daemonStartError : null. Retry calls only DaemonStartService.start(). Deletes initializeHostRuntime, setupDesktopManagedConnection, addConnectionFromListenAndWaitForOnline, bootstrapDesktop on the store, waitForAnyConnectionOnline (now unused in production), and the phase enum. Net -623 lines.
This commit is contained in:
127
packages/app/src/runtime/daemon-start-service.ts
Normal file
127
packages/app/src/runtime/daemon-start-service.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { startDesktopDaemon, type DesktopDaemonStatus } from "@/desktop/daemon/desktop-daemon";
|
||||
import { connectionFromListen } from "@/types/host-connection";
|
||||
import type { HostRuntimeStore } from "@/runtime/host-runtime";
|
||||
|
||||
export type DaemonStartResult = { ok: true } | { ok: false; error: string };
|
||||
|
||||
export interface DaemonStartServiceDeps {
|
||||
store: Pick<HostRuntimeStore, "upsertConnectionFromListen">;
|
||||
startDesktopDaemon?: () => Promise<DesktopDaemonStatus>;
|
||||
}
|
||||
|
||||
export class DaemonStartService {
|
||||
private readonly store: Pick<HostRuntimeStore, "upsertConnectionFromListen">;
|
||||
private readonly invokeStartDesktopDaemon: () => Promise<DesktopDaemonStatus>;
|
||||
private readonly listeners = new Set<() => void>();
|
||||
private lastError: string | null = null;
|
||||
private inFlightCount = 0;
|
||||
|
||||
constructor(deps: DaemonStartServiceDeps) {
|
||||
this.store = deps.store;
|
||||
this.invokeStartDesktopDaemon = deps.startDesktopDaemon ?? startDesktopDaemon;
|
||||
}
|
||||
|
||||
async start(): Promise<DaemonStartResult> {
|
||||
this.beginRequest();
|
||||
try {
|
||||
const daemon = await this.invokeStartDesktopDaemon();
|
||||
const listenAddress = daemon.listen?.trim() ?? "";
|
||||
const serverId = daemon.serverId.trim();
|
||||
if (!listenAddress) {
|
||||
return this.fail("Desktop daemon did not return a listen address.");
|
||||
}
|
||||
if (!serverId) {
|
||||
return this.fail("Desktop daemon did not return a server id.");
|
||||
}
|
||||
if (!connectionFromListen(listenAddress)) {
|
||||
return this.fail(`Desktop daemon returned an unsupported listen address: ${listenAddress}`);
|
||||
}
|
||||
await this.store.upsertConnectionFromListen({
|
||||
listenAddress,
|
||||
serverId,
|
||||
hostname: daemon.hostname,
|
||||
});
|
||||
return { ok: true };
|
||||
} catch (error) {
|
||||
return this.fail(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
this.endRequest();
|
||||
}
|
||||
}
|
||||
|
||||
getLastError(): string | null {
|
||||
return this.lastError;
|
||||
}
|
||||
|
||||
isRunning(): boolean {
|
||||
return this.inFlightCount > 0;
|
||||
}
|
||||
|
||||
subscribe(listener: () => void): () => void {
|
||||
this.listeners.add(listener);
|
||||
return () => {
|
||||
this.listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
private fail(message: string): DaemonStartResult {
|
||||
this.setLastError(message);
|
||||
return { ok: false, error: message };
|
||||
}
|
||||
|
||||
private setLastError(value: string | null): void {
|
||||
if (this.lastError === value) {
|
||||
return;
|
||||
}
|
||||
this.lastError = value;
|
||||
this.notify();
|
||||
}
|
||||
|
||||
private beginRequest(): void {
|
||||
const becameRunning = this.inFlightCount === 0;
|
||||
this.inFlightCount += 1;
|
||||
const errorChanged = this.lastError !== null;
|
||||
this.lastError = null;
|
||||
if (becameRunning || errorChanged) {
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private endRequest(): void {
|
||||
this.inFlightCount = Math.max(0, this.inFlightCount - 1);
|
||||
if (this.inFlightCount === 0) {
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private notify(): void {
|
||||
for (const listener of this.listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let singletonDaemonStartService: DaemonStartService | null = null;
|
||||
const DAEMON_START_SERVICE_GLOBAL_KEY = "__paseoDaemonStartService";
|
||||
|
||||
type DaemonStartServiceGlobal = typeof globalThis & {
|
||||
[DAEMON_START_SERVICE_GLOBAL_KEY]?: DaemonStartService;
|
||||
};
|
||||
|
||||
export function getDaemonStartService(deps: DaemonStartServiceDeps): DaemonStartService {
|
||||
if (singletonDaemonStartService) {
|
||||
return singletonDaemonStartService;
|
||||
}
|
||||
|
||||
const runtimeGlobal = globalThis as DaemonStartServiceGlobal;
|
||||
if (runtimeGlobal[DAEMON_START_SERVICE_GLOBAL_KEY]) {
|
||||
singletonDaemonStartService = runtimeGlobal[DAEMON_START_SERVICE_GLOBAL_KEY] ?? null;
|
||||
if (singletonDaemonStartService) {
|
||||
return singletonDaemonStartService;
|
||||
}
|
||||
}
|
||||
|
||||
singletonDaemonStartService = new DaemonStartService(deps);
|
||||
runtimeGlobal[DAEMON_START_SERVICE_GLOBAL_KEY] = singletonDaemonStartService;
|
||||
return singletonDaemonStartService;
|
||||
}
|
||||
Reference in New Issue
Block a user