Merge branch 'livekit' into settings-tab-order

This commit is contained in:
Robin Townsend
2023-07-20 23:54:47 -04:00
26 changed files with 243 additions and 351 deletions

View File

@@ -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"
},
```

View File

@@ -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

View File

@@ -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" ]

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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()
}

View File

@@ -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",

View File

@@ -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>

View File

@@ -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]
);
};

View File

@@ -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());
};

View File

@@ -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>
);

View File

@@ -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]}`);
}
}

View File

@@ -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) => {

View File

@@ -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();

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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.`

View File

@@ -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

View File

@@ -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}`;
}
}

View File

@@ -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 />;
}

View File

@@ -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,
},
});
}, [

View File

@@ -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"

View File

@@ -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
View 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();
});
});

View File

@@ -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);

View File

@@ -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"