node-red-dashboard icon indicating copy to clipboard operation
node-red-dashboard copied to clipboard

Add API to allow third party ui nodes to pass data between server and client code

Open colinl opened this issue 1 year ago • 9 comments

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

colinl avatar Jul 21 '24 09:07 colinl

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:

  1. As requested above by @colinl, send a message to a specified client (by _client) or to all clients which include this node
  2. 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 null in onInput?)
  3. 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).
  4. Send notifications upon client add/remove. This is available through ui-event/ui-control nodes, but our custom ui nodes need to be self-contained

omrid01 avatar Jul 21 '24 13:07 omrid01

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?

colinl avatar Jul 21 '24 13:07 colinl

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.

omrid01 avatar Jul 21 '24 13:07 omrid01

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

joepavitt avatar Jul 23 '24 15:07 joepavitt

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.

colinl avatar Jul 24 '24 09:07 colinl

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

joepavitt avatar Jul 24 '24 10:07 joepavitt

OK. Do I need to disconnect, as is done in the client code, or is that handled automatically in the server?

colinl avatar Jul 24 '24 11:07 colinl

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

joepavitt avatar Jul 24 '24 12:07 joepavitt

That all seems to be working, thanks.

colinl avatar Jul 24 '24 15:07 colinl

Hi @colinl, I think this issue can be closed, since pull request 1123 has been implemented?

bartbutenaers avatar Aug 22 '24 20:08 bartbutenaers

Yes.

colinl avatar Aug 22 '24 21:08 colinl