diff --git a/README.md b/README.md index 8210bf77..7731d6fe 100644 --- a/README.md +++ b/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" }, ``` diff --git a/backend-docker-compose.yml b/backend-docker-compose.yml index d575cf18..58b147e9 100644 --- a/backend-docker-compose.yml +++ b/backend-docker-compose.yml @@ -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 diff --git a/backend/auth/Dockerfile b/backend/auth/Dockerfile deleted file mode 100644 index c7bbd955..00000000 --- a/backend/auth/Dockerfile +++ /dev/null @@ -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" ] diff --git a/backend/auth/go.mod b/backend/auth/go.mod deleted file mode 100644 index 75d1a533..00000000 --- a/backend/auth/go.mod +++ /dev/null @@ -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 -) diff --git a/backend/auth/go.sum b/backend/auth/go.sum deleted file mode 100644 index e8419741..00000000 --- a/backend/auth/go.sum +++ /dev/null @@ -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= diff --git a/backend/auth/server.go b/backend/auth/server.go deleted file mode 100644 index b3721e2e..00000000 --- a/backend/auth/server.go +++ /dev/null @@ -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() -} diff --git a/package.json b/package.json index 6a5a47a1..7cf8eb1d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.tsx b/src/App.tsx index 0ef5bcd6..71cc9710 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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) { - - - - + diff --git a/src/UrlParams.ts b/src/UrlParams.ts index eb2b1795..4a37254a 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -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] + ); }; diff --git a/src/auth/RegisterPage.tsx b/src/auth/RegisterPage.tsx index a486aa46..c49d9b43 100644 --- a/src/auth/RegisterPage.tsx +++ b/src/auth/RegisterPage.tsx @@ -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()); }; diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx index 545dfade..c3d5a6ae 100644 --- a/src/home/CallList.tsx +++ b/src/home/CallList.tsx @@ -35,13 +35,13 @@ export function CallList({ rooms, client, disableFacepile }: CallListProps) { return ( <>
- {rooms.map(({ roomId, roomName, avatarUrl, participants }) => ( + {rooms.map(({ roomAlias, roomName, avatarUrl, participants }) => ( @@ -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 (
- + {name} - {getRoomUrl(roomId)} + {getRoomUrl(roomAlias)} {participants && !disableFacepile && (
); diff --git a/src/home/RegisteredView.tsx b/src/home/RegisteredView.tsx index 54f52491..76da921d 100644 --- a/src/home/RegisteredView.tsx +++ b/src/home/RegisteredView.tsx @@ -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]}`); } } diff --git a/src/home/UnauthenticatedView.tsx b/src/home/UnauthenticatedView.tsx index 554d8de6..f479901f 100644 --- a/src/home/UnauthenticatedView.tsx +++ b/src/home/UnauthenticatedView.tsx @@ -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) => { diff --git a/src/home/useGroupCallRooms.ts b/src/home/useGroupCallRooms.ts index 463a1bb3..0f5d8ef9 100644 --- a/src/home/useGroupCallRooms.ts +++ b/src/home/useGroupCallRooms.ts @@ -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(); diff --git a/src/initializer.tsx b/src/initializer.tsx index f1f37966..850de125 100644 --- a/src/initializer.tsx +++ b/src/initializer.tsx @@ -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", diff --git a/src/input/AvatarInputField.tsx b/src/input/AvatarInputField.tsx index 484f440e..0218258e 100644 --- a/src/input/AvatarInputField.tsx +++ b/src/input/AvatarInputField.tsx @@ -34,7 +34,7 @@ import styles from "./AvatarInputField.module.css"; interface Props extends AllHTMLAttributes { id: string; label: string; - avatarUrl: string; + avatarUrl: string | undefined; displayName: string; onRemoveAvatar: () => void; } diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index 3cc61fbb..083e95b2 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -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.` diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index 7a0a5330..dac6a052 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -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 diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index ffbdf4e6..6dff2de3 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -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}`; } } diff --git a/src/room/RoomRedirect.tsx b/src/room/RoomRedirect.tsx deleted file mode 100644 index 3d45086d..00000000 --- a/src/room/RoomRedirect.tsx +++ /dev/null @@ -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 ; -} diff --git a/src/room/VideoPreview.tsx b/src/room/VideoPreview.tsx index ae5e311c..5fac133e 100644 --- a/src/room/VideoPreview.tsx +++ b/src/room/VideoPreview.tsx @@ -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, }, }); }, [ diff --git a/src/settings/ProfileSettingsTab.tsx b/src/settings/ProfileSettingsTab.tsx index 8de8dd52..e6a59634 100644 --- a/src/settings/ProfileSettingsTab.tsx +++ b/src/settings/ProfileSettingsTab.tsx @@ -77,7 +77,7 @@ export function ProfileSettingsTab({ client }: Props) { return (
- {avatarUrl && displayName && ( + {displayName && ( { 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"); diff --git a/test/UrlParams-test.ts b/test/UrlParams-test.ts new file mode 100644 index 00000000..9d1e5198 --- /dev/null +++ b/test/UrlParams-test.ts @@ -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(); + }); +}); diff --git a/test/home/CallList-test.tsx b/test/home/CallList-test.tsx index 7b3925d4..a161960f 100644 --- a/test/home/CallList-test.tsx +++ b/test/home/CallList-test.tsx @@ -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); diff --git a/yarn.lock b/yarn.lock index d8207984..e35ce34b 100644 --- a/yarn.lock +++ b/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"