85 lines
2.7 KiB
TypeScript
85 lines
2.7 KiB
TypeScript
import type { ComputerMode, ComputerStatus as ContractComputerStatus } from "@rakazo/contracts";
|
||
|
||
export const COMPUTER_HEARTBEAT_MS = 60_000;
|
||
export const SCREEN_URL_OPEN_ATTEMPTS = 5;
|
||
export const SCREEN_URL_RETRY_DELAY_MS = 400;
|
||
|
||
export type ComputerStatus = ContractComputerStatus;
|
||
|
||
function isLocalHostname(hostname: string) {
|
||
return (
|
||
hostname === "127.0.0.1" ||
|
||
hostname === "localhost" ||
|
||
hostname === "[::1]" ||
|
||
hostname === "::1"
|
||
);
|
||
}
|
||
|
||
export async function readScreenUrl(
|
||
request: () => Promise<{ url: string | null }>,
|
||
options: {
|
||
attempts?: number;
|
||
delayMs?: number;
|
||
sleep?: (ms: number) => Promise<void>;
|
||
} = {},
|
||
): Promise<string | null> {
|
||
const attempts = Math.max(1, options.attempts ?? 1);
|
||
const delayMs = options.delayMs ?? SCREEN_URL_RETRY_DELAY_MS;
|
||
const sleep =
|
||
options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
|
||
let lastError: unknown;
|
||
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
||
try {
|
||
const screen = await request();
|
||
if (screen.url) return screen.url;
|
||
lastError = undefined;
|
||
} catch (error) {
|
||
lastError = error;
|
||
}
|
||
if (attempt < attempts) await sleep(delayMs);
|
||
}
|
||
if (lastError) throw lastError;
|
||
return null;
|
||
}
|
||
|
||
/** Point a loopback noVNC URL at the same host the app uses for the API. */
|
||
export function embeddableScreenUrl(url: string | null, apiBase: string): string | null {
|
||
if (!url) return null;
|
||
try {
|
||
const parsed = new URL(url);
|
||
const api = new URL(apiBase);
|
||
if (isLocalHostname(parsed.hostname) && !isLocalHostname(api.hostname)) {
|
||
parsed.hostname = api.hostname;
|
||
}
|
||
return parsed.toString();
|
||
} catch {
|
||
return url;
|
||
}
|
||
}
|
||
|
||
export function previewPlaceholder(
|
||
state: string | undefined,
|
||
booting: boolean,
|
||
name: string,
|
||
mode?: ComputerMode,
|
||
): string {
|
||
if (state === "booting" || booting) return "Booting live desktop…";
|
||
if (state === "running") return computerLabel(mode, name);
|
||
if (state === "suspended") return "Computer is asleep — take control to wake it";
|
||
if (state === "error") return "Computer failed to boot";
|
||
return "Computer is stopped";
|
||
}
|
||
|
||
export function controlLabel(computer: ComputerStatus | null, name: string, botId?: string) {
|
||
if (computer?.busyBotName) return `${computer.busyBotName} is using it`;
|
||
if (computer?.controlHolder === "user" && computer.controlBotId === botId) {
|
||
return "You have control";
|
||
}
|
||
if (computer?.state === "suspended") return "Asleep";
|
||
return computerLabel(computer?.mode, name);
|
||
}
|
||
|
||
export function computerLabel(mode: ComputerMode | undefined, name: string) {
|
||
return mode === "dedicated" ? `${name}’s computer` : "Team Computer";
|
||
}
|