diff --git a/changelog.d/20146.feature b/changelog.d/20146.feature new file mode 100644 index 0000000000..262ae828f8 --- /dev/null +++ b/changelog.d/20146.feature @@ -0,0 +1 @@ +Deprecate `livekit_service_url` and add support for specifying the SFU WebSocket URL for configured LiveKit transports. Please check [the relevant section in the upgrade notes](https://github.com/element-hq/synapse/blob/develop/docs/upgrade.md#upgrading-to-v11600). diff --git a/docs/upgrade.md b/docs/upgrade.md index a3c529f1f1..9eac9c5ebc 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -118,6 +118,20 @@ stacking them up. You can monitor the currently running background updates with [the Admin API](usage/administration/admin_api/background_updates.html#status). +# Upgrading to v1.161.0 + +## Deprecation of `matrix_rtc.livekit_service_url` + +When configuring the MatrixRTC LiveKit transport, the `livekit_service_url` is now +deprecated but should continue to be listed to ensure backwards compatibility with +older clients. A new sibling `url` config property is added that should be set to +your SFU's WebSocket URL. Clients that support `url` will use the Client-Server API +to (indirectly) interact with the LiveKit authorization service. The service needs +to be set up as an application service in order to support these endpoints. See +https://github.com/element-hq/lk-jwt-service and +https://element-hq.github.io/synapse/v1.161/usage/configuration/config_documentation.html#matrix_rtc +for further details. + # Upgrading to v1.159.0 ## Change of signing key expiry date for the Debian/Ubuntu package repository (2026) diff --git a/docs/usage/configuration/config_documentation.md b/docs/usage/configuration/config_documentation.md index 00a31ff447..876b74d5d3 100644 --- a/docs/usage/configuration/config_documentation.md +++ b/docs/usage/configuration/config_documentation.md @@ -2662,13 +2662,20 @@ This setting has the following sub-options: * `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. + * `url` (string): The WebSocket URL of the LiveKit SFU. If type is "livekit", either this or `livekit_service_url` is required. + + Clients that support `url` will use the Client-Server API to (indirectly) interact with the LiveKit authorization service. The service needs to be set up as an application service in order to support these endpoints. See https://github.com/element-hq/lk-jwt-service for further details. + + * `livekit_service_url` (string): Deprecated. The HTTP URL of the LiveKit authorization service. If type is "livekit", either this or `url` is required. + + Clients that don't support `url` will use `livekit_service_url` to directly interact with the LiveKit authorization service. This mode of operation is deprecated and should only be used for backwards compatibility. Example configuration: ```yaml matrix_rtc: transports: - type: livekit + url: wss://livekit.example.com livekit_service_url: https://matrix-rtc.example.com/livekit/jwt ``` --- diff --git a/schema/synapse-config.schema.yaml b/schema/synapse-config.schema.yaml index 59791a4a1f..cfa2cd4a41 100644 --- a/schema/synapse-config.schema.yaml +++ b/schema/synapse-config.schema.yaml @@ -3022,10 +3022,28 @@ properties: type: string description: The type of transport to use to connect to the selective forwarding unit (SFU). example: livekit + url: + type: string + description: >- + The WebSocket URL of the LiveKit SFU. If type is "livekit", either this or `livekit_service_url` is + required. + + + Clients that support `url` will use the Client-Server API to (indirectly) interact with the LiveKit + authorization service. The service needs to be set up as an application service in order to support + these endpoints. See https://github.com/element-hq/lk-jwt-service for further details. + example: + wss://livekit.example.com livekit_service_url: type: string description: >- - The base URL of the LiveKit service. Should only be used with LiveKit-based transports. + Deprecated. The HTTP URL of the LiveKit authorization service. If type is "livekit", either this or `url` is + required. + + + Clients that don't support `url` will use `livekit_service_url` to directly interact with the LiveKit + authorization service. This mode of operation is deprecated and should only be used for backwards + compatibility. example: https://matrix-rtc.example.com/livekit/jwt description: A list of transport types and arguments to use for MatrixRTC connections. default: [] @@ -3033,6 +3051,7 @@ properties: examples: - transports: - type: livekit + url: wss://livekit.example.com livekit_service_url: https://matrix-rtc.example.com/livekit/jwt enable_registration: type: boolean diff --git a/synapse/config/matrixrtc.py b/synapse/config/matrixrtc.py index 84c245e286..896317fca8 100644 --- a/synapse/config/matrixrtc.py +++ b/synapse/config/matrixrtc.py @@ -17,7 +17,7 @@ from typing import Any -from pydantic import Field, StrictStr, ValidationError, model_validator +from pydantic import Field, StrictStr, ValidationError, field_validator, model_validator from typing_extensions import Self from synapse.types import JsonDict @@ -29,20 +29,34 @@ from ._base import Config, ConfigError class TransportConfigModel(ParseModel): type: StrictStr + url: StrictStr | None = Field(default=None) + """An optional WebSocket URL pointing to the LiveKit SFU. If type is "livekit", either this or livekit_service_url is required.""" + livekit_service_url: StrictStr | None = Field(default=None) - """An optional livekit service URL. Only required if type is "livekit".""" + """Deprecated. An optional HTTP URL pointing to the LiveKit authorization service. If type is "livekit", either this or url is required.""" @model_validator(mode="after") - def validate_livekit_service_url(self) -> Self: - if self.type == "livekit" and not self.livekit_service_url: + def validate_livekit_transport(self) -> Self: + if self.type == "livekit" and not self.url and not self.livekit_service_url: raise ValueError( - "You must set a `livekit_service_url` when using the 'livekit' transport." + "You must set either `url` or `livekit_service_url` when using the 'livekit' transport." ) return self class MatrixRtcConfigModel(ParseModel): - transports: list = [] + transports: list[dict[str, Any]] = [] + + @field_validator("transports") + @classmethod + def validate_transports( + cls, transports: list[dict[str, Any]] + ) -> list[dict[str, Any]]: + """Validate each transport by attempting to construct a `TransportConfigModel` + from it, raising a `ValidationError` if construction fails.""" + for transport in transports: + TransportConfigModel(**transport) + return transports class MatrixRtcConfig(Config): diff --git a/tests/rest/client/test_matrixrtc.py b/tests/rest/client/test_matrixrtc.py index f2bf1596be..63c7632a88 100644 --- a/tests/rest/client/test_matrixrtc.py +++ b/tests/rest/client/test_matrixrtc.py @@ -17,8 +17,13 @@ """Tests REST events for /rtc/endpoints path.""" +import unittest as stdlib_unittest + +from pydantic import ValidationError + from twisted.internet.testing import MemoryReactor +from synapse.config.matrixrtc import TransportConfigModel from synapse.rest import admin from synapse.rest.client import login, matrixrtc, register, room, versions from synapse.server import HomeServer @@ -30,7 +35,16 @@ from tests.unittest import HomeserverTestCase, override_config PATH_PREFIX = "/_matrix/client/unstable/org.matrix.msc4143" RTC_ENDPOINT = {"type": "focusA", "required_field": "theField"} -LIVEKIT_ENDPOINT = { +LIVEKIT_TRANSPORT = { + "type": "livekit", + "url": "wss://livekit.example.com", +} +BACKWARDS_COMPATIBLE_LIVEKIT_TRANSPORT = { + "type": "livekit", + "url": "wss://livekit.example.com", + "livekit_service_url": "https://livekit.example.com", +} +LEGACY_LIVEKIT_TRANSPORT = { "type": "livekit", "livekit_service_url": "https://livekit.example.com", } @@ -96,7 +110,7 @@ class MatrixRtcTestCase(HomeserverTestCase): @override_config( { "experimental_features": {"msc4143_enabled": True}, - "matrix_rtc": {"transports": [LIVEKIT_ENDPOINT]}, + "matrix_rtc": {"transports": [LIVEKIT_TRANSPORT]}, } ) def test_matrixrtc_endpoint_livekit_transport(self) -> None: @@ -104,7 +118,38 @@ class MatrixRtcTestCase(HomeserverTestCase): "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) + self.assert_dict({"rtc_transports": [LIVEKIT_TRANSPORT]}, channel.json_body) + + @override_config( + { + "experimental_features": {"msc4143_enabled": True}, + "matrix_rtc": {"transports": [BACKWARDS_COMPATIBLE_LIVEKIT_TRANSPORT]}, + } + ) + def test_matrixrtc_endpoint_backwards_compatible_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": [BACKWARDS_COMPATIBLE_LIVEKIT_TRANSPORT]}, + channel.json_body, + ) + + @override_config( + { + "experimental_features": {"msc4143_enabled": True}, + "matrix_rtc": {"transports": [LEGACY_LIVEKIT_TRANSPORT]}, + } + ) + def test_matrixrtc_endpoint_legacy_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": [LEGACY_LIVEKIT_TRANSPORT]}, channel.json_body + ) class MatrixRtcVersionsTestCase(HomeserverTestCase): @@ -150,3 +195,23 @@ class MatrixRtcVersionsTestCase(HomeserverTestCase): channel = self.make_request("GET", "/_matrix/client/versions") self.assertEqual(channel.code, 200, channel.result) self.assertTrue(channel.json_body["unstable_features"]["org.matrix.msc4143"]) + + +class TransportConfigModelTestCase(stdlib_unittest.TestCase): + """Tests validation of the `TransportConfigModel` pydantic model.""" + + def test_livekit_transport_requires_url_or_livekit_service_url(self) -> None: + with self.assertRaises(ValidationError): + TransportConfigModel(type="livekit") + + def test_livekit_transport_with_only_url(self) -> None: + TransportConfigModel(type="livekit", url="wss://livekit.example.com") + + def test_livekit_transport_with_only_livekit_service_url(self) -> None: + TransportConfigModel( + type="livekit", livekit_service_url="https://livekit.example.com" + ) + + def test_invalid_field_type(self) -> None: + with self.assertRaises(ValidationError): + TransportConfigModel(type="livekit", url=1234) # type: ignore[arg-type]