Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions changelog.d/18967.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add experimental implementation for the latest draft of [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143).
22 changes: 22 additions & 0 deletions docs/usage/configuration/config_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2575,6 +2575,28 @@ Example configuration:
turn_allow_guests: false
```
---
### `matrix_rtc`

*(object)* Options related to MatrixRTC. Defaults to `{}`.

This setting has the following sub-options:

* `transports` (array): A list of transport types and arguments to use for MatrixRTC connections. Defaults to `[]`.

Options for each entry include:

* `type` (string): The type of transport to use to connect to the selective forwarding unit (SFU).

* `livekit_service_url` (string): The base URL of the LiveKit service. Should only be used with LiveKit-based transports.

Example configuration:
```yaml
matrix_rtc:
transports:
- type: livekit
livekit_service_url: https://matrix-rtc.example.com/livekit/jwt
```
---
## Registration

Registration can be rate-limited using the parameters in the [Ratelimiting](#ratelimiting) section of this manual.
Expand Down
29 changes: 29 additions & 0 deletions schema/synapse-config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2886,6 +2886,35 @@ properties:
default: true
examples:
- false
matrix_rtc:
type: object
description: >-
Options related to MatrixRTC.
properties:
transports:
type: array
items:
type: object
required:
- type
properties:
type:
type: string
description: The type of transport to use to connect to the selective forwarding unit (SFU).
example: livekit
livekit_service_url:
type: string
description: >-
The base URL of the LiveKit service. Should only be used with LiveKit-based transports.
example: https://matrix-rtc.example.com/livekit/jwt
description:
A list of transport types and arguments to use for MatrixRTC connections.
default: []
default: {}
examples:
- transports:
- type: livekit
livekit_service_url: https://matrix-rtc.example.com/livekit/jwt
enable_registration:
type: boolean
description: >-
Expand Down
2 changes: 2 additions & 0 deletions synapse/config/_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ from synapse.config import ( # noqa: F401
key,
logger,
mas,
matrixrtc,
metrics,
modules,
oembed,
Expand Down Expand Up @@ -126,6 +127,7 @@ class RootConfig:
auto_accept_invites: auto_accept_invites.AutoAcceptInvitesConfig
user_types: user_types.UserTypesConfig
mas: mas.MasConfig
matrix_rtc: matrixrtc.MatrixRtcConfig

config_classes: List[Type["Config"]] = ...
config_files: List[str]
Expand Down
3 changes: 3 additions & 0 deletions synapse/config/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,9 @@ def read_config(
# MSC4133: Custom profile fields
self.msc4133_enabled: bool = experimental.get("msc4133_enabled", False)

# MSC4143: Matrix RTC Transport using Livekit Backend
self.msc4143_enabled: bool = experimental.get("msc4143_enabled", False)

# MSC4169: Backwards-compatible redaction sending using `/send`
self.msc4169_enabled: bool = experimental.get("msc4169_enabled", False)

Expand Down
2 changes: 2 additions & 0 deletions synapse/config/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .key import KeyConfig
from .logger import LoggingConfig
from .mas import MasConfig
from .matrixrtc import MatrixRtcConfig
from .metrics import MetricsConfig
from .modules import ModulesConfig
from .oembed import OembedConfig
Expand Down Expand Up @@ -80,6 +81,7 @@ class HomeServerConfig(RootConfig):
OembedConfig,
CaptchaConfig,
VoipConfig,
MatrixRtcConfig,
RegistrationConfig,
AccountValidityConfig,
MetricsConfig,
Expand Down
67 changes: 67 additions & 0 deletions synapse/config/matrixrtc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2025 New Vector, Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
# [This file includes modifications made by New Vector Limited]
#
#

from typing import Any, Optional

from pydantic import ValidationError

from synapse._pydantic_compat import Field, StrictStr, validator
from synapse.types import JsonDict
from synapse.util.pydantic_models import ParseModel

from ._base import Config, ConfigError


class TransportConfigModel(ParseModel):
type: StrictStr

livekit_service_url: Optional[StrictStr] = Field(default=None)
"""An optional livekit service URL. Only required if type is "livekit"."""

@validator("livekit_service_url", always=True)
def validate_livekit_service_url(cls, v: Any, values: dict) -> Any:
if values.get("type") == "livekit" and not v:
raise ValueError(
"You must set a `livekit_service_url` when using the 'livekit' transport."
)

return v


class MatrixRtcConfigModel(ParseModel):
transports: list = []


class MatrixRtcConfig(Config):
section = "matrix_rtc"

def read_config(
self, config: JsonDict, allow_secrets_in_config: bool, **kwargs: Any
) -> None:
matrix_rtc = config.get("matrix_rtc", {})
if matrix_rtc is None:
matrix_rtc = {}

try:
parsed = MatrixRtcConfigModel(**matrix_rtc)
except ValidationError as e:
raise ConfigError(
"Could not validate matrix_rtc config",
("matrix_rtc",),
) from e

self.transports = parsed.transports
2 changes: 2 additions & 0 deletions synapse/rest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
login,
login_token_request,
logout,
matrixrtc,
mutual_rooms,
notifications,
openid,
Expand Down Expand Up @@ -89,6 +90,7 @@
presence.register_servlets,
directory.register_servlets,
voip.register_servlets,
matrixrtc.register_servlets,
pusher.register_servlets,
push_rule.register_servlets,
logout.register_servlets,
Expand Down
52 changes: 52 additions & 0 deletions synapse/rest/client/matrixrtc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2025 New Vector, Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
# [This file includes modifications made by New Vector Limited]
#
#

from typing import TYPE_CHECKING, Tuple

from synapse.http.server import HttpServer
from synapse.http.servlet import RestServlet
from synapse.http.site import SynapseRequest
from synapse.rest.client._base import client_patterns
from synapse.types import JsonDict

if TYPE_CHECKING:
from synapse.server import HomeServer


class MatrixRTCRestServlet(RestServlet):
PATTERNS = client_patterns(r"/org\.matrix\.msc4143/rtc/transports$", releases=())
CATEGORY = "Client API requests"

def __init__(self, hs: "HomeServer"):
super().__init__()
self._hs = hs
self._auth = hs.get_auth()
self._transports = hs.config.matrix_rtc.transports

async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
# Require authentication for this endpoint.
await self._auth.get_user_by_req(request)

if self._transports:
return 200, {"rtc_transports": self._transports}

return 200, {}


def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
if hs.config.experimental.msc4143_enabled:
MatrixRTCRestServlet(hs).register(http_server)
105 changes: 105 additions & 0 deletions tests/rest/client/test_matrixrtc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2025 New Vector, Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
# [This file includes modifications made by New Vector Limited]
#
#

"""Tests REST events for /rtc/endpoints path."""

from twisted.internet.testing import MemoryReactor

from synapse.rest import admin
from synapse.rest.client import login, matrixrtc, register, room
from synapse.server import HomeServer
from synapse.util.clock import Clock

from tests.unittest import HomeserverTestCase, override_config

PATH_PREFIX = "/_matrix/client/unstable/org.matrix.msc4143"
RTC_ENDPOINT = {"type": "focusA", "required_field": "theField"}
LIVEKIT_ENDPOINT = {
"type": "livekit",
"livekit_service_url": "https://livekit.example.com",
}


class MatrixRtcTestCase(HomeserverTestCase):
"""Tests /rtc/transports Client-Server REST API."""

servlets = [
admin.register_servlets,
room.register_servlets,
login.register_servlets,
register.register_servlets,
matrixrtc.register_servlets,
]

def prepare(
self, reactor: MemoryReactor, clock: Clock, homeserver: HomeServer
) -> None:
self.register_user("alice", "password")
self._alice_tok = self.login("alice", "password")

def test_matrixrtc_endpoint_not_enabled(self) -> None:
channel = self.make_request(
"GET", f"{PATH_PREFIX}/rtc/transports", access_token=self._alice_tok
)
self.assertEqual(404, channel.code, channel.json_body)
self.assertEqual(
"M_UNRECOGNIZED", channel.json_body["errcode"], channel.json_body
)

@override_config({"experimental_features": {"msc4143_enabled": True}})
def test_matrixrtc_endpoint_requires_authentication(self) -> None:
channel = self.make_request("GET", f"{PATH_PREFIX}/rtc/transports")
self.assertEqual(401, channel.code, channel.json_body)

@override_config(
{
"experimental_features": {"msc4143_enabled": True},
"matrix_rtc": {"transports": [RTC_ENDPOINT]},
}
)
def test_matrixrtc_endpoint_contains_expected_transport(self) -> None:
channel = self.make_request(
"GET", f"{PATH_PREFIX}/rtc/transports", access_token=self._alice_tok
)
self.assertEqual(200, channel.code, channel.json_body)
self.assert_dict({"rtc_transports": [RTC_ENDPOINT]}, channel.json_body)

@override_config(
{
"experimental_features": {"msc4143_enabled": True},
"matrix_rtc": {"transports": []},
}
)
def test_matrixrtc_endpoint_no_transports_configured(self) -> None:
channel = self.make_request(
"GET", f"{PATH_PREFIX}/rtc/transports", access_token=self._alice_tok
)
self.assertEqual(200, channel.code, channel.json_body)
self.assert_dict({}, channel.json_body)

@override_config(
{
"experimental_features": {"msc4143_enabled": True},
"matrix_rtc": {"transports": [LIVEKIT_ENDPOINT]},
}
)
def test_matrixrtc_endpoint_livekit_transport(self) -> None:
channel = self.make_request(
"GET", f"{PATH_PREFIX}/rtc/transports", access_token=self._alice_tok
)
self.assertEqual(200, channel.code, channel.json_body)
self.assert_dict({"rtc_transports": [LIVEKIT_ENDPOINT]}, channel.json_body)
Loading