2 * Canvas Desktop Hook - Connects to MeshCentral canvas endpoint
3 * Replaces the old RDS system with direct MeshCentral integration
6import { useRef, useState, useEffect, useCallback } from 'react';
8export function useCanvasDesktop(nodeId) {
9 const wsRef = useRef(null);
10 const canvasRef = useRef(null);
11 const ctxRef = useRef(null);
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);
19 * Connect to MeshCentral canvas desktop endpoint
21 const connect = useCallback(() => {
23 setError('No node ID provided');
27 // Get JWT token from localStorage
28 const token = localStorage.getItem('jwt');
30 setError('No authentication token found');
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)}`;
38 console.log('[Canvas Desktop] Connecting to:', wsUrl.replace(token, 'TOKEN'));
39 setStatus('connecting');
43 const ws = new WebSocket(wsUrl);
44 ws.binaryType = 'arraybuffer';
48 console.log('[Canvas Desktop] ✅ WebSocket connected');
49 setStatus('connected');
52 ws.onmessage = (event) => {
54 const msg = JSON.parse(event.data);
57 console.error('[Canvas Desktop] Failed to parse message:', err);
61 ws.onerror = (err) => {
62 console.error('[Canvas Desktop] ❌ WebSocket error:', err);
63 setError('WebSocket connection error');
68 console.log('[Canvas Desktop] 🔌 WebSocket closed');
69 setStatus('disconnected');
73 console.error('[Canvas Desktop] ❌ Failed to create WebSocket:', err);
74 setError(err.message);
80 * Handle incoming messages
82 const handleMessage = (msg) => {
83 console.log('[Canvas Desktop] 📨 Message:', msg);
87 console.log('[Canvas Desktop] ✅ Authenticated:', msg);
88 setCapabilities(msg.capabilities || []);
89 setPhase(msg.phase || 1);
93 console.log('[Canvas Desktop] 🏓 Pong received');
97 // Phase 2: Handle screen frame data
98 if (msg.data && canvasRef.current && ctxRef.current) {
99 renderFrame(msg.data);
104 console.error('[Canvas Desktop] ❌ Error from server:', msg.message);
105 setError(msg.message);
110 console.log('[Canvas Desktop] Unknown message type:', msg.type);
115 * Render frame to canvas (Phase 2)
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)');
124 * Send ping to test connection
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');
135 * Send mouse event (Phase 2)
137 const sendMouseEvent = useCallback((e) => {
138 if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
139 if (!canvasRef.current) return;
141 const rect = canvasRef.current.getBoundingClientRect();
142 const x = (e.clientX - rect.left) / rect.width;
143 const y = (e.clientY - rect.top) / rect.height;
154 wsRef.current.send(JSON.stringify(msg));
158 * Send keyboard event (Phase 2)
160 const sendKeyEvent = useCallback((e) => {
161 if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
169 shiftKey: e.shiftKey,
174 wsRef.current.send(JSON.stringify(msg));
180 const disconnect = useCallback(() => {
182 wsRef.current.close();
183 wsRef.current = null;
185 setStatus('disconnected');
189 * Auto-connect on mount
193 return () => disconnect();
194 }, [nodeId]); // Only reconnect if nodeId changes
197 * Setup canvas context
200 if (canvasRef.current) {
201 ctxRef.current = canvasRef.current.getContext('2d');