EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
RdsViewer.jsx
Go to the documentation of this file.
1import { useEffect, useRef, useState } from 'react';
2import { useParams } from 'react-router-dom';
3import MainLayout from '../components/Layout/MainLayout';
4import { apiFetch } from '../lib/api';
5
6function getWsUrl(agentId, token) {
7 const loc = window.location;
8 const proto = loc.protocol === 'https:' ? 'wss' : 'ws';
9 const host = loc.host;
10 return `${proto}://${host}/rds/stream/${agentId}?token=${token}`;
11}
12
13
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') || '';
23
24 // Clipboard send
25 const sendClipboard = () => {
26 if (wsRef.current && wsRef.current.readyState === 1) {
27 wsRef.current.send(JSON.stringify({ type: 'clipboard', data: clipboard }));
28 }
29 };
30
31 // Clipboard receive
32 const handleClipboardMessage = (data) => {
33 setAgentClipboard(data.data || '');
34 };
35
36 useEffect(() => {
37 if (!agentId || !token) return;
38 setStatus('connecting');
39 setError('');
40 const ws = new WebSocket(getWsUrl(agentId, token));
41 ws.binaryType = 'arraybuffer';
42 wsRef.current = ws;
43 let mediaSource, sourceBuffer;
44 let queue = [];
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
50 let parsed = null;
51 if (typeof event.data === 'string') {
52 try { parsed = JSON.parse(event.data); } catch {}
53 }
54 if (parsed && parsed.type === 'clipboard') {
55 handleClipboardMessage(parsed);
56 return;
57 }
58 // Video stream
59 if (!mediaSource) {
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());
67 }
68 });
69 });
70 }
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));
75 }
76 };
77 return () => {
78 ws.close();
79 if (mediaSource) mediaSource.endOfStream();
80 };
81 }, [agentId, token]);
82
83 // Copy agent clipboard to local clipboard
84 const copyAgentClipboard = async () => {
85 try {
86 await navigator.clipboard.writeText(agentClipboard);
87 } catch {}
88 };
89
90 return (
91 <MainLayout>
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 }}>
97 <h4>Clipboard</h4>
98 <textarea
99 value={clipboard}
100 onChange={e => setClipboard(e.target.value)}
101 placeholder="Type here to send clipboard to agent"
102 rows={2}
103 style={{ width: '100%' }}
104 />
105 <button onClick={sendClipboard} disabled={status !== 'connected'}>Send to Agent</button>
106 <div style={{ marginTop: 8 }}>
107 <strong>Agent Clipboard:</strong>
108 <textarea
109 value={agentClipboard}
110 readOnly
111 rows={2}
112 style={{ width: '100%' }}
113 />
114 <button onClick={copyAgentClipboard} disabled={!agentClipboard}>Copy to Local Clipboard</button>
115 </div>
116 </div>
117 </MainLayout>
118 );
119}