1import { useEffect, useRef, useState } from 'react';
2import { useParams } from 'react-router-dom';
3import MainLayout from '../components/Layout/MainLayout';
4import { apiFetch } from '../lib/api';
6function getWsUrl(agentId, token) {
7 const loc = window.location;
8 const proto = loc.protocol === 'https:' ? 'wss' : 'ws';
10 return `${proto}://${host}/rds/stream/${agentId}?token=${token}`;
14export default function RdsViewer() {
15 const { agentId } = useParams();
16 const [status, setStatus] = useState('disconnected');
17 const [error, setError] = useState('');
18 const [clipboard, setClipboard] = useState('');
19 const [agentClipboard, setAgentClipboard] = useState('');
20 const videoRef = useRef();
21 const wsRef = useRef();
22 const token = localStorage.getItem('jwt') || '';
25 const sendClipboard = () => {
26 if (wsRef.current && wsRef.current.readyState === 1) {
27 wsRef.current.send(JSON.stringify({ type: 'clipboard', data: clipboard }));
32 const handleClipboardMessage = (data) => {
33 setAgentClipboard(data.data || '');
37 if (!agentId || !token) return;
38 setStatus('connecting');
40 const ws = new WebSocket(getWsUrl(agentId, token));
41 ws.binaryType = 'arraybuffer';
43 let mediaSource, sourceBuffer;
45 ws.onopen = () => setStatus('connected');
46 ws.onerror = (e) => setError('WebSocket error');
47 ws.onclose = () => setStatus('disconnected');
48 ws.onmessage = (event) => {
49 // Try to parse as JSON for clipboard, else treat as video
51 if (typeof event.data === 'string') {
52 try { parsed = JSON.parse(event.data); } catch {}
54 if (parsed && parsed.type === 'clipboard') {
55 handleClipboardMessage(parsed);
60 mediaSource = new window.MediaSource();
61 videoRef.current.src = URL.createObjectURL(mediaSource);
62 mediaSource.addEventListener('sourceopen', () => {
63 sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
64 sourceBuffer.addEventListener('updateend', () => {
65 if (queue.length > 0 && !sourceBuffer.updating) {
66 sourceBuffer.appendBuffer(queue.shift());
71 if (sourceBuffer && !sourceBuffer.updating && event.data instanceof ArrayBuffer) {
72 sourceBuffer.appendBuffer(new Uint8Array(event.data));
73 } else if (sourceBuffer && event.data instanceof ArrayBuffer) {
74 queue.push(new Uint8Array(event.data));
79 if (mediaSource) mediaSource.endOfStream();
83 // Copy agent clipboard to local clipboard
84 const copyAgentClipboard = async () => {
86 await navigator.clipboard.writeText(agentClipboard);
92 <h2>Remote Desktop Session</h2>
93 <div>Status: {status}</div>
94 {error && <div className="error">{error}</div>}
95 <video ref={videoRef} autoPlay controls style={{ width: '100%', background: '#000' }} />
96 <div style={{ marginTop: 16 }}>
100 onChange={e => setClipboard(e.target.value)}
101 placeholder="Type here to send clipboard to agent"
103 style={{ width: '100%' }}
105 <button onClick={sendClipboard} disabled={status !== 'connected'}>Send to Agent</button>
106 <div style={{ marginTop: 8 }}>
107 <strong>Agent Clipboard:</strong>
109 value={agentClipboard}
112 style={{ width: '100%' }}
114 <button onClick={copyAgentClipboard} disabled={!agentClipboard}>Copy to Local Clipboard</button>