mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
Merge pull request #4153 from element-hq/valere/rtc/remove_well_known_fallback
Remove .well-known transport discovery
This commit is contained in:
40
README.md
40
README.md
@@ -115,30 +115,33 @@ For more details on the packages, see the
|
|||||||
For operating and deploying Element Call on your own server, refer to the
|
For operating and deploying Element Call on your own server, refer to the
|
||||||
[**Self-Hosting Guide**](./docs/self_hosting.md).
|
[**Self-Hosting Guide**](./docs/self_hosting.md).
|
||||||
|
|
||||||
## 🧭 MatrixRTC Backend Discovery and Selection
|
## MatrixRTC Transports
|
||||||
|
|
||||||
For proper Element Call operation each site deployment needs a MatrixRTC backend
|
For proper operation of Element Call, each deployment needs to set up a
|
||||||
setup as outlined in the [Self-Hosting Guide](./docs/self_hosting.md). A typical
|
MatrixRTC transport in the form of a LiveKit server as outlined in the
|
||||||
federated site deployment for three different sites A, B and C is depicted below.
|
[Self-Hosting Guide](./docs/self_hosting.md). A typical federated site
|
||||||
|
deployment for three different sites A, B and C is depicted below.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./docs/Federated_Setup.drawio.png" alt="Element Call federated setup">
|
<img src="./docs/Federated_Setup.drawio.png" alt="Element Call federated setup">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Backend Discovery
|
### Transport Discovery
|
||||||
|
|
||||||
The MatrixRTC backend (according to
|
Element Call discovers the available MatrixRTC transports (as defined by
|
||||||
[MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)) is
|
[MSC4519](https://github.com/matrix-org/matrix-spec-proposals/pull/4519)) by
|
||||||
announced by the Matrix site's `.well-known/matrix/client` file and discovered
|
hitting the `GET /_matrix/client/unstable/org.matrix.msc4143/rtc/transports`
|
||||||
via the `org.matrix.msc4143.rtc_foci` key, e.g.:
|
endpoint of the Client-Server API. An example response:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
{
|
||||||
|
"rtc_transports": [
|
||||||
{
|
{
|
||||||
"type": "livekit",
|
"type": "livekit",
|
||||||
"livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt"
|
"livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt"
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
where the format for MatrixRTC using LiveKit backend is defined in
|
where the format for MatrixRTC using LiveKit backend is defined in
|
||||||
@@ -149,7 +152,7 @@ via `livekit_service_url`.
|
|||||||
|
|
||||||
### Backend Selection
|
### Backend Selection
|
||||||
|
|
||||||
- Each call participant proposes their discovered MatrixRTC backend from
|
- Each call participant proposes their discovered MatrixRTC transport from
|
||||||
`org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event.
|
`org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event.
|
||||||
- For the **LiveKit** MatrixRTC backend
|
- For the **LiveKit** MatrixRTC backend
|
||||||
([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)),
|
([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)),
|
||||||
@@ -244,10 +247,9 @@ pnpm backend
|
|||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To ensure your local development frontend functions properly, you’ll need to
|
> To ensure your local development frontend functions properly, you’ll need to
|
||||||
> add certificate exceptions in your browser for `https://localhost:3000`,
|
> add certificate exceptions in your browser for `https://localhost:3000` and
|
||||||
> `https://matrix-rtc.m.localhost/livekit/jwt/healthz` and
|
> `https://matrix-rtc.m.localhost/livekit/jwt/healthz`. This can be done either
|
||||||
> `https://synapse.m.localhost/.well-known/matrix/client`. This can be either
|
> by adding the minimum localhost CA
|
||||||
> done by adding the minimum localhost CA
|
|
||||||
> ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web
|
> ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web
|
||||||
> browser's trusted certificates or by simply copying and pasting each URL into
|
> browser's trusted certificates or by simply copying and pasting each URL into
|
||||||
> your browser’s address bar and follow the prompts to add the exception.
|
> your browser’s address bar and follow the prompts to add the exception.
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -67,3 +68,8 @@ rc_message:
|
|||||||
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
||||||
per_second: 0.5
|
per_second: 0.5
|
||||||
burst_count: 30
|
burst_count: 30
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -67,3 +68,8 @@ rc_message:
|
|||||||
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
||||||
per_second: 0.5
|
per_second: 0.5
|
||||||
burst_count: 30
|
burst_count: 30
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Synapse reverse proxy including .well-known/matrix/client
|
# Synapse reverse proxy
|
||||||
# domain synapse.m.localhost
|
# domain synapse.m.localhost
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
@@ -11,18 +11,6 @@ server {
|
|||||||
ssl_certificate /root/ssl/cert.pem;
|
ssl_certificate /root/ssl/cert.pem;
|
||||||
ssl_certificate_key /root/ssl/key.pem;
|
ssl_certificate_key /root/ssl/key.pem;
|
||||||
|
|
||||||
# well-known config adding rtc_foci backend
|
|
||||||
# Note well-known is currently not effective due to:
|
|
||||||
# https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec
|
|
||||||
# says it must be at https://$server_name/... (implied port 443) Hence, we
|
|
||||||
# currently rely for local development environment on deprecated config.json
|
|
||||||
# setting for livekit_service_url
|
|
||||||
location /.well-known/matrix/client {
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
return 200 '{"m.homeserver": {"base_url": "https://synapse.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.m.localhost/livekit/jwt"}]}';
|
|
||||||
default_type application/json;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Reverse proxy for Matrix Synapse Homeserver
|
# Reverse proxy for Matrix Synapse Homeserver
|
||||||
# This is also required for development environment.
|
# This is also required for development environment.
|
||||||
# Reason: the lk-jwt-service uses the federation API for the openid token
|
# Reason: the lk-jwt-service uses the federation API for the openid token
|
||||||
@@ -44,7 +32,7 @@ server {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Synapse reverse proxy including .well-known/matrix/client
|
# Synapse reverse proxy
|
||||||
# domain synapse.othersite.m.localhost
|
# domain synapse.othersite.m.localhost
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
@@ -57,18 +45,6 @@ server {
|
|||||||
ssl_certificate /root/ssl/cert.pem;
|
ssl_certificate /root/ssl/cert.pem;
|
||||||
ssl_certificate_key /root/ssl/key.pem;
|
ssl_certificate_key /root/ssl/key.pem;
|
||||||
|
|
||||||
# well-known config adding rtc_foci backend
|
|
||||||
# Note well-known is currently not effective due to:
|
|
||||||
# https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec
|
|
||||||
# says it must be at https://$server_name/... (implied port 443) Hence, we
|
|
||||||
# currently rely for local development environment on deprecated config.json
|
|
||||||
# setting for livekit_service_url
|
|
||||||
location /.well-known/matrix/client {
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
return 200 '{"m.homeserver": {"base_url": "https://synapse.othersite.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.othersite.m.localhost/livekit/jwt"}]}';
|
|
||||||
default_type application/json;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Reverse proxy for Matrix Synapse Homeserver
|
# Reverse proxy for Matrix Synapse Homeserver
|
||||||
# This is also required for development environment.
|
# This is also required for development environment.
|
||||||
# Reason: the lk-jwt-service uses the federation API for the openid token
|
# Reason: the lk-jwt-service uses the federation API for the openid token
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -84,3 +85,8 @@ rc_login:
|
|||||||
rc_registration:
|
rc_registration:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -84,3 +85,8 @@ rc_login:
|
|||||||
rc_registration:
|
rc_registration:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ As a prerequisite for the
|
|||||||
make sure that your Synapse server has either a `federation` or `openid`
|
make sure that your Synapse server has either a `federation` or `openid`
|
||||||
[listener configured](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#listeners).
|
[listener configured](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#listeners).
|
||||||
|
|
||||||
### MatrixRTC Backend
|
### LiveKit backend
|
||||||
|
|
||||||
In order to **guarantee smooth operation** of Element Call, a MatrixRTC backend is
|
In order to **guarantee smooth operation** of Element Call, a dedicated LiveKit
|
||||||
required for each site deployment.
|
backend is required for each site deployment.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ Using Haproxy, you can achieve this by:
|
|||||||
use_backend mxrtc_auth_backend if is_mxrtc_auth matrixrtc_domain
|
use_backend mxrtc_auth_backend if is_mxrtc_auth matrixrtc_domain
|
||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
## MatrixRTC backend
|
## LiveKit backend
|
||||||
backend sfu_backend
|
backend sfu_backend
|
||||||
server livekit 127.0.0.1:7880
|
server livekit 127.0.0.1:7880
|
||||||
http-request set-path %[path,regsub(^/livekit/sfu/,/)]
|
http-request set-path %[path,regsub(^/livekit/sfu/,/)]
|
||||||
@@ -187,43 +187,22 @@ backend mxrtc_auth_backend
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### MatrixRTC backend announcement
|
#### MatrixRTC transport announcement
|
||||||
|
|
||||||
> [!IMPORTANT]
|
Enable the unstable feature flag `msc4143_enabled`, and update the
|
||||||
> As defined in
|
[`matrix_rtc` section](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#matrix_rtc)
|
||||||
> [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143),
|
of your Synapse config file:
|
||||||
> the MatrixRTC backend(s) must be announced to the client via your **Matrix site's
|
|
||||||
> `.well-known/matrix/client`** file (e.g.
|
|
||||||
> `example.com/.well-known/matrix/client` matching the site deployment example
|
|
||||||
> from above). The configuration is a list of Foci configs:
|
|
||||||
|
|
||||||
```json
|
```yaml
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
matrix_rtc:
|
||||||
{
|
transports:
|
||||||
"type": "livekit",
|
- type: livekit
|
||||||
"livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt"
|
livekit_service_url: https://matrix-rtc.example.com/livekit/jwt
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "livekit",
|
|
||||||
"livekit_service_url": "https://matrix-rtc-2.example.com/livekit/jwt"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure this file is served with the correct MIME type (`application/json`).
|
The transport you specify will be made available to clients over the
|
||||||
Additionally, ensure the appropriate CORS headers are set to allow web clients
|
`/_matrix/client/unstable/org.matrix.msc4143/rtc/transports` endpoint as defined
|
||||||
to access it across origins. For more details, refer to the
|
in [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143).
|
||||||
[Matrix Client-Server API: 2. Web Browser Clients](https://spec.matrix.org/latest/client-server-api/#web-browser-clients).
|
|
||||||
|
|
||||||
```
|
|
||||||
Access-Control-Allow-Origin: *
|
|
||||||
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
|
||||||
Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Most `org.matrix.msc4143.rtc_foci` configurations will only have one entry in
|
|
||||||
> the array.
|
|
||||||
|
|
||||||
## Building Element Call
|
## Building Element Call
|
||||||
|
|
||||||
@@ -278,7 +257,7 @@ runtime. Documentation and default values for `public/config.json` can be found
|
|||||||
in [ConfigOptions.ts](../src/config/ConfigOptions.ts).
|
in [ConfigOptions.ts](../src/config/ConfigOptions.ts).
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Please note configuring MatrixRTC backend via `config.json` of
|
> Please note configuring LiveKit backend via `config.json` of
|
||||||
> Element Call is only available for developing and debug purposes. Relying on
|
> Element Call is only available for developing and debug purposes. Relying on
|
||||||
> it might break Element Call going forward!
|
> it might break Element Call going forward!
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
"crypto_version": "Crypto version: {{version}}",
|
"crypto_version": "Crypto version: {{version}}",
|
||||||
"custom_livekit_url": {
|
"custom_livekit_url": {
|
||||||
"current_url": "Currently set to: ",
|
"current_url": "Currently set to: ",
|
||||||
"from_config": "Currently, no overwrite is set. Url from well-known or config is used.",
|
"from_config": "Currently, no overwrite is set. Url from config is used.",
|
||||||
"label": "Custom Livekit-url",
|
"label": "Custom Livekit-url",
|
||||||
"reset": "Reset overwrite",
|
"reset": "Reset overwrite",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
|||||||
@@ -74,10 +74,7 @@ export interface ConfigOptions {
|
|||||||
livekit?: {
|
livekit?: {
|
||||||
// The link to the service that returns a livekit url and token to use it.
|
// The link to the service that returns a livekit url and token to use it.
|
||||||
// This is a fallback link in case the homeserver in use does not advertise
|
// This is a fallback link in case the homeserver in use does not advertise
|
||||||
// a livekit service url in the client well-known.
|
// a livekit service url over the transports endpoint.
|
||||||
// The well known needs to be formatted like so:
|
|
||||||
// {"type":"livekit", "livekit_service_url":"https://livekit.example.com"}
|
|
||||||
// and stored under the key: "org.matrix.msc4143.rtc_foci"
|
|
||||||
livekit_service_url: string;
|
livekit_service_url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
|||||||
class="_message_1o4d9_86 _help-message_1o4d9_92"
|
class="_message_1o4d9_86 _help-message_1o4d9_92"
|
||||||
id="radix-_r_8_"
|
id="radix-_r_8_"
|
||||||
>
|
>
|
||||||
Currently, no overwrite is set. Url from well-known or config is used.
|
Currently, no overwrite is set. Url from config is used.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -138,9 +138,6 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
public getSyncState(): SyncState {
|
public getSyncState(): SyncState {
|
||||||
return syncState;
|
return syncState;
|
||||||
}
|
}
|
||||||
public getAccessToken(): string | null {
|
|
||||||
return "a-token";
|
|
||||||
}
|
|
||||||
})() as Partial<MatrixClient> as MatrixClient,
|
})() as Partial<MatrixClient> as MatrixClient,
|
||||||
getMembers: () => roomMembers,
|
getMembers: () => roomMembers,
|
||||||
getMembersWithMembership: () => roomMembers,
|
getMembersWithMembership: () => roomMembers,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
afterAll,
|
afterAll,
|
||||||
beforeEach,
|
beforeEach,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
|
||||||
import { BehaviorSubject, map, of } from "rxjs";
|
import { BehaviorSubject, map, of } from "rxjs";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { type LocalParticipant, type LocalTrack } from "livekit-client";
|
import { type LocalParticipant, type LocalTrack } from "livekit-client";
|
||||||
@@ -78,34 +77,10 @@ describe("LocalMembership", () => {
|
|||||||
livekit_alias: "my-oldest-member-service-alias",
|
livekit_alias: "my-oldest-member-service-alias",
|
||||||
};
|
};
|
||||||
|
|
||||||
const focusConfigFromWellKnown = {
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
|
||||||
};
|
|
||||||
const focusConfigFromWellKnown2 = {
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url2.com",
|
|
||||||
};
|
|
||||||
const clientWellKnown = {
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
focusConfigFromWellKnown,
|
|
||||||
focusConfigFromWellKnown2,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
mockConfig({
|
mockConfig({
|
||||||
livekit: { livekit_service_url: "http://my-default-service-url.com" },
|
livekit: { livekit_service_url: "http://my-default-service-url.com" },
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(AutoDiscovery, "getRawClientConfig").mockImplementation(
|
|
||||||
async (domain) => {
|
|
||||||
if (domain === "example.org") {
|
|
||||||
return Promise.resolve(clientWellKnown);
|
|
||||||
}
|
|
||||||
return Promise.resolve({});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockedSession = vi.mocked({
|
const mockedSession = vi.mocked({
|
||||||
room: {
|
room: {
|
||||||
roomId: "roomId",
|
roomId: "roomId",
|
||||||
@@ -132,7 +107,7 @@ describe("LocalMembership", () => {
|
|||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
{
|
{
|
||||||
livekit_alias: "roomId",
|
livekit_alias: "roomId",
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
livekit_service_url: "http://my-livekit-service-url.com",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -150,7 +125,7 @@ describe("LocalMembership", () => {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
livekit_alias: "roomId",
|
livekit_alias: "roomId",
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
livekit_service_url: "http://my-livekit-service-url.com",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -161,50 +136,6 @@ describe("LocalMembership", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("It should not fail with configuration error if homeserver config has livekit url but not fallback", () => {
|
|
||||||
mockConfig({});
|
|
||||||
vi.spyOn(AutoDiscovery, "getRawClientConfig").mockResolvedValue({
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockedSession = vi.mocked({
|
|
||||||
room: {
|
|
||||||
roomId: "roomId",
|
|
||||||
client: {
|
|
||||||
getDomain: vi.fn().mockReturnValue("example.org"),
|
|
||||||
getOpenIdToken: vi.fn().mockResolvedValue({
|
|
||||||
access_token: "ACCCESS_TOKEN",
|
|
||||||
token_type: "Bearer",
|
|
||||||
matrix_server_name: "localhost",
|
|
||||||
expires_in: 10000,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
memberships: [],
|
|
||||||
getFocusInUse: vi.fn(),
|
|
||||||
joinRTCSession: vi.fn(),
|
|
||||||
}) as unknown as MatrixRTCSession;
|
|
||||||
|
|
||||||
enterRTCSession(
|
|
||||||
mockedSession,
|
|
||||||
ownMemberMock,
|
|
||||||
{
|
|
||||||
livekit_alias: "roomId",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
|
||||||
type: "livekit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
encryptMedia: true,
|
|
||||||
matrixRTCMode: MATRIX_RTC_MODE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultCreateLocalMemberValues = {
|
const defaultCreateLocalMemberValues = {
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ export type LocalMemberState =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* - get well known
|
|
||||||
* - get oldest membership
|
* - get oldest membership
|
||||||
* - get transport to use
|
* - get transport to use
|
||||||
* - get openId + jwt token
|
* - get openId + jwt token
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ describe("LocalTransport", () => {
|
|||||||
client: {
|
client: {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
getDomain: () => "example.org",
|
||||||
getDomain: () => "",
|
|
||||||
baseUrl: "example.org",
|
baseUrl: "example.org",
|
||||||
// These won't be called in this error path but satisfy the type
|
// These won't be called in this error path but satisfy the type
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
@@ -77,9 +76,11 @@ describe("LocalTransport", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(() => advertised$.value).toThrow(
|
expect(() => advertised$.value).toThrow(
|
||||||
new MatrixRTCTransportMissingError(""),
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
|
);
|
||||||
|
expect(() => active$.value).toThrow(
|
||||||
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
);
|
);
|
||||||
expect(() => active$.value).toThrow(new MatrixRTCTransportMissingError(""));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => {
|
it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => {
|
||||||
@@ -103,10 +104,8 @@ describe("LocalTransport", () => {
|
|||||||
useOldestMember: false,
|
useOldestMember: false,
|
||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
// Use empty domain to skip .well-known and use config directly
|
getDomain: () => "example.org",
|
||||||
getDomain: () => "",
|
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
@@ -150,11 +149,10 @@ describe("LocalTransport", () => {
|
|||||||
client: {
|
client: {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
},
|
},
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
||||||
@@ -221,13 +219,12 @@ describe("LocalTransport", () => {
|
|||||||
useOldestMember: true,
|
useOldestMember: true,
|
||||||
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
},
|
},
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
||||||
@@ -278,14 +275,13 @@ describe("LocalTransport", () => {
|
|||||||
useOldestMember: true,
|
useOldestMember: true,
|
||||||
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () =>
|
_unstable_getRTCTransports: async () =>
|
||||||
Promise.resolve([aliceTransport]),
|
Promise.resolve([aliceTransport]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
},
|
},
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
||||||
@@ -330,10 +326,9 @@ describe("LocalTransport", () => {
|
|||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
baseUrl: "https://example.org",
|
baseUrl: "https://example.org",
|
||||||
getDomain: vi.fn().mockReturnValue(""),
|
getDomain: vi.fn().mockReturnValue("example.org"),
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
},
|
},
|
||||||
@@ -433,51 +428,6 @@ describe("LocalTransport", () => {
|
|||||||
).rejects.toThrow(expect.any(FailToGetOpenIdToken));
|
).rejects.toThrow(expect.any(FailToGetOpenIdToken));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("supports getting transport via well-known", async () => {
|
|
||||||
localTransportOpts.client.getDomain.mockReturnValue("example.org");
|
|
||||||
fetchMock.getOnce("https://example.org/.well-known/matrix/client", {
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const { advertised$, active$ } =
|
|
||||||
createLocalTransport$(localTransportOpts);
|
|
||||||
openIdResolver.resolve?.(openIdResponse);
|
|
||||||
expect(advertised$.value).toBe(null);
|
|
||||||
expect(active$.value).toBe(null);
|
|
||||||
await flushPromises();
|
|
||||||
const expectedTransport = {
|
|
||||||
livekit_service_url: "https://lk.example.org",
|
|
||||||
type: "livekit",
|
|
||||||
};
|
|
||||||
expect(advertised$.value).toStrictEqual(expectedTransport);
|
|
||||||
expect(active$.value).toStrictEqual({
|
|
||||||
transport: expectedTransport,
|
|
||||||
sfuConfig: {
|
|
||||||
jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=",
|
|
||||||
livekitAlias: "Akph4alDMhen",
|
|
||||||
livekitIdentity: "@lk_user:ABCDEF",
|
|
||||||
url: "https://lk.example.org",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(fetchMock.done()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fails fast if the openId request fails for the well-known config", async () => {
|
|
||||||
localTransportOpts.client.getDomain.mockReturnValue("example.org");
|
|
||||||
fetchMock.getOnce("https://example.org/.well-known/matrix/client", {
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
openIdResolver.reject(
|
|
||||||
new FailToGetOpenIdToken(new Error("Test driven error")),
|
|
||||||
);
|
|
||||||
await expect(async () =>
|
|
||||||
lastValueFrom(createLocalTransport$(localTransportOpts).active$),
|
|
||||||
).rejects.toThrow(expect.any(FailToGetOpenIdToken));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws if no options are available", async () => {
|
it("throws if no options are available", async () => {
|
||||||
const { advertised$, active$ } = createLocalTransport$({
|
const { advertised$, active$ } = createLocalTransport$({
|
||||||
scope: testScope(),
|
scope: testScope(),
|
||||||
@@ -488,11 +438,10 @@ describe("LocalTransport", () => {
|
|||||||
delayId$: constant(null),
|
delayId$: constant(null),
|
||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
baseUrl: "https://example.org",
|
baseUrl: "https://example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
// These won't be called in this error path but satisfy the type
|
// These won't be called in this error path but satisfy the type
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
@@ -501,10 +450,10 @@ describe("LocalTransport", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(() => advertised$.value).toThrow(
|
expect(() => advertised$.value).toThrow(
|
||||||
new MatrixRTCTransportMissingError(""),
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
);
|
);
|
||||||
expect(() => active$.value).toThrow(
|
expect(() => active$.value).toThrow(
|
||||||
new MatrixRTCTransportMissingError(""),
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -529,11 +478,10 @@ describe("LocalTransport", () => {
|
|||||||
delayId$: delayId$,
|
delayId$: delayId$,
|
||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
baseUrl: "https://example.org",
|
baseUrl: "https://example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
// These won't be called in this error path but satisfy the type
|
// These won't be called in this error path but satisfy the type
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
tap,
|
tap,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
|
import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
|
||||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import { type Behavior } from "../../Behavior.ts";
|
import { type Behavior } from "../../Behavior.ts";
|
||||||
@@ -57,7 +56,7 @@ interface Props {
|
|||||||
memberships$: Behavior<Epoch<CallMembership[]>>;
|
memberships$: Behavior<Epoch<CallMembership[]>>;
|
||||||
client: Pick<
|
client: Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "baseUrl" | "_unstable_getRTCTransports"
|
||||||
> &
|
> &
|
||||||
OpenIDClientParts;
|
OpenIDClientParts;
|
||||||
// Used by the jwt service to create the livekit room and compute the livekit alias.
|
// Used by the jwt service to create the livekit room and compute the livekit alias.
|
||||||
@@ -150,7 +149,6 @@ export const createLocalTransport$ = ({
|
|||||||
const transportDiscovery = new RtcTransportAutoDiscovery({
|
const transportDiscovery = new RtcTransportAutoDiscovery({
|
||||||
client: client,
|
client: client,
|
||||||
resolvedConfig: Config.get(),
|
resolvedConfig: Config.get(),
|
||||||
wellKnownFetcher: AutoDiscovery.getRawClientConfig.bind(AutoDiscovery),
|
|
||||||
logger: logger,
|
logger: logger,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,7 +307,7 @@ async function doOpenIdAndJWTFromUrl(
|
|||||||
roomId: string,
|
roomId: string,
|
||||||
client: Pick<
|
client: Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "baseUrl" | "_unstable_getRTCTransports"
|
||||||
> &
|
> &
|
||||||
OpenIDClientParts,
|
OpenIDClientParts,
|
||||||
delayId?: string,
|
delayId?: string,
|
||||||
@@ -339,7 +337,7 @@ function observeLocalTransportForOldestMembership(
|
|||||||
preferredTransport$: Observable<LocalTransportWithSFUConfig>,
|
preferredTransport$: Observable<LocalTransportWithSFUConfig>,
|
||||||
client: Pick<
|
client: Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "baseUrl" | "_unstable_getRTCTransports"
|
||||||
> &
|
> &
|
||||||
OpenIDClientParts,
|
OpenIDClientParts,
|
||||||
ownMembershipIdentity: CallMembershipIdentityParts,
|
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
type MockedObject,
|
type MockedObject,
|
||||||
vi,
|
vi,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { type IClientWellKnown, MatrixError } from "matrix-js-sdk";
|
import { MatrixError } from "matrix-js-sdk";
|
||||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||||
import {
|
import {
|
||||||
type LivekitTransportConfig,
|
type LivekitTransportConfig,
|
||||||
@@ -33,9 +33,9 @@ const backendTransport: LivekitTransportConfig = {
|
|||||||
livekit_service_url: "https://backend.example.org",
|
livekit_service_url: "https://backend.example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
const wellKnownTransport: LivekitTransportConfig = {
|
const configTransport: LivekitTransportConfig = {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: "https://well-known.example.org",
|
livekit_service_url: "https://config.example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
function makeClient(): MockedObject<DiscoveryClient> {
|
function makeClient(): MockedObject<DiscoveryClient> {
|
||||||
@@ -43,7 +43,6 @@ function makeClient(): MockedObject<DiscoveryClient> {
|
|||||||
getDomain: vi.fn().mockReturnValue("example.org"),
|
getDomain: vi.fn().mockReturnValue("example.org"),
|
||||||
baseUrl: "https://matrix.example.org",
|
baseUrl: "https://matrix.example.org",
|
||||||
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
} as unknown as MockedObject<DiscoveryClient>;
|
} as unknown as MockedObject<DiscoveryClient>;
|
||||||
@@ -59,12 +58,6 @@ function makeResolvedConfig(livekitServiceUrl?: string): ResolvedConfigOptions {
|
|||||||
} as ResolvedConfigOptions;
|
} as ResolvedConfigOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeWellKnown(rtcFoci?: Transport[]): IClientWellKnown {
|
|
||||||
return {
|
|
||||||
"org.matrix.msc4143.rtc_foci": rtcFoci,
|
|
||||||
} as unknown as IClientWellKnown;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("RtcTransportAutoDiscovery", () => {
|
describe("RtcTransportAutoDiscovery", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -75,33 +68,27 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
{ transports: [{ type: "not_livekit" }, backendTransport] },
|
{ transports: [{ type: "not_livekit" }, backendTransport] },
|
||||||
];
|
];
|
||||||
it.each(VALID_TEST_CASES)(
|
it.each(VALID_TEST_CASES)(
|
||||||
"prefers backend transport over well-known and app config $transports",
|
"prefers backend transport other app config $transports",
|
||||||
async ({ transports }) => {
|
async ({ transports }) => {
|
||||||
// it("prefers backend transport over well-known and app config", async () => {
|
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue(makeWellKnown([wellKnownTransport]));
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
resolvedConfig: makeResolvedConfig(configTransport.livekit_service_url),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
const discoveredTransport = await discovery.discoverPreferredTransport();
|
||||||
discovery.discoverPreferredTransport(),
|
|
||||||
).resolves.toStrictEqual(backendTransport);
|
expect(discoveredTransport).toStrictEqual(backendTransport);
|
||||||
|
expect(discoveredTransport).not.toStrictEqual(configTransport);
|
||||||
|
|
||||||
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(1);
|
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(1);
|
||||||
expect(wellKnownFetcher).not.toHaveBeenCalled();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("Retries limit_exceeded backend transport over well-known", async () => {
|
it("Retries limit_exceeded backend transport", async () => {
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports
|
client._unstable_getRTCTransports
|
||||||
.mockRejectedValueOnce(
|
.mockRejectedValueOnce(
|
||||||
@@ -116,14 +103,9 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
)
|
)
|
||||||
.mockResolvedValue([backendTransport]);
|
.mockResolvedValue([backendTransport]);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue(makeWellKnown([wellKnownTransport]));
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,7 +114,6 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(2);
|
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(2);
|
||||||
expect(wellKnownFetcher).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const INVALID_TEST_CASES: Array<{ transports: Transport[] }> = [
|
const INVALID_TEST_CASES: Array<{ transports: Transport[] }> = [
|
||||||
@@ -140,67 +121,30 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
{ transports: [{ type: "not_livekit" }] },
|
{ transports: [{ type: "not_livekit" }] },
|
||||||
];
|
];
|
||||||
it.each(INVALID_TEST_CASES)(
|
it.each(INVALID_TEST_CASES)(
|
||||||
"falls back to well-known when backend has no (valid) livekit transports $transports",
|
"falls back to config when backend has no (valid) livekit transports $transports",
|
||||||
async ({ transports }) => {
|
async ({ transports }) => {
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue(makeWellKnown([wellKnownTransport]));
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
resolvedConfig: makeResolvedConfig(configTransport.livekit_service_url),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
const discoveredTransport = await discovery.discoverPreferredTransport();
|
||||||
discovery.discoverPreferredTransport(),
|
expect(discoveredTransport).not.toStrictEqual(backendTransport);
|
||||||
).resolves.toStrictEqual(wellKnownTransport);
|
expect(discoveredTransport).toStrictEqual(configTransport);
|
||||||
|
|
||||||
expect(wellKnownFetcher).toHaveBeenCalledWith("example.org");
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("falls back to app config when backend fails and well-known has no rtc_foci", async () => {
|
it("returns null when backend and config are all unavailable", async () => {
|
||||||
const client = makeClient();
|
|
||||||
client._unstable_getRTCTransports.mockRejectedValue(
|
|
||||||
new MatrixError({ errcode: "M_UNKNOWN" }, 404),
|
|
||||||
);
|
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue({} as IClientWellKnown);
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
|
||||||
client,
|
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(discovery.discoverPreferredTransport()).resolves.toStrictEqual(
|
|
||||||
{
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "https://config.example.org",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns null when backend, well-known and config are all unavailable", async () => {
|
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports.mockResolvedValue([]);
|
client._unstable_getRTCTransports.mockResolvedValue([]);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue({} as IClientWellKnown);
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig(undefined),
|
resolvedConfig: makeResolvedConfig(undefined),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
isLivekitTransportConfig,
|
isLivekitTransportConfig,
|
||||||
type LivekitTransportConfig,
|
type LivekitTransportConfig,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { type IClientWellKnown, type MatrixClient } from "matrix-js-sdk";
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
import type { ResolvedConfigOptions } from "../../../config/ConfigOptions.ts";
|
import type { ResolvedConfigOptions } from "../../../config/ConfigOptions.ts";
|
||||||
@@ -16,33 +16,27 @@ import { doNetworkOperationWithRetry } from "../../../utils/matrix.ts";
|
|||||||
|
|
||||||
type TransportDiscoveryClient = Pick<
|
type TransportDiscoveryClient = Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "_unstable_getRTCTransports"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface RtcTransportAutoDiscoveryProps {
|
export interface RtcTransportAutoDiscoveryProps {
|
||||||
client: TransportDiscoveryClient;
|
client: TransportDiscoveryClient;
|
||||||
resolvedConfig: ResolvedConfigOptions;
|
resolvedConfig: ResolvedConfigOptions;
|
||||||
wellKnownFetcher: (domain: string) => Promise<IClientWellKnown>;
|
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RtcTransportAutoDiscovery {
|
export class RtcTransportAutoDiscovery {
|
||||||
private readonly client: TransportDiscoveryClient;
|
private readonly client: TransportDiscoveryClient;
|
||||||
private readonly resolvedConfig: ResolvedConfigOptions;
|
private readonly resolvedConfig: ResolvedConfigOptions;
|
||||||
private readonly wellKnownFetcher: (
|
|
||||||
domain: string,
|
|
||||||
) => Promise<IClientWellKnown>;
|
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
|
|
||||||
public constructor({
|
public constructor({
|
||||||
client,
|
client,
|
||||||
resolvedConfig,
|
resolvedConfig,
|
||||||
wellKnownFetcher,
|
|
||||||
logger,
|
logger,
|
||||||
}: RtcTransportAutoDiscoveryProps) {
|
}: RtcTransportAutoDiscoveryProps) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.resolvedConfig = resolvedConfig;
|
this.resolvedConfig = resolvedConfig;
|
||||||
this.wellKnownFetcher = wellKnownFetcher;
|
|
||||||
this.logger = logger.getChild("[RtcTransportAutoDiscovery]");
|
this.logger = logger.getChild("[RtcTransportAutoDiscovery]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,21 +50,7 @@ export class RtcTransportAutoDiscovery {
|
|||||||
return backendTransport;
|
return backendTransport;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info("No backend transport found, falling back to well-known");
|
// 2) app config URL
|
||||||
// 2) .well-known transports
|
|
||||||
const wellKnownTransport = await this.tryWellKnownTransports();
|
|
||||||
if (wellKnownTransport) {
|
|
||||||
this.logger.info(
|
|
||||||
`Found .well-known transport: ${wellKnownTransport.livekit_service_url}`,
|
|
||||||
);
|
|
||||||
return wellKnownTransport;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(
|
|
||||||
"No .well-known transport found, falling back to app config",
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3) app config URL
|
|
||||||
const configTransport = this.tryConfigTransport();
|
const configTransport = this.tryConfigTransport();
|
||||||
if (configTransport) {
|
if (configTransport) {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
@@ -110,44 +90,6 @@ export class RtcTransportAutoDiscovery {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the first rtc_foci from the .well-known/matrix/client.
|
|
||||||
* This will not throw errors, but instead just log them and return null if the expected config is not found or malformed.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async tryWellKnownTransports(): Promise<LivekitTransportConfig | null> {
|
|
||||||
// Legacy MSC4143 (to be removed) WELL_KNOWN: Prioritize the .well-known/matrix/client, if available.
|
|
||||||
const client = this.client;
|
|
||||||
const domain = client.getDomain();
|
|
||||||
if (domain) {
|
|
||||||
// we use AutoDiscovery instead of relying on the MatrixClient having already
|
|
||||||
// been fully configured and started
|
|
||||||
|
|
||||||
const wellKnownFoci = await this.wellKnownFetcher(domain);
|
|
||||||
|
|
||||||
const fociConfig = wellKnownFoci["org.matrix.msc4143.rtc_foci"];
|
|
||||||
if (fociConfig) {
|
|
||||||
if (!Array.isArray(fociConfig)) {
|
|
||||||
this.logger.warn(
|
|
||||||
`org.matrix.msc4143.rtc_foci is not an array in .well-known`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return fociConfig[0];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.logger.info(
|
|
||||||
`No .well-known "org.matrix.msc4143.rtc_foci" found for ${domain}`,
|
|
||||||
wellKnownFoci,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Should never happen, but just in case
|
|
||||||
this.logger.warn(`No domain configured for client`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private tryConfigTransport(): LivekitTransportConfig | null {
|
private tryConfigTransport(): LivekitTransportConfig | null {
|
||||||
const url = this.resolvedConfig.livekit?.livekit_service_url;
|
const url = this.resolvedConfig.livekit?.livekit_service_url;
|
||||||
if (url) {
|
if (url) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { i18nKey } from "./i18n";
|
|||||||
|
|
||||||
export enum ErrorCode {
|
export enum ErrorCode {
|
||||||
/**
|
/**
|
||||||
* Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured.
|
* Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured.
|
||||||
*/
|
*/
|
||||||
MISSING_MATRIX_RTC_TRANSPORT = "MISSING_MATRIX_RTC_TRANSPORT",
|
MISSING_MATRIX_RTC_TRANSPORT = "MISSING_MATRIX_RTC_TRANSPORT",
|
||||||
CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
|
CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
|
||||||
@@ -67,7 +67,7 @@ export class ElementCallError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured.
|
* Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured.
|
||||||
*/
|
*/
|
||||||
export class MatrixRTCTransportMissingError extends ElementCallError {
|
export class MatrixRTCTransportMissingError extends ElementCallError {
|
||||||
public domain: string;
|
public domain: string;
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export const initializeWidget = (
|
|||||||
// be otherwise)
|
// be otherwise)
|
||||||
await Config.init();
|
await Config.init();
|
||||||
seedSettingsFromConfig(Config.get().media_quality);
|
seedSettingsFromConfig(Config.get().media_quality);
|
||||||
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
|
await client.startClient();
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user