first commit

This commit is contained in:
sriram
2026-06-05 17:29:09 +05:30
commit b984b55bc0
31 changed files with 5008 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Remote Access Agent</title>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; margin: 0; padding: 1.5rem; }
h1 { font-size: 1.1rem; }
.status { background: #1e293b; padding: 0.7rem 1rem; border-radius: 8px; margin: 0.8rem 0; font-size: 0.9rem; }
.status.on { background: #14532d; }
.status.warn { background: #7c2d12; }
.consent { background: #1e293b; border: 1px solid #3b82f6; border-radius: 10px; padding: 1.2rem; margin-top: 1rem; }
.consent h2 { font-size: 1rem; margin: 0 0 0.5rem; }
button { padding: 0.6rem 1.2rem; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
.grant { background: #22c55e; color: #052e16; }
.deny { background: #ef4444; color: #fff; margin-left: 0.5rem; }
.muted { color: #94a3b8; font-size: 0.82rem; }
.indicator { position: fixed; bottom: 0; left: 0; right: 0; background: #b91c1c; color: #fff; text-align: center; padding: 0.4rem; font-size: 0.85rem; display: none; }
.indicator.show { display: block; }
#log { font-family: monospace; font-size: 0.72rem; color: #64748b; height: 120px; overflow-y: auto; margin-top: 1rem; }
</style>
</head>
<body>
<h1>🛡️ Remote Access Agent</h1>
<div id="status" class="status">Starting…</div>
<div id="consentBox"></div>
<p class="muted">This machine accepts remote support sessions. You'll be asked to approve each connection unless an unattended-access policy is set.</p>
<div id="log"></div>
<div id="indicator" class="indicator">● A technician is currently viewing/controlling this screen</div>
<script src="agent.js"></script>
</body>
</html>
+135
View File
@@ -0,0 +1,135 @@
// Agent renderer: connects to the signaling server, handles consent,
// captures the screen, and streams it over WebRTC to the technician's viewer.
// Remote input events arrive on the data channel and are handed to the main
// process (via the preload bridge) for OS-level injection.
const statusEl = document.getElementById('status');
const consentBox = document.getElementById('consentBox');
const indicator = document.getElementById('indicator');
const logEl = document.getElementById('log');
let cfg = null;
let ws = null;
let pc = null;
let localStream = null;
let currentSessionId = null;
const log = (t) => { const d = document.createElement('div'); d.textContent = t; logEl.appendChild(d); logEl.scrollTop = logEl.scrollHeight; };
const setStatus = (t, cls = '') => { statusEl.textContent = t; statusEl.className = 'status ' + cls; };
window.agent.onConfig((c) => {
cfg = c;
if (!cfg.enrollToken) {
setStatus('No enroll token. Set AGENT_ENROLL_TOKEN and restart.', 'warn');
return;
}
connect();
});
function wsUrl() {
const u = new URL(cfg.serverUrl);
const proto = u.protocol === 'https:' ? 'wss:' : 'ws:';
return `${proto}//${u.host}/ws`;
}
function connect() {
setStatus('Connecting to server…');
ws = new WebSocket(wsUrl());
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'agent-hello', enrollToken: cfg.enrollToken }));
};
ws.onmessage = (e) => handle(JSON.parse(e.data));
ws.onclose = () => { setStatus('Disconnected. Reconnecting in 3s…', 'warn'); setTimeout(connect, 3000); };
}
async function handle(m) {
switch (m.type) {
case 'agent-registered':
setStatus(`Online as "${m.name}". Waiting for sessions.`, 'on');
log(`registered: ${m.name}`);
break;
case 'session-request':
if (m.unattended) { log(`unattended session ${m.sessionId} — auto-granting`); grant(m.sessionId); }
else showConsent(m);
break;
case 'start-stream':
currentSessionId = m.sessionId;
await startStreaming();
break;
case 'answer':
if (pc) await pc.setRemoteDescription(new RTCSessionDescription(m.sdp));
break;
case 'ice-candidate':
if (m.candidate && pc) await pc.addIceCandidate(new RTCIceCandidate(m.candidate));
break;
case 'session-ended':
teardown();
break;
case 'error':
setStatus('Server: ' + m.message, 'warn');
break;
}
}
function showConsent(m) {
consentBox.innerHTML = `
<div class="consent">
<h2>Allow remote support?</h2>
<p class="muted"><b>${escapeHtml(m.technician)}</b> is requesting to view and control this PC.</p>
<button class="grant" id="grantBtn">Allow</button>
<button class="deny" id="denyBtn">Deny</button>
</div>`;
document.getElementById('grantBtn').onclick = () => { consentBox.innerHTML = ''; grant(m.sessionId); };
document.getElementById('denyBtn').onclick = () => { consentBox.innerHTML = ''; deny(m.sessionId); };
}
const grant = (sessionId) => ws.send(JSON.stringify({ type: 'consent', sessionId, granted: true }));
const deny = (sessionId) => ws.send(JSON.stringify({ type: 'consent', sessionId, granted: false }));
async function startStreaming() {
setStatus('Sharing screen with technician…', 'on');
indicator.classList.add('show');
try {
localStream = await navigator.mediaDevices.getDisplayMedia({ video: { frameRate: { ideal: 30 } }, audio: false });
} catch (err) {
log('getDisplayMedia failed: ' + err.message);
setStatus('Screen capture failed.', 'warn');
return;
}
pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
localStream.getTracks().forEach((t) => pc.addTrack(t, localStream));
// Viewer creates the input data channel; we receive it here.
pc.ondatachannel = (ev) => {
const ch = ev.channel;
ch.onmessage = (msg) => {
let evt; try { evt = JSON.parse(msg.data); } catch { return; }
window.agent.injectInput(evt); // -> main process -> OS injection
};
};
pc.onicecandidate = (ev) => {
if (ev.candidate) ws.send(JSON.stringify({ type: 'ice-candidate', sessionId: currentSessionId, candidate: ev.candidate }));
};
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({ type: 'offer', sessionId: currentSessionId, sdp: pc.localDescription }));
log('sent offer for session ' + currentSessionId);
}
function teardown() {
indicator.classList.remove('show');
window.agent.sessionEnded();
if (localStream) { localStream.getTracks().forEach((t) => t.stop()); localStream = null; }
if (pc) { pc.close(); pc = null; }
currentSessionId = null;
setStatus('Session ended. Waiting for sessions.', 'on');
}
function escapeHtml(s) { return String(s).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }