matrix-doc/proposals/3009-widgets-ws-transport.md

6.2 KiB

MSC3009: Websocket transport for client <--> widget communications

If a widget wishes to speak with a client (or a client with a widget) then the implementation must support postmessage as a capability, however not all scenarios for widgets make this possible. For example, mobile apps and thinly wrapped widgets may not have enough JavaScript available to handle the postmessage API.

Note: A thinly wrapped widget is one which is in a theoretical web view which disconnects the widget from the client, for clients which cannot reasonably support a webview or similar. This is primarly a solution for desktop clients, which puts the actual client and widget in different processes (the desktop app running on its own with the widget running in a web browser after being 'popped out').

Proposal

A websocket transport is defined in addition to the existing postmessage API support. This alternative transport is opt-in by the client for the widget to handle. The API over postmessage is quite simply the same as the postmessage API but with the added implicit behaviour that waitForIframeLoad is false (this causes the client to wait for the widget to reach out to the client instead of the client assuming the widget has loaded).

Because there would be two transports available, clients need to know which ones are available on the widget, and the widget should not be required to implement them all. This proposal opens up an opportunity for custom transports through a namespaced array of transports being listed on the standardized widget definition:

{
  "type": "m.custom",
  "url": "http://localhost:8082#?widgetId=$matrix_widget_id&transport=$matrix_widget_transport",
  "name": "Widget Debugger",
  "avatar_url": "mxc://t2bot.io/c977fc5396241194e426e6eb9da64f025f813f1b",
  "data": {
    "title": "Widget testing"
  },
  "creatorUserId": "@travis:localhost",
  "id": "debugger_test",

  // This is the only added field.
  "transports": [
    "m.postmessage",
    "m.websockets"
  ]
}

If the transports array is empty, undefined, or the wrong data type then the client will assume that the widget definition meant ["m.postmessage"] for a value. If the array contains no known transports to communicate with the widget, the client will assume the widget supports m.postmessage as a baseline. This means m.postmessage remains the bare minimum every widget must support.

Prior to rendering the widget, the client picks the transport and spins up any applicable servers (websocket receivers, etc) for the widget to communicate with. The client communicates the transport it chose with an available $matrix_widget_transport template variable.

When the m.websockets transport is chosen, a $matrix_websocket_uri template variable is made available by the client containing the ws:// URI for the widget to connect to. Because the widget is expected to make first contact, the client should already be listening and not try to connect to the widget.

Rationale for websockets

Websockets are bidirectional and about as easy as HTTP to set up and deploy in modern applications. Most use cases will see local clients spinning up servers and therefore the complexities of corporate firewalls should not apply.

However, using websockets means that the widget cannot be contacted and is assumed to reach out to the client first. This could potentially leave the client in an unknown state where it is waiting for the widget to reach out, but the widget died or refuses to do so. Reconnection attempts by the widget may also be confusing to the client (client restarts but widget doesn't, or local network issues) - this has undefined behaviour under this MSC currently.

Potential issues

Web clients (and widgets) will not be able to create websocket servers to listen for requests from widgets, making this MSC targeted specifically at desktop/mobile clients which are typically more able to do so. Web clients in particular are likely to need their own alternative transport for cases when the widget is popped out of the client's context, such as in a new tab.

This MSC also introduced capabilities for arbitrary alternative transports. This isn't necessarily seen as a bad thing, though could lead to fragmentation among the widget authoring community. This MSC attempts to mitigate that fragmentation by maintaining a baseline transport.

There is also a question if this is even needed: if MSC3008 - Scoped access were to be accepted, the only remaining functionality would be the capabilities API (for which few capabilities are useful in this use case) and other smaller client manipulation MSCs. Given the widget is separated from the client, it may not be desirable to support MSC2931 - matrix.to navigation over this alternative transport, for example. Similarly, there is nothing to pin to the screen and therefore the widget's attempts to request an "always on screen" capability would be for naught.

Alternatives

Regular HTTP might also work, as would Server-Sent Events, though these are inherently one-way or require excessive resources to make two-way. Other efficient transports are not easily available on all platforms, such as lower level TCP/UDP-based transports.

Security considerations

There is a high likelihood that when a client spins up a websocket server that it'll be bound to localhost without TLS. This could be problematic if widgets are transferring sensitive information over the API which could be intercepted by local processes. This is akin to a malicious browser extension listening for postmessage requests on *, however.

Unstable prefix

The transports array becomes org.matrix.msc3009.transports while this MSC is not considered stable. The template variables $matrix_widget_transport and $matrix_websocket_uri become $org.matrix.msc3009.widget_transport and $org.matrix.msc3009.websocket_uri respectively.

The transports become org.matrix.msc3009.postmessage and org.matrix.msc3009.websockets. Communication over either transport is not required to be namespaced in any particular way, however any unstable actions over the widget API must be appropriately prefixed.