2.6.23: websocket memory leak fix, fixes for device page refresh on websocket notification

This commit is contained in:
Charles
2022-05-05 20:34:58 +01:00
parent d2fd895582
commit f008fd082e
10 changed files with 48 additions and 56 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "2.6.20", "version": "2.6.23",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ucentral-client", "name": "ucentral-client",
"version": "2.6.20", "version": "2.6.23",
"dependencies": { "dependencies": {
"@coreui/coreui": "^3.4.0", "@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1", "@coreui/icons": "^2.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "2.6.20", "version": "2.6.23",
"dependencies": { "dependencies": {
"@coreui/coreui": "^3.4.0", "@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1", "@coreui/icons": "^2.0.1",

View File

@@ -18,6 +18,7 @@ const DeviceList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { addToast } = useToast(); const { addToast } = useToast();
const history = useHistory(); const history = useHistory();
const [overrides, setOverrides] = useState({});
const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10)); const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10));
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const [upgradeStatus, setUpgradeStatus] = useState({ const [upgradeStatus, setUpgradeStatus] = useState({
@@ -58,6 +59,7 @@ const DeviceList = () => {
const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => { const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
setLoading(true); setLoading(true);
setOverrides({});
const options = { const options = {
headers: { headers: {
@@ -357,6 +359,15 @@ const DeviceList = () => {
}); });
}; };
const displayDevices = () =>
devices.map((device) => ({
...device,
connected:
overrides[device.serialNumber] !== undefined
? overrides[device.serialNumber]
: device.connected,
}));
useEffect(() => { useEffect(() => {
getCount(); getCount();
}, []); }, []);
@@ -364,18 +375,11 @@ const DeviceList = () => {
useEffect(() => { useEffect(() => {
if (lastMessage && lastMessage.type === 'DEVICE') { if (lastMessage && lastMessage.type === 'DEVICE') {
const { serialNumber: msgSerial, isConnected } = lastMessage; const { serialNumber: msgSerial, isConnected } = lastMessage;
if (devices.find(({ serialNumber }) => serialNumber === msgSerial)) { if (overrides[msgSerial] === undefined || overrides[msgSerial] !== isConnected) {
const newDevices = devices.map((device) => { setOverrides({ ...overrides, [msgSerial]: isConnected });
if (device.serialNumber !== msgSerial) return device;
return {
...device,
connected: isConnected,
};
});
setDevices(newDevices);
} }
} }
}, [lastMessage, devices]); }, [lastMessage, overrides]);
useEffect(() => { useEffect(() => {
if (upgradeStatus.result !== undefined) { if (upgradeStatus.result !== undefined) {
@@ -400,7 +404,7 @@ const DeviceList = () => {
currentPage={page} currentPage={page}
t={t} t={t}
searchBar={<DeviceSearchBar />} searchBar={<DeviceSearchBar />}
devices={devices} devices={displayDevices()}
loading={loading} loading={loading}
updateDevicesPerPage={updateDevicesPerPage} updateDevicesPerPage={updateDevicesPerPage}
devicesPerPage={devicesPerPage} devicesPerPage={devicesPerPage}

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs'; import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs';
import { checkIfJson } from 'utils/helper'; import { toJson } from 'utils/helper';
const DeviceSearchBar = ({ action }) => { const DeviceSearchBar = ({ action }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -44,11 +44,9 @@ const DeviceSearchBar = ({ action }) => {
}; };
socket.onmessage = (event) => { socket.onmessage = (event) => {
if (checkIfJson(event.data)) { const result = toJson(event.data);
const result = JSON.parse(event.data); if (result && result.serialNumbers) {
if (result.serialNumbers) { setResults(result.serialNumbers);
setResults(result.serialNumbers);
}
} }
}; };

View File

@@ -87,12 +87,6 @@ const ActionModal = ({ show, toggleModal }) => {
autohide: true, autohide: true,
}), }),
}); });
addToast({
title: t('common.success'),
body: t('commands.reboot_start'),
color: 'success',
autohide: true,
});
toggleModal(); toggleModal();
}) })
.catch(() => { .catch(() => {

View File

@@ -8,7 +8,6 @@ import { extractWebSocketResponse } from './utils';
const WebSocketContext = React.createContext({ const WebSocketContext = React.createContext({
webSocket: undefined, webSocket: undefined,
isOpen: false, isOpen: false,
allMessages: [],
addDeviceListener: () => {}, addDeviceListener: () => {},
}); });
@@ -16,7 +15,7 @@ export const WebSocketProvider = ({ children }) => {
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const ws = useRef(undefined); const ws = useRef(undefined);
const { allMessages, lastMessage, dispatch } = useSocketReducer(); const { lastMessage, dispatch } = useSocketReducer();
const { pushNotification } = useWebSocketNotification(); const { pushNotification } = useWebSocketNotification();
const onMessage = useCallback((message) => { const onMessage = useCallback((message) => {
@@ -68,7 +67,6 @@ export const WebSocketProvider = ({ children }) => {
}, [ws?.current]); }, [ws?.current]);
const values = useMemo( const values = useMemo(
() => ({ () => ({
allMessages,
lastMessage, lastMessage,
webSocket: ws.current, webSocket: ws.current,
addDeviceListener: ({ serialNumber, types, addToast, onTrigger }) => addDeviceListener: ({ serialNumber, types, addToast, onTrigger }) =>
@@ -77,7 +75,7 @@ export const WebSocketProvider = ({ children }) => {
dispatch({ type: 'REMOVE_DEVICE_LISTENER', serialNumber }), dispatch({ type: 'REMOVE_DEVICE_LISTENER', serialNumber }),
isOpen, isOpen,
}), }),
[ws, isOpen, allMessages, lastMessage], [ws, isOpen, lastMessage],
); );
return <WebSocketContext.Provider value={values}>{children}</WebSocketContext.Provider>; return <WebSocketContext.Provider value={values}>{children}</WebSocketContext.Provider>;

View File

@@ -15,7 +15,7 @@ const reducer = (state, action) => {
switch (action.type) { switch (action.type) {
case 'NEW_NOTIFICATION': { case 'NEW_NOTIFICATION': {
const obj = { type: 'NOTIFICATION', data: action.notification, timestamp: new Date() }; const obj = { type: 'NOTIFICATION', data: action.notification, timestamp: new Date() };
return { allMessages: [...state.allMessages, obj], lastMessage: obj }; return { ...state, lastMessage: obj };
} }
case 'NEW_COMMAND': { case 'NEW_COMMAND': {
const obj = { const obj = {
@@ -24,7 +24,7 @@ const reducer = (state, action) => {
timestamp: new Date(), timestamp: new Date(),
id: action.data.command_response_id, id: action.data.command_response_id,
}; };
return { allMessages: [...state.allMessages, obj], lastMessage: obj }; return { ...state, lastMessage: obj };
} }
case 'NEW_DEVICE_NOTIFICATION': { case 'NEW_DEVICE_NOTIFICATION': {
const newListeners = state.deviceListeners; const newListeners = state.deviceListeners;
@@ -59,11 +59,7 @@ const reducer = (state, action) => {
} }
} }
return { return { ...state, lastMessage: obj ?? state.lastMessage, deviceListeners: newListeners };
allMessages: state.allMessages,
lastMessage: obj ?? state.lastMessage,
deviceListeners: newListeners,
};
} }
case 'ADD_DEVICE_LISTENER': { case 'ADD_DEVICE_LISTENER': {
let newListeners = action.types.map((actionType) => ({ let newListeners = action.types.map((actionType) => ({
@@ -73,29 +69,18 @@ const reducer = (state, action) => {
onTrigger: action.onTrigger, onTrigger: action.onTrigger,
})); }));
newListeners = newListeners.concat(state.deviceListeners); newListeners = newListeners.concat(state.deviceListeners);
return { return { ...state, lastMessage: state.lastMessage, deviceListeners: newListeners };
allMessages: state.allMessages,
lastMessage: state.lastMessage,
deviceListeners: newListeners,
};
} }
case 'REMOVE_DEVICE_LISTENER': { case 'REMOVE_DEVICE_LISTENER': {
const newListeners = state.deviceListeners.filter( const newListeners = state.deviceListeners.filter(
(listener) => (listener) =>
listener.serialNumber !== action.serialNumber || listener.onTrigger === undefined, listener.serialNumber !== action.serialNumber || listener.onTrigger === undefined,
); );
return { return { ...state, lastMessage: state.lastMessage, deviceListeners: newListeners };
allMessages: state.allMessages,
lastMessage: state.lastMessage,
deviceListeners: newListeners,
};
} }
case 'UNKNOWN': { case 'UNKNOWN': {
const obj = { type: 'UNKNOWN', data: action.newMessage, timestamp: new Date() }; const obj = { type: 'UNKNOWN', data: action.newMessage, timestamp: new Date() };
return { return { ...state, lastMessage: obj };
allMessages: [...state.allMessages, obj],
lastMessage: obj,
};
} }
default: default:
throw new Error(); throw new Error();
@@ -103,12 +88,11 @@ const reducer = (state, action) => {
}; };
const useSocketReducer = () => { const useSocketReducer = () => {
const [{ allMessages, lastMessage, deviceListeners }, dispatch] = useReducer(reducer, { const [{ lastMessage, deviceListeners }, dispatch] = useReducer(reducer, {
allMessages: [],
deviceListeners: [], deviceListeners: [],
}); });
return { allMessages, lastMessage, deviceListeners, dispatch }; return { lastMessage, deviceListeners, dispatch };
}; };
export default useSocketReducer; export default useSocketReducer;

View File

@@ -6,6 +6,7 @@ export const deviceNotificationTypes = [
'device_connection', 'device_connection',
'device_disconnection', 'device_disconnection',
'device_firmware_upgrade', 'device_firmware_upgrade',
'device_statistics',
]; ];
export const extractWebSocketResponse = (message) => { export const extractWebSocketResponse = (message) => {

View File

@@ -119,8 +119,13 @@ const DevicePage = () => {
if (deviceId) { if (deviceId) {
addDeviceListener({ addDeviceListener({
serialNumber: deviceId, serialNumber: deviceId,
types: ['device_connection', 'device_disconnection', 'device_firmware_upgrade'], types: [
onTrigger: () => getData(), 'device_connection',
'device_disconnection',
'device_firmware_upgrade',
'device_statistics',
],
onTrigger: () => refresh(),
}); });
getDevice(); getDevice();
getData(); getData();

View File

@@ -59,6 +59,14 @@ export const checkIfJson = (string) => {
return true; return true;
}; };
export const toJson = (string) => {
try {
return JSON.parse(string);
} catch (e) {
return undefined;
}
};
export const secondsToDetailed = ( export const secondsToDetailed = (
seconds, seconds,
dayLabel, dayLabel,