Merge remote-tracking branch 'upstream/livekit' into SimonBrandner/feat/e2ee
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
18
README.md
18
README.md
@@ -9,7 +9,7 @@ For prior version of the Element Call that relied solely on full-mesh logic, che
|
||||
|
||||

|
||||
|
||||
To try it out, visit our hosted version at [call.element.io](https://call.element.io). You can also find the latest development version continuously deployed to [element-call.netlify.app](https://element-call.netlify.app).
|
||||
To try it out, visit our hosted version at [call.element.io](https://call.element.io). You can also find the latest development version continuously deployed to [call.element.dev](https://call.element.dev/).
|
||||
|
||||
## Host it yourself
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
21
package.json
21
package.json
@@ -46,9 +46,6 @@
|
||||
"@react-stately/tree": "^3.2.0",
|
||||
"@sentry/react": "^6.13.3",
|
||||
"@sentry/tracing": "^6.13.3",
|
||||
"@types/grecaptcha": "^3.0.4",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/sdp-transform": "^2.4.5",
|
||||
"@use-gesture/react": "^10.2.11",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
@@ -78,9 +75,7 @@
|
||||
"sdp-transform": "^2.14.1",
|
||||
"tinyqueue": "^2.0.3",
|
||||
"unique-names-generator": "^4.6.0",
|
||||
"uuid": "9",
|
||||
"@types/uuid": "9",
|
||||
"@types/content-type": "^1.1.5"
|
||||
"uuid": "9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
@@ -90,8 +85,14 @@
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/node": "^18.13.0",
|
||||
"@types/request": "^2.48.8",
|
||||
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
||||
"@typescript-eslint/parser": "^5.52.0",
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/dom-screen-wake-lock": "^1.0.1",
|
||||
"@types/grecaptcha": "^3.0.4",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/sdp-transform": "^2.4.5",
|
||||
"@types/uuid": "9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-transform-vite-meta-env": "^1.0.3",
|
||||
"eslint": "^8.14.0",
|
||||
@@ -106,11 +107,11 @@
|
||||
"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",
|
||||
"typescript": "^4.9.5",
|
||||
"typescript-strict-plugin": "^2.0.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.2.0",
|
||||
"vite-plugin-html-template": "^1.1.0",
|
||||
"vite-plugin-svgr": "^3.2.0"
|
||||
|
||||
@@ -115,5 +115,7 @@
|
||||
"Show connection stats": "Verbindungsstatistiken zeigen",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Mit einem Klick auf „Anruf beitreten“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Mit einem Klick auf „Los geht’s“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Diese Seite wird durch reCAPTCHA geschützt und es gelten Googles <2>Datenschutzerklärung</2> und <6>Nutzungsbedingungen</6>. <9></9>Mit einem Klick auf „Registrieren“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>"
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Diese Seite wird durch reCAPTCHA geschützt und es gelten Googles <2>Datenschutzerklärung</2> und <6>Nutzungsbedingungen</6>. <9></9>Mit einem Klick auf „Registrieren“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call ist temporär nicht Ende-zu-Ende-verschlüsselt, während wir die Skalierbarkeit testen.",
|
||||
"Connectivity to the server has been lost.": "Die Verbindung zum Server wurde getrennt."
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"Change layout": "Change layout",
|
||||
"Close": "Close",
|
||||
"Confirm password": "Confirm password",
|
||||
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
|
||||
"Copied!": "Copied!",
|
||||
"Copy": "Copy",
|
||||
"Copy and share this call link": "Copy and share this call link",
|
||||
@@ -78,9 +79,11 @@
|
||||
"Profile": "Profile",
|
||||
"Recaptcha dismissed": "Recaptcha dismissed",
|
||||
"Recaptcha not loaded": "Recaptcha not loaded",
|
||||
"Reconnect": "Reconnect",
|
||||
"Register": "Register",
|
||||
"Registering…": "Registering…",
|
||||
"Remove": "Remove",
|
||||
"Retry sending logs": "Retry sending logs",
|
||||
"Return to home screen": "Return to home screen",
|
||||
"Select an option": "Select an option",
|
||||
"Send debug logs": "Send debug logs",
|
||||
@@ -100,7 +103,7 @@
|
||||
"Submitting…": "Submitting…",
|
||||
"Take me Home": "Take me Home",
|
||||
"Thanks, we received your feedback!": "Thanks, we received your feedback!",
|
||||
"Thanks! We'll get right on it.": "Thanks! We'll get right on it.",
|
||||
"Thanks!": "Thanks!",
|
||||
"This call already exists, would you like to join?": "This call already exists, would you like to join?",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
|
||||
"Turn off camera": "Turn off camera",
|
||||
@@ -117,6 +120,7 @@
|
||||
"Walkie-talkie call name": "Walkie-talkie call name",
|
||||
"WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.",
|
||||
"Yes, join call": "Yes, join call",
|
||||
"You were disconnected from the call": "You were disconnected from the call",
|
||||
"Your feedback": "Your feedback",
|
||||
"Your recent calls": "Your recent calls"
|
||||
}
|
||||
|
||||
@@ -98,5 +98,23 @@
|
||||
"Expose developer settings in the settings window.": "Muestra los ajustes de desarrollador en la ventana de ajustes.",
|
||||
"Developer Settings": "Ajustes de desarrollador",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Al participar en esta beta, consientes a la recogida de datos anónimos, los cuales usaremos para mejorar el producto. Puedes encontrar más información sobre que datos recogemos en nuestra <2>Política de privacidad</2> y en nuestra <5>Política sobre Cookies</5>.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Puedes retirar tu consentimiento desmarcando esta casilla. Si estás en una llamada, este ajuste se aplicará al final de esta."
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Puedes retirar tu consentimiento desmarcando esta casilla. Si estás en una llamada, este ajuste se aplicará al final de esta.",
|
||||
"{{displayName}} is presenting": "{{displayName}} está presentando",
|
||||
"<0>Thanks for your feedback!</0>": "<0>¡Gracias por tus comentarios!</0>",
|
||||
"How did it go?": "¿Cómo ha ido?",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Este sitio está protegido por ReCAPTCHA y se aplican la <2>Política de Privacidad</2> y los <6>Términos de Servicio de Google.<9></9>Al hacer clic en \"Registrar\", aceptas nuestro <12>Contrato de Licencia de Usuario Final (CLUF)</12>",
|
||||
"Show connection stats": "Mostrar estadísticas de conexión",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call no está encriptado de extremo a extremo de manera temporal mientras probamos la escalabilidad del servicio.",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Al hacer clic en \"Comenzar\", aceptas nuestro <2>Contrato de Licencia de Usuario Final (CLUF)</2>",
|
||||
"Thanks, we received your feedback!": "¡Gracias, hemos recibido tus comentarios!",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Si tienes algún problema o simplemente quieres darnos tu opinión, por favor envíanos una breve descripción.",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Al hacer clic en \"Unirse a la llamada ahora\", aceptas nuestro <2>Contrato de Licencia de Usuario Final (CLUF)</2>",
|
||||
"<0>We'd love to hear your feedback so we can improve your experience.</0>": "<0>Nos encantaría conocer tu opinión para que podamos mejorar tu experiencia</0>",
|
||||
"Feedback": "Danos tu opinión",
|
||||
"Submit": "Enviar",
|
||||
"{{count}} stars|one": "{{count}} estrella",
|
||||
"{{count}} stars|other": "{{count}} estrellas",
|
||||
"{{displayName}}, your call has ended.": "{{displayName}}, tu llamada ha finalizado.",
|
||||
"Submitting…": "Enviando…",
|
||||
"Your feedback": "Tus comentarios"
|
||||
}
|
||||
|
||||
@@ -115,5 +115,7 @@
|
||||
"{{displayName}} is presenting": "{{displayName}} on esitlemas",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klõpsides „Jätka“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)</2>",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klõpsides „Liitu kõnega kohe“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Selles saidis on kasutusel ReCAPTCHA ja kehtivad Google'i <2>Privaatsuspoliitika</2> ning <6>Teenusetingimused</6>.<9></9>Klõpsides „Registreeru“, sa nõustud meie <12>Lõppkasutaja litsentsilepingu (EULA) tingimustega</12>"
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Selles saidis on kasutusel ReCAPTCHA ja kehtivad Google'i <2>Privaatsuspoliitika</2> ning <6>Teenusetingimused</6>.<9></9>Klõpsides „Registreeru“, sa nõustud meie <12>Lõppkasutaja litsentsilepingu (EULA) tingimustega</12>",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Seni kuni me testime skaleeritavust, siis Element Call ajutiselt pole läbivalt krüptitud.",
|
||||
"Connectivity to the server has been lost.": "Võrguühendus serveriga on katkenud."
|
||||
}
|
||||
|
||||
@@ -115,5 +115,6 @@
|
||||
"Show connection stats": "Afficher les statistiques de la connexion",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "En cliquant sur « Rejoindre l’appel maintenant », vous acceptez notre <2>Contrat de Licence Utilisateur Final (CLUF)</2>",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "En cliquant sur « Commencer », vous acceptez notre <2>Contrat de Licence Utilisateur Final (CLUF)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Ce site est protégé par ReCAPTCHA, la <2>politique de confidentialité</2> et les <6>conditions d’utilisation</6> de Google s’appliquent.<9></9>En cliquant sur « S’enregistrer » vous acceptez également notre <12>Contrat de Licence Utilisateur Final (CLUF)</12>"
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Ce site est protégé par ReCAPTCHA, la <2>politique de confidentialité</2> et les <6>conditions d’utilisation</6> de Google s’appliquent.<9></9>En cliquant sur « S’enregistrer » vous acceptez également notre <12>Contrat de Licence Utilisateur Final (CLUF)</12>",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call n’est temporairement plus chiffré de bout en bout le temps de tester l’extensibilité."
|
||||
}
|
||||
|
||||
@@ -115,5 +115,6 @@
|
||||
"{{count}} stars|other": "{{count}} bintang",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Dengan mengeklik \"Bergabung\", Anda menyetujui <2>Perjanjian Lisensi Pengguna Akhir (EULA)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Situs ini dilindungi oleh reCAPTCHA dan <2>Kebijakan Privasi</2> dan <6>Ketentuan Layanan</6> Google berlaku.<9></9>Dengan mengeklik \"Daftar\", Anda menyetujui <12>Perjanjian Lisensi Pengguna Akhir (EULA)</12> kami",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Dengan mengeklik \"Bergabung ke panggilan sekarang\", Anda menyetujui <2>Perjanjian Lisensi Pengguna Akhir (EULA)</2> kami"
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Dengan mengeklik \"Bergabung ke panggilan sekarang\", Anda menyetujui <2>Perjanjian Lisensi Pengguna Akhir (EULA)</2> kami",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call sementara tidak dienkripsi secara ujung ke ujung selagi kami menguji skalabilitas."
|
||||
}
|
||||
|
||||
@@ -112,5 +112,9 @@
|
||||
"<0>We'd love to hear your feedback so we can improve your experience.</0>": "<0>Z przyjemnością wysłuchamy Twojej opinii, aby poprawić Twoje doświadczenia.</0>",
|
||||
"How did it go?": "Jak poszło?",
|
||||
"{{displayName}} is presenting": "{{displayName}} prezentuje",
|
||||
"Show connection stats": "Pokaż statystyki połączenia"
|
||||
"Show connection stats": "Pokaż statystyki połączenia",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Usługa Element Call tymczasowo nie jest szyfrowana end-to-end w trakcie, gdy testujemy możliwość jej rozszerzenia.",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klikając \"Przejdź\", zgadzasz się na naszą <2>Umowę licencyjną (EULA)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Ta witryna jest chroniona przez ReCAPTCHA, więc obowiązują <2>Polityka prywatności</2> i <6>Warunki usług</6> Google. Klikając \"Zarejestruj\", zgadzasz się na naszą <12>Umowę licencyjną (EULA)</12>",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klikając \"Dołącz teraz do rozmowy\", zgadzasz się na naszą <2>Umowę licencyjną (EULA)</2>"
|
||||
}
|
||||
|
||||
@@ -115,5 +115,7 @@
|
||||
"Show connection stats": "Zobraziť štatistiky pripojenia",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Kliknutím na \"Pripojiť sa k hovoru teraz\" súhlasíte s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)</2>",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Kliknutím na tlačidlo \"Prejsť\" vyjadrujete súhlas s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Táto stránka je chránená systémom ReCAPTCHA a platia na ňu <2>Pravidlá ochrany osobných údajov spoločnosti Google</2> a <6>Podmienky poskytovania služieb</6>.<9></9>Kliknutím na tlačidlo \"Registrovať sa\" súhlasíte s našou <12>Licenčnou zmluvou s koncovým používateľom (EULA)</12>"
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Táto stránka je chránená systémom ReCAPTCHA a platia na ňu <2>Pravidlá ochrany osobných údajov spoločnosti Google</2> a <6>Podmienky poskytovania služieb</6>.<9></9>Kliknutím na tlačidlo \"Registrovať sa\" súhlasíte s našou <12>Licenčnou zmluvou s koncovým používateľom (EULA)</12>",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call nie je dočasne šifrovaný, kým testujeme škálovateľnosť.",
|
||||
"Connectivity to the server has been lost.": "Spojenie so serverom sa stratilo."
|
||||
}
|
||||
|
||||
@@ -115,5 +115,6 @@
|
||||
"Show connection stats": "Показати стан з'єднання",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Натискаючи \"Далі\", ви погоджуєтеся з нашою <2>Ліцензійною угодою з кінцевим користувачем (EULA)</2>",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Натискаючи \"Приєднатися до виклику зараз\", ви погоджуєтеся з нашою <2>Ліцензійною угодою з кінцевим користувачем (EULA)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Цей сайт захищений ReCAPTCHA і до нього застосовується <2>Політика приватності</2> і <6>Умови надання послуг</6> Google.<9></9>Натискаючи \"Зареєструватися\", ви погоджуєтеся з нашою <12>Ліцензійною угодою з кінцевим користувачем (EULA)</12>"
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Цей сайт захищений ReCAPTCHA і до нього застосовується <2>Політика приватності</2> і <6>Умови надання послуг</6> Google.<9></9>Натискаючи \"Зареєструватися\", ви погоджуєтеся з нашою <12>Ліцензійною угодою з кінцевим користувачем (EULA)</12>",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Виклики Element тимчасово не захищаються наскрізним шифруванням, поки ми тестуємо масштабованість."
|
||||
}
|
||||
|
||||
@@ -115,5 +115,6 @@
|
||||
"Show connection stats": "顯示連線統計資料",
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "點擊「前往」即表示您同意我們的<2>終端使用者授權協議 (EULA)</2>",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "點擊「立刻加入通話」即表示您同意我們的<2>終端使用者授權協議 (EULA)</2>",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "此網站被 ReCAPTCHA 保護,並適用 Google 的<2>隱私權政策</2>與<6>服務條款</6>。<9></9>點擊「註冊」即表示您同意我們的<12>終端使用者授權協議 (EULA)</12>"
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "此網站被 ReCAPTCHA 保護,並適用 Google 的<2>隱私權政策</2>與<6>服務條款</6>。<9></9>點擊「註冊」即表示您同意我們的<12>終端使用者授權協議 (EULA)</12>",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "在我們測試可擴展性時,Element Call 暫時未進行端到端加密。"
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ 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";
|
||||
import { InspectorContextProvider } from "./room/GroupCallInspector";
|
||||
import { CrashView, LoadingView } from "./FullScreenView";
|
||||
import { DisconnectedBanner } from "./DisconnectedBanner";
|
||||
import { Initializer } from "./initializer";
|
||||
|
||||
const SentryRoute = Sentry.withSentryRouting(Route);
|
||||
@@ -61,6 +61,7 @@ export default function App({ history }: AppProps) {
|
||||
<InspectorContextProvider>
|
||||
<Sentry.ErrorBoundary fallback={errorPage}>
|
||||
<OverlayProvider>
|
||||
<DisconnectedBanner />
|
||||
<Switch>
|
||||
<SentryRoute exact path="/">
|
||||
<HomePage />
|
||||
@@ -71,14 +72,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>
|
||||
|
||||
@@ -25,9 +25,10 @@ import {
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
|
||||
|
||||
import { ErrorView } from "./FullScreenView";
|
||||
import {
|
||||
@@ -56,6 +57,9 @@ export type ClientState = ValidClientState | ErrorState;
|
||||
export type ValidClientState = {
|
||||
state: "valid";
|
||||
authenticated?: AuthenticatedClient;
|
||||
// 'Disconnected' rather than 'connected' because it tracks specifically
|
||||
// whether the client is supposed to be connected but is not
|
||||
disconnected: boolean;
|
||||
setClient: (params?: SetClientParams) => void;
|
||||
};
|
||||
|
||||
@@ -149,8 +153,9 @@ interface Props {
|
||||
export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
const history = useHistory();
|
||||
|
||||
// null = signed out, undefined = loading
|
||||
const [initClientState, setInitClientState] = useState<
|
||||
InitResult | undefined
|
||||
InitResult | null | undefined
|
||||
>(undefined);
|
||||
|
||||
const initializing = useRef(false);
|
||||
@@ -162,14 +167,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
initializing.current = true;
|
||||
|
||||
loadClient()
|
||||
.then((maybeClient) => {
|
||||
if (!maybeClient) {
|
||||
logger.error("Failed to initialize client");
|
||||
return;
|
||||
}
|
||||
|
||||
setInitClientState(maybeClient);
|
||||
})
|
||||
.then(setInitClientState)
|
||||
.catch((err) => logger.error(err))
|
||||
.finally(() => (initializing.current = false));
|
||||
}, []);
|
||||
@@ -264,23 +262,46 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
}, [initClientState?.client, setAlreadyOpenedErr, t])
|
||||
);
|
||||
|
||||
const state: ClientState = useMemo(() => {
|
||||
const [isDisconnected, setIsDisconnected] = useState(false);
|
||||
|
||||
const state: ClientState | undefined = useMemo(() => {
|
||||
if (alreadyOpenedErr) {
|
||||
return { state: "error", error: alreadyOpenedErr };
|
||||
}
|
||||
|
||||
let authenticated = undefined;
|
||||
if (initClientState) {
|
||||
authenticated = {
|
||||
client: initClientState.client,
|
||||
isPasswordlessUser: initClientState.passwordlessUser,
|
||||
changePassword,
|
||||
logout,
|
||||
};
|
||||
}
|
||||
if (initClientState === undefined) return undefined;
|
||||
|
||||
return { state: "valid", authenticated, setClient };
|
||||
}, [alreadyOpenedErr, changePassword, initClientState, logout, setClient]);
|
||||
const authenticated =
|
||||
initClientState === null
|
||||
? undefined
|
||||
: {
|
||||
client: initClientState.client,
|
||||
isPasswordlessUser: initClientState.passwordlessUser,
|
||||
changePassword,
|
||||
logout,
|
||||
};
|
||||
|
||||
return {
|
||||
state: "valid",
|
||||
authenticated,
|
||||
setClient,
|
||||
disconnected: isDisconnected,
|
||||
};
|
||||
}, [
|
||||
alreadyOpenedErr,
|
||||
changePassword,
|
||||
initClientState,
|
||||
logout,
|
||||
setClient,
|
||||
isDisconnected,
|
||||
]);
|
||||
|
||||
const onSync = useCallback(
|
||||
(state: SyncState, _old: SyncState | null, data?: ISyncStateData) => {
|
||||
setIsDisconnected(clientIsDisconnected(state, data));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initClientState) {
|
||||
@@ -292,7 +313,17 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
|
||||
if (PosthogAnalytics.hasInstance())
|
||||
PosthogAnalytics.instance.onLoginStatusChanged();
|
||||
}, [initClientState]);
|
||||
|
||||
if (initClientState.client) {
|
||||
initClientState.client.on(ClientEvent.Sync, onSync);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (initClientState.client) {
|
||||
initClientState.client.removeListener(ClientEvent.Sync, onSync);
|
||||
}
|
||||
};
|
||||
}, [initClientState, onSync]);
|
||||
|
||||
if (alreadyOpenedErr) {
|
||||
return <ErrorView error={alreadyOpenedErr} />;
|
||||
@@ -308,7 +339,7 @@ type InitResult = {
|
||||
passwordlessUser: boolean;
|
||||
};
|
||||
|
||||
async function loadClient(): Promise<InitResult> {
|
||||
async function loadClient(): Promise<InitResult | null> {
|
||||
if (widget) {
|
||||
// We're inside a widget, so let's engage *matryoshka mode*
|
||||
logger.log("Using a matryoshka client");
|
||||
@@ -322,15 +353,12 @@ async function loadClient(): Promise<InitResult> {
|
||||
try {
|
||||
const session = loadSession();
|
||||
if (!session) {
|
||||
throw new Error("No session stored");
|
||||
logger.log("No session stored; continuing without a client");
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.log("Using a standalone client");
|
||||
|
||||
const foci = Config.get().livekit
|
||||
? [{ livekitServiceUrl: Config.get().livekit!.livekit_service_url }]
|
||||
: undefined;
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
const { user_id, device_id, access_token, passwordlessUser } = session;
|
||||
const initClientParams = {
|
||||
@@ -339,7 +367,7 @@ async function loadClient(): Promise<InitResult> {
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
fallbackICEServerAllowed: fallbackICEServerAllowed,
|
||||
foci,
|
||||
livekitServiceURL: Config.get().livekit!.livekit_service_url,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -391,3 +419,8 @@ const loadSession = (): Session | undefined => {
|
||||
|
||||
return JSON.parse(data);
|
||||
};
|
||||
|
||||
const clientIsDisconnected = (
|
||||
syncState: SyncState,
|
||||
syncData?: ISyncStateData
|
||||
) => syncState === "ERROR" && syncData?.error?.name === "ConnectionError";
|
||||
|
||||
27
src/DisconnectedBanner.module.css
Normal file
27
src/DisconnectedBanner.module.css
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.banner {
|
||||
position: absolute;
|
||||
padding: 29px;
|
||||
background-color: var(--quaternary-content);
|
||||
vertical-align: middle;
|
||||
font-size: var(--font-size-body);
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
top: 76px;
|
||||
width: calc(100% - 58px);
|
||||
}
|
||||
53
src/DisconnectedBanner.tsx
Normal file
53
src/DisconnectedBanner.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 classNames from "classnames";
|
||||
import { HTMLAttributes, ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./DisconnectedBanner.module.css";
|
||||
import { ValidClientState, useClientState } from "./ClientContext";
|
||||
|
||||
interface DisconnectedBannerProps extends HTMLAttributes<HTMLElement> {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function DisconnectedBanner({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: DisconnectedBannerProps) {
|
||||
const { t } = useTranslation();
|
||||
const clientState = useClientState();
|
||||
let shouldShowBanner = false;
|
||||
|
||||
if (clientState?.state === "valid") {
|
||||
const validClientState = clientState as ValidClientState;
|
||||
shouldShowBanner = validClientState.disconnected;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldShowBanner && (
|
||||
<div className={classNames(styles.banner, className)} {...rest}>
|
||||
{children}
|
||||
{t("Connectivity to the server has been lost.")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -18,14 +18,14 @@ import { ReactNode, useCallback, useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||
import { LinkButton, Button } from "./button";
|
||||
import { useSubmitRageshake } from "./settings/submit-rageshake";
|
||||
import { ErrorMessage } from "./input/Input";
|
||||
import styles from "./FullScreenView.module.css";
|
||||
import { translatedError, TranslatedError } from "./TranslatedError";
|
||||
import { TranslatedError } from "./TranslatedError";
|
||||
import { Config } from "./config/Config";
|
||||
import { RageshakeButton } from "./settings/RageshakeButton";
|
||||
|
||||
interface FullScreenViewProps {
|
||||
className?: string;
|
||||
@@ -58,6 +58,7 @@ export function ErrorView({ error }: ErrorViewProps) {
|
||||
|
||||
useEffect(() => {
|
||||
console.error(error);
|
||||
Sentry.captureException(error);
|
||||
}, [error]);
|
||||
|
||||
const onReload = useCallback(() => {
|
||||
@@ -97,37 +98,11 @@ export function ErrorView({ error }: ErrorViewProps) {
|
||||
|
||||
export function CrashView() {
|
||||
const { t } = useTranslation();
|
||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||
|
||||
const sendDebugLogs = useCallback(() => {
|
||||
submitRageshake({
|
||||
description: "**Soft Crash**",
|
||||
sendLogs: true,
|
||||
});
|
||||
}, [submitRageshake]);
|
||||
|
||||
const onReload = useCallback(() => {
|
||||
window.location.href = "/";
|
||||
}, []);
|
||||
|
||||
let logsComponent: JSX.Element | null = null;
|
||||
if (sent) {
|
||||
logsComponent = <div>{t("Thanks! We'll get right on it.")}</div>;
|
||||
} else if (sending) {
|
||||
logsComponent = <div>{t("Sending…")}</div>;
|
||||
} else if (Config.get().rageshake?.submit_url) {
|
||||
logsComponent = (
|
||||
<Button
|
||||
size="lg"
|
||||
variant="default"
|
||||
onPress={sendDebugLogs}
|
||||
className={styles.wideButton}
|
||||
>
|
||||
{t("Send debug logs")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FullScreenView>
|
||||
<Trans>
|
||||
@@ -139,10 +114,7 @@ export function CrashView() {
|
||||
</Trans>
|
||||
)}
|
||||
|
||||
<div className={styles.sendLogsSection}>{logsComponent}</div>
|
||||
{error && (
|
||||
<ErrorMessage error={translatedError("Couldn't send debug logs!", t)} />
|
||||
)}
|
||||
<RageshakeButton description="***Soft Crash***" />
|
||||
<Button
|
||||
size="lg"
|
||||
variant="default"
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
MuteMicrophoneTracker,
|
||||
UndecryptableToDeviceEventTracker,
|
||||
QualitySurveyEventTracker,
|
||||
CallDisconnectedEventTracker,
|
||||
} from "./PosthogEvents";
|
||||
import { Config } from "../config/Config";
|
||||
import { getUrlParams } from "../UrlParams";
|
||||
@@ -437,4 +438,5 @@ export class PosthogAnalytics {
|
||||
public eventMuteCamera = new MuteCameraTracker();
|
||||
public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker();
|
||||
public eventQualitySurvey = new QualitySurveyEventTracker();
|
||||
public eventCallDisconnected = new CallDisconnectedEventTracker();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { DisconnectReason } from "livekit-client";
|
||||
|
||||
import {
|
||||
IPosthogEvent,
|
||||
PosthogAnalytics,
|
||||
@@ -181,3 +183,17 @@ export class QualitySurveyEventTracker {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface CallDisconnectedEvent {
|
||||
eventName: "CallDisconnected";
|
||||
reason?: DisconnectReason;
|
||||
}
|
||||
|
||||
export class CallDisconnectedEventTracker {
|
||||
track(reason?: DisconnectReason) {
|
||||
PosthogAnalytics.instance.trackEvent<CallDisconnectedEvent>({
|
||||
eventName: "CallDisconnected",
|
||||
reason,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -45,6 +45,12 @@ class DependencyLoadStates {
|
||||
|
||||
export class Initializer {
|
||||
private static internalInstance: Initializer;
|
||||
private isInitialized = false;
|
||||
|
||||
public static isInitialized(): boolean {
|
||||
return Initializer.internalInstance?.isInitialized;
|
||||
}
|
||||
|
||||
public static initBeforeReact() {
|
||||
// this maybe also needs to return a promise in the future,
|
||||
// if we have to do async inits before showing the loading screen
|
||||
@@ -55,7 +61,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 +146,7 @@ export class Initializer {
|
||||
}
|
||||
|
||||
// Custom fonts
|
||||
const { fonts, fontScale } = getUrlParams();
|
||||
const { fonts, fontScale } = getUrlParams(true);
|
||||
if (fontScale !== null) {
|
||||
document.documentElement.style.setProperty(
|
||||
"--font-scale",
|
||||
@@ -227,6 +233,7 @@ export class Initializer {
|
||||
if (this.loadStates.allDepsAreLoaded()) {
|
||||
// resolve if there is no dependency that is not loaded
|
||||
resolve();
|
||||
this.isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
useState,
|
||||
} from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { GroupCall } from "matrix-js-sdk";
|
||||
|
||||
import {
|
||||
OpenIDClientParts,
|
||||
@@ -32,7 +33,7 @@ import { ErrorView, LoadingView } from "../FullScreenView";
|
||||
|
||||
interface Props {
|
||||
client: OpenIDClientParts;
|
||||
livekitServiceURL: string;
|
||||
groupCall: GroupCall;
|
||||
roomName: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
@@ -41,12 +42,7 @@ const SFUConfigContext = createContext<SFUConfig | undefined>(undefined);
|
||||
|
||||
export const useSFUConfig = () => useContext(SFUConfigContext);
|
||||
|
||||
export function OpenIDLoader({
|
||||
client,
|
||||
livekitServiceURL,
|
||||
roomName,
|
||||
children,
|
||||
}: Props) {
|
||||
export function OpenIDLoader({ client, groupCall, roomName, children }: Props) {
|
||||
const [state, setState] = useState<
|
||||
SFUConfigLoading | SFUConfigLoaded | SFUConfigFailed
|
||||
>({ kind: "loading" });
|
||||
@@ -56,7 +52,7 @@ export function OpenIDLoader({
|
||||
try {
|
||||
const result = await getSFUConfigWithOpenID(
|
||||
client,
|
||||
livekitServiceURL,
|
||||
groupCall,
|
||||
roomName
|
||||
);
|
||||
setState({ kind: "loaded", sfuConfig: result });
|
||||
@@ -65,7 +61,7 @@ export function OpenIDLoader({
|
||||
setState({ kind: "failed", error: e as Error });
|
||||
}
|
||||
})();
|
||||
}, [client, livekitServiceURL, roomName]);
|
||||
}, [client, groupCall, roomName]);
|
||||
|
||||
switch (state.kind) {
|
||||
case "loading":
|
||||
|
||||
@@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient } from "matrix-js-sdk";
|
||||
import { GroupCall, IOpenIDToken, MatrixClient } from "matrix-js-sdk";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { Config } from "../config/Config";
|
||||
|
||||
export interface SFUConfig {
|
||||
url: string;
|
||||
jwt: string;
|
||||
@@ -30,25 +32,90 @@ export type OpenIDClientParts = Pick<
|
||||
|
||||
export async function getSFUConfigWithOpenID(
|
||||
client: OpenIDClientParts,
|
||||
livekitServiceURL: string,
|
||||
groupCall: GroupCall,
|
||||
roomName: string
|
||||
): Promise<SFUConfig> {
|
||||
const openIdToken = await client.getOpenIdToken();
|
||||
logger.debug("Got openID token", openIdToken);
|
||||
|
||||
const res = await fetch(livekitServiceURL + "/sfu/get", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
room: roomName,
|
||||
openid_token: openIdToken,
|
||||
device_id: client.getDeviceId(),
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error("SFU Config fetch failed with status code " + res.status);
|
||||
// if the call has a livekit service URL, try it.
|
||||
if (groupCall.livekitServiceURL) {
|
||||
try {
|
||||
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) {
|
||||
logger.warn(
|
||||
`Failed to get JWT from group call's configured URL of ${groupCall.livekitServiceURL}.`,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, try our configured one and, if it works, update the call's service URL in the state event
|
||||
// NB. This wuill update it for everyone so we may end up with multiple clients updating this when they
|
||||
// join at similar times, but we don't have a huge number of options here.
|
||||
const urlFromConf = Config.get().livekit!.livekit_service_url;
|
||||
logger.info(`Trying livekit service URL from our config: ${urlFromConf}...`);
|
||||
try {
|
||||
const sfuConfig = await getLiveKitJWT(
|
||||
client,
|
||||
urlFromConf,
|
||||
roomName,
|
||||
openIdToken
|
||||
);
|
||||
|
||||
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.`
|
||||
);
|
||||
}
|
||||
|
||||
return sfuConfig;
|
||||
} catch (e) {
|
||||
logger.error("Failed to get JWT from URL defined in Config.", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async function getLiveKitJWT(
|
||||
client: OpenIDClientParts,
|
||||
livekitServiceURL: string,
|
||||
roomName: string,
|
||||
openIDToken: IOpenIDToken
|
||||
): Promise<SFUConfig> {
|
||||
try {
|
||||
const res = await fetch(livekitServiceURL + "/sfu/get", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
room: roomName,
|
||||
openid_token: openIDToken,
|
||||
device_id: client.getDeviceId(),
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error("SFU Config fetch failed with status code " + res.status);
|
||||
}
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
throw new Error("SFU Config fetch failed with exception " + e);
|
||||
}
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ExternalE2EEKeyProvider,
|
||||
Room,
|
||||
RoomOptions,
|
||||
setLogLevel,
|
||||
} from "livekit-client";
|
||||
import { useLiveKitRoom } from "@livekit/components-react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
@@ -17,7 +18,7 @@ export type UserChoices = {
|
||||
};
|
||||
|
||||
export type DeviceChoices = {
|
||||
selectedId: string;
|
||||
selectedId?: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
@@ -25,6 +26,8 @@ export type E2EEConfig = {
|
||||
sharedKey: string;
|
||||
};
|
||||
|
||||
setLogLevel("debug");
|
||||
|
||||
export function useLiveKit(
|
||||
userChoices: UserChoices,
|
||||
sfuConfig?: SFUConfig,
|
||||
|
||||
@@ -60,11 +60,11 @@ function waitForSync(client: MatrixClient) {
|
||||
data?: ISyncStateData
|
||||
) => {
|
||||
if (state === "PREPARED") {
|
||||
client.removeListener(ClientEvent.Sync, onSync);
|
||||
resolve();
|
||||
client.removeListener(ClientEvent.Sync, onSync);
|
||||
} else if (state === "ERROR") {
|
||||
reject(data?.error);
|
||||
client.removeListener(ClientEvent.Sync, onSync);
|
||||
reject(data?.error);
|
||||
}
|
||||
};
|
||||
client.on(ClientEvent.Sync, onSync);
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,15 @@ limitations under the License.
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.disconnectedButtons {
|
||||
display: grid;
|
||||
gap: 50px;
|
||||
}
|
||||
|
||||
.rageshakeButton {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.callEndedButton {
|
||||
margin-top: 54px;
|
||||
margin-left: 30px;
|
||||
|
||||
@@ -28,15 +28,20 @@ import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { StarRatingInput } from "../input/StarRatingInput";
|
||||
import { RageshakeButton } from "../settings/RageshakeButton";
|
||||
|
||||
export function CallEndedView({
|
||||
client,
|
||||
isPasswordlessUser,
|
||||
endedCallId,
|
||||
leaveError,
|
||||
reconnect,
|
||||
}: {
|
||||
client: MatrixClient;
|
||||
isPasswordlessUser: boolean;
|
||||
endedCallId: string;
|
||||
leaveError?: Error;
|
||||
reconnect: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
@@ -76,6 +81,7 @@ export function CallEndedView({
|
||||
},
|
||||
[endedCallId, history, isPasswordlessUser, starRating]
|
||||
);
|
||||
|
||||
const createAccountDialog = isPasswordlessUser && (
|
||||
<div className={styles.callEndedContent}>
|
||||
<Trans>
|
||||
@@ -138,6 +144,59 @@ export function CallEndedView({
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderBody = () => {
|
||||
if (leaveError) {
|
||||
return (
|
||||
<>
|
||||
<main className={styles.main}>
|
||||
<Headline className={styles.headline}>
|
||||
<Trans>You were disconnected from the call</Trans>
|
||||
</Headline>
|
||||
<div className={styles.disconnectedButtons}>
|
||||
<Button size="lg" variant="default" onClick={reconnect}>
|
||||
{t("Reconnect")}
|
||||
</Button>
|
||||
<div className={styles.rageshakeButton}>
|
||||
<RageshakeButton description="***Call disconnected***" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Body className={styles.footer}>
|
||||
<Link color="primary" to="/">
|
||||
{t("Return to home screen")}
|
||||
</Link>
|
||||
</Body>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<main className={styles.main}>
|
||||
<Headline className={styles.headline}>
|
||||
{surveySubmitted
|
||||
? t("{{displayName}}, your call has ended.", {
|
||||
displayName,
|
||||
})
|
||||
: t("{{displayName}}, your call has ended.", {
|
||||
displayName,
|
||||
}) +
|
||||
"\n" +
|
||||
t("How did it go?")}
|
||||
</Headline>
|
||||
{!surveySubmitted && PosthogAnalytics.instance.isEnabled()
|
||||
? qualitySurveyDialog
|
||||
: createAccountDialog}
|
||||
</main>
|
||||
<Body className={styles.footer}>
|
||||
<Link color="primary" to="/">
|
||||
{t("Not now, return to home screen")}
|
||||
</Link>
|
||||
</Body>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
@@ -146,29 +205,7 @@ export function CallEndedView({
|
||||
</LeftNav>
|
||||
<RightNav />
|
||||
</Header>
|
||||
<div className={styles.container}>
|
||||
<main className={styles.main}>
|
||||
<Headline className={styles.headline}>
|
||||
{surveySubmitted
|
||||
? t("{{displayName}}, your call has ended.", {
|
||||
displayName,
|
||||
})
|
||||
: t("{{displayName}}, your call has ended.", {
|
||||
displayName,
|
||||
}) +
|
||||
"\n" +
|
||||
t("How did it go?")}
|
||||
</Headline>
|
||||
{!surveySubmitted && PosthogAnalytics.instance.isEnabled()
|
||||
? qualitySurveyDialog
|
||||
: createAccountDialog}
|
||||
</main>
|
||||
<Body className={styles.footer}>
|
||||
<Link color="primary" to="/">
|
||||
{t("Not now, return to home screen")}
|
||||
</Link>
|
||||
</Body>
|
||||
</div>
|
||||
<div className={styles.container}>{renderBody()}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,12 @@ import { E2EEConfig, UserChoices } from "../livekit/useLiveKit";
|
||||
import { findDeviceByName } from "../media-utils";
|
||||
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
||||
import { ActiveCall } from "./InCallView";
|
||||
import { Config } from "../config/Config";
|
||||
|
||||
/**
|
||||
* If there already is this many participants in the call, we automatically mute
|
||||
* the user
|
||||
*/
|
||||
const MUTE_PARTICIPANT_COUNT = 8;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -164,42 +169,47 @@ export function GroupCallView({
|
||||
useSentryGroupCallHandler(groupCall);
|
||||
|
||||
const [left, setLeft] = useState(false);
|
||||
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||
const history = useHistory();
|
||||
|
||||
const onLeave = useCallback(async () => {
|
||||
setLeft(true);
|
||||
const onLeave = useCallback(
|
||||
async (leaveError?: Error) => {
|
||||
setLeaveError(leaveError);
|
||||
setLeft(true);
|
||||
|
||||
let participantCount = 0;
|
||||
for (const deviceMap of groupCall.participants.values()) {
|
||||
participantCount += deviceMap.size;
|
||||
}
|
||||
let participantCount = 0;
|
||||
for (const deviceMap of groupCall.participants.values()) {
|
||||
participantCount += deviceMap.size;
|
||||
}
|
||||
|
||||
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
||||
// therefore we want the event to be sent instantly without getting queued/batched.
|
||||
const sendInstantly = !!widget;
|
||||
PosthogAnalytics.instance.eventCallEnded.track(
|
||||
groupCall.groupCallId,
|
||||
participantCount,
|
||||
sendInstantly
|
||||
);
|
||||
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
||||
// therefore we want the event to be sent instantly without getting queued/batched.
|
||||
const sendInstantly = !!widget;
|
||||
PosthogAnalytics.instance.eventCallEnded.track(
|
||||
groupCall.groupCallId,
|
||||
participantCount,
|
||||
sendInstantly
|
||||
);
|
||||
|
||||
leave();
|
||||
if (widget) {
|
||||
// we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
||||
widget.api.setAlwaysOnScreen(false);
|
||||
PosthogAnalytics.instance.logout();
|
||||
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
}
|
||||
leave();
|
||||
if (widget) {
|
||||
// we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
||||
widget.api.setAlwaysOnScreen(false);
|
||||
PosthogAnalytics.instance.logout();
|
||||
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
}
|
||||
|
||||
if (
|
||||
!isPasswordlessUser &&
|
||||
!isEmbedded &&
|
||||
!PosthogAnalytics.instance.isEnabled()
|
||||
) {
|
||||
history.push("/");
|
||||
}
|
||||
}, [groupCall, leave, isPasswordlessUser, isEmbedded, history]);
|
||||
if (
|
||||
!isPasswordlessUser &&
|
||||
!isEmbedded &&
|
||||
!PosthogAnalytics.instance.isEnabled()
|
||||
) {
|
||||
history.push("/");
|
||||
}
|
||||
},
|
||||
[groupCall, leave, isPasswordlessUser, isEmbedded, history]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (widget && state === GroupCallState.Entered) {
|
||||
@@ -222,6 +232,12 @@ export function GroupCallView({
|
||||
undefined
|
||||
);
|
||||
|
||||
const onReconnect = useCallback(() => {
|
||||
setLeft(false);
|
||||
setLeaveError(undefined);
|
||||
groupCall.enter();
|
||||
}, [groupCall]);
|
||||
|
||||
const livekitServiceURL =
|
||||
groupCall.livekitServiceURL ?? Config.get().livekit?.livekit_service_url;
|
||||
if (!livekitServiceURL) {
|
||||
@@ -234,7 +250,7 @@ export function GroupCallView({
|
||||
return (
|
||||
<OpenIDLoader
|
||||
client={client}
|
||||
livekitServiceURL={livekitServiceURL}
|
||||
groupCall={groupCall}
|
||||
roomName={`${groupCall.room.roomId}-${groupCall.groupCallId}`}
|
||||
>
|
||||
<ActiveCall
|
||||
@@ -259,13 +275,16 @@ export function GroupCallView({
|
||||
// submitting anything.
|
||||
if (
|
||||
isPasswordlessUser ||
|
||||
(PosthogAnalytics.instance.isEnabled() && !isEmbedded)
|
||||
(PosthogAnalytics.instance.isEnabled() && !isEmbedded) ||
|
||||
leaveError
|
||||
) {
|
||||
return (
|
||||
<CallEndedView
|
||||
endedCallId={groupCall.groupCallId}
|
||||
client={client}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
leaveError={leaveError}
|
||||
reconnect={onReconnect}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -291,6 +310,7 @@ export function GroupCallView({
|
||||
setE2EEConfig(e2eeConfig);
|
||||
enter();
|
||||
}}
|
||||
initWithMutedAudio={participants.size > MUTE_PARTICIPANT_COUNT}
|
||||
isEmbedded={isEmbedded}
|
||||
hideHeader={hideHeader}
|
||||
/>
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from "@livekit/components-react";
|
||||
import { usePreventScroll } from "@react-aria/overlays";
|
||||
import classNames from "classnames";
|
||||
import { Room, Track } from "livekit-client";
|
||||
import { DisconnectReason, Room, RoomEvent, Track } from "livekit-client";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
@@ -33,6 +33,7 @@ import { useTranslation } from "react-i18next";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||
import {
|
||||
@@ -83,6 +84,8 @@ import { useFullscreen } from "./useFullscreen";
|
||||
import { useLayoutStates } from "../video-grid/Layout";
|
||||
import { useSFUConfig } from "../livekit/OpenIDLoader";
|
||||
import { E2EELock } from "../E2EELock";
|
||||
import { useEventEmitterThree } from "../useEvents";
|
||||
import { useWakeLock } from "../useWakeLock";
|
||||
|
||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||
@@ -121,7 +124,7 @@ export interface InCallViewProps {
|
||||
groupCall: GroupCall;
|
||||
livekitRoom: Room;
|
||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
||||
onLeave: () => void;
|
||||
onLeave: (error?: Error) => void;
|
||||
unencryptedEventsFromUsers: Set<string>;
|
||||
hideHeader: boolean;
|
||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||
@@ -139,6 +142,7 @@ export function InCallView({
|
||||
}: InCallViewProps) {
|
||||
const { t } = useTranslation();
|
||||
usePreventScroll();
|
||||
useWakeLock();
|
||||
|
||||
const containerRef1 = useRef<HTMLDivElement | null>(null);
|
||||
const [containerRef2, bounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
@@ -195,6 +199,23 @@ export function InCallView({
|
||||
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
|
||||
);
|
||||
|
||||
const onDisconnected = useCallback(
|
||||
(reason?: DisconnectReason) => {
|
||||
PosthogAnalytics.instance.eventCallDisconnected.track(reason);
|
||||
logger.info("Disconnected from livekit call with reason ", reason);
|
||||
onLeave(
|
||||
new Error("Disconnected from LiveKit call with reason " + reason)
|
||||
);
|
||||
},
|
||||
[onLeave]
|
||||
);
|
||||
|
||||
const onLeavePress = useCallback(() => {
|
||||
onLeave();
|
||||
}, [onLeave]);
|
||||
|
||||
useEventEmitterThree(livekitRoom, RoomEvent.Disconnected, onDisconnected);
|
||||
|
||||
useEffect(() => {
|
||||
widget?.api.transport.send(
|
||||
layout === "freedom"
|
||||
@@ -391,7 +412,7 @@ export function InCallView({
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<HangupButton key="6" onPress={onLeave} data-testid="incall_leave" />
|
||||
<HangupButton key="6" onPress={onLeavePress} data-testid="incall_leave" />
|
||||
);
|
||||
footer = <div className={styles.footer}>{buttons}</div>;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ interface Props {
|
||||
onEnter: (userChoices: UserChoices, e2eeConfig?: E2EEConfig) => void;
|
||||
isEmbedded: boolean;
|
||||
hideHeader: boolean;
|
||||
initWithMutedAudio: boolean;
|
||||
}
|
||||
|
||||
export function LobbyView(props: Props) {
|
||||
@@ -81,6 +82,7 @@ export function LobbyView(props: Props) {
|
||||
<div className={styles.joinRoomContent}>
|
||||
<VideoPreview
|
||||
matrixInfo={props.matrixInfo}
|
||||
initWithMutedAudio={props.initWithMutedAudio}
|
||||
onUserChoicesChanged={setUserChoices}
|
||||
/>
|
||||
{enableE2EE && (
|
||||
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -40,10 +40,15 @@ export type MatrixInfo = {
|
||||
|
||||
interface Props {
|
||||
matrixInfo: MatrixInfo;
|
||||
initWithMutedAudio: boolean;
|
||||
onUserChoicesChanged: (choices: UserChoices) => void;
|
||||
}
|
||||
|
||||
export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
export function VideoPreview({
|
||||
matrixInfo,
|
||||
initWithMutedAudio,
|
||||
onUserChoicesChanged,
|
||||
}: Props) {
|
||||
const { client } = useClient();
|
||||
const [previewRef, previewBounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
|
||||
@@ -64,12 +69,9 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
|
||||
// Create local media tracks.
|
||||
const [videoEnabled, setVideoEnabled] = useState<boolean>(true);
|
||||
const [audioEnabled, setAudioEnabled] = useState<boolean>(true);
|
||||
|
||||
// we store if the tracks are currently initializing to not show them as muted.
|
||||
// showing them as muted while they are not yet available makes the buttons flicker undesirable during startup.
|
||||
const [initializingVideo, setInitializingVideo] = useState<boolean>(true);
|
||||
const [initializingAudio, setInitializingAudio] = useState<boolean>(true);
|
||||
const [audioEnabled, setAudioEnabled] = useState<boolean>(
|
||||
!initWithMutedAudio
|
||||
);
|
||||
|
||||
// The settings are updated as soon as the device changes. We wrap the settings value in a ref to store their initial value.
|
||||
// Not changing the device options prohibits the usePreviewTracks hook to recreate the tracks.
|
||||
@@ -99,32 +101,23 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
const requestPermissions = !!audioTrack && !!videoTrack;
|
||||
const mediaSwitcher = useMediaDevicesSwitcher(
|
||||
undefined,
|
||||
{
|
||||
videoTrack,
|
||||
audioTrack,
|
||||
},
|
||||
{ videoTrack, audioTrack },
|
||||
requestPermissions
|
||||
);
|
||||
const { videoIn, audioIn } = mediaSwitcher;
|
||||
|
||||
const videoEl = React.useRef(null);
|
||||
|
||||
// pretend the video is available until the initialization is over
|
||||
const videoAvailableAndEnabled =
|
||||
videoEnabled && (!!videoTrack || initializingVideo);
|
||||
const audioAvailableAndEnabled =
|
||||
audioEnabled && (!!videoTrack || initializingAudio);
|
||||
|
||||
useEffect(() => {
|
||||
// Effect to update the settings
|
||||
onUserChoicesChanged({
|
||||
video: {
|
||||
selectedId: videoIn.selectedId,
|
||||
enabled: videoAvailableAndEnabled,
|
||||
enabled: videoEnabled,
|
||||
},
|
||||
audio: {
|
||||
selectedId: audioIn.selectedId,
|
||||
enabled: audioAvailableAndEnabled,
|
||||
enabled: audioEnabled,
|
||||
},
|
||||
});
|
||||
}, [
|
||||
@@ -133,24 +126,18 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
videoEnabled,
|
||||
audioIn.selectedId,
|
||||
audioEnabled,
|
||||
videoAvailableAndEnabled,
|
||||
audioAvailableAndEnabled,
|
||||
videoTrack,
|
||||
audioTrack,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// Effect to update the initial device selection for the ui elements based on the current preview track.
|
||||
if (!videoIn.selectedId || videoIn.selectedId == "") {
|
||||
if (videoTrack) {
|
||||
setInitializingVideo(false);
|
||||
}
|
||||
videoTrack?.getDeviceId().then((videoId) => {
|
||||
videoIn.setSelected(videoId ?? "default");
|
||||
});
|
||||
}
|
||||
if (!audioIn.selectedId || audioIn.selectedId == "") {
|
||||
if (audioTrack) {
|
||||
setInitializingAudio(false);
|
||||
}
|
||||
audioTrack?.getDeviceId().then((audioId) => {
|
||||
// getDeviceId() can return undefined for audio devices. This happens if
|
||||
// the devices list uses "default" as the device id for the current
|
||||
@@ -197,13 +184,13 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
)}
|
||||
<div className={styles.previewButtons}>
|
||||
<MicButton
|
||||
muted={!audioAvailableAndEnabled}
|
||||
onPress={() => setAudioEnabled(!audioAvailableAndEnabled)}
|
||||
muted={!audioEnabled}
|
||||
onPress={() => setAudioEnabled(!audioEnabled)}
|
||||
disabled={!audioTrack}
|
||||
/>
|
||||
<VideoButton
|
||||
muted={!videoAvailableAndEnabled}
|
||||
onPress={() => setVideoEnabled(!videoAvailableAndEnabled)}
|
||||
muted={!videoEnabled}
|
||||
onPress={() => setVideoEnabled(!videoEnabled)}
|
||||
disabled={!videoTrack}
|
||||
/>
|
||||
<SettingsButton onPress={openSettings} />
|
||||
|
||||
@@ -15,6 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useReducer, useState } from "react";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import {
|
||||
GroupCallEvent,
|
||||
GroupCallState,
|
||||
@@ -170,7 +171,7 @@ export function useGroupCall(
|
||||
isScreensharing: false,
|
||||
screenshareFeeds: [],
|
||||
requestingScreenshare: false,
|
||||
participants: new Map(),
|
||||
participants: getParticipants(groupCall),
|
||||
hasLocalParticipant: false,
|
||||
});
|
||||
|
||||
@@ -331,6 +332,7 @@ export function useGroupCall(
|
||||
}
|
||||
|
||||
function onError(e: GroupCallError): void {
|
||||
Sentry.captureException(e);
|
||||
if (e.code === GroupCallErrorCode.UnknownDevice) {
|
||||
const unknownDeviceError = e as GroupCallUnknownDeviceError;
|
||||
addUnencryptedEventUser(unknownDeviceError.userId);
|
||||
|
||||
@@ -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"
|
||||
|
||||
21
src/settings/RageshakeButton.module.css
Normal file
21
src/settings/RageshakeButton.module.css
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.rageshakeControl {
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
67
src/settings/RageshakeButton.tsx
Normal file
67
src/settings/RageshakeButton.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { Button } from "../button";
|
||||
import { Config } from "../config/Config";
|
||||
import styles from "./RageshakeButton.module.css";
|
||||
import { useSubmitRageshake } from "./submit-rageshake";
|
||||
|
||||
interface Props {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const RageshakeButton = ({ description }: Props) => {
|
||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const sendDebugLogs = useCallback(() => {
|
||||
submitRageshake({
|
||||
description,
|
||||
sendLogs: true,
|
||||
});
|
||||
}, [submitRageshake, description]);
|
||||
|
||||
if (!Config.get().rageshake?.submit_url) return null;
|
||||
|
||||
let logsComponent: JSX.Element | null = null;
|
||||
if (sending) {
|
||||
logsComponent = <span>{t("Sending…")}</span>;
|
||||
} else if (sent) {
|
||||
logsComponent = <div>{t("Thanks!")}</div>;
|
||||
} else {
|
||||
let caption = t("Send debug logs");
|
||||
if (error) {
|
||||
caption = t("Retry sending logs");
|
||||
}
|
||||
|
||||
logsComponent = (
|
||||
<Button
|
||||
size="lg"
|
||||
variant="default"
|
||||
onPress={sendDebugLogs}
|
||||
className={styles.wideButton}
|
||||
disabled={sending}
|
||||
>
|
||||
{caption}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={styles.rageshakeControl}>{logsComponent}</div>;
|
||||
};
|
||||
@@ -120,7 +120,7 @@ export const SettingsModal = (props: Props) => {
|
||||
|
||||
const devices = props.mediaDevicesSwitcher;
|
||||
|
||||
const tabs = [
|
||||
const audioTab = (
|
||||
<TabItem
|
||||
key="audio"
|
||||
title={
|
||||
@@ -132,7 +132,10 @@ export const SettingsModal = (props: Props) => {
|
||||
>
|
||||
{devices && generateDeviceSelection(devices.audioIn, t("Microphone"))}
|
||||
{devices && generateDeviceSelection(devices.audioOut, t("Speaker"))}
|
||||
</TabItem>,
|
||||
</TabItem>
|
||||
);
|
||||
|
||||
const videoTab = (
|
||||
<TabItem
|
||||
key="video"
|
||||
title={
|
||||
@@ -143,7 +146,24 @@ export const SettingsModal = (props: Props) => {
|
||||
}
|
||||
>
|
||||
{devices && generateDeviceSelection(devices.videoIn, t("Camera"))}
|
||||
</TabItem>,
|
||||
</TabItem>
|
||||
);
|
||||
|
||||
const profileTab = (
|
||||
<TabItem
|
||||
key="profile"
|
||||
title={
|
||||
<>
|
||||
<UserIcon width={15} height={15} />
|
||||
<span>{t("Profile")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ProfileSettingsTab client={props.client} />
|
||||
</TabItem>
|
||||
);
|
||||
|
||||
const feedbackTab = (
|
||||
<TabItem
|
||||
key="feedback"
|
||||
title={
|
||||
@@ -154,7 +174,10 @@ export const SettingsModal = (props: Props) => {
|
||||
}
|
||||
>
|
||||
<FeedbackSettingsTab roomId={props.roomId} />
|
||||
</TabItem>,
|
||||
</TabItem>
|
||||
);
|
||||
|
||||
const moreTab = (
|
||||
<TabItem
|
||||
key="more"
|
||||
title={
|
||||
@@ -190,85 +213,73 @@ export const SettingsModal = (props: Props) => {
|
||||
}}
|
||||
/>
|
||||
</FieldRow>
|
||||
</TabItem>,
|
||||
];
|
||||
</TabItem>
|
||||
);
|
||||
|
||||
if (!isEmbedded) {
|
||||
tabs.push(
|
||||
<TabItem
|
||||
key="profile"
|
||||
title={
|
||||
<>
|
||||
<UserIcon width={15} height={15} />
|
||||
<span>{t("Profile")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ProfileSettingsTab client={props.client} />
|
||||
</TabItem>
|
||||
);
|
||||
}
|
||||
const developerTab = (
|
||||
<TabItem
|
||||
key="developer"
|
||||
title={
|
||||
<>
|
||||
<DeveloperIcon width={16} height={16} />
|
||||
<span>{t("Developer")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FieldRow>
|
||||
<Body className={styles.fieldRowText}>
|
||||
{t("Version: {{version}}", {
|
||||
version: import.meta.env.VITE_APP_VERSION || "dev",
|
||||
})}
|
||||
</Body>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showInspector"
|
||||
name="inspector"
|
||||
label={t("Show call inspector")}
|
||||
type="checkbox"
|
||||
checked={showInspector}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowInspector(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showConnectionStats"
|
||||
name="connection-stats"
|
||||
label={t("Show connection stats")}
|
||||
type="checkbox"
|
||||
checked={showConnectionStats}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowConnectionStats(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="enableE2EE"
|
||||
name="end-to-end-encryption"
|
||||
label={t("Enable end-to-end encryption (password protected calls)")}
|
||||
type="checkbox"
|
||||
checked={enableE2EE}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setEnableE2EE(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<Button onPress={downloadDebugLog}>{t("Download debug logs")}</Button>
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
);
|
||||
|
||||
if (developerSettingsTab) {
|
||||
tabs.push(
|
||||
<TabItem
|
||||
key="developer"
|
||||
title={
|
||||
<>
|
||||
<DeveloperIcon width={16} height={16} />
|
||||
<span>{t("Developer")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FieldRow>
|
||||
<Body className={styles.fieldRowText}>
|
||||
{t("Version: {{version}}", {
|
||||
version: import.meta.env.VITE_APP_VERSION || "dev",
|
||||
})}
|
||||
</Body>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showInspector"
|
||||
name="inspector"
|
||||
label={t("Show call inspector")}
|
||||
type="checkbox"
|
||||
checked={showInspector}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowInspector(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showConnectionStats"
|
||||
name="connection-stats"
|
||||
label={t("Show connection stats")}
|
||||
type="checkbox"
|
||||
checked={showConnectionStats}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowConnectionStats(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="enableE2EE"
|
||||
name="end-to-end-encryption"
|
||||
label={t("Enable end-to-end encryption (password protected calls)")}
|
||||
type="checkbox"
|
||||
checked={enableE2EE}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setEnableE2EE(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<Button onPress={downloadDebugLog}>{t("Download debug logs")}</Button>
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
);
|
||||
}
|
||||
const tabs: JSX.Element[] = [];
|
||||
if (devices) tabs.push(audioTab, videoTab);
|
||||
if (!isEmbedded) tabs.push(profileTab);
|
||||
tabs.push(feedbackTab, moreTab);
|
||||
if (developerSettingsTab) tabs.push(developerTab);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -15,6 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
import type {
|
||||
Listener,
|
||||
@@ -59,3 +60,20 @@ export const useTypedEventEmitter = <
|
||||
};
|
||||
}, [emitter, eventType, listener]);
|
||||
};
|
||||
|
||||
// Shortcut for registering a listener on an eventemitter3 EventEmitter (ie. what the LiveKit SDK uses)
|
||||
export const useEventEmitterThree = <
|
||||
EventType extends EventEmitter.ValidEventTypes,
|
||||
T extends EventEmitter.EventNames<EventType>
|
||||
>(
|
||||
emitter: EventEmitter<EventType>,
|
||||
eventType: T,
|
||||
listener: EventEmitter.EventListener<EventType, T>
|
||||
) => {
|
||||
useEffect(() => {
|
||||
emitter.on(eventType, listener);
|
||||
return () => {
|
||||
emitter.off(eventType, listener);
|
||||
};
|
||||
}, [emitter, eventType, listener]);
|
||||
};
|
||||
|
||||
60
src/useWakeLock.ts
Normal file
60
src/useWakeLock.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 { logger } from "matrix-js-sdk/src/logger";
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* React hook that inhibits the device from automatically going to sleep.
|
||||
*/
|
||||
export const useWakeLock = () => {
|
||||
useEffect(() => {
|
||||
if ("wakeLock" in navigator) {
|
||||
let mounted = true;
|
||||
let lock: WakeLockSentinel | null = null;
|
||||
|
||||
// The lock is automatically released whenever the window goes invisible,
|
||||
// so we need to reacquire it on visiblity changes
|
||||
const onVisiblityChange = async () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
try {
|
||||
lock = await navigator.wakeLock.request("screen");
|
||||
// Handle the edge case where this component unmounts before the
|
||||
// promise resolves
|
||||
if (!mounted)
|
||||
lock
|
||||
.release()
|
||||
.catch((e) => logger.warn("Can't release wake lock", e));
|
||||
} catch (e) {
|
||||
logger.warn("Can't acquire wake lock", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onVisiblityChange();
|
||||
document.addEventListener("visiblitychange", onVisiblityChange);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
if (lock !== null)
|
||||
lock
|
||||
.release()
|
||||
.catch((e) => logger.warn("Can't release wake lock", e));
|
||||
document.removeEventListener("visiblitychange", onVisiblityChange);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
413
yarn.lock
413
yarn.lock
@@ -1739,6 +1739,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
|
||||
integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@eslint-community/regexpp@^4.5.1":
|
||||
version "4.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884"
|
||||
integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==
|
||||
|
||||
"@eslint/eslintrc@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
|
||||
@@ -2014,6 +2026,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 +2107,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"
|
||||
@@ -2186,10 +2217,10 @@
|
||||
"@react-hook/latest" "^1.0.3"
|
||||
clsx "^1.2.1"
|
||||
|
||||
"@matrix-org/matrix-sdk-crypto-js@^0.1.1":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.2.tgz#b58679e161f3d734359a8665922956309b1a4417"
|
||||
integrity sha512-bbal0RcWwerS/DgqhOgM7wkXJ2YSv9fySK/qgLlrAsdYLpMSTqG8wDQ89/v+RYo9WmA5hwUN/wXcCDdFaFEXQQ==
|
||||
"@matrix-org/matrix-sdk-crypto-js@^0.1.0":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.4.tgz#c13c7c8c3a1d8da08e6ad195d25e5e61cc402df7"
|
||||
integrity sha512-OxG84iSeR89zYLFeb+DCaFtZT+DDiIu+kTkqY8OYfhE5vpGLFX2sDVBRrAdos1IUqEoboDloDBR9+yU7hNRyog==
|
||||
|
||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
|
||||
version "3.2.14"
|
||||
@@ -3098,6 +3129,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"
|
||||
@@ -3898,6 +3934,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.5.tgz#aa02dca40864749a9e2bf0161a6216da57e3ede5"
|
||||
integrity sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ==
|
||||
|
||||
"@types/dom-screen-wake-lock@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/dom-screen-wake-lock/-/dom-screen-wake-lock-1.0.1.tgz#8f125f91821d49f51feee95e6db128eb6974aea9"
|
||||
integrity sha512-WJQas3OFGcC8AeMzaa7FwzzbNNfanuV2R12kQYNp4BkUMghsRz5JxJ5RgVhJifhw7t0s6LvRSWZArmKbMDZ+5g==
|
||||
|
||||
"@types/eslint-scope@^3.7.3":
|
||||
version "3.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
|
||||
@@ -4012,11 +4053,16 @@
|
||||
"@types/tough-cookie" "*"
|
||||
parse5 "^7.0.0"
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
|
||||
version "7.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||
|
||||
"@types/json-schema@^7.0.12":
|
||||
version "7.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||
|
||||
"@types/json5@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
@@ -4172,10 +4218,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/sdp-transform/-/sdp-transform-2.4.5.tgz#3167961e0a1a5265545e278627aa37c606003f53"
|
||||
integrity sha512-GVO0gnmbyO3Oxm2HdPsYUNcyihZE3GyCY8ysMYHuQGfLhGZq89Nm4lSzULWTzZoyHtg+VO/IdrnxZHPnPSGnAg==
|
||||
|
||||
"@types/semver@^7.3.12":
|
||||
version "7.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
||||
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
|
||||
"@types/semver@^7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a"
|
||||
integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
|
||||
|
||||
"@types/source-list-map@*":
|
||||
version "0.1.2"
|
||||
@@ -4264,89 +4310,91 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz#5fb0d43574c2411f16ea80f5fc335b8eaa7b28a8"
|
||||
integrity sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==
|
||||
"@typescript-eslint/eslint-plugin@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz#96f3ca6615717659d06c9f7161a1d14ab0c49c66"
|
||||
integrity sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.52.0"
|
||||
"@typescript-eslint/type-utils" "5.52.0"
|
||||
"@typescript-eslint/utils" "5.52.0"
|
||||
"@eslint-community/regexpp" "^4.5.1"
|
||||
"@typescript-eslint/scope-manager" "6.1.0"
|
||||
"@typescript-eslint/type-utils" "6.1.0"
|
||||
"@typescript-eslint/utils" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
debug "^4.3.4"
|
||||
grapheme-splitter "^1.0.4"
|
||||
ignore "^5.2.0"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.2.4"
|
||||
natural-compare "^1.4.0"
|
||||
natural-compare-lite "^1.4.0"
|
||||
regexpp "^3.2.0"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/parser@^5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.52.0.tgz#73c136df6c0133f1d7870de7131ccf356f5be5a4"
|
||||
integrity sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==
|
||||
"@typescript-eslint/parser@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.1.0.tgz#3135bf65dca5340d8650703eb8cb83113e156ee5"
|
||||
integrity sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.52.0"
|
||||
"@typescript-eslint/types" "5.52.0"
|
||||
"@typescript-eslint/typescript-estree" "5.52.0"
|
||||
"@typescript-eslint/scope-manager" "6.1.0"
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/typescript-estree" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz#a993d89a0556ea16811db48eabd7c5b72dcb83d1"
|
||||
integrity sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==
|
||||
"@typescript-eslint/scope-manager@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz#a6cdbe11630614f8c04867858a42dd56590796ed"
|
||||
integrity sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.52.0"
|
||||
"@typescript-eslint/visitor-keys" "5.52.0"
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
|
||||
"@typescript-eslint/type-utils@5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz#9fd28cd02e6f21f5109e35496df41893f33167aa"
|
||||
integrity sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==
|
||||
"@typescript-eslint/type-utils@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz#21cc6c3bc1980b03f9eb4e64580d0c5be6f08215"
|
||||
integrity sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "5.52.0"
|
||||
"@typescript-eslint/utils" "5.52.0"
|
||||
"@typescript-eslint/typescript-estree" "6.1.0"
|
||||
"@typescript-eslint/utils" "6.1.0"
|
||||
debug "^4.3.4"
|
||||
tsutils "^3.21.0"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/types@5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.52.0.tgz#19e9abc6afb5bd37a1a9bea877a1a836c0b3241b"
|
||||
integrity sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==
|
||||
"@typescript-eslint/types@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.1.0.tgz#2d607c62827bb416ada5c96ebfa2ef84e45a8dfa"
|
||||
integrity sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz#6408cb3c2ccc01c03c278cb201cf07e73347dfca"
|
||||
integrity sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==
|
||||
"@typescript-eslint/typescript-estree@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz#ea382f6482ba698d7e993a88ce5391ea7a66c33d"
|
||||
integrity sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.52.0"
|
||||
"@typescript-eslint/visitor-keys" "5.52.0"
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/utils@5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.52.0.tgz#b260bb5a8f6b00a0ed51db66bdba4ed5e4845a72"
|
||||
integrity sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==
|
||||
"@typescript-eslint/utils@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.1.0.tgz#1641843792b4e3451cc692e2c73055df8b26f453"
|
||||
integrity sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@types/semver" "^7.3.12"
|
||||
"@typescript-eslint/scope-manager" "5.52.0"
|
||||
"@typescript-eslint/types" "5.52.0"
|
||||
"@typescript-eslint/typescript-estree" "5.52.0"
|
||||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
semver "^7.3.7"
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@types/json-schema" "^7.0.12"
|
||||
"@types/semver" "^7.5.0"
|
||||
"@typescript-eslint/scope-manager" "6.1.0"
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/typescript-estree" "6.1.0"
|
||||
semver "^7.5.4"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.52.0":
|
||||
version "5.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz#e38c971259f44f80cfe49d97dbffa38e3e75030f"
|
||||
integrity sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==
|
||||
"@typescript-eslint/visitor-keys@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz#d2b84dff6b58944d3257ea03687e269a788c73be"
|
||||
integrity sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.52.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@use-gesture/core@10.2.16":
|
||||
version "10.2.16"
|
||||
@@ -5312,7 +5360,7 @@ base16@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||
integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
|
||||
|
||||
base64-js@^1.0.2, base64-js@^1.3.1:
|
||||
base64-js@^1.0.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
@@ -5364,15 +5412,6 @@ bindings@^1.5.0:
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
bl@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
|
||||
dependencies:
|
||||
buffer "^5.5.0"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
bluebird@^3.5.5:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
@@ -5619,14 +5658,6 @@ buffer@^4.3.0:
|
||||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@^5.5.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
bufrw@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/bufrw/-/bufrw-1.3.0.tgz#28d6cfdaf34300376836310f5c31d57eeb40c8fa"
|
||||
@@ -5968,18 +5999,6 @@ cli-boxes@^2.2.1:
|
||||
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
|
||||
integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
|
||||
|
||||
cli-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
||||
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
|
||||
dependencies:
|
||||
restore-cursor "^3.1.0"
|
||||
|
||||
cli-spinners@^2.5.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a"
|
||||
integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==
|
||||
|
||||
cli-table3@^0.6.1:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.2.tgz#aaf5df9d8b5bf12634dc8b3040806a0c07120d2a"
|
||||
@@ -6026,11 +6045,6 @@ clone-stats@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
|
||||
integrity sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==
|
||||
|
||||
clone@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
||||
|
||||
clone@^2.1.1, clone@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
@@ -6436,11 +6450,6 @@ crypto-browserify@^3.11.0:
|
||||
randombytes "^2.0.0"
|
||||
randomfill "^1.0.3"
|
||||
|
||||
crypto-js@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
|
||||
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
|
||||
|
||||
css-blank-pseudo@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561"
|
||||
@@ -7170,13 +7179,6 @@ default-browser-id@^1.0.4:
|
||||
meow "^3.1.0"
|
||||
untildify "^2.0.0"
|
||||
|
||||
defaults@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
|
||||
integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==
|
||||
dependencies:
|
||||
clone "^1.0.2"
|
||||
|
||||
define-lazy-prop@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
|
||||
@@ -7964,7 +7966,7 @@ eslint-plugin-react@^7.29.4:
|
||||
semver "^6.3.0"
|
||||
string.prototype.matchall "^4.0.7"
|
||||
|
||||
eslint-scope@5.1.1, eslint-scope@^5.1.1:
|
||||
eslint-scope@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
||||
@@ -8005,6 +8007,11 @@ eslint-visitor-keys@^3.3.0:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
||||
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
||||
|
||||
eslint-visitor-keys@^3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
|
||||
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
|
||||
|
||||
eslint@^8.14.0:
|
||||
version "8.19.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.19.0.tgz#7342a3cbc4fbc5c106a1eefe0fd0b50b6b1a7d28"
|
||||
@@ -8126,21 +8133,6 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
||||
md5.js "^1.3.4"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
execa@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
||||
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.0"
|
||||
get-stream "^5.0.0"
|
||||
human-signals "^1.1.1"
|
||||
is-stream "^2.0.0"
|
||||
merge-stream "^2.0.0"
|
||||
npm-run-path "^4.0.0"
|
||||
onetime "^5.1.0"
|
||||
signal-exit "^3.0.2"
|
||||
strip-final-newline "^2.0.0"
|
||||
|
||||
execa@^5.0.0, execa@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
||||
@@ -8798,13 +8790,6 @@ get-stdin@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
integrity sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==
|
||||
|
||||
get-stream@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
|
||||
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
@@ -8960,10 +8945,10 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
grapheme-splitter@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
||||
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
|
||||
graphemer@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
|
||||
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
|
||||
|
||||
graphlib@^2.1.8:
|
||||
version "2.1.8"
|
||||
@@ -9348,11 +9333,6 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
human-signals@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||
|
||||
human-signals@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
@@ -9431,7 +9411,7 @@ identity-obj-proxy@^3.0.0:
|
||||
dependencies:
|
||||
harmony-reflect "^1.4.6"
|
||||
|
||||
ieee754@^1.1.13, ieee754@^1.1.4:
|
||||
ieee754@^1.1.4:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
@@ -9451,6 +9431,11 @@ ignore@^5.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
|
||||
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
|
||||
|
||||
ignore@^5.2.4:
|
||||
version "5.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
|
||||
|
||||
immutable@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
|
||||
@@ -9775,11 +9760,6 @@ is-hexadecimal@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
|
||||
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
|
||||
|
||||
is-interactive@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
|
||||
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
|
||||
|
||||
is-map@^2.0.1, is-map@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
|
||||
@@ -9905,11 +9885,6 @@ is-unc-path@^1.0.0:
|
||||
dependencies:
|
||||
unc-path-regex "^0.1.2"
|
||||
|
||||
is-unicode-supported@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
||||
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
||||
|
||||
is-utf8@^0.2.0, is-utf8@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
@@ -10309,6 +10284,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 +10435,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"
|
||||
@@ -10866,14 +10862,6 @@ lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
log-symbols@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
|
||||
integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
is-unicode-supported "^0.1.0"
|
||||
|
||||
loglevel@^1.7.1, loglevel@^1.8.0, loglevel@^1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4"
|
||||
@@ -11017,7 +11005,7 @@ matrix-events-sdk@0.0.1:
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b698217445318f453e0b1086364a33113eaa85d9"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.1"
|
||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0"
|
||||
another-json "^0.2.0"
|
||||
bs58 "^5.0.0"
|
||||
content-type "^1.0.4"
|
||||
@@ -11562,7 +11550,7 @@ now-and-later@^2.0.0:
|
||||
dependencies:
|
||||
once "^1.3.2"
|
||||
|
||||
npm-run-path@^4.0.0, npm-run-path@^4.0.1:
|
||||
npm-run-path@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
|
||||
@@ -11712,14 +11700,6 @@ objectorarray@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5"
|
||||
integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==
|
||||
|
||||
oidc-client-ts@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.2.4.tgz#7d86b5efe2248f3637a6f3a0ee1af86764aea125"
|
||||
integrity sha512-nOZwIomju+AmXObl5Oq5PjrES/qTt8bLsENJCIydVgi9TEWk7SCkOU6X3RNkY7yfySRM1OJJvDKdREZdmnDT2g==
|
||||
dependencies:
|
||||
crypto-js "^4.1.1"
|
||||
jwt-decode "^3.1.2"
|
||||
|
||||
on-finished@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||
@@ -11739,7 +11719,7 @@ once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
onetime@^5.1.0, onetime@^5.1.2:
|
||||
onetime@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
||||
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||
@@ -11792,21 +11772,6 @@ optionator@^0.9.1:
|
||||
type-check "^0.4.0"
|
||||
word-wrap "^1.2.3"
|
||||
|
||||
ora@^5.4.1:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
|
||||
integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==
|
||||
dependencies:
|
||||
bl "^4.1.0"
|
||||
chalk "^4.1.0"
|
||||
cli-cursor "^3.1.0"
|
||||
cli-spinners "^2.5.0"
|
||||
is-interactive "^1.0.0"
|
||||
is-unicode-supported "^0.1.0"
|
||||
log-symbols "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wcwidth "^1.0.1"
|
||||
|
||||
ordered-read-streams@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
|
||||
@@ -13131,7 +13096,7 @@ read-pkg@^5.2.0:
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||
readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
@@ -13452,14 +13417,6 @@ resolve@^2.0.0-next.3:
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
restore-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
||||
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
|
||||
dependencies:
|
||||
onetime "^5.1.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
ret@~0.1.10:
|
||||
version "0.1.15"
|
||||
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
||||
@@ -13670,13 +13627,20 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
|
||||
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.5.4:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
send@0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
||||
@@ -14587,6 +14551,11 @@ trough@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
||||
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
||||
|
||||
ts-api-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d"
|
||||
integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==
|
||||
|
||||
ts-debounce@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-debounce/-/ts-debounce-4.0.0.tgz#33440ef64fab53793c3d546a8ca6ae539ec15841"
|
||||
@@ -14612,7 +14581,7 @@ tsconfig-paths@^3.14.1:
|
||||
minimist "^1.2.6"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@^1.8.1, tslib@^1.9.3:
|
||||
tslib@^1.9.3:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
@@ -14627,13 +14596,6 @@ tslib@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
tty-browserify@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
||||
@@ -14691,25 +14653,15 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||
|
||||
typescript-strict-plugin@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript-strict-plugin/-/typescript-strict-plugin-2.0.1.tgz#4e676704818c4458a8b11125e9d32032e0513de4"
|
||||
integrity sha512-8LHbwpkeQN12KZMK4BsmC6U1AyF+QisiLlaPH6GoCDV3xd52emyg6mOsL4I3C1Uy2n65HrnAdSkc8yi6bWb/6Q==
|
||||
dependencies:
|
||||
chalk "^3.0.0"
|
||||
execa "^4.0.0"
|
||||
ora "^5.4.1"
|
||||
yargs "^16.2.0"
|
||||
|
||||
typescript@^4.2.4:
|
||||
version "4.8.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
||||
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
|
||||
|
||||
typescript@^4.9.5:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
typescript@^5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
|
||||
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
|
||||
|
||||
ua-parser-js@^0.7.30:
|
||||
version "0.7.31"
|
||||
@@ -15305,13 +15257,6 @@ watchpack@^2.2.0, watchpack@^2.3.1:
|
||||
glob-to-regexp "^0.4.1"
|
||||
graceful-fs "^4.1.2"
|
||||
|
||||
wcwidth@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||
integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==
|
||||
dependencies:
|
||||
defaults "^1.0.3"
|
||||
|
||||
web-namespaces@^1.0.0:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
|
||||
|
||||
Reference in New Issue
Block a user