Merge branch 'livekit' into settings-tab-order
This commit is contained in:
16
README.md
16
README.md
@@ -88,20 +88,16 @@ yarn dev
|
||||
|
||||
### Backend
|
||||
|
||||
Add in you `.env` in root dir with:
|
||||
A docker compose file is provided to start a LiveKit server and auth
|
||||
service for development. 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._**
|
||||
|
||||
```yaml
|
||||
# Develop backend settings:
|
||||
LIVEKIT_KEY="devkey"
|
||||
LIVEKIT_SECRET="secret"
|
||||
```
|
||||
|
||||
Add SFU parameter in your local config `./public/config.yml`:
|
||||
To use it, add SFU parameter in your local config `./public/config.yml`:
|
||||
|
||||
```yaml
|
||||
"livekit": {
|
||||
"server_url": "ws://localhost:7880",
|
||||
"jwt_service_url": "http:/localhost:8881"
|
||||
"jwt_service_url": "http://localhost:8881"
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# LiveKit requires host networking, which is only available on Linux
|
||||
# This compose will not function correctly on Mac or Windows
|
||||
version: "3.9"
|
||||
|
||||
networks:
|
||||
@@ -7,15 +5,14 @@ networks:
|
||||
|
||||
services:
|
||||
auth-service:
|
||||
build:
|
||||
context: ./backend/auth
|
||||
container_name: auth-server
|
||||
image: ghcr.io/vector-im/lk-jwt-service:latest-ci
|
||||
hostname: auth-server
|
||||
ports:
|
||||
- 8881:8080
|
||||
environment:
|
||||
- LIVEKIT_KEY=${LIVEKIT_KEY}
|
||||
- LIVEKIT_SECRET=${LIVEKIT_SECRET}
|
||||
- LIVEKIT_URL=ws://localhost:7880
|
||||
- LIVEKIT_KEY=devkey
|
||||
- LIVEKIT_SECRET=secret
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM golang:1.20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod ./
|
||||
COPY go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY *.go ./
|
||||
|
||||
RUN go build -o /auth-server
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD [ "/auth-server" ]
|
||||
@@ -1,20 +0,0 @@
|
||||
module vector-auth-server
|
||||
|
||||
go 1.20
|
||||
|
||||
require github.com/livekit/protocol v1.5.7
|
||||
|
||||
require (
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -1,94 +0,0 @@
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/frostbyte73/core v0.0.9 h1:AmE9GjgGpPsWk9ZkmY3HsYUs2hf2tZt+/W6r49URBQI=
|
||||
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58=
|
||||
github.com/livekit/protocol v1.5.7 h1:jZeFQEmLuIhFblXDGPRCBbfjVJHb+YU7AsO+SMoXF70=
|
||||
github.com/livekit/protocol v1.5.7/go.mod h1:ZaOnsvP+JS4s7vI1UO+JVdBagvvLp/lBXDAl2hkDS0I=
|
||||
github.com/livekit/psrpc v0.3.0 h1:giBZsfM3CWA0oIYXofsMITbVQtyW7u/ES9sQmVspHPM=
|
||||
github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/nats-io/nats.go v1.25.0 h1:t5/wCPGciR7X3Mu8QOi4jiJaXaWM8qtkLu4lzGZvYHE=
|
||||
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4=
|
||||
github.com/pion/ice/v2 v2.3.4 h1:tjYjTLpWyZzUjpDnzk6T1y3oQyhyY2DiM2t095iDhyQ=
|
||||
github.com/pion/interceptor v0.1.16 h1:0GDZrfNO+BmVNWymS31fMlVtPO2IJVBzy2Qq5XCYMIg=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/srtp/v2 v2.0.14 h1:Glt0MqEvINrDxL+aanmK4DiFjvs+uN2iYc6XD/iKpoY=
|
||||
github.com/pion/stun v0.5.2 h1:J/8glQnDV91dfk2+ZnGN0o9bUJgABhTNljwfQWByoXE=
|
||||
github.com/pion/transport/v2 v2.2.0 h1:u5lFqFHkXLMXMzai8tixZDfVjb8eOjH35yCunhPeb1c=
|
||||
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
|
||||
github.com/pion/webrtc/v3 v3.2.4 h1:gWSx4dqQb77051qBT9ipDrOyP6/sGYcAQP3UPjM8pU8=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
|
||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -1,98 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
key, secret string
|
||||
}
|
||||
|
||||
func (h *Handler) handle(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Request from %s", r.RemoteAddr)
|
||||
|
||||
// Set the CORS headers
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||
|
||||
// Handle preflight request (CORS)
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
roomName := r.URL.Query().Get("roomName")
|
||||
name := r.URL.Query().Get("name")
|
||||
identity := r.URL.Query().Get("identity")
|
||||
|
||||
log.Printf("roomName: %s, name: %s, identity: %s", roomName, name, identity)
|
||||
|
||||
if roomName == "" || name == "" || identity == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := getJoinToken(h.key, h.secret, roomName, identity, name)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res := Response{token}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func main() {
|
||||
key := os.Getenv("LIVEKIT_KEY")
|
||||
secret := os.Getenv("LIVEKIT_SECRET")
|
||||
|
||||
// Check if the key and secret are empty.
|
||||
if key == "" || secret == "" {
|
||||
log.Fatal("LIVEKIT_KEY and LIVEKIT_SECRET environment variables must be set")
|
||||
}
|
||||
|
||||
log.Printf("LIVEKIT_KEY: %s and LIVEKIT_SECRET %s", key, secret)
|
||||
|
||||
handler := &Handler{
|
||||
key: key,
|
||||
secret: secret,
|
||||
}
|
||||
|
||||
http.HandleFunc("/token", handler.handle)
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Token string `json:"accessToken"`
|
||||
}
|
||||
|
||||
func getJoinToken(apiKey, apiSecret, room, identity, name string) (string, error) {
|
||||
at := auth.NewAccessToken(apiKey, apiSecret)
|
||||
|
||||
canPublish := true
|
||||
canSubscribe := true
|
||||
grant := &auth.VideoGrant{
|
||||
RoomJoin: true,
|
||||
RoomCreate: true,
|
||||
CanPublish: &canPublish,
|
||||
CanSubscribe: &canSubscribe,
|
||||
Room: room,
|
||||
}
|
||||
|
||||
at.AddGrant(grant).
|
||||
SetIdentity(identity).
|
||||
SetValidFor(time.Hour).
|
||||
SetName(name)
|
||||
|
||||
return at.ToJWT()
|
||||
}
|
||||
@@ -106,6 +106,7 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^29.2.2",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"jest-mock": "^29.5.0",
|
||||
"prettier": "^2.6.2",
|
||||
"sass": "^1.42.1",
|
||||
"storybook-builder-vite": "^0.1.12",
|
||||
|
||||
@@ -24,7 +24,6 @@ import { HomePage } from "./home/HomePage";
|
||||
import { LoginPage } from "./auth/LoginPage";
|
||||
import { RegisterPage } from "./auth/RegisterPage";
|
||||
import { RoomPage } from "./room/RoomPage";
|
||||
import { RoomRedirect } from "./room/RoomRedirect";
|
||||
import { ClientProvider } from "./ClientContext";
|
||||
import { usePageFocusStyle } from "./usePageFocusStyle";
|
||||
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
||||
@@ -71,14 +70,11 @@ export default function App({ history }: AppProps) {
|
||||
<SentryRoute exact path="/register">
|
||||
<RegisterPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/room/:roomId?">
|
||||
<RoomPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/inspector">
|
||||
<SequenceDiagramViewerPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="*">
|
||||
<RoomRedirect />
|
||||
<RoomPage />
|
||||
</SentryRoute>
|
||||
</Switch>
|
||||
</OverlayProvider>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
import { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
import { Config } from "./config/Config";
|
||||
|
||||
interface UrlParams {
|
||||
roomAlias: string | null;
|
||||
roomId: string | null;
|
||||
@@ -88,19 +90,46 @@ interface UrlParams {
|
||||
|
||||
/**
|
||||
* Gets the app parameters for the current URL.
|
||||
* @param query The URL query string
|
||||
* @param fragment The URL fragment string
|
||||
* @param ignoreRoomAlias If true, does not try to parse a room alias from the URL
|
||||
* @param search The URL search string
|
||||
* @param pathname The URL path name
|
||||
* @param hash The URL hash
|
||||
* @returns The app parameters encoded in the URL
|
||||
*/
|
||||
export const getUrlParams = (
|
||||
query: string = window.location.search,
|
||||
fragment: string = window.location.hash
|
||||
ignoreRoomAlias?: boolean,
|
||||
search = window.location.search,
|
||||
pathname = window.location.pathname,
|
||||
hash = window.location.hash
|
||||
): UrlParams => {
|
||||
const fragmentQueryStart = fragment.indexOf("?");
|
||||
let roomAlias: string | undefined;
|
||||
if (!ignoreRoomAlias) {
|
||||
if (hash === "") {
|
||||
roomAlias = pathname.substring(1); // Strip the "/"
|
||||
|
||||
// Delete "/room/" and "?", if present
|
||||
if (roomAlias.startsWith("room/")) {
|
||||
roomAlias = roomAlias.substring("room/".length);
|
||||
}
|
||||
// Add "#", if not present
|
||||
if (!roomAlias.startsWith("#")) {
|
||||
roomAlias = `#${roomAlias}`;
|
||||
}
|
||||
} else {
|
||||
roomAlias = hash;
|
||||
}
|
||||
|
||||
// Add server part, if not present
|
||||
if (!roomAlias.includes(":")) {
|
||||
roomAlias = `${roomAlias}:${Config.defaultServerName()}`;
|
||||
}
|
||||
}
|
||||
|
||||
const fragmentQueryStart = hash.indexOf("?");
|
||||
const fragmentParams = new URLSearchParams(
|
||||
fragmentQueryStart === -1 ? "" : fragment.substring(fragmentQueryStart)
|
||||
fragmentQueryStart === -1 ? "" : hash.substring(fragmentQueryStart)
|
||||
);
|
||||
const queryParams = new URLSearchParams(query);
|
||||
const queryParams = new URLSearchParams(search);
|
||||
|
||||
// Normally, URL params should be encoded in the fragment so as to avoid
|
||||
// leaking them to the server. However, we also check the normal query
|
||||
@@ -114,16 +143,10 @@ export const getUrlParams = (
|
||||
...queryParams.getAll(name),
|
||||
];
|
||||
|
||||
// The part of the fragment before the ?
|
||||
const fragmentRoute =
|
||||
fragmentQueryStart === -1
|
||||
? fragment
|
||||
: fragment.substring(0, fragmentQueryStart);
|
||||
|
||||
const fontScale = parseFloat(getParam("fontScale") ?? "");
|
||||
|
||||
return {
|
||||
roomAlias: fragmentRoute.length > 1 ? fragmentRoute : null,
|
||||
roomAlias: !roomAlias || roomAlias.includes("!") ? null : roomAlias,
|
||||
roomId: getParam("roomId"),
|
||||
viaServers: getAllParams("via"),
|
||||
isEmbedded: hasParam("embed"),
|
||||
@@ -149,6 +172,9 @@ export const getUrlParams = (
|
||||
* @returns The app parameters for the current URL
|
||||
*/
|
||||
export const useUrlParams = (): UrlParams => {
|
||||
const { hash, search } = useLocation();
|
||||
return useMemo(() => getUrlParams(search, hash), [search, hash]);
|
||||
const { search, pathname, hash } = useLocation();
|
||||
return useMemo(
|
||||
() => getUrlParams(false, search, pathname, hash),
|
||||
[search, pathname, hash]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,11 +80,7 @@ export const RegisterPage: FC = () => {
|
||||
passwordlessUser
|
||||
);
|
||||
|
||||
if (!client || !client.groupCallEventHandler || !setClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordlessUser) {
|
||||
if (client && client?.groupCallEventHandler && passwordlessUser) {
|
||||
// Migrate the user's rooms
|
||||
for (const groupCall of client.groupCallEventHandler.groupCalls.values()) {
|
||||
const roomId = groupCall.room.roomId;
|
||||
@@ -107,7 +103,7 @@ export const RegisterPage: FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
setClient({ client: newClient, session });
|
||||
setClient?.({ client: newClient, session });
|
||||
PosthogAnalytics.instance.eventSignup.cacheSignupEnd(new Date());
|
||||
};
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@ export function CallList({ rooms, client, disableFacepile }: CallListProps) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.callList}>
|
||||
{rooms.map(({ roomId, roomName, avatarUrl, participants }) => (
|
||||
{rooms.map(({ roomAlias, roomName, avatarUrl, participants }) => (
|
||||
<CallTile
|
||||
key={roomId}
|
||||
key={roomAlias}
|
||||
client={client}
|
||||
name={roomName}
|
||||
avatarUrl={avatarUrl}
|
||||
roomId={roomId}
|
||||
roomAlias={roomAlias}
|
||||
participants={participants}
|
||||
disableFacepile={disableFacepile}
|
||||
/>
|
||||
@@ -59,7 +59,7 @@ export function CallList({ rooms, client, disableFacepile }: CallListProps) {
|
||||
interface CallTileProps {
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
roomId: string;
|
||||
roomAlias: string;
|
||||
participants: RoomMember[];
|
||||
client: MatrixClient;
|
||||
disableFacepile?: boolean;
|
||||
@@ -67,14 +67,17 @@ interface CallTileProps {
|
||||
function CallTile({
|
||||
name,
|
||||
avatarUrl,
|
||||
roomId,
|
||||
roomAlias,
|
||||
participants,
|
||||
client,
|
||||
disableFacepile,
|
||||
}: CallTileProps) {
|
||||
return (
|
||||
<div className={styles.callTile}>
|
||||
<Link to={`/room/${roomId}`} className={styles.callTileLink}>
|
||||
<Link
|
||||
to={`/${roomAlias.substring(1).split(":")[0]}`}
|
||||
className={styles.callTileLink}
|
||||
>
|
||||
<Avatar
|
||||
size={Size.LG}
|
||||
bgKey={name}
|
||||
@@ -86,7 +89,7 @@ function CallTile({
|
||||
<Body overflowEllipsis fontWeight="semiBold">
|
||||
{name}
|
||||
</Body>
|
||||
<Caption overflowEllipsis>{getRoomUrl(roomId)}</Caption>
|
||||
<Caption overflowEllipsis>{getRoomUrl(roomAlias)}</Caption>
|
||||
{participants && !disableFacepile && (
|
||||
<Facepile
|
||||
className={styles.facePile}
|
||||
@@ -100,7 +103,7 @@ function CallTile({
|
||||
<CopyButton
|
||||
className={styles.copyButton}
|
||||
variant="icon"
|
||||
value={getRoomUrl(roomId)}
|
||||
value={getRoomUrl(roomAlias)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -71,8 +71,9 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
setLoading(true);
|
||||
|
||||
const [roomAlias] = await createRoom(client, roomName, ptt);
|
||||
|
||||
if (roomAlias) {
|
||||
history.push(`/room/${roomAlias}`);
|
||||
history.push(`/${roomAlias.substring(1).split(":")[0]}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
setOnFinished(() => {
|
||||
setClient({ client, session });
|
||||
const aliasLocalpart = roomAliasLocalpartFromRoomName(roomName);
|
||||
const [, serverName] = client.getUserId()!.split(":");
|
||||
history.push(`/room/#${aliasLocalpart}:${serverName}`);
|
||||
history.push(`/${aliasLocalpart}`);
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
@@ -111,7 +110,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
}
|
||||
|
||||
setClient({ client, session });
|
||||
history.push(`/room/${roomAlias}`);
|
||||
history.push(`/${roomAlias.substring(1).split(":")[0]}`);
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { GroupCallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/groupCallEv
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export interface GroupCallRoom {
|
||||
roomId: string;
|
||||
roomAlias: string;
|
||||
roomName: string;
|
||||
avatarUrl: string;
|
||||
room: Room;
|
||||
@@ -89,12 +89,13 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||
|
||||
const groupCalls = client.groupCallEventHandler.groupCalls.values();
|
||||
const rooms = Array.from(groupCalls).map((groupCall) => groupCall.room);
|
||||
const sortedRooms = sortRooms(client, rooms);
|
||||
const filteredRooms = rooms.filter((r) => r.getCanonicalAlias()); // We don't display rooms without an alias
|
||||
const sortedRooms = sortRooms(client, filteredRooms);
|
||||
const items = sortedRooms.map((room) => {
|
||||
const groupCall = client.getGroupCallForRoom(room.roomId)!;
|
||||
|
||||
return {
|
||||
roomId: room.getCanonicalAlias() || room.roomId,
|
||||
roomAlias: room.getCanonicalAlias(),
|
||||
roomName: room.name,
|
||||
avatarUrl: room.getMxcAvatarUrl()!,
|
||||
room,
|
||||
@@ -103,7 +104,7 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||
};
|
||||
});
|
||||
|
||||
setRooms(items);
|
||||
setRooms(items as GroupCallRoom[]);
|
||||
}
|
||||
|
||||
updateRooms();
|
||||
|
||||
@@ -55,7 +55,7 @@ export class Initializer {
|
||||
languageDetector.addDetector({
|
||||
name: "urlFragment",
|
||||
// Look for a language code in the URL's fragment
|
||||
lookup: () => getUrlParams().lang ?? undefined,
|
||||
lookup: () => getUrlParams(true).lang ?? undefined,
|
||||
});
|
||||
|
||||
i18n
|
||||
@@ -140,7 +140,7 @@ export class Initializer {
|
||||
}
|
||||
|
||||
// Custom fonts
|
||||
const { fonts, fontScale } = getUrlParams();
|
||||
const { fonts, fontScale } = getUrlParams(true);
|
||||
if (fontScale !== null) {
|
||||
document.documentElement.style.setProperty(
|
||||
"--font-scale",
|
||||
|
||||
@@ -34,7 +34,7 @@ import styles from "./AvatarInputField.module.css";
|
||||
interface Props extends AllHTMLAttributes<HTMLInputElement> {
|
||||
id: string;
|
||||
label: string;
|
||||
avatarUrl: string;
|
||||
avatarUrl: string | undefined;
|
||||
displayName: string;
|
||||
onRemoveAvatar: () => void;
|
||||
}
|
||||
|
||||
@@ -41,13 +41,16 @@ export async function getSFUConfigWithOpenID(
|
||||
// if the call has a livekit service URL, try it.
|
||||
if (groupCall.livekitServiceURL) {
|
||||
try {
|
||||
logger.info(`Trying to get JWT from ${groupCall.livekitServiceURL}...`);
|
||||
logger.info(
|
||||
`Trying to get JWT from call's configured URL of ${groupCall.livekitServiceURL}...`
|
||||
);
|
||||
const sfuConfig = await getLiveKitJWT(
|
||||
client,
|
||||
groupCall.livekitServiceURL,
|
||||
roomName,
|
||||
openIdToken
|
||||
);
|
||||
logger.info(`Got JWT from call state event URL.`);
|
||||
|
||||
return sfuConfig;
|
||||
} catch (e) {
|
||||
@@ -71,9 +74,12 @@ export async function getSFUConfigWithOpenID(
|
||||
openIdToken
|
||||
);
|
||||
|
||||
logger.info(`Updating call livekit service URL with: ${urlFromConf}...`);
|
||||
logger.info(
|
||||
`Got JWT, updating call livekit service URL with: ${urlFromConf}...`
|
||||
);
|
||||
try {
|
||||
await groupCall.updateLivekitServiceURL(urlFromConf);
|
||||
logger.info(`Call livekit service URL updated.`);
|
||||
} catch (e) {
|
||||
logger.warn(
|
||||
`Failed to update call livekit service URL: continuing anyway.`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Room, RoomOptions } from "livekit-client";
|
||||
import { Room, RoomOptions, setLogLevel } from "livekit-client";
|
||||
import { useLiveKitRoom } from "@livekit/components-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
@@ -15,6 +15,8 @@ export type DeviceChoices = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
setLogLevel("debug");
|
||||
|
||||
export function useLiveKit(
|
||||
userChoices: UserChoices,
|
||||
sfuConfig?: SFUConfig
|
||||
|
||||
@@ -345,15 +345,11 @@ export async function createRoom(
|
||||
// Returns a URL to that will load Element Call with the given room
|
||||
export function getRoomUrl(roomIdOrAlias: string): string {
|
||||
if (roomIdOrAlias.startsWith("#")) {
|
||||
const [localPart, host] = roomIdOrAlias.replace("#", "").split(":");
|
||||
|
||||
if (host !== Config.defaultServerName()) {
|
||||
return `${window.location.protocol}//${window.location.host}/room/${roomIdOrAlias}`;
|
||||
} else {
|
||||
return `${window.location.protocol}//${window.location.host}/${localPart}`;
|
||||
}
|
||||
return `${window.location.protocol}//${window.location.host}/${
|
||||
roomIdOrAlias.substring(1).split(":")[0]
|
||||
}`;
|
||||
} else {
|
||||
return `${window.location.protocol}//${window.location.host}/room/#?roomId=${roomIdOrAlias}`;
|
||||
return `${window.location.protocol}//${window.location.host}/room?roomId=${roomIdOrAlias}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useLocation, useHistory } from "react-router-dom";
|
||||
|
||||
import { Config } from "../config/Config";
|
||||
import { LoadingView } from "../FullScreenView";
|
||||
|
||||
// A component that, when loaded, redirects the client to a full room URL
|
||||
// based on the current URL being an abbreviated room URL
|
||||
export function RoomRedirect() {
|
||||
const { pathname } = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
let roomId = pathname;
|
||||
|
||||
if (pathname.startsWith("/")) {
|
||||
roomId = roomId.substring(1, roomId.length);
|
||||
}
|
||||
|
||||
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
||||
roomId = `#${roomId}:${Config.defaultServerName()}`;
|
||||
}
|
||||
|
||||
history.replace(`/room/${roomId.toLowerCase()}`);
|
||||
}, [pathname, history]);
|
||||
|
||||
return <LoadingView />;
|
||||
}
|
||||
@@ -106,11 +106,11 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
onUserChoicesChanged({
|
||||
video: {
|
||||
selectedId: videoIn.selectedId,
|
||||
enabled: videoEnabled && !!videoTrack,
|
||||
enabled: videoEnabled,
|
||||
},
|
||||
audio: {
|
||||
selectedId: audioIn.selectedId,
|
||||
enabled: audioEnabled && !!audioTrack,
|
||||
enabled: audioEnabled,
|
||||
},
|
||||
});
|
||||
}, [
|
||||
|
||||
@@ -77,7 +77,7 @@ export function ProfileSettingsTab({ client }: Props) {
|
||||
return (
|
||||
<form onChange={onFormChange} ref={formRef} className={styles.content}>
|
||||
<FieldRow className={styles.avatarFieldRow}>
|
||||
{avatarUrl && displayName && (
|
||||
{displayName && (
|
||||
<AvatarInputField
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
|
||||
@@ -109,7 +109,7 @@ export const widget: WidgetHelpers | null = (() => {
|
||||
baseUrl,
|
||||
e2eEnabled,
|
||||
allowIceFallback,
|
||||
} = getUrlParams();
|
||||
} = getUrlParams(true);
|
||||
if (!roomId) throw new Error("Room ID must be supplied");
|
||||
if (!userId) throw new Error("User ID must be supplied");
|
||||
if (!deviceId) throw new Error("Device ID must be supplied");
|
||||
|
||||
98
test/UrlParams-test.ts
Normal file
98
test/UrlParams-test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
Copyright 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { getUrlParams } from "../src/UrlParams";
|
||||
import { Config } from "../src/config/Config";
|
||||
|
||||
const ROOM_NAME = "roomNameHere";
|
||||
const ROOM_ID = "d45f138fsd";
|
||||
const ORIGIN = "https://call.element.io";
|
||||
const HOMESERVER = "call.ems.host";
|
||||
|
||||
jest.mock("../src/config/Config");
|
||||
|
||||
describe("UrlParams", () => {
|
||||
beforeAll(() => {
|
||||
mocked(Config.defaultServerName).mockReturnValue("call.ems.host");
|
||||
});
|
||||
|
||||
describe("handles URL with /room/", () => {
|
||||
it("and nothing else", () => {
|
||||
expect(getUrlParams(false, "", `/room/${ROOM_NAME}`, "").roomAlias).toBe(
|
||||
`#${ROOM_NAME}:${HOMESERVER}`
|
||||
);
|
||||
});
|
||||
|
||||
it("and #", () => {
|
||||
expect(
|
||||
getUrlParams(false, "", `${ORIGIN}/room/`, `#${ROOM_NAME}`).roomAlias
|
||||
).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
|
||||
});
|
||||
|
||||
it("and # and server part", () => {
|
||||
expect(
|
||||
getUrlParams(false, "", `/room/`, `#${ROOM_NAME}:${HOMESERVER}`)
|
||||
.roomAlias
|
||||
).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
|
||||
});
|
||||
|
||||
it("and server part", () => {
|
||||
expect(
|
||||
getUrlParams(false, "", `/room/${ROOM_NAME}:${HOMESERVER}`, "")
|
||||
.roomAlias
|
||||
).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handles URL without /room/", () => {
|
||||
it("and nothing else", () => {
|
||||
expect(getUrlParams(false, "", `/${ROOM_NAME}`, "").roomAlias).toBe(
|
||||
`#${ROOM_NAME}:${HOMESERVER}`
|
||||
);
|
||||
});
|
||||
|
||||
it("and with #", () => {
|
||||
expect(getUrlParams(false, "", "", `#${ROOM_NAME}`).roomAlias).toBe(
|
||||
`#${ROOM_NAME}:${HOMESERVER}`
|
||||
);
|
||||
});
|
||||
|
||||
it("and with # and server part", () => {
|
||||
expect(
|
||||
getUrlParams(false, "", "", `#${ROOM_NAME}:${HOMESERVER}`).roomAlias
|
||||
).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
|
||||
});
|
||||
|
||||
it("and with server part", () => {
|
||||
expect(
|
||||
getUrlParams(false, "", `/${ROOM_NAME}:${HOMESERVER}`, "").roomAlias
|
||||
).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handles search params", () => {
|
||||
it("(roomId)", () => {
|
||||
expect(getUrlParams(true, `?roomId=${ROOM_ID}`).roomId).toBe(ROOM_ID);
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores room alias", () => {
|
||||
expect(
|
||||
getUrlParams(true, "", `/room/${ROOM_NAME}:${HOMESERVER}`).roomAlias
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -34,7 +34,7 @@ describe("CallList", () => {
|
||||
|
||||
it("should show room", async () => {
|
||||
const rooms = [
|
||||
{ roomName: "Room #1", roomId: "!roomId" },
|
||||
{ roomName: "Room #1", roomAlias: "#room-name:server.org" },
|
||||
] as GroupCallRoom[];
|
||||
|
||||
const result = renderComponent(rooms);
|
||||
|
||||
45
yarn.lock
45
yarn.lock
@@ -2014,6 +2014,13 @@
|
||||
dependencies:
|
||||
"@sinclair/typebox" "^0.24.1"
|
||||
|
||||
"@jest/schemas@^29.4.3":
|
||||
version "29.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788"
|
||||
integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==
|
||||
dependencies:
|
||||
"@sinclair/typebox" "^0.25.16"
|
||||
|
||||
"@jest/source-map@^29.2.0":
|
||||
version "29.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.2.0.tgz#ab3420c46d42508dcc3dc1c6deee0b613c235744"
|
||||
@@ -2088,6 +2095,18 @@
|
||||
"@types/yargs" "^17.0.8"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@jest/types@^29.5.0":
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593"
|
||||
integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==
|
||||
dependencies:
|
||||
"@jest/schemas" "^29.4.3"
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^3.0.0"
|
||||
"@types/node" "*"
|
||||
"@types/yargs" "^17.0.8"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@joshwooding/vite-plugin-react-docgen-typescript@0.0.2":
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.0.2.tgz#e0ae8c94f468da3a273a7b0acf23ba3565f86cbc"
|
||||
@@ -3098,6 +3117,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
|
||||
integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==
|
||||
|
||||
"@sinclair/typebox@^0.25.16":
|
||||
version "0.25.24"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
|
||||
integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
||||
@@ -10309,6 +10333,15 @@ jest-mock@^29.3.1:
|
||||
"@types/node" "*"
|
||||
jest-util "^29.3.1"
|
||||
|
||||
jest-mock@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed"
|
||||
integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==
|
||||
dependencies:
|
||||
"@jest/types" "^29.5.0"
|
||||
"@types/node" "*"
|
||||
jest-util "^29.5.0"
|
||||
|
||||
jest-pnp-resolver@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
|
||||
@@ -10451,6 +10484,18 @@ jest-util@^29.3.1:
|
||||
graceful-fs "^4.2.9"
|
||||
picomatch "^2.2.3"
|
||||
|
||||
jest-util@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f"
|
||||
integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==
|
||||
dependencies:
|
||||
"@jest/types" "^29.5.0"
|
||||
"@types/node" "*"
|
||||
chalk "^4.0.0"
|
||||
ci-info "^3.2.0"
|
||||
graceful-fs "^4.2.9"
|
||||
picomatch "^2.2.3"
|
||||
|
||||
jest-validate@^29.2.2:
|
||||
version "29.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.2.2.tgz#e43ce1931292dfc052562a11bc681af3805eadce"
|
||||
|
||||
Reference in New Issue
Block a user