EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
useCanvasDesktop.js
Go to the documentation of this file.
1/**
2 * Canvas Desktop Hook - Connects to MeshCentral canvas endpoint
3 * Replaces the old RDS system with direct MeshCentral integration
4 */
5
6import { useRef, useState, useEffect, useCallback } from 'react';
7
8export function useCanvasDesktop(nodeId) {
9 const wsRef = useRef(null);
10 const canvasRef = useRef(null);
11 const ctxRef = useRef(null);
12
13 const [status, setStatus] = useState('disconnected'); // disconnected, connecting, connected, error
14 const [error, setError] = useState(null);
15 const [capabilities, setCapabilities] = useState([]);
16 const [phase, setPhase] = useState(0);
17
18 /**
19 * Connect to MeshCentral canvas desktop endpoint
20 */
21 const connect = useCallback(() => {
22 if (!nodeId) {
23 setError('No node ID provided');
24 return;
25 }
26
27 // Get JWT token from localStorage
28 const token = localStorage.getItem('jwt');
29 if (!token) {
30 setError('No authentication token found');
31 return;
32 }
33
34 // Build WebSocket URL
35 const meshCentralUrl = process.env.REACT_APP_MESHCENTRAL_URL || 'wss://rmm-psa-meshcentral-aq48h.ondigitalocean.app';
36 const wsUrl = `${meshCentralUrl}/api/canvas-desktop/${encodeURIComponent(nodeId)}?token=${encodeURIComponent(token)}`;
37
38 console.log('[Canvas Desktop] Connecting to:', wsUrl.replace(token, 'TOKEN'));
39 setStatus('connecting');
40 setError(null);
41
42 try {
43 const ws = new WebSocket(wsUrl);
44 ws.binaryType = 'arraybuffer';
45 wsRef.current = ws;
46
47 ws.onopen = () => {
48 console.log('[Canvas Desktop] ✅ WebSocket connected');
49 setStatus('connected');
50 };
51
52 ws.onmessage = (event) => {
53 try {
54 const msg = JSON.parse(event.data);
55 handleMessage(msg);
56 } catch (err) {
57 console.error('[Canvas Desktop] Failed to parse message:', err);
58 }
59 };
60
61 ws.onerror = (err) => {
62 console.error('[Canvas Desktop] ❌ WebSocket error:', err);
63 setError('WebSocket connection error');
64 setStatus('error');
65 };
66
67 ws.onclose = () => {
68 console.log('[Canvas Desktop] 🔌 WebSocket closed');
69 setStatus('disconnected');
70 };
71
72 } catch (err) {
73 console.error('[Canvas Desktop] ❌ Failed to create WebSocket:', err);
74 setError(err.message);
75 setStatus('error');
76 }
77 }, [nodeId]);
78
79 /**
80 * Handle incoming messages
81 */
82 const handleMessage = (msg) => {
83 console.log('[Canvas Desktop] 📨 Message:', msg);
84
85 switch (msg.type) {
86 case 'connected':
87 console.log('[Canvas Desktop] ✅ Authenticated:', msg);
88 setCapabilities(msg.capabilities || []);
89 setPhase(msg.phase || 1);
90 break;
91
92 case 'pong':
93 console.log('[Canvas Desktop] 🏓 Pong received');
94 break;
95
96 case 'frame':
97 // Phase 2: Handle screen frame data
98 if (msg.data && canvasRef.current && ctxRef.current) {
99 renderFrame(msg.data);
100 }
101 break;
102
103 case 'error':
104 console.error('[Canvas Desktop] ❌ Error from server:', msg.message);
105 setError(msg.message);
106 setStatus('error');
107 break;
108
109 default:
110 console.log('[Canvas Desktop] Unknown message type:', msg.type);
111 }
112 };
113
114 /**
115 * Render frame to canvas (Phase 2)
116 */
117 const renderFrame = (frameData) => {
118 // TODO: Implement canvas rendering
119 // This will be added in Phase 2 when screen streaming is implemented
120 console.log('[Canvas Desktop] Frame received (not yet implemented)');
121 };
122
123 /**
124 * Send ping to test connection
125 */
126 const sendPing = useCallback(() => {
127 if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
128 const msg = { type: 'ping', timestamp: Date.now() };
129 wsRef.current.send(JSON.stringify(msg));
130 console.log('[Canvas Desktop] 📤 Ping sent');
131 }
132 }, []);
133
134 /**
135 * Send mouse event (Phase 2)
136 */
137 const sendMouseEvent = useCallback((e) => {
138 if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
139 if (!canvasRef.current) return;
140
141 const rect = canvasRef.current.getBoundingClientRect();
142 const x = (e.clientX - rect.left) / rect.width;
143 const y = (e.clientY - rect.top) / rect.height;
144
145 const msg = {
146 type: 'mouse',
147 event: e.type,
148 x,
149 y,
150 button: e.button,
151 buttons: e.buttons
152 };
153
154 wsRef.current.send(JSON.stringify(msg));
155 }, []);
156
157 /**
158 * Send keyboard event (Phase 2)
159 */
160 const sendKeyEvent = useCallback((e) => {
161 if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
162
163 const msg = {
164 type: 'key',
165 event: e.type,
166 key: e.key,
167 code: e.code,
168 ctrlKey: e.ctrlKey,
169 shiftKey: e.shiftKey,
170 altKey: e.altKey,
171 metaKey: e.metaKey
172 };
173
174 wsRef.current.send(JSON.stringify(msg));
175 }, []);
176
177 /**
178 * Disconnect
179 */
180 const disconnect = useCallback(() => {
181 if (wsRef.current) {
182 wsRef.current.close();
183 wsRef.current = null;
184 }
185 setStatus('disconnected');
186 }, []);
187
188 /**
189 * Auto-connect on mount
190 */
191 useEffect(() => {
192 connect();
193 return () => disconnect();
194 }, [nodeId]); // Only reconnect if nodeId changes
195
196 /**
197 * Setup canvas context
198 */
199 useEffect(() => {
200 if (canvasRef.current) {
201 ctxRef.current = canvasRef.current.getContext('2d');
202 }
203 }, []);
204
205 return {
206 // State
207 status,
208 error,
209 capabilities,
210 phase,
211
212 // Refs
213 canvasRef,
214
215 // Actions
216 connect,
217 disconnect,
218 sendPing,
219 sendMouseEvent,
220 sendKeyEvent
221 };
222}