Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC3846: Allowing widgets to access TURN servers #3846

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions proposals/3846-widget-turn-servers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# MSC3846: Allowing widgets to access TURN servers

*The following is adapted from [MSC3819](https://github.com/matrix-org/matrix-spec-proposals/pull/3819).*

Widgets (embedded HTML applications in Matrix) currently have a relatively large surface area they can use for
interacting with their attached client, primarily in the context of a room. They can send/receive
[events with MSC2762](https://github.com/matrix-org/matrix-spec-proposals/pull/2762) and
[to-device events with MSC3819](https://github.com/matrix-org/matrix-spec-proposals/pull/3819), allowing them to
simulate an entire Matrix client in application code for specific, limited use cases.

This MSC forms part of a larger, ongoing, question about how to embed other Matrix clients into another client or room
for access. An increasingly more popular client development option is to build out an entirely new Matrix client and
want to embed that within another client (as a widget) to avoid the user needing to switch apps. To support this, we
need to consider both long term and short term impact of the changes we propose. This MSC aims closer to the short term.

A longer term solution to the problem of clients wanting to be embedded in other clients might still be widgets, though
with a system like [MSC3008](https://github.com/matrix-org/matrix-spec-proposals/pull/3008) to restrict access to the
client-server API more effectively. For this MSC's purpose though, we're aiming to cover a specific functionality of the
client-server API: providing access TURN servers.

While we could expose the entire client-server API over `postMessage` (or similar) for embedded clients to access, the
permissions model gets hairy and difficult to secure on the client side. Instead, we're exploring what it would look
like to special case what is needed for specific applications, as needed.

The specific goal of exposing TURN servers is to make it possible for widget-ized Matrix clients to implement
[MSC3401 - Native group VoIP](https://github.com/matrix-org/matrix-spec-proposals/pull/3401), when combined with the
abilities of [MSC2762](https://github.com/matrix-org/matrix-spec-proposals/pull/3819) and
[MSC3819](https://github.com/matrix-org/matrix-spec-proposals/pull/3819).

## Proposal

To set up calls with minimal latency and support the full-mesh group call mode of
[MSC3401](https://github.com/matrix-org/matrix-spec-proposals/pull/3401), widgets need to be able to continuously
receive updates about the latest valid TURN server credentials, rather than requesting them once at start-up or
on-demand. Otherwise, call setup with new participants may start failing midway through a call, or suffer the latency
penalty of making a widget API call for every new participant.

To this end, we introduce a new capability, `m.turn_servers`, used for accessing TURN server credentials. When approved,
it opens up access to the following actions:

**`fromWidget` action of `watch_turn_servers`**
robintown marked this conversation as resolved.
Show resolved Hide resolved

```json5
{
// This is a standardized widget API request.
"api": "fromWidget",
"widgetId": "20220712_WidgetExample",
"requestid": "generated-id-1234",
"action": "watch_turn_servers", // value defined by this proposal
"data": {} // nothing
}
```

Upon receipt, the client begins polling for TURN servers over the client-server API (if it wasn't already). It should
queue an `update_turn_servers` action as soon as possible, and then continue to update the widget with new TURN server
data whenever the previous data expires, using further `update_turn_servers` actions.

Both the `data` and `response` fields for this action are empty:

```json5
{
// This is a standardized widget API request.
"api": "fromWidget",
"widgetId": "20220712_WidgetExample",
"requestid": "generated-id-1234",
"action": "watch_turn_servers",
"data": {},
"response": {} // nothing
}
```

If the widget was already watching, this action has no effect. If the client is for whatever reason unable to start
polling (for example if TURN access for the account is 403'd), the client sends back an error response.

**`fromWidget` action of `unwatch_turn_servers`**

```json5
{
// This is a standardized widget API request.
"api": "fromWidget",
"widgetId": "20220712_WidgetExample",
"requestid": "generated-id-1234",
"action": "unwatch_turn_servers", // value defined by this proposal
"data": {} // nothing
}
```

This action tells the client to stop sending the widget TURN server updates. As with `watch_turn_servers`, the `data`
and `response` fields for this action are empty. If the widget was not already watching for TURN servers, the action has
no effect.

```json5
{
// This is a standardized widget API request.
"api": "fromWidget",
"widgetId": "20220712_WidgetExample",
"requestid": "generated-id-1234",
"action": "unwatch_turn_servers",
"data": {},
"response": {} // nothing
}
```

**`toWidget` action of `update_turn_servers`**

```json5
{
// This is a standardized widget API request.
"api": "toWidget",
"widgetId": "20220712_WidgetExample",
"requestid": "generated-id-1234",
"action": "update_turn_servers", // value defined by this proposal
"data": {
"uris": [
"turn:turn.example.com:3478?transport=udp",
"turn:10.20.30.40:3478?transport=tcp",
"turns:10.20.30.40:443?transport=tcp"
],
"username": "1443779631:@user:example.com",
"password": "JlKfBy1QwLrO20385QyAtEyIv0="
}
}
```

This action informs the widget of the current TURN server URIs and credentials provided by the homeserver. As shown, the
`data` for this action contains a list of TURN URIs, along with the username and password to use with them.

The widget acknowledges the request with an empty response object:

```json5
{
// This is a standardized widget API request.
"api": "toWidget",
"widgetId": "20220712_WidgetExample",
"requestid": "generated-id-1234",
"action": "update_turn_servers",
"data": {
"uris": [
"turn:turn.example.com:3478?transport=udp",
"turn:10.20.30.40:3478?transport=tcp",
"turns:10.20.30.40:443?transport=tcp"
],
"username": "1443779631:@user:example.com",
"password": "JlKfBy1QwLrO20385QyAtEyIv0="
},
"response": {} // nothing
}
```

## Potential issues

For simplicity, this proposal gives the widget no way to know *when* TURN server data will expire. It is instead
intended that the client will handle the TTL returned by the homeserver opaquely, for the sole purpose of refreshing the
TURN server data when it expires. It also gives no way to inform the widget when network errors or rate limits occur
during polling, leaving it up to the client to either wait for the error to be resolved on its own, or inform the widget
in the meantime that no TURN servers are available, by sending an empty `uris` list.

## Alternatives

A simpler approach would be to expose a single from-widget action to fetch the TURN data along with a TTL, just like
what would be returned by the `/_matrix/client/v3/voip/turnServer` endpoint. However, in practice this would not make
the implementation any simpler, but instead just shift the burden of refreshing the data and handling transient errors
onto to the widget. Clients wishing to support the `m.turn_servers` capability will likely already be doing VoIP in some
form, so by placing the burden on clients to handle the polling opaquely, we enable them to reuse their existing TURN
server logic.

## Security considerations

By granting a widget access to TURN credentials, the client is entrusting the widget with the same responsibility to not
misuse the TURN server that the homeserver is entrusting to the client. So, as with other widget capabilities, the
client must either prompt the user for permission, or take appropriate measures to verify that the capability can be
safely auto-approved, such as if the widget is running code from a specific, trusted domain.

## Unstable prefix

While this MSC is not yet included in the spec, implementations should use `town.robin.msc3846.turn_servers` in place of
the `m.turn_servers` capability identifier, and only call/support the actions if a widget API version of
`town.robin.msc3846` is advertised.

## Dependencies

None, though in practice, widgets should probably be formally included in the spec before this MSC gets included.