2026-08-06 14:41:39 -05:00
|
|
|
import {
|
|
|
|
|
authApi,
|
|
|
|
|
getRemoteGuacamoleApi,
|
|
|
|
|
handleApiError,
|
|
|
|
|
isElectron,
|
|
|
|
|
} from "@/main-axios";
|
|
|
|
|
import type { AxiosInstance } from "axios";
|
2026-08-19 14:12:06 -05:00
|
|
|
import type { GuacamoleConfig } from "@/types/guacamole-config";
|
2026-08-06 14:41:39 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The embedded desktop backend does not bundle guacd, which is why
|
|
|
|
|
* resolveConnectionOrigin() pins RDP/VNC/Telnet to "remote". These calls have to
|
|
|
|
|
* follow: asking the embedded backend reports the guacd *it* cannot reach,
|
|
|
|
|
* rather than the one on the connected server that serves the session.
|
|
|
|
|
*/
|
|
|
|
|
function guacamoleApi(): AxiosInstance {
|
|
|
|
|
return isElectron() ? getRemoteGuacamoleApi() : authApi;
|
|
|
|
|
}
|
2026-06-04 15:16:53 -04:00
|
|
|
|
|
|
|
|
export interface GuacamoleTokenRequest {
|
|
|
|
|
protocol: "rdp" | "vnc" | "telnet";
|
|
|
|
|
hostname: string;
|
|
|
|
|
port?: number;
|
|
|
|
|
username?: string;
|
|
|
|
|
password?: string;
|
|
|
|
|
domain?: string;
|
|
|
|
|
security?: string;
|
|
|
|
|
ignoreCert?: boolean;
|
2026-08-19 14:12:06 -05:00
|
|
|
guacamoleConfig?: GuacamoleConfig;
|
2026-06-04 15:16:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface GuacamoleTokenResponse {
|
|
|
|
|
token: string;
|
2026-07-26 18:47:27 -05:00
|
|
|
guacamoleConnectionId?: string | null;
|
2026-06-04 15:16:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type GuacamoleConfigSource = {
|
|
|
|
|
guacamoleConfig?: string | Record<string, unknown> | null;
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-19 14:12:06 -05:00
|
|
|
export function parseGuacamoleConfig(
|
|
|
|
|
config?: string | GuacamoleConfig | null,
|
|
|
|
|
): GuacamoleConfig {
|
|
|
|
|
if (!config) return {};
|
|
|
|
|
if (typeof config !== "string") return config;
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(config) as GuacamoleConfig;
|
|
|
|
|
} catch {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 15:16:53 -04:00
|
|
|
export function getGuacamoleDpi(
|
|
|
|
|
source?: GuacamoleConfigSource,
|
|
|
|
|
): number | undefined {
|
|
|
|
|
const config = source?.guacamoleConfig;
|
|
|
|
|
if (!config) return undefined;
|
|
|
|
|
|
|
|
|
|
let dpi: unknown;
|
|
|
|
|
if (typeof config === "string") {
|
|
|
|
|
try {
|
|
|
|
|
dpi = JSON.parse(config).dpi;
|
|
|
|
|
} catch {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
dpi = config.dpi;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parsedDpi = typeof dpi === "string" ? Number(dpi) : dpi;
|
|
|
|
|
if (
|
|
|
|
|
typeof parsedDpi !== "number" ||
|
|
|
|
|
!Number.isFinite(parsedDpi) ||
|
|
|
|
|
parsedDpi <= 0
|
|
|
|
|
) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Math.trunc(parsedDpi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toGuacamoleParams(
|
|
|
|
|
config: GuacamoleTokenRequest["guacamoleConfig"],
|
|
|
|
|
): Record<string, unknown> {
|
|
|
|
|
if (!config) return {};
|
|
|
|
|
|
|
|
|
|
const params: Record<string, unknown> = {};
|
|
|
|
|
|
|
|
|
|
const mappings: Record<string, string> = {
|
|
|
|
|
colorDepth: "color-depth",
|
|
|
|
|
resizeMethod: "resize-method",
|
|
|
|
|
forceLossless: "force-lossless",
|
|
|
|
|
disableAudio: "disable-audio",
|
|
|
|
|
enableAudioInput: "enable-audio-input",
|
|
|
|
|
enableWallpaper: "enable-wallpaper",
|
|
|
|
|
enableTheming: "enable-theming",
|
|
|
|
|
enableFontSmoothing: "enable-font-smoothing",
|
|
|
|
|
enableFullWindowDrag: "enable-full-window-drag",
|
|
|
|
|
enableDesktopComposition: "enable-desktop-composition",
|
|
|
|
|
enableMenuAnimations: "enable-menu-animations",
|
|
|
|
|
disableBitmapCaching: "disable-bitmap-caching",
|
|
|
|
|
disableOffscreenCaching: "disable-offscreen-caching",
|
|
|
|
|
disableGlyphCaching: "disable-glyph-caching",
|
|
|
|
|
disableGfx: "disable-gfx",
|
|
|
|
|
enablePrinting: "enable-printing",
|
|
|
|
|
printerName: "printer-name",
|
|
|
|
|
enableDrive: "enable-drive",
|
|
|
|
|
driveName: "drive-name",
|
|
|
|
|
drivePath: "drive-path",
|
|
|
|
|
createDrivePath: "create-drive-path",
|
|
|
|
|
disableDownload: "disable-download",
|
|
|
|
|
disableUpload: "disable-upload",
|
|
|
|
|
enableTouch: "enable-touch",
|
|
|
|
|
clientName: "client-name",
|
|
|
|
|
initialProgram: "initial-program",
|
|
|
|
|
serverLayout: "server-layout",
|
|
|
|
|
gatewayHostname: "gateway-hostname",
|
|
|
|
|
gatewayPort: "gateway-port",
|
|
|
|
|
gatewayUsername: "gateway-username",
|
|
|
|
|
gatewayPassword: "gateway-password",
|
|
|
|
|
gatewayDomain: "gateway-domain",
|
|
|
|
|
remoteApp: "remote-app",
|
|
|
|
|
remoteAppDir: "remote-app-dir",
|
|
|
|
|
remoteAppArgs: "remote-app-args",
|
|
|
|
|
normalizeClipboard: "normalize-clipboard",
|
|
|
|
|
disableCopy: "disable-copy",
|
|
|
|
|
disablePaste: "disable-paste",
|
|
|
|
|
swapRedBlue: "swap-red-blue",
|
|
|
|
|
readOnly: "read-only",
|
|
|
|
|
recordingPath: "recording-path",
|
|
|
|
|
recordingName: "recording-name",
|
|
|
|
|
createRecordingPath: "create-recording-path",
|
|
|
|
|
recordingExcludeOutput: "recording-exclude-output",
|
|
|
|
|
recordingExcludeMouse: "recording-exclude-mouse",
|
|
|
|
|
recordingIncludeKeys: "recording-include-keys",
|
|
|
|
|
wolSendPacket: "wol-send-packet",
|
|
|
|
|
wolMacAddr: "wol-mac-addr",
|
|
|
|
|
wolBroadcastAddr: "wol-broadcast-addr",
|
|
|
|
|
wolUdpPort: "wol-udp-port",
|
|
|
|
|
wolWaitTime: "wol-wait-time",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(config)) {
|
|
|
|
|
if (value !== undefined && value !== null && value !== "") {
|
|
|
|
|
const paramName = mappings[key] || key;
|
|
|
|
|
if (typeof value === "boolean") {
|
|
|
|
|
params[paramName] = value ? "true" : "false";
|
|
|
|
|
} else {
|
|
|
|
|
params[paramName] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getGuacamoleToken(
|
|
|
|
|
request: GuacamoleTokenRequest,
|
|
|
|
|
): Promise<GuacamoleTokenResponse> {
|
|
|
|
|
try {
|
|
|
|
|
const guacParams = toGuacamoleParams(request.guacamoleConfig);
|
|
|
|
|
|
2026-08-06 14:41:39 -05:00
|
|
|
const response = await guacamoleApi().post("/guacamole/token", {
|
2026-06-04 15:16:53 -04:00
|
|
|
type: request.protocol,
|
|
|
|
|
hostname: request.hostname,
|
|
|
|
|
port: request.port,
|
|
|
|
|
username: request.username,
|
|
|
|
|
password: request.password,
|
|
|
|
|
domain: request.domain,
|
|
|
|
|
security: request.security,
|
|
|
|
|
"ignore-cert": request.ignoreCert,
|
|
|
|
|
...guacParams,
|
|
|
|
|
});
|
|
|
|
|
return response.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw handleApiError(error, "get guacamole token");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getGuacamoleTokenFromHost(
|
|
|
|
|
hostId: number,
|
|
|
|
|
protocol?: "rdp" | "vnc" | "telnet",
|
2026-08-19 14:12:06 -05:00
|
|
|
promptedCredentials?: {
|
|
|
|
|
username?: string;
|
|
|
|
|
password?: string;
|
|
|
|
|
domain?: string;
|
|
|
|
|
},
|
2026-06-04 15:16:53 -04:00
|
|
|
): Promise<GuacamoleTokenResponse> {
|
|
|
|
|
try {
|
2026-08-06 14:41:39 -05:00
|
|
|
const response = await guacamoleApi().post(
|
|
|
|
|
`/guacamole/connect-host/${hostId}`,
|
|
|
|
|
{
|
|
|
|
|
...(protocol ? { protocol } : {}),
|
|
|
|
|
...(promptedCredentials?.username
|
|
|
|
|
? { promptedUsername: promptedCredentials.username }
|
|
|
|
|
: {}),
|
|
|
|
|
...(promptedCredentials?.password
|
|
|
|
|
? { promptedPassword: promptedCredentials.password }
|
|
|
|
|
: {}),
|
2026-08-19 14:12:06 -05:00
|
|
|
...(promptedCredentials
|
|
|
|
|
? { promptedDomain: promptedCredentials.domain ?? "" }
|
|
|
|
|
: {}),
|
2026-08-06 14:41:39 -05:00
|
|
|
},
|
|
|
|
|
);
|
2026-06-04 15:16:53 -04:00
|
|
|
return response.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw handleApiError(error, "get guacamole token from host");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getGuacdStatus(): Promise<{
|
|
|
|
|
guacd: { status: string };
|
|
|
|
|
}> {
|
2026-08-06 14:41:39 -05:00
|
|
|
const response = await guacamoleApi().get("/guacamole/status");
|
2026-06-04 15:16:53 -04:00
|
|
|
return response.data;
|
|
|
|
|
}
|