mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-20 20:49:20 +00:00
Compare commits
1 Commits
robin/webc
...
v0.11.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e9b837fe4 |
31
README.md
31
README.md
@@ -192,6 +192,11 @@ To use it, create a local config by, e.g.,
|
||||
The `config.devenv.json` config should work with the backend development
|
||||
environment as outlined in the next section out of box.
|
||||
|
||||
> [!NOTE]
|
||||
> Be aware, that this `config.devenv.json` is exposing a deprecated fallback
|
||||
> LiveKit config key. If the homeserver advertises SFU backend via
|
||||
> `.well-known/matrix/client` this has precedence.
|
||||
|
||||
You're now ready to launch the development server:
|
||||
|
||||
```sh
|
||||
@@ -207,20 +212,12 @@ See also:
|
||||
A docker compose file `dev-backend-docker-compose.yml` is provided to start the
|
||||
whole stack of components which is required for a local development environment:
|
||||
|
||||
- Minimum Synapse Setup (servername: `synapse.m.localhost`)
|
||||
- LiveKit Authorization Service (Note requires Federation API and hence a TLS reverse proxy)
|
||||
- Minimum Synapse Setup (servername: `synapse.localhost`)
|
||||
- LiveKit JWT Service (Note requires Federation API and hence a TLS reverse proxy)
|
||||
- Minimum TLS reverse proxy (servername: `synapse.localhost`) Note certificates
|
||||
are valid for at least 10 years from now
|
||||
- Minimum LiveKit SFU Setup using dev defaults for config
|
||||
- Redis db for completeness
|
||||
- Minimum `localhost` Certificate Authority (CA) for Transport Layer Security (TLS)
|
||||
- Hostnames: `m.localhost`, `*.m.localhost`
|
||||
- Add [./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt) to your web browsers trusted
|
||||
certificates
|
||||
- Minimum TLS reverse proxy for
|
||||
- Synapse homeserver: `synapse.m.localhost`
|
||||
- MatrixRTC backend: `matrix-rtc.m.localhost`
|
||||
- Local Element Call development `call.m.localhost` via `yarn dev --host `
|
||||
- Element Web `app.m.localhost`
|
||||
- Note certificates will expire on Thu, 03 May 2035 10:32:02 GMT
|
||||
|
||||
These use a test 'secret' published in this repository, so this must be used
|
||||
only for local development and **_never be exposed to the public Internet._**
|
||||
@@ -233,16 +230,6 @@ yarn backend
|
||||
# podman-compose -f dev-backend-docker-compose.yml up
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> To ensure your local development frontend functions properly, you’ll need to
|
||||
> add certificate exceptions in your browser for `https://localhost:3000`,
|
||||
> `https://matrix-rtc.m.localhost/livekit/jwt/healthz` and
|
||||
> `https://synapse.m.localhost/.well-known/matrix/client`. This can be either
|
||||
> done by adding the minimum localhost CA
|
||||
> ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web
|
||||
> browsers trusted certificates or by simply copying and pasting each URL into
|
||||
> your browser’s address bar and follow the prompts to add the exception.
|
||||
|
||||
### Playwright tests
|
||||
|
||||
Our Playwright tests run automatically as part of our CI along with our other
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
server_name: "synapse.m.localhost"
|
||||
public_baseurl: https://synapse.m.localhost/
|
||||
server_name: "synapse.localhost"
|
||||
public_baseurl: http://synapse.localhost:8008/
|
||||
|
||||
pid_file: /data/homeserver.pid
|
||||
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
# Synapse reverse proxy including .well-known/matrix/client
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
listen 443 ssl;
|
||||
listen 8448 ssl;
|
||||
listen [::]:443 ssl;
|
||||
listen [::]:8448 ssl;
|
||||
server_name synapse.m.localhost;
|
||||
ssl_certificate /root/ssl/cert.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
|
||||
# This is also required for development environment.
|
||||
# Reason: the lk-jwt-service uses the federation API for the openid token
|
||||
# verification, which requires TLS
|
||||
location / {
|
||||
proxy_pass "http://homeserver:8008";
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
}
|
||||
|
||||
# MatrixRTC reverse proxy
|
||||
# - MatrixRTC Authorization Service
|
||||
# - LiveKit SFU websocket signaling connection
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
listen 8448 ssl;
|
||||
listen [::]:8448 ssl;
|
||||
server_name matrix-rtc.m.localhost;
|
||||
ssl_certificate /root/ssl/cert.pem;
|
||||
ssl_certificate_key /root/ssl/key.pem;
|
||||
|
||||
|
||||
location ^~ /livekit/jwt/ {
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# JWT Service running at port 8080
|
||||
proxy_pass http://auth-server:8080/;
|
||||
}
|
||||
|
||||
location ^~ /livekit/sfu/ {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_send_timeout 120;
|
||||
proxy_read_timeout 120;
|
||||
proxy_buffering off;
|
||||
|
||||
proxy_set_header Accept-Encoding gzip;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# LiveKit SFU websocket connection running at port 7880
|
||||
proxy_pass http://livekit-sfu:7880/;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
}
|
||||
|
||||
# Convenience reverse proxy for the call.m.localhost domain to yarn dev --host
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name call.m.localhost;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name call.m.localhost;
|
||||
ssl_certificate /root/ssl/cert.pem;
|
||||
ssl_certificate_key /root/ssl/key.pem;
|
||||
|
||||
|
||||
location ^~ / {
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_pass https://host.docker.internal:3000;
|
||||
proxy_ssl_verify off;
|
||||
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
}
|
||||
|
||||
# Convenience reverse proxy app.m.localhost for element web
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name app.m.localhost;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name app.m.localhost;
|
||||
ssl_certificate /root/ssl/cert.pem;
|
||||
ssl_certificate_key /root/ssl/key.pem;
|
||||
|
||||
|
||||
location ^~ / {
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_pass http://element-web:81;
|
||||
proxy_ssl_verify off;
|
||||
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDGjCCAgKgAwIBAgIUGdiFHhH4KL2pqBjMQHQ+PVIkSV8wDQYJKoZIhvcNAQEL
|
||||
BQAwHjEcMBoGA1UEAwwTRWxlbWVudCBDYWxsIERldiBDQTAeFw0yNTA1MDUxMDMy
|
||||
MDJaFw0zNTA1MDMxMDMyMDJaMB4xHDAaBgNVBAMME0VsZW1lbnQgQ2FsbCBEZXYg
|
||||
Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDA2y0hjmNn1vRsVSdy
|
||||
8IOfo8N1q9UgkhQWpGKXzPh+D5d1fnuJEmHIVwtDEtS/PwQ43LTmegChPtKH9jdT
|
||||
tG0IihW9Ja5YNG+9xAwaoA/sB3CGCBYsz+2/XjVUpXoBJXIPoFBWsn+K0oeFw9fw
|
||||
eRO1z9abM4cl+LjKzMNM8CCyu9uI1MaGjYez2YIWvG854VucLxX7HSlMJxZNWnie
|
||||
Ui7fMakuJhB2+aiIQjdKxy4E5RHNhzYG/LXhvP+wBYBDPNRsP3rtzEaE9HAveL9K
|
||||
FGqd3R4cBia6r1WIXmpAzyu5RGP5Eou0TZlGkal96/bF0I7q/pKlL23Jt1BLPiQU
|
||||
KGKrAgMBAAGjUDBOMB0GA1UdDgQWBBQJqBjMu61c1p24txw/y+kv3D+V6DAfBgNV
|
||||
HSMEGDAWgBQJqBjMu61c1p24txw/y+kv3D+V6DAMBgNVHRMEBTADAQH/MA0GCSqG
|
||||
SIb3DQEBCwUAA4IBAQB8m2YfFGLugNt5vAAOvNxVqDA8c72yCVYr3CBCpmTIEY5Z
|
||||
d3qVGhG9//ux6+J8ntkSwd9nV5GJyYXHukCG1VavnAWolWdNF/WAllf0jhLuz7kD
|
||||
/cJnuI1By4tBsBmSz851i6HJ4t5k99Be+6GQVzi0e7zzfxTHZE4xP2J6Ox8QbPsP
|
||||
n0m76nIp/WbWaJqzvIIjJhmUUPPv+4wN+eOArgjiGLzptM2qTtGZtd0c9nS5gvep
|
||||
+mEbSUN9zkhAroZf80wf+hEvy+fJ94VbZ9QjTzTg7odZLrsXGIe8DaG63EYRQ25b
|
||||
W5iYBAreln5fGSt7qHsGfqwZibTEk/Lx3dydO1Kg
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDA2y0hjmNn1vRs
|
||||
VSdy8IOfo8N1q9UgkhQWpGKXzPh+D5d1fnuJEmHIVwtDEtS/PwQ43LTmegChPtKH
|
||||
9jdTtG0IihW9Ja5YNG+9xAwaoA/sB3CGCBYsz+2/XjVUpXoBJXIPoFBWsn+K0oeF
|
||||
w9fweRO1z9abM4cl+LjKzMNM8CCyu9uI1MaGjYez2YIWvG854VucLxX7HSlMJxZN
|
||||
WnieUi7fMakuJhB2+aiIQjdKxy4E5RHNhzYG/LXhvP+wBYBDPNRsP3rtzEaE9HAv
|
||||
eL9KFGqd3R4cBia6r1WIXmpAzyu5RGP5Eou0TZlGkal96/bF0I7q/pKlL23Jt1BL
|
||||
PiQUKGKrAgMBAAECggEAAPX2kxi5AQ7ul82SzT1KgpSXyDHLdYaUyAoYnaX9RO+B
|
||||
8ylmpyeqygs4+KQS4EMJm9jpo85Oy37bIKdG3kljU6wQcKlL5Y+ZUOo1nzpV6fid
|
||||
hGVs6ts8VXw8KshKQ9AyccZ8L/pirUfgOffgTwfjY7/90zceAL/s98GuZWc62nkX
|
||||
55joQv/OikqYfAGP/U6Bp2Zyf23DwJB09Z3B6NnZj/ZyAbDrDEHuA15LhCOcCczp
|
||||
IU/mFEywBPHT9Tg4w4Beq78PeAETvku2UalYRLhP3RLlXr2oEbwUtINRVt2QjZ85
|
||||
Esps4uCqL/mgQluIebtudD9HL/YMlNPXue1mDXFxJQKBgQDgZZY4yJBcf488T1V6
|
||||
HNm06b/LvVGj253pKgw14hpY1xQu3Ymgzv1GEqzhSYdzxhpmj0tMUNHxAp+YdGQu
|
||||
SZ0wcPKhw0aYVkIjDRYDC3Wn5GJhyIEYHGYMo/n4l49UzHRBPOTDzp49DkHTKBgh
|
||||
XgIIazYT3CkjTIMRrkUv+qfIPQKBgQDcBGu/mqbjxs4sN3zqPS4aB21o6t6W0sXs
|
||||
ZP9w6RlTPQi5U2oRbftjZtYc0bbEgkMUImB1HwYPQT5pJ+MyC414xDvSc2exBr5d
|
||||
To6yyPIy78Tf5PHM12fpKV92nSvoz/pSjYcGxxDtKfPqu+t8mOJfjCV1lLLA+xuB
|
||||
DDaE4p8dBwKBgQCdAne6A5v/HMH8UQZeCxHJpESvKiiVnnU/UEx651nID7XvlNNX
|
||||
0X0mKqsMd4ZvW43ddSYan/JF0LAa3FW8jYWO/3jF9vzOWoysOdvNBZetgf/Uq5ao
|
||||
aDZ/YbzmVCXWD7jIbPMkjs3pqrAkL0mzDzQc7+dGviWKrV6IYIfIqnn7gQKBgDCz
|
||||
vdIk/qpO+JZrFfiX4Fucp0hhLTJ/p5ZDaRPqVVPKn+K+Jy2ChfIj8mNgvK9VEloj
|
||||
nexvGJ1J2PHYBX+vdPp1nbRhHWPfVUY8PHQw7QP/dToGaMvqJrNDGEGeWvjnCMc7
|
||||
UtdaO1H0Rm0AegkTopB56lTTvJnhO95eALd7nrMDAoGAEPdzJtWoKafp49svhSj0
|
||||
hiXQv2SPBwVUN4LZ4SOWiXUcmYYm80aNpYKLkBxYjrfqFWhE7NUHLGp8YorQWKY2
|
||||
acD9AReHk/xku0ABy6jeYmSCmCxASxst5liKD+l12sk0gB0rk5MBxB4Uu1MIbQZ2
|
||||
aCASX3AVD2/XyC2MKkzc8Eg=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,21 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDZzCCAk+gAwIBAgIUXizLjwkdqepX0bh0K3abeJxj68IwDQYJKoZIhvcNAQEL
|
||||
BQAwHjEcMBoGA1UEAwwTRWxlbWVudCBDYWxsIERldiBDQTAeFw0yNTA1MDUxMzU5
|
||||
MTFaFw0zNTA1MDMxMzU5MTFaMBgxFjAUBgNVBAMMDSoubS5sb2NhbGhvc3QwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrzGSScSgaQuZdELGFYiLiYRwr
|
||||
LKyUdNr0rsPcOo0bvbeZ3zQMeUMRNlA69zGFdarumiDRXUoAmZI39WmH95aX3d+A
|
||||
U7EFnWev7xpWSVhSYj8T0d4rke8HjGk3LpaffJ93tbJuagBIH1ouuN6AOdzWs8hp
|
||||
RYIomWleEeeuVnnfaMwaXOdc+ihJJ6wzm2hwQSfdpjZPWBDd/DFft1ZXxIZOCjDs
|
||||
rEIiI7uU8iZPLB3QEM/tgxSSAOxrcKvQvxZokk+FD7aMJFP71IfieLCEzMTP1VXa
|
||||
tP7UTAKAqB2NyDJ8m3IHbOINiqcdFvFR3R1D9bXOYE4oRynNvYZrQUGnL2RtAgMB
|
||||
AAGjgaIwgZ8wHwYDVR0jBBgwFoAUCagYzLutXNaduLccP8vpL9w/legwCQYDVR0T
|
||||
BAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwMAYDVR0RBCkw
|
||||
J4IJbG9jYWxob3N0ggttLmxvY2FsaG9zdIINKi5tLmxvY2FsaG9zdDAdBgNVHQ4E
|
||||
FgQUfdh1p52ZgWyZcBgBXGwKi4EnUE0wDQYJKoZIhvcNAQELBQADggEBAKrHEuB6
|
||||
33j8+EwSHw3zrvt/DRXK2BDHI1Ir9JcztSunaKAjZXVvf/dvZp0Xs1dEdJIdnv6G
|
||||
iZYhBbOqDqpQZbf2h/h0kuu5yZSBUdnQXnYNxlhp2UaC/UEgw5iZT/p1rm7RjVie
|
||||
y4Dp2WytV5iZOLmLj6xDvd3DXazgJPWIRX8p8qJZbKTkwCjTr7nDIj8jjG1sVFf7
|
||||
1RJBO5/6WSnImrpDmlLUrvjiKvbxcdseDJyBOhTwdRdSk4S2M+s5tR5j2I1gXLOq
|
||||
J5ioN76+SCrTY0K0WKRy9oOXWO1/X3+VYcekp+0F3SGkd5w17jylCv1XIGHAdEsQ
|
||||
v2z2/aMI/7sAD2Q=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrzGSScSgaQuZd
|
||||
ELGFYiLiYRwrLKyUdNr0rsPcOo0bvbeZ3zQMeUMRNlA69zGFdarumiDRXUoAmZI3
|
||||
9WmH95aX3d+AU7EFnWev7xpWSVhSYj8T0d4rke8HjGk3LpaffJ93tbJuagBIH1ou
|
||||
uN6AOdzWs8hpRYIomWleEeeuVnnfaMwaXOdc+ihJJ6wzm2hwQSfdpjZPWBDd/DFf
|
||||
t1ZXxIZOCjDsrEIiI7uU8iZPLB3QEM/tgxSSAOxrcKvQvxZokk+FD7aMJFP71Ifi
|
||||
eLCEzMTP1VXatP7UTAKAqB2NyDJ8m3IHbOINiqcdFvFR3R1D9bXOYE4oRynNvYZr
|
||||
QUGnL2RtAgMBAAECggEAJaFQii8U/KOYt9vXNoMnZvSkaeSQLLhn2V6Kciu1CtWE
|
||||
aMTWLsFE6nk+G5xXkYcTmM3T0GghtH3u5CjyI6EcsEkeEorCZJt0wbmayDmqiekR
|
||||
LfMzOdHuTHX5+edPgMGYYG1BFyRKyYFsjH1b5zRFZhXdGQnrl5760GsVlz9D1KZQ
|
||||
iHcT+q1S2tmZeoUukQnADENKXUMCyTGM5FCddgNtsWnGDsTDayh7hUdvDkB+mW4G
|
||||
lSp+BZuc3PCwpbD6qkXvfugWs6CUAAtXoV3ceWgxQ+TEnNlwxaG1AyugfgNUBolk
|
||||
8xgeZt4r5QId03jsHDf7hpBAofcaCd5EMIIQYFvWoQKBgQDlbAvAzEFPTZZn2nRV
|
||||
Xagw4xjqVc1LLEKLCWq0N5rEkwn0h90Dz5N7/3NuonP/sIDsDHCbyiOYBI1Ck6Xi
|
||||
0WuB+OyKDh+xeF2mekN9G9ywPahdK5lT/TVsxXFyZlwtVv1x/6KBO4yv5URizxqU
|
||||
gyAPDDxfD/KcNjkOBaodWEwQGQKBgQC/s2gPDBtQkjLwkHXchBomLww5eLlVrac1
|
||||
WK4UX6uSdOgrjJ375OOgMTxe8NVZdOuAKytGXRWDwgH3nVWvuZhe7dGlX3JMuSer
|
||||
e9VwDpBESrvqcR4ruL6wm8wej6BXyjH0wD3FHb0S5HfuBDxTn+4bDwrbRzOUMNgy
|
||||
lSppuflxdQKBgQDiZcIfazFT8evn5nMAvuC4BZNTxIJHmZC9JfjPiUPIkpWzYtOe
|
||||
7BvNtKOT3Op9uw8uYYRKqKqBXJSNy6ha8XCXHS9HeXKbLn20SFkLQBCDNwVLlDfF
|
||||
40zyXtF6JDr4XyzSb4NM5pgKCER5AYloXxGm59s3sEQpFXUuOjbKqJS/GQKBgAoI
|
||||
c7vF4HAZFr1sch62cz/oWnVvkhOf4Q5zs7ixQSOLJtOQqnwSgK9TpFs7s47ZBbJR
|
||||
kBRAru2Ua9Hv1Bo8VnMxczV6h1roneDlvEf/GyHX33nnrbKQGrrXjJlU3wl5NaAf
|
||||
p5v3cHvapUQ5yIZ/6lBUOzc6xMJOxCHxmKSr7Rg5AoGAbEE4lt6Xh2dnBPJ81eNI
|
||||
IDrw/3ITY53qAY4Bx88CByIFuu8CEUdUZprh98jSl6ic1tMinZfUhRMwABLrUD51
|
||||
DGst8iGLPD9u83iMcUHI/L+p7AbxrKLvWXZrF5UZm440c9mSWqfhPaTBosPtNDsG
|
||||
LfETwH1flKXMTXd2xA9RTE4=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Step 1: Create a Root CA key and cert
|
||||
openssl genrsa -out dev_tls_local-ca.key 2048
|
||||
openssl req -x509 -new -nodes \
|
||||
-days 3650 \
|
||||
-subj "/CN=Element Call Dev CA" \
|
||||
-key dev_tls_local-ca.key \
|
||||
-out dev_tls_local-ca.crt \
|
||||
-sha256 -addext "basicConstraints=CA:TRUE"
|
||||
|
||||
# Step 2: Create a private key and CSR for *.m.localhost
|
||||
openssl req -new -nodes -newkey rsa:2048 \
|
||||
-keyout dev_tls_m.localhost.key \
|
||||
-out dev_tls_m.localhost.csr \
|
||||
-subj "/CN=*.m.localhost"
|
||||
|
||||
# Step 3: Sign the CSR with your CA
|
||||
openssl x509 \
|
||||
-req -in dev_tls_m.localhost.csr \
|
||||
-CA dev_tls_local-ca.crt -CAkey dev_tls_local-ca.key \
|
||||
-CAcreateserial \
|
||||
-out dev_tls_m.localhost.crt \
|
||||
-days 3650 \
|
||||
-sha256 \
|
||||
-extfile <( cat <<EOF
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
basicConstraints=CA:FALSE
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
DNS.2 = m.localhost
|
||||
DNS.3 = *.m.localhost
|
||||
EOF
|
||||
)
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"default_server_config": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://synapse.m.localhost",
|
||||
"server_name": "synapse.m.localhost"
|
||||
"base_url": "http://synapse.localhost:8008",
|
||||
"server_name": "synapse.localhost"
|
||||
}
|
||||
},
|
||||
"disable_custom_urls": false,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
server_name: "synapse.m.localhost"
|
||||
public_baseurl: https://synapse.m.localhost/
|
||||
server_name: "synapse.localhost"
|
||||
public_baseurl: http://synapse.localhost:8008/
|
||||
|
||||
pid_file: /data/homeserver.pid
|
||||
|
||||
|
||||
22
backend/tls_localhost_cert.pem
Normal file
22
backend/tls_localhost_cert.pem
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtzCCAp+gAwIBAgIUCmJjl3HAeLmrPwRg+/OzikW6peQwDQYJKoZIhvcNAQEL
|
||||
BQAwazELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9u
|
||||
ZG9uMQ4wDAYDVQQKDAVBbHJvczEWMBQGA1UECwwNSVQgRGVwYXJ0bWVudDESMBAG
|
||||
A1UEAwwJbG9jYWxob3N0MB4XDTI0MTEwNDIxNDcwMFoXDTM0MTEwMjIxNDcwMFow
|
||||
azELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9u
|
||||
MQ4wDAYDVQQKDAVBbHJvczEWMBQGA1UECwwNSVQgRGVwYXJ0bWVudDESMBAGA1UE
|
||||
AwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs368
|
||||
ExLSudP8luNoY5UfaPqBSVJUPYBi+JGyd36tyN75p5OI7xSfHTttQxuD4KrExBFP
|
||||
C8mAhE1eoZPBVBOZJ4FYWBJfMaQnCjeqU+laP36td65kSJYbUYlKYH1WpxEpCdgx
|
||||
wWOKkP/kPX5YXbYqODx9aBJXgoT3yAJW7AniIoL+eLFnS9Xo86TPqCDBTJU9ocwK
|
||||
gPIDLhDv60724rhZT1kbGp7ECqRovndoDTQjuws2D3yNMfQ+4rrQGPXHGmP5PcaR
|
||||
0R7uueB+6APyC7MJbuhbxxg/+DFHrRi3lJsgwxuh2hi/+vWw8zgKlgYIwHFA9X0l
|
||||
cX0UlQdENMH3bgcGIwIDAQABo1MwUTAdBgNVHQ4EFgQUUFGxw7zoiHXGwRqtagjZ
|
||||
RPYc85cwHwYDVR0jBBgwFoAUUFGxw7zoiHXGwRqtagjZRPYc85cwDwYDVR0TAQH/
|
||||
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEALokb1z2lu3qW141b2wm14ilZQKCZ
|
||||
reNNuUR95Uom96FXPH4QVEH+mYTXXJ5UrfNhQYKQFpdE+5S4HL/UqEOxtWvbAHpK
|
||||
nsLQ62J8m+0+uwiJGqeQpWr03KJgXDAVE9X3XwMlp/+buxSLhc+GIHWuXW56itV2
|
||||
jiZJYjhO5SnhhgTWNoVZk93qXuuWEN0yacw7c3Fr1IvFYYYWufbXTk70dbZihPDK
|
||||
VD141o8tpp6FerSKHNYDqkVFDyTz3DVOhQQJ59zfMre7bFr+PpTTl4vIuGzXEY+E
|
||||
HPjUSlOzwkCoh5fu7Fs3qG55rJt8akhTEoKpiBTaLucgAjVWNHeci1+Yxg==
|
||||
-----END CERTIFICATE-----
|
||||
28
backend/tls_localhost_key.pem
Normal file
28
backend/tls_localhost_key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzfrwTEtK50/yW
|
||||
42hjlR9o+oFJUlQ9gGL4kbJ3fq3I3vmnk4jvFJ8dO21DG4PgqsTEEU8LyYCETV6h
|
||||
k8FUE5kngVhYEl8xpCcKN6pT6Vo/fq13rmRIlhtRiUpgfVanESkJ2DHBY4qQ/+Q9
|
||||
flhdtio4PH1oEleChPfIAlbsCeIigv54sWdL1ejzpM+oIMFMlT2hzAqA8gMuEO/r
|
||||
TvbiuFlPWRsansQKpGi+d2gNNCO7CzYPfI0x9D7iutAY9ccaY/k9xpHRHu654H7o
|
||||
A/ILswlu6FvHGD/4MUetGLeUmyDDG6HaGL/69bDzOAqWBgjAcUD1fSVxfRSVB0Q0
|
||||
wfduBwYjAgMBAAECggEACTqdSExxzJ+LX5ARFaWyOBSWly2GKqSyR14+aInOklhx
|
||||
9QgkmfOxJrCf3TvJ8RWhXloW0Aqr8qGDxG0Ixgjn7rG7gskXCey1xn8MNppLS0kj
|
||||
ztaG+NB3AR89ABm8XdoHsSY45geh3/Ni9I0i1VardGQafUJhgNLTZqjwIodzkBtJ
|
||||
S/bi4uFk1lGNfuvWQvWqzGXUvd1l1YupV6iA4GfhXlUvrSBZwftLBD6xEvQaSqsA
|
||||
pHvBxTfMXG4RMAkNPDIElkuQ8++CGi1gIRkJfmrv4OgbbitteMnxqqqGYV0zSNCg
|
||||
R/5FG6umIV7lDLBHZCSCk7wmfmq2UUvzhHThHy4yMQKBgQDu4TwFJCIcVIj7Wj4r
|
||||
DUBFvz6Lgbltqb+YAMUBtpiDcAQxDJWmedh6dK04ts5CFAFRlRjjuz2uFn7qlVBm
|
||||
uye9R7tL+tOv5viqDXU78a4snFywoXub6yzpbxrW8B4W1pdIUvQmhwCcDwvO1V24
|
||||
7Vj2vxcM5I9dsk1aCQSi3VY5yQKBgQDAW/VoTRwhU6OUc6sji5Z5dnkMjkP6NZK9
|
||||
CSrTWLAMGaLPY+g6fFS7JMNSvfWm/okypD6rcN7p0cxMK3mfFKmMiyPRde0wdrci
|
||||
sGFjGxM/2d2D7KTMC9iMYwA0K17UIna+UiYPfhR/muIg/dCyjlkKDFs9Z4jk//r1
|
||||
91bmznt2iwKBgFdiYXhn/Wprqih4nKFXGZnqGdEixVhObl4GegrkZuo+AeqHdf8O
|
||||
N5ikMfG7PbyCYPEdH5u/FRMn+4mI0X6jHChroyJqQSHp1jEu9yHUiSicknOyvusM
|
||||
nsNN932FHRyxp2m3nsSxQhHUlzc0ajKJ8K9iu+XlfmSCIzW6cs25Nh+xAoGBAJro
|
||||
M0wIdPPdsCj3sUVRvx8XqknTM6kGhaIYBNXoYPWNm5BaC4U15OJEq8sxUOdnqcMP
|
||||
g6x6m/k+S8C3bh0O/a9Bydl/l0BlCfw0gGjYP/s2ju4Tn272xy/e9iYNGzPIgUmp
|
||||
TB9D0GwmpZ4d6HgyrD+sTbm4bATGpCp6QhBjDggbAoGBAJVMMtZ4pF8D6mLMRZGR
|
||||
pQjNPy+MH13XYmDRc/BSF8KJ4yKk3tohr9LSXzxR0SEB43NoL1bHkucZrNjGyL8x
|
||||
jktnwkoIs96kO2mPrl1TqWkXs5RjGkkSTbAJovIcvkRU31SWap/WzN2kHpmRVcQc
|
||||
KEFKXT5fUYZCLLWxhgZFlGPp
|
||||
-----END PRIVATE KEY-----
|
||||
40
backend/tls_localhost_nginx.conf
Normal file
40
backend/tls_localhost_nginx.conf
Normal file
@@ -0,0 +1,40 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
listen 443 ssl;
|
||||
listen 8448 ssl;
|
||||
listen [::]:443 ssl;
|
||||
listen [::]:8448 ssl;
|
||||
server_name synapse.localhost;
|
||||
ssl_certificate /root/ssl/cert.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 {
|
||||
return 200 '{"m.homeserver": {"base_url": "http://synapse.localhost:8008"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "http://localhost:8080"}]}';
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
|
||||
# Reverse proxy for Matrix Synapse Homeserver
|
||||
# This is also required for development environment.
|
||||
# Reason: the lk-jwt-service uses the federation API for the openid token
|
||||
# verification, which requires TLS
|
||||
location / {
|
||||
proxy_pass "http://homeserver:8008";
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
{
|
||||
"default_server_config": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://synapse.m.localhost",
|
||||
"server_name": "synapse.m.localhost"
|
||||
"base_url": "http://synapse.localhost:8008",
|
||||
"server_name": "synapse.localhost"
|
||||
}
|
||||
},
|
||||
"livekit": {
|
||||
"livekit_service_url": "http://localhost:8009"
|
||||
},
|
||||
"features": {
|
||||
"feature_use_device_session_member_events": true
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
hostname: auth-server
|
||||
environment:
|
||||
- LK_JWT_PORT=8080
|
||||
- LIVEKIT_URL=wss://matrix-rtc.m.localhost/livekit/sfu
|
||||
- LIVEKIT_URL=ws://localhost:7880
|
||||
- LIVEKIT_KEY=devkey
|
||||
- LIVEKIT_SECRET=secret
|
||||
# If the configured homeserver runs on localhost, it'll probably be using
|
||||
@@ -18,13 +18,12 @@ services:
|
||||
condition: on-failure
|
||||
ports:
|
||||
# HOST_PORT:CONTAINER_PORT
|
||||
- 8080:8080
|
||||
- 8009:8080
|
||||
networks:
|
||||
- ecbackend
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:latest
|
||||
hostname: livekit-sfu
|
||||
command: --dev --config /etc/livekit.yaml
|
||||
restart: unless-stopped
|
||||
# The SFU seems to work far more reliably when we let it share the host
|
||||
@@ -82,22 +81,17 @@ services:
|
||||
- ecbackend
|
||||
|
||||
nginx:
|
||||
# see backend/dev_tls_setup for how to generate the tls certs
|
||||
hostname: synapse.m.localhost
|
||||
# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls_localhost_key.pem -out tls_localhost_cert.pem -subj "/C=GB/ST=London/L=London/O=Alros/OU=IT Department/CN=localhost"
|
||||
hostname: synapse.localhost
|
||||
image: nginx:latest
|
||||
volumes:
|
||||
- ./backend/dev_nginx.conf:/etc/nginx/conf.d/default.conf:Z
|
||||
- ./backend/dev_tls_m.localhost.key:/root/ssl/key.pem:Z
|
||||
- ./backend/dev_tls_m.localhost.crt:/root/ssl/cert.pem:Z
|
||||
- ./backend/tls_localhost_nginx.conf:/etc/nginx/conf.d/default.conf:Z
|
||||
- ./backend/tls_localhost_key.pem:/root/ssl/key.pem:Z
|
||||
- ./backend/tls_localhost_cert.pem:/root/ssl/cert.pem:Z
|
||||
ports:
|
||||
# HOST_PORT:CONTAINER_PORT
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8008:80"
|
||||
- "4443:443"
|
||||
- "8448:8448"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
- synapse
|
||||
networks:
|
||||
|
||||
4
knip.ts
4
knip.ts
@@ -27,10 +27,6 @@ export default {
|
||||
// then Knip will flag it as a false positive
|
||||
// https://github.com/webpro-nl/knip/issues/766
|
||||
"@vector-im/compound-web",
|
||||
// We need this so that TypeScript is happy with @livekit/track-processors.
|
||||
// This might be a bug in the LiveKit repo but for now we fix it on the
|
||||
// Element Call side.
|
||||
"@types/dom-mediacapture-transform",
|
||||
"matrix-widget-api",
|
||||
],
|
||||
ignoreExportsUsedInFile: true,
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/dom-mediacapture-transform": "^0.1.11",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
@@ -78,6 +77,7 @@
|
||||
"@use-gesture/react": "^10.2.11",
|
||||
"@vector-im/compound-design-tokens": "^3.0.0",
|
||||
"@vector-im/compound-web": "^7.2.0",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
"@vitest/coverage-v8": "^3.0.0",
|
||||
"babel-plugin-transform-vite-meta-env": "^1.0.3",
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
services:
|
||||
synapse:
|
||||
volumes:
|
||||
- ./backend/playwright_homeserver.yaml:/data/cfg/homeserver.yaml:Z
|
||||
@@ -1,2 +1,97 @@
|
||||
include:
|
||||
- dev-backend-docker-compose.yml
|
||||
networks:
|
||||
ecbackend:
|
||||
|
||||
services:
|
||||
auth-service:
|
||||
image: ghcr.io/element-hq/lk-jwt-service:latest-ci
|
||||
hostname: auth-server
|
||||
environment:
|
||||
- LK_JWT_PORT=8080
|
||||
- LIVEKIT_URL=ws://localhost:7880
|
||||
- LIVEKIT_KEY=devkey
|
||||
- LIVEKIT_SECRET=secret
|
||||
# If the configured homeserver runs on localhost, it'll probably be using
|
||||
# a self-signed certificate
|
||||
- LIVEKIT_INSECURE_SKIP_VERIFY_TLS=YES_I_KNOW_WHAT_I_AM_DOING
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
ports:
|
||||
# HOST_PORT:CONTAINER_PORT
|
||||
- 8009:8080
|
||||
networks:
|
||||
- ecbackend
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:latest
|
||||
command: --dev --config /etc/livekit.yaml
|
||||
restart: unless-stopped
|
||||
# The SFU seems to work far more reliably when we let it share the host
|
||||
# network rather than opening specific ports (but why?? we're not missing
|
||||
# any…)
|
||||
ports:
|
||||
# HOST_PORT:CONTAINER_PORT
|
||||
- 7880:7880/tcp
|
||||
- 7881:7881/tcp
|
||||
- 7882:7882/tcp
|
||||
- 50100-50200:50100-50200/udp
|
||||
volumes:
|
||||
- ./backend/dev_livekit.yaml:/etc/livekit.yaml:Z
|
||||
networks:
|
||||
- ecbackend
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
command: redis-server /etc/redis.conf
|
||||
ports:
|
||||
# HOST_PORT:CONTAINER_PORT
|
||||
- 6379:6379
|
||||
volumes:
|
||||
- ./backend/redis.conf:/etc/redis.conf:Z
|
||||
networks:
|
||||
- ecbackend
|
||||
|
||||
element-web:
|
||||
image: ghcr.io/element-hq/element-web:develop
|
||||
volumes:
|
||||
- ./backend/ew.test.config.json:/app/config.json
|
||||
environment:
|
||||
ELEMENT_WEB_PORT: 81
|
||||
ports:
|
||||
- "8081:81"
|
||||
networks:
|
||||
- ecbackend
|
||||
|
||||
synapse:
|
||||
hostname: homeserver
|
||||
image: docker.io/matrixdotorg/synapse:latest
|
||||
environment:
|
||||
- SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml
|
||||
# Needed for rootless podman-compose such that the uid/gid mapping does
|
||||
# fit local user uid. If the container runs as root (uid 0) it is fine as
|
||||
# it actually maps to your non-root user on the host (e.g. 1000).
|
||||
# Otherwise uid mapping will not match your non-root user.
|
||||
- UID=0
|
||||
- GID=0
|
||||
volumes:
|
||||
- ./backend/synapse_tmp:/data:Z
|
||||
- ./backend/playwright_homeserver.yaml:/data/cfg/homeserver.yaml:Z
|
||||
networks:
|
||||
- ecbackend
|
||||
|
||||
nginx:
|
||||
# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls_localhost_key.pem -out tls_localhost_cert.pem -subj "/C=GB/ST=London/L=London/O=Alros/OU=IT Department/CN=localhost"
|
||||
hostname: synapse.localhost
|
||||
image: nginx:latest
|
||||
volumes:
|
||||
- ./backend/tls_localhost_nginx.conf:/etc/nginx/conf.d/default.conf:Z
|
||||
- ./backend/tls_localhost_key.pem:/root/ssl/key.pem:Z
|
||||
- ./backend/tls_localhost_cert.pem:/root/ssl/cert.pem:Z
|
||||
ports:
|
||||
# HOST_PORT:CONTAINER_PORT
|
||||
- "8008:80"
|
||||
- "4443:443"
|
||||
depends_on:
|
||||
- synapse
|
||||
networks:
|
||||
- ecbackend
|
||||
|
||||
146
src/@types/dom-mediacapture-transform.d.ts
vendored
Normal file
146
src/@types/dom-mediacapture-transform.d.ts
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
/* eslint-disable */
|
||||
// The contents of this file below the line are copied from
|
||||
// @types/dom-mediacapture-transform, which is inlined here into Element Call so
|
||||
// that we can apply the patch to @types/dom-webcodecs found in
|
||||
// ./dom-webcodecs.d.ts, which it depends on.
|
||||
// (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72625)
|
||||
// Once that PR is merged and released, we can remove this file and return to
|
||||
// depending on @types/dom-mediacapture-transform.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// This project is licensed under the MIT license.
|
||||
// Copyrights are respective of each contributor listed at the beginning of each definition file.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// In general, these types are only available behind a command line flag or an origin trial in
|
||||
// Chrome 90+.
|
||||
|
||||
// This API depends on WebCodecs.
|
||||
|
||||
// Versioning:
|
||||
// Until the above-mentioned spec is finalized, the major version number is 0. Although not
|
||||
// necessary for version 0, consider incrementing the minor version number for breaking changes.
|
||||
|
||||
// The following modify existing DOM types to allow defining type-safe APIs on audio and video tracks.
|
||||
|
||||
/** Specialize MediaStreamTrack so that we can refer specifically to an audio track. */
|
||||
interface MediaStreamAudioTrack extends MediaStreamTrack {
|
||||
readonly kind: "audio";
|
||||
clone(): MediaStreamAudioTrack;
|
||||
}
|
||||
|
||||
/** Specialize MediaStreamTrack so that we can refer specifically to a video track. */
|
||||
interface MediaStreamVideoTrack extends MediaStreamTrack {
|
||||
readonly kind: "video";
|
||||
clone(): MediaStreamVideoTrack;
|
||||
}
|
||||
|
||||
/** Assert that getAudioTracks and getVideoTracks return the tracks with the appropriate kind. */
|
||||
interface MediaStream {
|
||||
getAudioTracks(): MediaStreamAudioTrack[];
|
||||
getVideoTracks(): MediaStreamVideoTrack[];
|
||||
}
|
||||
|
||||
// The following were originally generated from the spec using
|
||||
// https://github.com/microsoft/TypeScript-DOM-lib-generator, then heavily modified.
|
||||
|
||||
/**
|
||||
* A track sink that is capable of exposing the unencoded frames from the track to a
|
||||
* ReadableStream, and exposes a control channel for signals going in the oppposite direction.
|
||||
*/
|
||||
interface MediaStreamTrackProcessor<T extends AudioData | VideoFrame> {
|
||||
/**
|
||||
* Allows reading the frames flowing through the MediaStreamTrack provided to the constructor.
|
||||
*/
|
||||
readonly readable: ReadableStream<T>;
|
||||
/** Allows sending control signals to the MediaStreamTrack provided to the constructor. */
|
||||
readonly writableControl: WritableStream<MediaStreamTrackSignal>;
|
||||
}
|
||||
|
||||
declare var MediaStreamTrackProcessor: {
|
||||
prototype: MediaStreamTrackProcessor<any>;
|
||||
|
||||
/** Constructor overrides based on the type of track. */
|
||||
new (
|
||||
init: MediaStreamTrackProcessorInit & { track: MediaStreamAudioTrack },
|
||||
): MediaStreamTrackProcessor<AudioData>;
|
||||
new (
|
||||
init: MediaStreamTrackProcessorInit & { track: MediaStreamVideoTrack },
|
||||
): MediaStreamTrackProcessor<VideoFrame>;
|
||||
};
|
||||
|
||||
interface MediaStreamTrackProcessorInit {
|
||||
track: MediaStreamTrack;
|
||||
/**
|
||||
* If media frames are not read from MediaStreamTrackProcessor.readable quickly enough, the
|
||||
* MediaStreamTrackProcessor will internally buffer up to maxBufferSize of the frames produced
|
||||
* by the track. If the internal buffer is full, each time the track produces a new frame, the
|
||||
* oldest frame in the buffer will be dropped and the new frame will be added to the buffer.
|
||||
*/
|
||||
maxBufferSize?: number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes video frames as input, and emits control signals that result from subsequent processing.
|
||||
*/
|
||||
interface MediaStreamTrackGenerator<T extends AudioData | VideoFrame>
|
||||
extends MediaStreamTrack {
|
||||
/**
|
||||
* Allows writing media frames to the MediaStreamTrackGenerator, which is itself a
|
||||
* MediaStreamTrack. When a frame is written to writable, the frame’s close() method is
|
||||
* automatically invoked, so that its internal resources are no longer accessible from
|
||||
* JavaScript.
|
||||
*/
|
||||
readonly writable: WritableStream<T>;
|
||||
/**
|
||||
* Allows reading control signals sent from any sinks connected to the
|
||||
* MediaStreamTrackGenerator.
|
||||
*/
|
||||
readonly readableControl: ReadableStream<MediaStreamTrackSignal>;
|
||||
}
|
||||
|
||||
type MediaStreamAudioTrackGenerator = MediaStreamTrackGenerator<AudioData> &
|
||||
MediaStreamAudioTrack;
|
||||
type MediaStreamVideoTrackGenerator = MediaStreamTrackGenerator<VideoFrame> &
|
||||
MediaStreamVideoTrack;
|
||||
|
||||
declare var MediaStreamTrackGenerator: {
|
||||
prototype: MediaStreamTrackGenerator<any>;
|
||||
|
||||
/** Constructor overrides based on the type of track. */
|
||||
new (
|
||||
init: MediaStreamTrackGeneratorInit & {
|
||||
kind: "audio";
|
||||
signalTarget?: MediaStreamAudioTrack | undefined;
|
||||
},
|
||||
): MediaStreamAudioTrackGenerator;
|
||||
new (
|
||||
init: MediaStreamTrackGeneratorInit & {
|
||||
kind: "video";
|
||||
signalTarget?: MediaStreamVideoTrack | undefined;
|
||||
},
|
||||
): MediaStreamVideoTrackGenerator;
|
||||
};
|
||||
|
||||
interface MediaStreamTrackGeneratorInit {
|
||||
kind: MediaStreamTrackGeneratorKind;
|
||||
/**
|
||||
* (Optional) track to which the MediaStreamTrackGenerator will automatically forward control
|
||||
* signals. If signalTarget is provided and signalTarget.kind and kind do not match, the
|
||||
* MediaStreamTrackGenerator’s constructor will raise an exception.
|
||||
*/
|
||||
signalTarget?: MediaStreamTrack | undefined;
|
||||
}
|
||||
|
||||
type MediaStreamTrackGeneratorKind = "audio" | "video";
|
||||
|
||||
type MediaStreamTrackSignalType = "request-frame";
|
||||
|
||||
interface MediaStreamTrackSignal {
|
||||
signalType: MediaStreamTrackSignalType;
|
||||
}
|
||||
745
src/@types/dom-webcodecs.d.ts
vendored
Normal file
745
src/@types/dom-webcodecs.d.ts
vendored
Normal file
@@ -0,0 +1,745 @@
|
||||
/* eslint-disable */
|
||||
// The contents of this file below the line are copied from
|
||||
// @types/dom-webcodecs, which is inlined here into Element Call so that we can
|
||||
// apply the patch https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72625
|
||||
// which is needed for TypeScript 5.8 compatibility. Once that PR is merged and
|
||||
// released, we can remove this file and return to depending on
|
||||
// @types/dom-webcodecs.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// This project is licensed under the MIT license.
|
||||
// Copyrights are respective of each contributor listed at the beginning of each definition file.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// Versioning:
|
||||
// Until the WebCodecs spec is finalized, the major version number is 0. I have chosen to use minor
|
||||
// version 1 to denote the API as defined by the IDL files from the Chromium repo at
|
||||
// https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/webcodecs.
|
||||
// Please use a version number above 0.1 if using the spec at https://w3c.github.io/webcodecs/ as
|
||||
// the source.
|
||||
|
||||
// The declarations in webcodecs.generated.d.ts have been generated using the code in
|
||||
// https://github.com/yume-chan/webcodecs-lib-generator. See
|
||||
// https://github.com/yume-chan/webcodecs-lib-generator/blob/main/README.md for more detail.
|
||||
|
||||
// The following declarations are copied from
|
||||
// https://github.com/microsoft/TypeScript-DOM-lib-generator/blob/a75338e1ea8a958bf08a5745141d2ab8f14ba2ca/baselines/dom.generated.d.ts
|
||||
// and modified to expand the types to include VideoFrame.
|
||||
|
||||
/** Shim for OffscreenCanvas, which was removed in TS 4.4 */
|
||||
interface OffscreenCanvas extends EventTarget {}
|
||||
|
||||
/**
|
||||
* Replaces CanvasImageSource; only applies if WebCodecs is available.
|
||||
*/
|
||||
type CanvasImageSourceWebCodecs =
|
||||
| HTMLOrSVGImageElement
|
||||
| HTMLVideoElement
|
||||
| HTMLCanvasElement
|
||||
| ImageBitmap
|
||||
| OffscreenCanvas
|
||||
| VideoFrame;
|
||||
|
||||
interface CanvasRenderingContext2D {
|
||||
drawImage(image: CanvasImageSourceWebCodecs, dx: number, dy: number): void;
|
||||
drawImage(
|
||||
image: CanvasImageSourceWebCodecs,
|
||||
dx: number,
|
||||
dy: number,
|
||||
dw: number,
|
||||
dh: number,
|
||||
): void;
|
||||
drawImage(
|
||||
image: CanvasImageSourceWebCodecs,
|
||||
sx: number,
|
||||
sy: number,
|
||||
sw: number,
|
||||
sh: number,
|
||||
dx: number,
|
||||
dy: number,
|
||||
dw: number,
|
||||
dh: number,
|
||||
): void;
|
||||
createPattern(
|
||||
image: CanvasImageSourceWebCodecs,
|
||||
repetition: string | null,
|
||||
): CanvasPattern | null;
|
||||
}
|
||||
|
||||
interface OffscreenCanvasRenderingContext2D {
|
||||
drawImage(image: CanvasImageSourceWebCodecs, dx: number, dy: number): void;
|
||||
drawImage(
|
||||
image: CanvasImageSourceWebCodecs,
|
||||
dx: number,
|
||||
dy: number,
|
||||
dw: number,
|
||||
dh: number,
|
||||
): void;
|
||||
drawImage(
|
||||
image: CanvasImageSourceWebCodecs,
|
||||
sx: number,
|
||||
sy: number,
|
||||
sw: number,
|
||||
sh: number,
|
||||
dx: number,
|
||||
dy: number,
|
||||
dw: number,
|
||||
dh: number,
|
||||
): void;
|
||||
createPattern(
|
||||
image: CanvasImageSourceWebCodecs,
|
||||
repetition: string | null,
|
||||
): CanvasPattern | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces ImageBitmapSource; only applies if WebCodecs is available.
|
||||
*/
|
||||
type ImageBitmapSourceWebCodecs = CanvasImageSourceWebCodecs | Blob | ImageData;
|
||||
|
||||
declare function createImageBitmap(
|
||||
image: ImageBitmapSourceWebCodecs,
|
||||
options?: ImageBitmapOptions,
|
||||
): Promise<ImageBitmap>;
|
||||
declare function createImageBitmap(
|
||||
image: ImageBitmapSourceWebCodecs,
|
||||
sx: number,
|
||||
sy: number,
|
||||
sw: number,
|
||||
sh: number,
|
||||
options?: ImageBitmapOptions,
|
||||
): Promise<ImageBitmap>;
|
||||
|
||||
/**
|
||||
* Replaces TexImageSource; only applies if WebCodecs is available.
|
||||
*/
|
||||
type TexImageSourceWebCodecs =
|
||||
| ImageBitmap
|
||||
| ImageData
|
||||
| HTMLImageElement
|
||||
| HTMLCanvasElement
|
||||
| HTMLVideoElement
|
||||
| OffscreenCanvas
|
||||
| VideoFrame;
|
||||
|
||||
interface WebGLRenderingContextOverloads {
|
||||
texImage2D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
internalformat: GLint,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
texSubImage2D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
xoffset: GLint,
|
||||
yoffset: GLint,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface WebGL2RenderingContextBase {
|
||||
texImage3D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
internalformat: GLint,
|
||||
width: GLsizei,
|
||||
height: GLsizei,
|
||||
depth: GLsizei,
|
||||
border: GLint,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
texSubImage3D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
xoffset: GLint,
|
||||
yoffset: GLint,
|
||||
zoffset: GLint,
|
||||
width: GLsizei,
|
||||
height: GLsizei,
|
||||
depth: GLsizei,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface WebGL2RenderingContextOverloads {
|
||||
texImage2D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
internalformat: GLint,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
texImage2D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
internalformat: GLint,
|
||||
width: GLsizei,
|
||||
height: GLsizei,
|
||||
border: GLint,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
texSubImage2D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
xoffset: GLint,
|
||||
yoffset: GLint,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
texSubImage2D(
|
||||
target: GLenum,
|
||||
level: GLint,
|
||||
xoffset: GLint,
|
||||
yoffset: GLint,
|
||||
width: GLsizei,
|
||||
height: GLsizei,
|
||||
format: GLenum,
|
||||
type: GLenum,
|
||||
source: TexImageSourceWebCodecs,
|
||||
): void;
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
/// webcodecs APIs
|
||||
/////////////////////////////
|
||||
|
||||
interface AudioDataCopyToOptions {
|
||||
format?: AudioSampleFormat | undefined;
|
||||
frameCount?: number | undefined;
|
||||
frameOffset?: number | undefined;
|
||||
planeIndex: number;
|
||||
}
|
||||
|
||||
interface AudioDataInit {
|
||||
data: AllowSharedBufferSource;
|
||||
format: AudioSampleFormat;
|
||||
numberOfChannels: number;
|
||||
numberOfFrames: number;
|
||||
sampleRate: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface AudioDecoderConfig {
|
||||
codec: string;
|
||||
description?: AllowSharedBufferSource | undefined;
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
}
|
||||
|
||||
interface AudioDecoderInit {
|
||||
error: WebCodecsErrorCallback;
|
||||
output: AudioDataOutputCallback;
|
||||
}
|
||||
|
||||
interface AudioDecoderSupport {
|
||||
config?: AudioDecoderConfig;
|
||||
supported?: boolean;
|
||||
}
|
||||
|
||||
interface AudioEncoderConfig {
|
||||
bitrate?: number | undefined;
|
||||
codec: string;
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
}
|
||||
|
||||
interface AudioEncoderInit {
|
||||
error: WebCodecsErrorCallback;
|
||||
output: EncodedAudioChunkOutputCallback;
|
||||
}
|
||||
|
||||
interface AudioEncoderSupport {
|
||||
config?: AudioEncoderConfig;
|
||||
supported?: boolean;
|
||||
}
|
||||
|
||||
interface AvcEncoderConfig {
|
||||
format?: AvcBitstreamFormat | undefined;
|
||||
}
|
||||
|
||||
interface EncodedAudioChunkInit {
|
||||
data: AllowSharedBufferSource;
|
||||
duration?: number | undefined;
|
||||
timestamp: number;
|
||||
type: EncodedAudioChunkType;
|
||||
}
|
||||
|
||||
interface EncodedAudioChunkMetadata {
|
||||
decoderConfig?: AudioDecoderConfig | undefined;
|
||||
}
|
||||
|
||||
interface EncodedVideoChunkInit {
|
||||
data: AllowSharedBufferSource;
|
||||
duration?: number | undefined;
|
||||
timestamp: number;
|
||||
type: EncodedVideoChunkType;
|
||||
}
|
||||
|
||||
interface EncodedVideoChunkMetadata {
|
||||
decoderConfig?: VideoDecoderConfig | undefined;
|
||||
temporalLayerId?: number | undefined;
|
||||
}
|
||||
|
||||
interface ImageDecodeOptions {
|
||||
completeFramesOnly?: boolean | undefined;
|
||||
frameIndex?: number | undefined;
|
||||
}
|
||||
|
||||
interface ImageDecodeResult {
|
||||
complete: boolean;
|
||||
image: VideoFrame;
|
||||
}
|
||||
|
||||
interface ImageDecoderInit {
|
||||
colorSpaceConversion?: ColorSpaceConversion | undefined;
|
||||
data: ImageBufferSource;
|
||||
desiredHeight?: number | undefined;
|
||||
desiredWidth?: number | undefined;
|
||||
preferAnimation?: boolean | undefined;
|
||||
premultiplyAlpha?: PremultiplyAlpha | undefined;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface PlaneLayout {
|
||||
offset: number;
|
||||
stride: number;
|
||||
}
|
||||
|
||||
interface VideoColorSpaceInit {
|
||||
fullRange?: boolean | null | undefined;
|
||||
matrix?: VideoMatrixCoefficients | null | undefined;
|
||||
primaries?: VideoColorPrimaries | null | undefined;
|
||||
transfer?: VideoTransferCharacteristics | null | undefined;
|
||||
}
|
||||
|
||||
interface VideoDecoderConfig {
|
||||
codec: string;
|
||||
codedHeight?: number | undefined;
|
||||
codedWidth?: number | undefined;
|
||||
colorSpace?: VideoColorSpaceInit | undefined;
|
||||
description?: AllowSharedBufferSource | undefined;
|
||||
displayAspectHeight?: number | undefined;
|
||||
displayAspectWidth?: number | undefined;
|
||||
hardwareAcceleration?: HardwarePreference | undefined;
|
||||
optimizeForLatency?: boolean | undefined;
|
||||
}
|
||||
|
||||
interface VideoDecoderInit {
|
||||
error: WebCodecsErrorCallback;
|
||||
output: VideoFrameOutputCallback;
|
||||
}
|
||||
|
||||
interface VideoDecoderSupport {
|
||||
config?: VideoDecoderConfig;
|
||||
supported?: boolean;
|
||||
}
|
||||
|
||||
interface VideoEncoderConfig {
|
||||
alpha?: AlphaOption | undefined;
|
||||
avc?: AvcEncoderConfig | undefined;
|
||||
bitrate?: number | undefined;
|
||||
bitrateMode?: VideoEncoderBitrateMode | undefined;
|
||||
codec: string;
|
||||
displayHeight?: number | undefined;
|
||||
displayWidth?: number | undefined;
|
||||
framerate?: number | undefined;
|
||||
hardwareAcceleration?: HardwarePreference | undefined;
|
||||
height: number;
|
||||
latencyMode?: LatencyMode | undefined;
|
||||
scalabilityMode?: string | undefined;
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface VideoEncoderEncodeOptions {
|
||||
keyFrame?: boolean;
|
||||
}
|
||||
|
||||
interface VideoEncoderInit {
|
||||
error: WebCodecsErrorCallback;
|
||||
output: EncodedVideoChunkOutputCallback;
|
||||
}
|
||||
|
||||
interface VideoEncoderSupport {
|
||||
config?: VideoEncoderConfig;
|
||||
supported?: boolean;
|
||||
}
|
||||
|
||||
interface VideoFrameBufferInit {
|
||||
codedHeight: number;
|
||||
codedWidth: number;
|
||||
colorSpace?: VideoColorSpaceInit | undefined;
|
||||
displayHeight?: number | undefined;
|
||||
displayWidth?: number | undefined;
|
||||
duration?: number | undefined;
|
||||
format: VideoPixelFormat;
|
||||
layout?: PlaneLayout[] | undefined;
|
||||
timestamp: number;
|
||||
visibleRect?: DOMRectInit | undefined;
|
||||
}
|
||||
|
||||
interface VideoFrameCopyToOptions {
|
||||
layout?: PlaneLayout[] | undefined;
|
||||
rect?: DOMRectInit | undefined;
|
||||
}
|
||||
|
||||
interface VideoFrameInit {
|
||||
alpha?: AlphaOption | undefined;
|
||||
displayHeight?: number | undefined;
|
||||
displayWidth?: number | undefined;
|
||||
duration?: number | undefined;
|
||||
timestamp?: number | undefined;
|
||||
visibleRect?: DOMRectInit | undefined;
|
||||
}
|
||||
|
||||
interface AudioData {
|
||||
readonly duration: number;
|
||||
readonly format: AudioSampleFormat | null;
|
||||
readonly numberOfChannels: number;
|
||||
readonly numberOfFrames: number;
|
||||
readonly sampleRate: number;
|
||||
readonly timestamp: number;
|
||||
allocationSize(options: AudioDataCopyToOptions): number;
|
||||
clone(): AudioData;
|
||||
close(): void;
|
||||
copyTo(
|
||||
destination: AllowSharedBufferSource,
|
||||
options: AudioDataCopyToOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
declare var AudioData: {
|
||||
prototype: AudioData;
|
||||
new (init: AudioDataInit): AudioData;
|
||||
};
|
||||
|
||||
interface AudioDecoderEventMap {
|
||||
dequeue: Event;
|
||||
}
|
||||
|
||||
/** Available only in secure contexts. */
|
||||
interface AudioDecoder {
|
||||
readonly decodeQueueSize: number;
|
||||
readonly state: CodecState;
|
||||
ondequeue: ((this: AudioDecoder, ev: Event) => any) | null;
|
||||
close(): void;
|
||||
configure(config: AudioDecoderConfig): void;
|
||||
decode(chunk: EncodedAudioChunk): void;
|
||||
flush(): Promise<void>;
|
||||
reset(): void;
|
||||
addEventListener<K extends keyof AudioDecoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: AudioDecoder, ev: AudioDecoderEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
addEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
removeEventListener<K extends keyof AudioDecoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: AudioDecoder, ev: AudioDecoderEventMap[K]) => any,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
declare var AudioDecoder: {
|
||||
prototype: AudioDecoder;
|
||||
new (init: AudioDecoderInit): AudioDecoder;
|
||||
isConfigSupported(config: AudioDecoderConfig): Promise<AudioDecoderSupport>;
|
||||
};
|
||||
|
||||
interface AudioEncoderEventMap {
|
||||
dequeue: Event;
|
||||
}
|
||||
|
||||
/** Available only in secure contexts. */
|
||||
interface AudioEncoder {
|
||||
readonly encodeQueueSize: number;
|
||||
readonly state: CodecState;
|
||||
ondequeue: ((this: AudioEncoder, ev: Event) => any) | null;
|
||||
close(): void;
|
||||
configure(config: AudioEncoderConfig): void;
|
||||
encode(data: AudioData): void;
|
||||
flush(): Promise<void>;
|
||||
reset(): void;
|
||||
addEventListener<K extends keyof AudioEncoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: AudioEncoder, ev: AudioEncoderEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
addEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
removeEventListener<K extends keyof AudioEncoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: AudioEncoder, ev: AudioEncoderEventMap[K]) => any,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
declare var AudioEncoder: {
|
||||
prototype: AudioEncoder;
|
||||
new (init: AudioEncoderInit): AudioEncoder;
|
||||
isConfigSupported(config: AudioEncoderConfig): Promise<AudioEncoderSupport>;
|
||||
};
|
||||
|
||||
interface EncodedAudioChunk {
|
||||
readonly byteLength: number;
|
||||
readonly duration: number | null;
|
||||
readonly timestamp: number;
|
||||
readonly type: EncodedAudioChunkType;
|
||||
copyTo(destination: AllowSharedBufferSource): void;
|
||||
}
|
||||
|
||||
declare var EncodedAudioChunk: {
|
||||
prototype: EncodedAudioChunk;
|
||||
new (init: EncodedAudioChunkInit): EncodedAudioChunk;
|
||||
};
|
||||
|
||||
interface EncodedVideoChunk {
|
||||
readonly byteLength: number;
|
||||
readonly duration: number | null;
|
||||
readonly timestamp: number;
|
||||
readonly type: EncodedVideoChunkType;
|
||||
copyTo(destination: AllowSharedBufferSource): void;
|
||||
}
|
||||
|
||||
declare var EncodedVideoChunk: {
|
||||
prototype: EncodedVideoChunk;
|
||||
new (init: EncodedVideoChunkInit): EncodedVideoChunk;
|
||||
};
|
||||
|
||||
/** Available only in secure contexts. */
|
||||
interface ImageDecoder {
|
||||
readonly complete: boolean;
|
||||
readonly completed: Promise<void>;
|
||||
readonly tracks: ImageTrackList;
|
||||
readonly type: string;
|
||||
close(): void;
|
||||
decode(options?: ImageDecodeOptions): Promise<ImageDecodeResult>;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
// declare var ImageDecoder: {
|
||||
// prototype: ImageDecoder;
|
||||
// new(init: ImageDecoderInit): ImageDecoder;
|
||||
// isTypeSupported(type: string): Promise<boolean>;
|
||||
// };
|
||||
|
||||
// interface ImageTrack {
|
||||
// readonly animated: boolean;
|
||||
// readonly frameCount: number;
|
||||
// readonly repetitionCount: number;
|
||||
// selected: boolean;
|
||||
// }
|
||||
|
||||
// declare var ImageTrack: {
|
||||
// prototype: ImageTrack;
|
||||
// new(): ImageTrack;
|
||||
// };
|
||||
|
||||
// interface ImageTrackList {
|
||||
// readonly length: number;
|
||||
// readonly ready: Promise<void>;
|
||||
// readonly selectedIndex: number;
|
||||
// readonly selectedTrack: ImageTrack | null;
|
||||
// [index: number]: ImageTrack;
|
||||
// }
|
||||
|
||||
// declare var ImageTrackList: {
|
||||
// prototype: ImageTrackList;
|
||||
// new(): ImageTrackList;
|
||||
// };
|
||||
|
||||
interface VideoColorSpace {
|
||||
readonly fullRange: boolean | null;
|
||||
readonly matrix: VideoMatrixCoefficients | null;
|
||||
readonly primaries: VideoColorPrimaries | null;
|
||||
readonly transfer: VideoTransferCharacteristics | null;
|
||||
toJSON(): VideoColorSpaceInit;
|
||||
}
|
||||
|
||||
declare var VideoColorSpace: {
|
||||
prototype: VideoColorSpace;
|
||||
new (init?: VideoColorSpaceInit): VideoColorSpace;
|
||||
};
|
||||
|
||||
interface VideoDecoderEventMap {
|
||||
dequeue: Event;
|
||||
}
|
||||
|
||||
/** Available only in secure contexts. */
|
||||
interface VideoDecoder {
|
||||
readonly decodeQueueSize: number;
|
||||
readonly state: CodecState;
|
||||
ondequeue: ((this: VideoDecoder, ev: Event) => any) | null;
|
||||
close(): void;
|
||||
configure(config: VideoDecoderConfig): void;
|
||||
decode(chunk: EncodedVideoChunk): void;
|
||||
flush(): Promise<void>;
|
||||
reset(): void;
|
||||
addEventListener<K extends keyof VideoDecoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: VideoDecoder, ev: VideoDecoderEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
addEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
removeEventListener<K extends keyof VideoDecoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: VideoDecoder, ev: VideoDecoderEventMap[K]) => any,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
declare var VideoDecoder: {
|
||||
prototype: VideoDecoder;
|
||||
new (init: VideoDecoderInit): VideoDecoder;
|
||||
isConfigSupported(config: VideoDecoderConfig): Promise<VideoDecoderSupport>;
|
||||
};
|
||||
|
||||
interface VideoEncoderEventMap {
|
||||
dequeue: Event;
|
||||
}
|
||||
|
||||
/** Available only in secure contexts. */
|
||||
interface VideoEncoder {
|
||||
readonly encodeQueueSize: number;
|
||||
readonly state: CodecState;
|
||||
close(): void;
|
||||
ondequeue: ((this: VideoEncoder, ev: Event) => any) | null;
|
||||
configure(config: VideoEncoderConfig): void;
|
||||
encode(frame: VideoFrame, options?: VideoEncoderEncodeOptions): void;
|
||||
flush(): Promise<void>;
|
||||
reset(): void;
|
||||
addEventListener<K extends keyof VideoEncoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: VideoEncoder, ev: VideoEncoderEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
addEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
removeEventListener<K extends keyof VideoEncoderEventMap>(
|
||||
type: K,
|
||||
listener: (this: VideoEncoder, ev: VideoEncoderEventMap[K]) => any,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
declare var VideoEncoder: {
|
||||
prototype: VideoEncoder;
|
||||
new (init: VideoEncoderInit): VideoEncoder;
|
||||
isConfigSupported(config: VideoEncoderConfig): Promise<VideoEncoderSupport>;
|
||||
};
|
||||
|
||||
interface VideoFrame {
|
||||
readonly codedHeight: number;
|
||||
readonly codedRect: DOMRectReadOnly | null;
|
||||
readonly codedWidth: number;
|
||||
readonly colorSpace: VideoColorSpace;
|
||||
readonly displayHeight: number;
|
||||
readonly displayWidth: number;
|
||||
readonly duration: number | null;
|
||||
readonly format: VideoPixelFormat | null;
|
||||
readonly timestamp: number;
|
||||
readonly visibleRect: DOMRectReadOnly | null;
|
||||
allocationSize(options?: VideoFrameCopyToOptions): number;
|
||||
clone(): VideoFrame;
|
||||
close(): void;
|
||||
copyTo(
|
||||
destination: AllowSharedBufferSource,
|
||||
options?: VideoFrameCopyToOptions,
|
||||
): Promise<PlaneLayout[]>;
|
||||
}
|
||||
|
||||
declare var VideoFrame: {
|
||||
prototype: VideoFrame;
|
||||
new (source: CanvasImageSource, init?: VideoFrameInit): VideoFrame;
|
||||
new (data: AllowSharedBufferSource, init: VideoFrameBufferInit): VideoFrame;
|
||||
};
|
||||
|
||||
interface AudioDataOutputCallback {
|
||||
(output: AudioData): void;
|
||||
}
|
||||
|
||||
interface EncodedAudioChunkOutputCallback {
|
||||
(output: EncodedAudioChunk, metadata: EncodedAudioChunkMetadata): void;
|
||||
}
|
||||
|
||||
interface EncodedVideoChunkOutputCallback {
|
||||
(chunk: EncodedVideoChunk, metadata: EncodedVideoChunkMetadata): void;
|
||||
}
|
||||
|
||||
interface VideoFrameOutputCallback {
|
||||
(output: VideoFrame): void;
|
||||
}
|
||||
|
||||
interface WebCodecsErrorCallback {
|
||||
(error: DOMException): void;
|
||||
}
|
||||
|
||||
// type AllowSharedBufferSource = ArrayBuffer | ArrayBufferView;
|
||||
// type BitrateMode = "constant" | "variable";
|
||||
// type ImageBufferSource = ArrayBuffer | ArrayBufferView | ReadableStream;
|
||||
// type AlphaOption = "discard" | "keep";
|
||||
// type AudioSampleFormat = "f32" | "f32-planar" | "s16" | "s16-planar" | "s32" | "s32-planar" | "u8" | "u8-planar";
|
||||
// type AvcBitstreamFormat = "annexb" | "avc";
|
||||
// type CodecState = "closed" | "configured" | "unconfigured";
|
||||
// type EncodedAudioChunkType = "delta" | "key";
|
||||
// type EncodedVideoChunkType = "delta" | "key";
|
||||
type HardwarePreference =
|
||||
| "no-preference"
|
||||
| "prefer-hardware"
|
||||
| "prefer-software";
|
||||
// type LatencyMode = "quality" | "realtime";
|
||||
// type VideoColorPrimaries = "bt470bg" | "bt709" | "smpte170m";
|
||||
// type VideoMatrixCoefficients = "bt470bg" | "bt709" | "rgb" | "smpte170m";
|
||||
// type VideoPixelFormat = "BGRA" | "BGRX" | "I420" | "I420A" | "I422" | "I444" | "NV12" | "RGBA" | "RGBX";
|
||||
// type VideoTransferCharacteristics = "bt709" | "iec61966-2-1" | "smpte170m";
|
||||
@@ -136,11 +136,6 @@ export class Initializer {
|
||||
lookup: () => getUrlParams().lang ?? undefined,
|
||||
});
|
||||
|
||||
// Synchronise the HTML lang attribute with the i18next language
|
||||
i18n.on("languageChanged", (lng) => {
|
||||
document.documentElement.lang = lng;
|
||||
});
|
||||
|
||||
await i18n
|
||||
.use(Backend)
|
||||
.use(languageDetector)
|
||||
|
||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type FC, useCallback, useState } from "react";
|
||||
import { test, vi } from "vitest";
|
||||
import { describe, expect, test, vi, vitest } from "vitest";
|
||||
import {
|
||||
ConnectionError,
|
||||
ConnectionErrorReason,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { defer, sleep } from "matrix-js-sdk/lib/utils";
|
||||
|
||||
import { useECConnectionState } from "./useECConnectionState";
|
||||
import { type SFUConfig } from "./openIDSFU";
|
||||
@@ -57,7 +58,7 @@ test.each<[string, ConnectionError]>([
|
||||
() => setSfuConfig({ url: "URL", jwt: "JWT token" }),
|
||||
[],
|
||||
);
|
||||
useECConnectionState({}, false, mockRoom, sfuConfig);
|
||||
useECConnectionState("default", false, mockRoom, sfuConfig);
|
||||
return <button onClick={connect}>Connect</button>;
|
||||
};
|
||||
|
||||
@@ -73,3 +74,111 @@ test.each<[string, ConnectionError]>([
|
||||
screen.getByText("Insufficient capacity");
|
||||
},
|
||||
);
|
||||
|
||||
describe("Leaking connection prevention", () => {
|
||||
function createTestComponent(mockRoom: Room): FC {
|
||||
const TestComponent: FC = () => {
|
||||
const [sfuConfig, setSfuConfig] = useState<SFUConfig | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const connect = useCallback(
|
||||
() => setSfuConfig({ url: "URL", jwt: "JWT token" }),
|
||||
[],
|
||||
);
|
||||
useECConnectionState("default", false, mockRoom, sfuConfig);
|
||||
return <button onClick={connect}>Connect</button>;
|
||||
};
|
||||
return TestComponent;
|
||||
}
|
||||
|
||||
test("Should cancel pending connections when the component is unmounted", async () => {
|
||||
const connectCall = vi.fn();
|
||||
const pendingConnection = defer<void>();
|
||||
// let pendingDisconnection = defer<void>()
|
||||
const disconnectMock = vi.fn();
|
||||
|
||||
const mockRoom = {
|
||||
on: () => {},
|
||||
off: () => {},
|
||||
once: () => {},
|
||||
connect: async () => {
|
||||
connectCall.call(undefined);
|
||||
return await pendingConnection.promise;
|
||||
},
|
||||
disconnect: disconnectMock,
|
||||
localParticipant: {
|
||||
getTrackPublication: () => {},
|
||||
createTracks: () => [],
|
||||
},
|
||||
} as unknown as Room;
|
||||
|
||||
const TestComponent = createTestComponent(mockRoom);
|
||||
|
||||
const { unmount } = render(<TestComponent />);
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByRole("button", { name: "Connect" }));
|
||||
|
||||
expect(connectCall).toHaveBeenCalled();
|
||||
// unmount while the connection is pending
|
||||
unmount();
|
||||
|
||||
// resolve the pending connection
|
||||
pendingConnection.resolve();
|
||||
|
||||
await vitest.waitUntil(
|
||||
() => {
|
||||
return disconnectMock.mock.calls.length > 0;
|
||||
},
|
||||
{
|
||||
timeout: 1000,
|
||||
interval: 100,
|
||||
},
|
||||
);
|
||||
|
||||
// There should be some cleaning up to avoid leaking an open connection
|
||||
expect(disconnectMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("Should cancel about to open but not yet opened connection", async () => {
|
||||
const createTracksCall = vi.fn();
|
||||
const pendingCreateTrack = defer<void>();
|
||||
// let pendingDisconnection = defer<void>()
|
||||
const disconnectMock = vi.fn();
|
||||
const connectMock = vi.fn();
|
||||
|
||||
const mockRoom = {
|
||||
on: () => {},
|
||||
off: () => {},
|
||||
once: () => {},
|
||||
connect: connectMock,
|
||||
disconnect: disconnectMock,
|
||||
localParticipant: {
|
||||
getTrackPublication: () => {},
|
||||
createTracks: async () => {
|
||||
createTracksCall.call(undefined);
|
||||
await pendingCreateTrack.promise;
|
||||
return [];
|
||||
},
|
||||
},
|
||||
} as unknown as Room;
|
||||
|
||||
const TestComponent = createTestComponent(mockRoom);
|
||||
|
||||
const { unmount } = render(<TestComponent />);
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByRole("button", { name: "Connect" }));
|
||||
|
||||
expect(createTracksCall).toHaveBeenCalled();
|
||||
// unmount while createTracks is pending
|
||||
unmount();
|
||||
|
||||
// resolve createTracks
|
||||
pendingCreateTrack.resolve();
|
||||
|
||||
// Yield to the event loop to let the connection attempt finish
|
||||
await sleep(100);
|
||||
|
||||
// The operation should have been aborted before even calling connect.
|
||||
expect(connectMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import {
|
||||
type AudioCaptureOptions,
|
||||
ConnectionError,
|
||||
ConnectionState,
|
||||
type LocalTrack,
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
InsufficientCapacityError,
|
||||
UnknownCallError,
|
||||
} from "../utils/errors.ts";
|
||||
import { AbortHandle } from "../utils/abortHandle.ts";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -59,7 +59,8 @@ async function doConnect(
|
||||
livekitRoom: Room,
|
||||
sfuConfig: SFUConfig,
|
||||
audioEnabled: boolean,
|
||||
audioOptions: AudioCaptureOptions,
|
||||
initialDeviceId: string | undefined,
|
||||
abortHandle: AbortHandle,
|
||||
): Promise<void> {
|
||||
// Always create an audio track manually.
|
||||
// livekit (by default) keeps the mic track open when you mute, but if you start muted,
|
||||
@@ -82,19 +83,40 @@ async function doConnect(
|
||||
let preCreatedAudioTrack: LocalTrack | undefined;
|
||||
try {
|
||||
const audioTracks = await livekitRoom!.localParticipant.createTracks({
|
||||
audio: audioOptions,
|
||||
audio: { deviceId: initialDeviceId },
|
||||
});
|
||||
|
||||
if (audioTracks.length < 1) {
|
||||
logger.info("Tried to pre-create local audio track but got no tracks");
|
||||
} else {
|
||||
preCreatedAudioTrack = audioTracks[0];
|
||||
}
|
||||
// There was a yield point previously (awaiting for the track to be created) so we need to check
|
||||
// if the operation was cancelled and stop connecting if needed.
|
||||
if (abortHandle.isAborted()) {
|
||||
logger.info(
|
||||
"[Lifecycle] Signal Aborted: Pre-created audio track but connection aborted",
|
||||
);
|
||||
preCreatedAudioTrack?.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Pre-created microphone track");
|
||||
} catch (e) {
|
||||
logger.error("Failed to pre-create microphone track", e);
|
||||
}
|
||||
|
||||
if (!audioEnabled) await preCreatedAudioTrack?.mute();
|
||||
if (!audioEnabled) {
|
||||
await preCreatedAudioTrack?.mute();
|
||||
// There was a yield point. Check if the operation was cancelled and stop connecting.
|
||||
if (abortHandle.isAborted()) {
|
||||
logger.info(
|
||||
"[Lifecycle] Signal Aborted: Pre-created audio track but connection aborted",
|
||||
);
|
||||
preCreatedAudioTrack?.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check again having awaited for the track to create
|
||||
if (
|
||||
@@ -107,9 +129,18 @@ async function doConnect(
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Connecting & publishing");
|
||||
logger.info("[Lifecycle] Connecting & publishing");
|
||||
try {
|
||||
await connectAndPublish(livekitRoom, sfuConfig, preCreatedAudioTrack, []);
|
||||
if (abortHandle.isAborted()) {
|
||||
logger.info(
|
||||
"[Lifecycle] Signal Aborted: Connected but operation was cancelled. Force disconnect",
|
||||
);
|
||||
livekitRoom?.disconnect().catch((err) => {
|
||||
logger.error("Failed to disconnect from SFU", err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
preCreatedAudioTrack?.stop();
|
||||
logger.debug("Stopped precreated audio tracks.");
|
||||
@@ -137,13 +168,16 @@ async function connectAndPublish(
|
||||
livekitRoom.once(RoomEvent.SignalConnected, tracker.cacheWsConnect);
|
||||
|
||||
try {
|
||||
logger.info(`[Lifecycle] Connecting to livekit room ${sfuConfig!.url} ...`);
|
||||
await livekitRoom!.connect(sfuConfig!.url, sfuConfig!.jwt, {
|
||||
// Due to stability issues on Firefox we are testing the effect of different
|
||||
// timeouts, and allow these values to be set through the console
|
||||
peerConnectionTimeout: window.peerConnectionTimeout ?? 45000,
|
||||
websocketTimeout: window.websocketTimeout ?? 45000,
|
||||
});
|
||||
logger.info(`[Lifecycle] ... connected to livekit room`);
|
||||
} catch (e) {
|
||||
logger.error("[Lifecycle] Failed to connect", e);
|
||||
// LiveKit uses 503 to indicate that the server has hit its track limits.
|
||||
// https://github.com/livekit/livekit/blob/fcb05e97c5a31812ecf0ca6f7efa57c485cea9fb/pkg/service/rtcservice.go#L171
|
||||
// It also errors with a status code of 200 (yes, really) for room
|
||||
@@ -184,7 +218,7 @@ async function connectAndPublish(
|
||||
}
|
||||
|
||||
export function useECConnectionState(
|
||||
initialAudioOptions: AudioCaptureOptions,
|
||||
initialDeviceId: string | undefined,
|
||||
initialAudioEnabled: boolean,
|
||||
livekitRoom?: Room,
|
||||
sfuConfig?: SFUConfig,
|
||||
@@ -247,6 +281,22 @@ export function useECConnectionState(
|
||||
|
||||
const currentSFUConfig = useRef(Object.assign({}, sfuConfig));
|
||||
|
||||
// Protection against potential leaks, where the component to be unmounted and there is
|
||||
// still a pending doConnect promise. This would lead the user to still be in the call even
|
||||
// if the component is unmounted.
|
||||
const abortHandlesBag = useRef(new Set<AbortHandle>());
|
||||
|
||||
// This is a cleanup function that will be called when the component is about to be unmounted.
|
||||
// It will cancel all abortHandles in the bag
|
||||
useEffect(() => {
|
||||
const bag = abortHandlesBag.current;
|
||||
return (): void => {
|
||||
bag.forEach((handle) => {
|
||||
handle.abort();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Id we are transitioning from a valid config to another valid one, we need
|
||||
// to explicitly switch focus
|
||||
useEffect(() => {
|
||||
@@ -273,11 +323,14 @@ export function useECConnectionState(
|
||||
// always capturing audio: it helps keep bluetooth headsets in the right mode and
|
||||
// mobile browsers to know we're doing a call.
|
||||
setIsInDoConnect(true);
|
||||
const abortHandle = new AbortHandle();
|
||||
abortHandlesBag.current.add(abortHandle);
|
||||
doConnect(
|
||||
livekitRoom!,
|
||||
sfuConfig!,
|
||||
initialAudioEnabled,
|
||||
initialAudioOptions,
|
||||
initialDeviceId,
|
||||
abortHandle,
|
||||
)
|
||||
.catch((e) => {
|
||||
if (e instanceof ElementCallError) {
|
||||
@@ -286,14 +339,17 @@ export function useECConnectionState(
|
||||
setError(new UnknownCallError(e));
|
||||
} else logger.error("Failed to connect to SFU", e);
|
||||
})
|
||||
.finally(() => setIsInDoConnect(false));
|
||||
.finally(() => {
|
||||
abortHandlesBag.current.delete(abortHandle);
|
||||
setIsInDoConnect(false);
|
||||
});
|
||||
}
|
||||
|
||||
currentSFUConfig.current = Object.assign({}, sfuConfig);
|
||||
}, [
|
||||
sfuConfig,
|
||||
livekitRoom,
|
||||
initialAudioOptions,
|
||||
initialDeviceId,
|
||||
initialAudioEnabled,
|
||||
doFocusSwitch,
|
||||
]);
|
||||
|
||||
@@ -155,9 +155,7 @@ export function useLiveKit(
|
||||
);
|
||||
|
||||
const connectionState = useECConnectionState(
|
||||
{
|
||||
deviceId: initialDevices.current.audioInput.selectedId,
|
||||
},
|
||||
initialDevices.current.audioInput.selectedId,
|
||||
initialMuteStates.current.audio.enabled,
|
||||
room,
|
||||
sfuConfig,
|
||||
|
||||
@@ -118,6 +118,13 @@ export const GroupCallView: FC<Props> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
logger.info("[Lifecycle] GroupCallView Component mounted");
|
||||
return (): void => {
|
||||
logger.info("[Lifecycle] GroupCallView Component unmounted");
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.rtcSession = rtcSession;
|
||||
return (): void => {
|
||||
|
||||
@@ -127,10 +127,23 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
const [vm, setVm] = useState<CallViewModel | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
logger.info(
|
||||
`[Lifecycle] InCallView Component mounted, livekitroom state ${livekitRoom?.state}`,
|
||||
);
|
||||
return (): void => {
|
||||
livekitRoom?.disconnect().catch((e) => {
|
||||
logger.error("Failed to disconnect from livekit room", e);
|
||||
});
|
||||
logger.info(
|
||||
`[Lifecycle] InCallView Component unmounted, livekitroom state ${livekitRoom?.state}`,
|
||||
);
|
||||
livekitRoom
|
||||
?.disconnect()
|
||||
.then(() => {
|
||||
logger.info(
|
||||
`[Lifecycle] Disconnected from livekite room, state:${livekitRoom?.state}`,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("[Lifecycle] Failed to disconnect from livekit room", e);
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -5,7 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type FC, useCallback, useMemo, useState, type JSX } from "react";
|
||||
import {
|
||||
type FC,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
type JSX,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type MatrixClient } from "matrix-js-sdk";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
@@ -72,6 +79,13 @@ export const LobbyView: FC<Props> = ({
|
||||
onShareClick,
|
||||
waitingForInvite,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
logger.info("[Lifecycle] GroupCallView Component mounted");
|
||||
return (): void => {
|
||||
logger.info("[Lifecycle] GroupCallView Component unmounted");
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
usePageTitle(matrixInfo.roomName);
|
||||
|
||||
|
||||
18
src/utils/abortHandle.ts
Normal file
18
src/utils/abortHandle.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
export class AbortHandle {
|
||||
public constructor(private aborted = false) {}
|
||||
|
||||
public abort(): void {
|
||||
this.aborted = true;
|
||||
}
|
||||
|
||||
public isAborted(): boolean {
|
||||
return this.aborted;
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import { createHtmlPlugin } from "vite-plugin-html";
|
||||
import { codecovVitePlugin } from "@codecov/vite-plugin";
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import basicSsl from "@vitejs/plugin-basic-ssl";
|
||||
import { realpathSync } from "fs";
|
||||
import * as fs from "node:fs";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode, packageType }) => {
|
||||
@@ -24,6 +24,7 @@ export default defineConfig(({ mode, packageType }) => {
|
||||
process.env.VITE_PACKAGE = packageType ?? "full";
|
||||
const plugins = [
|
||||
react(),
|
||||
basicSsl(),
|
||||
svgrPlugin({
|
||||
svgrOptions: {
|
||||
// This enables ref forwarding on SVGR components, which is needed, for
|
||||
@@ -83,10 +84,6 @@ export default defineConfig(({ mode, packageType }) => {
|
||||
server: {
|
||||
port: 3000,
|
||||
fs: { allow },
|
||||
https: {
|
||||
key: fs.readFileSync("./backend/dev_tls_m.localhost.key"),
|
||||
cert: fs.readFileSync("./backend/dev_tls_m.localhost.crt"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
|
||||
27
yarn.lock
27
yarn.lock
@@ -4621,22 +4621,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/dom-mediacapture-transform@npm:^0.1.11":
|
||||
version: 0.1.11
|
||||
resolution: "@types/dom-mediacapture-transform@npm:0.1.11"
|
||||
dependencies:
|
||||
"@types/dom-webcodecs": "npm:*"
|
||||
checksum: 10c0/19c76d54cf31aa2a925011fc5f973dff9a10bdecfdf2285e5e568e61850a0fa2b8c9f1807a1462cbefd57ec26d32eeaa9c359117aca9d9fe7f0d6f2fff33f51e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/dom-webcodecs@npm:*":
|
||||
version: 0.1.15
|
||||
resolution: "@types/dom-webcodecs@npm:0.1.15"
|
||||
checksum: 10c0/1407f0352156c99c9b4378fb4c0c799b061520d031903a7f359ad09a6f706cc1fd56bafb272bb1a3decffcb32e54a51d2f07442eb72622464a950cff7f9e8862
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree@npm:1.0.6, @types/estree@npm:^1.0.0":
|
||||
version: 1.0.6
|
||||
resolution: "@types/estree@npm:1.0.6"
|
||||
@@ -5143,6 +5127,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitejs/plugin-basic-ssl@npm:^1.0.1":
|
||||
version: 1.2.0
|
||||
resolution: "@vitejs/plugin-basic-ssl@npm:1.2.0"
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
|
||||
checksum: 10c0/0d360fcca01f91ade6e451edbea09a107ff9e95cd3c3766c7a069d1a168709df92d96c0bd1eccc66e2739a153e07c75a45321ec487450c0da942606200d8441d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitejs/plugin-react@npm:^4.0.1":
|
||||
version: 4.3.4
|
||||
resolution: "@vitejs/plugin-react@npm:4.3.4"
|
||||
@@ -6960,7 +6953,6 @@ __metadata:
|
||||
"@testing-library/react": "npm:^16.0.0"
|
||||
"@testing-library/user-event": "npm:^14.5.1"
|
||||
"@types/content-type": "npm:^1.1.5"
|
||||
"@types/dom-mediacapture-transform": "npm:^0.1.11"
|
||||
"@types/grecaptcha": "npm:^3.0.9"
|
||||
"@types/jsdom": "npm:^21.1.7"
|
||||
"@types/lodash-es": "npm:^4.17.12"
|
||||
@@ -6976,6 +6968,7 @@ __metadata:
|
||||
"@use-gesture/react": "npm:^10.2.11"
|
||||
"@vector-im/compound-design-tokens": "npm:^3.0.0"
|
||||
"@vector-im/compound-web": "npm:^7.2.0"
|
||||
"@vitejs/plugin-basic-ssl": "npm:^1.0.1"
|
||||
"@vitejs/plugin-react": "npm:^4.0.1"
|
||||
"@vitest/coverage-v8": "npm:^3.0.0"
|
||||
babel-plugin-transform-vite-meta-env: "npm:^1.0.3"
|
||||
|
||||
Reference in New Issue
Block a user