Add API to allow third party ui nodes to pass data between server and client code
Description
Please provide methods for third party ui nodes that:
- Allow code in the node's server .js file to send a message (subject to msg._client) to the node's attached clients.
- Allow code in the node's client implementation to send a message to the server code so that, for example, the state store can be updated without sending a message to attached nodes, and without directly updating the datastore.
See this forum post for background information.
Have you provided an initial effort estimate for this issue?
I am no FlowFuse team member
I believe sending messages from the client to the server node is supported via custom events (just need to fix the reconnection bug #913 )
Let me propose some further requirements to the server node API, to make it complete:
- As requested above by @colinl, send a message to a specified client (by
_client) or to all clients which include this node - Be able to drop an incoming msg on the server node, before it is broadcasted to the clients (can this be done today by setting the msg to
nullinonInput?) - Expose a live list of all connected clients (which contain this node), with their respective Ids. I assume such list is maintained in the framework, so just need to expose it to the server node (e.g., like the Datastore API).
- Send notifications upon client add/remove. This is available through
ui-event/ui-controlnodes, but our custom ui nodes need to be self-contained
3. Expose a live list of all connected clients (which contain this node),
Clients which contain the node, or just those showing a page with the node active? Out of interest, what do you want to use that list for?
Clients which contain the node, or just those showing a page with the node active?
With the node active.
Out of interest, what do you want to use that list for?
In general, that is good info for the server node to have.
Specifically for my ui-tabulator node, the tabulator package is instantiated and processing in the client. When in Shared (as opposed to Multi-user) mode, when I send a msg to the table node, I receive multiple identical replies (one per client), and need to collapse them by msg Id, which is very messy (or not possible in case of events).
If I had a client list, I could select any client as "spokesman" and ignore responses/events from others.
Allow code in the node's server .js file to send a message (subject to msg._client) to the node's attached clients.
base.emit(event, msg, node) will do the trick. You just need to get reference to the UI Base in your code. If your node bind to a group via a config option, you can do group.getBase(), otherwise a RED.getConfigNode will be required.
It's not a well documented method, but it's used heavily in ui-control if you need examples
Allow code in the node's client implementation to send a message to the server code so that, for example, the state store can be updated without sending a message to attached nodes, and without directly updating the datastore.
As @omrid01 pointed out - the correct approach here is a custom socket handlers
base.emit('my-event:' + node.id, msg, node) in the server, along with
this.$socket.on('my-event:' + this.id, (msg) => {...} in the client, works to send a message to the client, thanks.
When using a custom handler as you suggest for sending data back to the server, if in the client I use
this.$socket.emit('my-custom-event', this.id, data)
and in the server
onSocket: {
'my-custom-event': function (conn, id, msg) { ...}
}
then again that does work. However, if I have multiple nodes then all server instances receive the message from any node in the clients. Is there a way of including the node id in the event name as is done for sending to the clients? I can't use
'my-custom-event' + msg.id: function (conn, id, msg) { ...}
as that is not valid javascript.
I see that I can test the passed in id and compare it with node.id if that is the only way.
Yeah, two options as you've detailed:
Option 1: handler per node instance
const events = {}
events['my-custom-event' + node.id] = function (conn, id, msg) { ...}
onSocket = events
Option 2: Check in the single handler
onSocket: {
'my-custom-event': function (conn, id, msg) {
if (id === node.id) {
// do stuff
}
}
}
Generally SocketIO doesn't like a significant overload of handlers, so I've generally stuck with Option 2
OK. Do I need to disconnect, as is done in the client code, or is that handled automatically in the server?
Do I need to disconnect, as is done in the client code, or is that handled automatically in the server?
All handled server-side, automatically. ui-base checks for any open handlers when you delete your last instance of your node and stops the listeners
That all seems to be working, thanks.
Hi @colinl, I think this issue can be closed, since pull request 1123 has been implemented?
Yes.