aboutsummaryrefslogtreecommitdiff
path: root/src/SocketController.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/SocketController.jsx')
-rw-r--r--src/SocketController.jsx140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/SocketController.jsx b/src/SocketController.jsx
new file mode 100644
index 00000000..fe39d2b7
--- /dev/null
+++ b/src/SocketController.jsx
@@ -0,0 +1,140 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { useDispatch, useSelector, connect } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { Snackbar } from '@mui/material';
+import { devicesActions, sessionActions } from './store';
+import { useEffectAsync } from './reactHelper';
+import { useTranslation } from './common/components/LocalizationProvider';
+import { snackBarDurationLongMs } from './common/util/duration';
+import alarm from './resources/alarm.mp3';
+import { eventsActions } from './store/events';
+import useFeatures from './common/util/useFeatures';
+import { useAttributePreference } from './common/util/preferences';
+
+const logoutCode = 4000;
+
+const SocketController = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const t = useTranslation();
+
+ const authenticated = useSelector((state) => !!state.session.user);
+ const devices = useSelector((state) => state.devices.items);
+ const includeLogs = useSelector((state) => state.session.includeLogs);
+
+ const socketRef = useRef();
+
+ const [events, setEvents] = useState([]);
+ const [notifications, setNotifications] = useState([]);
+
+ const soundEvents = useAttributePreference('soundEvents', '');
+ const soundAlarms = useAttributePreference('soundAlarms', 'sos');
+
+ const features = useFeatures();
+
+ const connectSocket = () => {
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const socket = new WebSocket(`${protocol}//${window.location.host}/api/socket`);
+ socketRef.current = socket;
+
+ socket.onopen = () => {
+ dispatch(sessionActions.updateSocket(true));
+ };
+
+ socket.onclose = async (event) => {
+ dispatch(sessionActions.updateSocket(false));
+ if (event.code !== logoutCode) {
+ try {
+ const devicesResponse = await fetch('/api/devices');
+ if (devicesResponse.ok) {
+ dispatch(devicesActions.update(await devicesResponse.json()));
+ }
+ const positionsResponse = await fetch('/api/positions');
+ if (positionsResponse.ok) {
+ dispatch(sessionActions.updatePositions(await positionsResponse.json()));
+ }
+ if (devicesResponse.status === 401 || positionsResponse.status === 401) {
+ navigate('/login');
+ }
+ } catch (error) {
+ // ignore errors
+ }
+ setTimeout(() => connectSocket(), 60000);
+ }
+ };
+
+ socket.onmessage = (event) => {
+ const data = JSON.parse(event.data);
+ if (data.devices) {
+ dispatch(devicesActions.update(data.devices));
+ }
+ if (data.positions) {
+ dispatch(sessionActions.updatePositions(data.positions));
+ }
+ if (data.events) {
+ if (!features.disableEvents) {
+ dispatch(eventsActions.add(data.events));
+ }
+ setEvents(data.events);
+ }
+ if (data.logs) {
+ dispatch(sessionActions.updateLogs(data.logs));
+ }
+ };
+ };
+
+ useEffect(() => {
+ socketRef.current?.send(JSON.stringify({ logs: includeLogs }));
+ }, [socketRef, includeLogs]);
+
+ useEffectAsync(async () => {
+ if (authenticated) {
+ const response = await fetch('/api/devices');
+ if (response.ok) {
+ dispatch(devicesActions.refresh(await response.json()));
+ } else {
+ throw Error(await response.text());
+ }
+ connectSocket();
+ return () => {
+ const socket = socketRef.current;
+ if (socket) {
+ socket.close(logoutCode);
+ }
+ };
+ }
+ return null;
+ }, [authenticated]);
+
+ useEffect(() => {
+ setNotifications(events.map((event) => ({
+ id: event.id,
+ message: event.attributes.message,
+ show: true,
+ })));
+ }, [events, devices, t]);
+
+ useEffect(() => {
+ events.forEach((event) => {
+ if (soundEvents.includes(event.type) || (event.type === 'alarm' && soundAlarms.includes(event.attributes.alarm))) {
+ new Audio(alarm).play();
+ }
+ });
+ }, [events, soundEvents, soundAlarms]);
+
+ return (
+ <>
+ {notifications.map((notification) => (
+ <Snackbar
+ key={notification.id}
+ open={notification.show}
+ message={notification.message}
+ autoHideDuration={snackBarDurationLongMs}
+ onClose={() => setEvents(events.filter((e) => e.id !== notification.id))}
+ />
+ ))}
+ </>
+ );
+};
+
+export default connect()(SocketController);