companion icon indicating copy to clipboard operation
companion copied to clipboard

[Feat] instance namespace for Companions socket.io

Open thedist opened this issue 3 years ago • 7 comments

Is this a feature relevant to companion itself, and not a module?

  • [X] I believe this to be a feature for companion, not a module

Is there an existing issue for this?

  • [X] I have searched the existing issues

Describe the feature

Currently Companion allows routing of HTTP requests to a modules instance, along these same lines I would like to add the ability for socket.io to create a namespace for each instance (with the same /instance/NAME), and then pass on messages to a connection on that namespace to the appropriate module. Much like the HTTP requests, this would not be providing access to the raw socket to modules, but rather just passing messages back and forth.

This would also help open the way in the future to provide an API for Companion that as transport agnostic, allowing for more options for 3rd party integration.

Usecases

I'm working on a web-based app that will integrate with some Companion modules, and while those modules could each individually run a HTTPS server, generate self-signed certs, and run socket.io on that connection, it wouldn't scale as well and would not be as an elegant solution as being able to use the existing socket.io server/certs from Companion itself.

thedist avatar Nov 11 '22 20:11 thedist

I am open to this, but I think some careful thought is needed on this. The main concern I have is that this means we will be tied to 4.x of socket.io or we will most likely break any integration/module/something that relies on this

I also wonder how many modules will benefit from this, in what scenario should modules be setting up a server for something to connect to? Typically they are initiating the connections

Julusian avatar Nov 11 '22 23:11 Julusian

in what scenario should modules be setting up a server for something to connect to? Typically they are initiating the connections

One immediate example that I'm working on is a web-based audio mixer for vMix. Because this will be served from HTTPS it will not be able to directly interact with vMix due to browsers security policies preventing insecure content. What it will be able to do though is get the data needed from a vMix instance in Companion as while self-signed certs aren't the cleanest solution it does allow for web apps on HTTPS to connect with Companion. Socket.io just enhances this by allowing better real-time communication instead of polling.

In the long term I also have a company that's interested in the idea of being able to power a web app for graphics to be used in browser sources from multiple different modules in Companion, and socket.io would allow for more responsive interactions.

thedist avatar Nov 11 '22 23:11 thedist

As for dealing with being tied to a specific version of Socket.io, this could be entirely obfuscated to modules as the socket itself wouldn't be provided to them.

For example, when each module initiates, Companion would create a /instance/NAME namespace on socket.io server. All connection management would be handled by Companion, as it already is, and any events other than the likes of ping would be passed through IPC to the module similarly to HTTP requests except there's no need for a response, just sending data to the module containing that it's an socket event (so is passed to the socket handler method in the module), the socket id, what the event name is, and what the payload is (if any). The module could have whatever logic it likes if it receives an event, and could send data back using a method on the instance object which is passed back through IPC to Companion itself which then emits it to one or more sockets in that namespace.

The use of namespace would segregate connections to only being able to interact with that specific instance, so a 3rd party app integrating with an instance wouldn't impact other instances of the same module, or the underlying Companion functionality (although at some point in the future there could even be a /api namespace to allow for controlled access of some Companion functions like the HTTP/OSC APIs).

thedist avatar Nov 12 '22 11:11 thedist

One immediate example that I'm working on is a web-based audio mixer for vMix. the idea of being able to power a web app for graphics to be used in browser sources from multiple different modules in Companion

Is this to serve some additional little bits of ui served by companion, or for a separate application to connect to?

For this to work we would need to provide a wrapper to setup a client with a simplified api, so that your audio mixer page doesnt need to know its using socketio, and we can mask any potential changes across versions. Or if we do switch from socket.io to a similar library, then we can provide the same api.

And even if we do say that this is for tools served from withing companion modules, someone will no doubt start using it with an external application. Maybe it isnt our fault/concern if those applications break at random points due to changes we made (which could have been made in a patch release), as long as we document this.

Julusian avatar Nov 13 '22 19:11 Julusian

Is this to serve some additional little bits of ui served by companion, or for a separate application to connect to?

This would be for separate applications to connect for. For my vMix Audio Mixer example you can see the sort of thing I'm working on in my tweet https://twitter.com/theDist/status/1581405058490519552.

In my vMix Audio Mixer, I could poll the existing HTTPS API every 100ms or I could potentially connect through Socket.io on on the companion URL,:PORT, with a path of /instance/vmix as my instance is called vmix. Then the vMix instance could emit a 'data' event to all sockets any time there is a change to the data, and any time I drag one of the faders on my mixer, or click a routing button, my web app could send a 'function' event through Socket.IO that'd be passed from Companion, to the vmix instance, and the instance would know how to take that event and format it to send to vMix itself.

In terms of other applications for graphical overlays, it could be used with a web app used in a browser source to connect over Socket.IO to connect to Companion and receive data from Google Sheets to generate lower thirds, or any video router app to listen to what's in Program to know when to transition in/out a graphic.. While these sort of web apps could connect directly to devices without proxying through Companion it'd mean reinventing the wheel, and some devices have a limited amount of connections so may not be able to be directly connected to both Companion and another 3rd party app.

This would mean it'd be a breaking change if Companion moved away from Socket.IO, as Socket.IO while using Websockets doesn't share compatibility (ie, a Socket.IO client has issues connecting to a raw WebSocket server). but between versions of Socket.IO it should not be an issue as namespaces are a core feature.

Even if it's not through Socket.IO, what I'm asking for is the capability to proxy a Websocket connection to a module inmspaces without the need for any additional coding to handle what connection goes to what instance that a raw Websocket connection would need.

In terms of enhancing Companion itself, this could allow for a Socket.IO connection for functionality such as pressing buttons, retrieving instance list and their status, retrieving logs, and things of that nature to create monitoring dashboards that are required in more high-end situations.

thedist avatar Nov 13 '22 20:11 thedist

I would really like to see a feature like this in the future as well. I'd have some ideas where a module interface for real-time communication to a web-interface would be very useful.

I would use it for additional/more advanced ui served through the httpRequestHandler. I would prefer a client api as a js module provided by companion that I could just import to a module-webpage and use familiar listeners like "connect", "disconnect", "message" and "error" as well as typical connect(), disconnect() and sendMessage(message) functions. The client api could figure out all the connection information on its own (parsing the url path to get details), which would require sameorigin.

On the companion module side I thought about a new sort of "webCommunicationHandler" that provides an object with the same listeners that'd additionally provide an idetifyer for each connection, as well as a sendMessage(message) and disconnect() function that can also take those identifyers to target a specific connection other than all connections together.

For my usecases limiting the client api script just to requests from the same origin would be fine and should prevent extensive use of external applications. I know that it'd still be possible but requires developing a custom client where companion no longer needs to prevent/document breaking (like for the GUI).

JaaHann avatar Sep 29 '24 11:09 JaaHann

@haakonnessjoen - heads up.

willosof avatar Sep 29 '24 14:09 willosof