193 lines
5.7 KiB
JavaScript
193 lines
5.7 KiB
JavaScript
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
import { io } from 'socket.io-client';
|
|
import { useAuth } from './AuthContext';
|
|
import toast from 'react-hot-toast';
|
|
|
|
const SocketContext = createContext();
|
|
|
|
export const SocketProvider = ({ children }) => {
|
|
const [socket, setSocket] = useState(null);
|
|
const [connected, setConnected] = useState(false);
|
|
const [recentDetections, setRecentDetections] = useState([]);
|
|
const [deviceStatus, setDeviceStatus] = useState({});
|
|
const [movementAlerts, setMovementAlerts] = useState([]);
|
|
const [droneTracking, setDroneTracking] = useState(new Map());
|
|
const { isAuthenticated } = useAuth();
|
|
|
|
useEffect(() => {
|
|
if (isAuthenticated) {
|
|
// Initialize socket connection
|
|
const newSocket = io(process.env.NODE_ENV === 'production'
|
|
? window.location.origin
|
|
: 'http://localhost:3001', {
|
|
path: process.env.NODE_ENV === 'production' ? '/drones/socket.io/' : '/socket.io/'
|
|
});
|
|
|
|
newSocket.on('connect', () => {
|
|
console.log('Connected to server');
|
|
setConnected(true);
|
|
|
|
// Join dashboard room for general updates
|
|
newSocket.emit('join_dashboard');
|
|
|
|
toast.success('Connected to real-time updates');
|
|
});
|
|
|
|
newSocket.on('disconnect', () => {
|
|
console.log('Disconnected from server');
|
|
setConnected(false);
|
|
toast.error('Disconnected from server');
|
|
});
|
|
|
|
newSocket.on('connect_error', (error) => {
|
|
console.error('Connection error:', error);
|
|
setConnected(false);
|
|
toast.error('Failed to connect to server');
|
|
});
|
|
|
|
// Listen for drone detections
|
|
newSocket.on('drone_detection', (detection) => {
|
|
console.log('New drone detection:', detection);
|
|
|
|
setRecentDetections(prev => [detection, ...prev.slice(0, 49)]); // Keep last 50
|
|
|
|
// Update drone tracking
|
|
if (detection.movement_analysis) {
|
|
const trackingKey = `${detection.drone_id}_${detection.device_id}`;
|
|
setDroneTracking(prev => {
|
|
const newTracking = new Map(prev);
|
|
newTracking.set(trackingKey, {
|
|
droneId: detection.drone_id,
|
|
deviceId: detection.device_id,
|
|
lastDetection: detection,
|
|
analysis: detection.movement_analysis,
|
|
timestamp: Date.now()
|
|
});
|
|
return newTracking;
|
|
});
|
|
}
|
|
|
|
// Show toast notification
|
|
toast.error(
|
|
`Drone ${detection.drone_id} detected by ${detection.device?.name || `Device ${detection.device_id}`}`,
|
|
{
|
|
duration: 5000,
|
|
icon: '🚨',
|
|
}
|
|
);
|
|
});
|
|
|
|
// Listen for drone movement alerts
|
|
newSocket.on('drone_movement_alert', (alertData) => {
|
|
console.log('Drone movement alert:', alertData);
|
|
|
|
setMovementAlerts(prev => [alertData, ...prev.slice(0, 19)]); // Keep last 20 alerts
|
|
|
|
// Show priority-based notifications
|
|
const alertIcon = alertData.analysis.alertLevel >= 3 ? '🚨' :
|
|
alertData.analysis.alertLevel >= 2 ? '⚠️' : '📍';
|
|
|
|
const alertColor = alertData.analysis.alertLevel >= 3 ? 'error' :
|
|
alertData.analysis.alertLevel >= 2 ? 'warning' : 'info';
|
|
|
|
toast[alertColor === 'error' ? 'error' : alertColor === 'warning' ? 'error' : 'info'](
|
|
alertData.analysis.description,
|
|
{
|
|
duration: alertData.analysis.alertLevel >= 2 ? 10000 : 6000,
|
|
icon: alertIcon,
|
|
style: {
|
|
background: alertData.analysis.alertLevel >= 3 ? '#fee2e2' :
|
|
alertData.analysis.alertLevel >= 2 ? '#fef3c7' : '#e0f2fe'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
// Listen for device heartbeats
|
|
newSocket.on('device_heartbeat', (heartbeat) => {
|
|
console.log('Device heartbeat:', heartbeat);
|
|
|
|
setDeviceStatus(prev => ({
|
|
...prev,
|
|
[heartbeat.device_id]: {
|
|
...heartbeat,
|
|
last_seen: new Date()
|
|
}
|
|
}));
|
|
});
|
|
|
|
// Listen for device updates
|
|
newSocket.on('device_updated', (device) => {
|
|
console.log('Device updated:', device);
|
|
toast.success(`Device ${device.name || device.id} updated`);
|
|
});
|
|
|
|
setSocket(newSocket);
|
|
|
|
return () => {
|
|
newSocket.disconnect();
|
|
};
|
|
} else {
|
|
// Disconnect if not authenticated
|
|
if (socket) {
|
|
socket.disconnect();
|
|
setSocket(null);
|
|
setConnected(false);
|
|
}
|
|
}
|
|
}, [isAuthenticated]);
|
|
|
|
const joinDeviceRoom = (deviceId) => {
|
|
if (socket) {
|
|
socket.emit('join_device_room', deviceId);
|
|
}
|
|
};
|
|
|
|
const leaveDeviceRoom = (deviceId) => {
|
|
if (socket) {
|
|
socket.emit('leave_device_room', deviceId);
|
|
}
|
|
};
|
|
|
|
const clearRecentDetections = () => {
|
|
setRecentDetections([]);
|
|
};
|
|
|
|
const clearMovementAlerts = () => {
|
|
setMovementAlerts([]);
|
|
};
|
|
|
|
const getDroneTracking = (droneId, deviceId) => {
|
|
const trackingKey = `${droneId}_${deviceId}`;
|
|
return droneTracking.get(trackingKey);
|
|
};
|
|
|
|
const value = {
|
|
socket,
|
|
connected,
|
|
recentDetections,
|
|
deviceStatus,
|
|
movementAlerts,
|
|
droneTracking,
|
|
joinDeviceRoom,
|
|
leaveDeviceRoom,
|
|
clearRecentDetections,
|
|
clearMovementAlerts,
|
|
getDroneTracking
|
|
};
|
|
|
|
return (
|
|
<SocketContext.Provider value={value}>
|
|
{children}
|
|
</SocketContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useSocket = () => {
|
|
const context = useContext(SocketContext);
|
|
if (!context) {
|
|
throw new Error('useSocket must be used within a SocketProvider');
|
|
}
|
|
return context;
|
|
};
|