Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing data after reconnect #1343

Merged
merged 7 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions nodes/config/ui_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
112 changes: 72 additions & 40 deletions ui/src/widgets/data-tracker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
})
}