diff --git a/nodes/config/ui_base.js b/nodes/config/ui_base.js index f7573a283..d85f680cf 100644 --- a/nodes/config/ui_base.js +++ b/nodes/config/ui_base.js @@ -31,8 +31,8 @@ module.exports = function (RED) { statestore.setConfig(RED) /** - * @typedef {import('socket.io/dist').Socket} Socket - * @typedef {import('socket.io/dist').Server} Server + * @typedef {import('socket.io').Socket} Socket + * @typedef {import('socket.io').Server} Server */ // store state that can maintain cross re-deployments @@ -152,7 +152,7 @@ module.exports = function (RED) { const root = RED.settings.httpNodeRoot || '/' const fullPath = join(root, config.path) const socketIoPath = join('/', fullPath, 'socket.io') - /** @type {import('socket.io/dist').ServerOptions} */ + /** @type {import('socket.io').ServerOptions} */ const serverOptions = { path: socketIoPath, maxHttpBufferSize: uiShared.settings.maxHttpBufferSize || 1e6 // SocketIO default size diff --git a/ui/src/widgets/data-tracker.mjs b/ui/src/widgets/data-tracker.mjs index 96ff66f12..cc6669fa8 100644 --- a/ui/src/widgets/data-tracker.mjs +++ b/ui/src/widgets/data-tracker.mjs @@ -8,7 +8,9 @@ export function useDataTracker (widgetId, onInput, onLoad, onDynamicProperties) } const store = useStore() + /** @type {import('socket.io-client').Socket} */ const socket = inject('$socket') + let emitWidgetLoadOnConnect = false function checkDynamicProperties (msg) { // set standard dynamic properties states if passed into msg @@ -45,55 +47,85 @@ export function useDataTracker (widgetId, onInput, onLoad, onDynamicProperties) } } + function onWidgetLoad (msg, state) { + // automatic handle state/dynamic updates for ALL widgets + if (state) { + store.commit('ui/widgetState', { + widgetId, + config: state + }) + } + // then see if there is custom onLoad functionality to deal with the latest data payloads + if (onLoad) { + onLoad(msg) + } else { + if (msg) { + store.commit('data/bind', { + widgetId, + msg + }) + } + } + } + + function onMsgInput (msg) { + // check for common dynamic properties cross all widget types + checkDynamicProperties(msg) + + if (onInput) { + // sometimes we need to have different behaviour + onInput(msg) + } else { + // but most of the time, we just care about the value of msg + store.commit('data/bind', { + widgetId, + msg // TODO: we should sanitise what is stored in the store? + // One way to do this is to permit only keys explicitly listed in the widget's config (default to topic+payload if none are specified) + // A smarter? way to do this is to scan the template for msg.? binds and store only those keys + // For now, we'll just store the whole msg + }) + } + } + + function onDisconnect () { + // To get a disconnect, we must have previously been connected. + // Set flag to inform onConnect to emit widget-load + emitWidgetLoadOnConnect = true + } + + function onConnect () { + // when we unexpectedly disconnect, this is set to true + if (emitWidgetLoadOnConnect) { + emitWidgetLoadOnConnect = false + socket.emit('widget-load', widgetId) + } + } + + function removeAllListeners () { + emitWidgetLoadOnConnect = false + socket?.off('disconnect', onDisconnect) + socket?.off('msg-input:' + widgetId, onMsgInput) + socket?.off('widget-load:' + widgetId, onWidgetLoad) + socket?.off('connect', onConnect) + } + // a composable can also hook into its owner component's - // lifecycle to setup and teardown side effects. + // lifecycle to setup and tear-down side effects. onMounted(() => { if (socket && widgetId) { - socket.on('widget-load:' + widgetId, (msg, state) => { - // automatic handle state/dynamic updates for ALL widgets - if (state) { - store.commit('ui/widgetState', { - widgetId, - config: state - }) - } - // then see if there is custom onLoad functionality to deal with the latest data payloads - if (onLoad) { - onLoad(msg) - } else { - if (msg) { - store.commit('data/bind', { - widgetId, - msg - }) - } - } - }) - // This will on in msg input for ALL components - socket.on('msg-input:' + widgetId, (msg) => { - // check for common dynamic properties cross all widget types - checkDynamicProperties(msg) + removeAllListeners() + + socket.on('disconnect', onDisconnect) + socket.on('msg-input:' + widgetId, onMsgInput) + socket.on('widget-load:' + widgetId, onWidgetLoad) + socket.on('connect', onConnect) - if (onInput) { - // sometimes we need to have different behaviour - onInput(msg) - } else { - // but most of the time, we just care about the value of msg - store.commit('data/bind', { - widgetId, - msg // TODO: we should sanitise what is stored in the store? - // One way to do this is to permit only keys explicitly listed in the widget's config (default to topic+payload if none are specified) - // A smarter? way to do this is to scan the template for msg.? binds and store only those keys - // For now, we'll just store the whole msg - }) - } - }) // let Node-RED know that this widget has loaded // useful as Node-RED can return (via msg-input) any stored data socket.emit('widget-load', widgetId) } }) onUnmounted(() => { - socket?.off('msg-input:' + widgetId) + removeAllListeners() }) }