Skip to content

Allow pushing WS messages directly to addons#3291

Closed
flatsiedatsie wants to merge 2 commits intoWebThingsIO:masterfrom
flatsiedatsie:snoop
Closed

Allow pushing WS messages directly to addons#3291
flatsiedatsie wants to merge 2 commits intoWebThingsIO:masterfrom
flatsiedatsie:snoop

Conversation

@flatsiedatsie
Copy link
Copy Markdown
Contributor

This is probably not how you would implement this, but I thought I'd share as a conversation starter.

This little experiment has been incredibly useful as a light-weight method for addons to subscribe to things. It avoids addons having to create a new websocket client to get things data that is already being received in the browser window.

The Photo Frame addon uses this to get event updates. Now users can use their phone as a remote control for the Photo Frame.

I don't know if this breaks a security model. I figured that any addon can already read the key from window.API. And if the addon is loaded, that means the user is logged in.

As someone who builds a lot of addons I love this. It replaces so much code and complexity around websocket management.

This avoids addons having to create a new websocket to get things data that is already being received in the browser window.
@flatsiedatsie
Copy link
Copy Markdown
Contributor Author

flatsiedatsie commented Apr 4, 2026

(The Candle controller also has a gateway variety, which is a bit simpler: https://github.com/flatsiedatsie/photo-frame/blob/7a53a382850cbd3fafa1cffeb259176c5a523c38/js/extension.js#L34)

@flatsiedatsie
Copy link
Copy Markdown
Contributor Author

Strange, I ran prettier.

@benfrancis
Copy link
Copy Markdown
Member

Could you maybe start by explaining the use case in the form of a user story in a new GitHub issue? What are you trying to achieve?

It seems like you maybe want client-side JavaScript in an extension add-on to be notified when some state of a Thing changes, and you're currently achieving this by forwarding all WebSocket message objects relating to that Thing to an array of callback functions registered with a global object on window (and yes, you correctly guessed this is not how I would implement that 😁).

This almost feels like a use case for the ConsumedThing interface of the WoT Scripting API (which WebThings Gateway does not currently implement), which has methods to register callbacks for observing properties and subscribing to events for example.

Hypothetically if we implemented the ConsumedThing interface as the primary API the gateway front end itself uses for consuming web things from the gateway back end, a global collection of ConsumedThing objects could be exposed to extension add-ons so that they can register their own callbacks, e.g. via Thing.observeProperty() and Thing.subscribeEvent() methods without having to open their own HTTP or WebSocket connection. Add-ons would also be able to manipulate Things with methods like writeProperty() and invokeAction().

I would be open to exploring that as an implementation approach when we transition away from the legacy Web Thing API to the upcoming standardised W3C WoT Thing Protocol.

@flatsiedatsie
Copy link
Copy Markdown
Contributor Author

  • Yes you understood correctly what I tried to do
  • What you describe around consumedThing sounds great. Would there be an Adapter.observeThing() too? Or even a Gateway.observeAddon()?

Pragmatically though, would you be open to accepting this 'hack', or something that is more palletable to you, in the short term? The reason is that I would like to keep it so that my addons also work on the Webthings Gateway. If my addons start using this "shortcut", and the Gateway doesn't support it, then that would create incompatibility.

  • Can you envision something to this effect that I/we could implement in the short term? "No" is a perfectly acceptable and expected answer here :-)
  • Is there anything you would recommend I do regardless to improve this shortcut? For example, I'm not thrilled about mounting it to the window object either.

About adding it to the window object: one of my thoughts was that it could make more sense to make this faux observeThing a method of the frond-end addon management code. Perhaps in views/adapter.js? But I wouldn't know how to route the data there. I am, as always, learning as I go.

.. or I could just keep creating Websocket clients for each addon. Would you say the overhead/duplication from paralel clients is not really an issue? It somewhat sounds like you agree that creating a way to observe from a single front-end "source of truth" is indeed preferable?

@flatsiedatsie
Copy link
Copy Markdown
Contributor Author

(Another readon why I explored this: the front-end is starting to consume quite a bit of memory if multiple addons with extensions are enabled. In some cases I've started implementing unloading HTML when an addon is navigated away from, for example, in the hopes of recovering some memory).

@benfrancis
Copy link
Copy Markdown
Member

@flatsiedatsie wrote:

Would there be an Adapter.observeThing() too? Or even a Gateway.observeAddon()?

If we were implementing the WoT Scripting API, then no. But we could potentially add Thing.observeAllProperties() and Thing.subscribeAllEvents() methods which are already being discussed for the specification. I personally think that's better because front end extension add-ons shouldn't need to care what back end adapter add-on a Thing is exposed by, they operate at a higher level of abstraction. We could potentially implement a non-standard Gateway.observeAllPropertiesOfAllThings() but I'm not sure such a firehose is a good idea.

Note that the Web Thing Protocol WebSocket Sub-protocol community draft (upon which the WoT Thing Protocol will be based) is a lot more sophisticated than our current Web Thing WebSocket API and involves explicitly observing and unobserving properties rather than just being notified about all property changes on a Thing by default, and the WoT Scripting API is a natural fit on top of that.

Pragmatically though, would you be open to accepting this 'hack', or something that is more palletable to you, in the short term? The reason is that I would like to keep it so that my addons also work on the Webthings Gateway. If my addons start using this "shortcut", and the Gateway doesn't support it, then that would create incompatibility.

If you want a shorter term solution then our current equivalent of ConsumedThing is ThingModel, which has a subscribe() method you can use to register a callback for a particular "event". Have you tried using ThingModel.subscribe(Constants.PROPERTY_STATUS) to listen to property changes on a Thing?

The Web Thing API (REST & WebSockets) is currently the most stable API between the front end and back end, but if you want a client-side JavaScript API then ThingModel is how the gateway current abstracts that API on the client-side. It's intended as an internal API rather than for extensions to Consume, but using that is probably better than a hack which forwards WebSocket messages around the front end.

I could just keep creating Websocket clients for each addon. Would you say the overhead/duplication from paralel clients is not really an issue?

This is currently the intended way to do it, because that's the stable API between the client and server. WebSocket connections are surprisingly lightweight, with each connection only consuming about 10-50KB of RAM so unless you're planning on creating more than about 100,000 connections to a Raspberry Pi all sending data at the same time I wouldn't worry about it.

It somewhat sounds like you agree that creating a way to observe from a single front-end "source of truth" is indeed preferable?

Yes, I think that's a good idea, and it kind of already exists it's just currently intended as an internal API rather than a stable API for extensions to consume. Implementing the WoT Scripting API could potentially be a way to formalise/stabilise that API in order to prevent breaking add-ons whenever we change the ThingModel.

Another readon why I explored this: the front-end is starting to consume quite a bit of memory if multiple addons with extensions are enabled.

FWIW I doubt that the memory consumption issues are cause by lots of open WebSocket connections, it's more likely to be caused by memory leaks. It's not something I've paid a lot of attention to recently to be honest, but browser developer tools could help identify the cause.

--

In the short term I would recommend that your extensions either open WebSocket connections or try to use ThingModel on the client side. Usually the WebSocket API is the more stable of the two, but to be fair I'm expecting that to change eventually as well as we replace the Web Thing WebSocket API with the WoT Thing Protocol, possibly in gateway version 3.0.

@benfrancis
Copy link
Copy Markdown
Member

I'm going to close this PR because I don't think this particular solution is quite the right approach, but let me know how you get on if you try to use ThingModel in your extension, and I've filed #3292 to track a longer term solution to a stable client-side API for extension add-ons to consume.

@benfrancis benfrancis closed this Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants