Merge branch 'livekit' into valere/async_error_show_boundary
This commit is contained in:
17
.github/workflows/blocked.yaml
vendored
Normal file
17
.github/workflows/blocked.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Prevent blocked
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, labeled, unlabeled]
|
||||||
|
jobs:
|
||||||
|
prevent-blocked:
|
||||||
|
name: Prevent blocked
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- name: Add notice
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'X-Blocked')
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.setFailed("PR has been labeled with X-Blocked; it cannot be merged.");
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ inputs.artifact_run_id }}
|
run-id: ${{ inputs.artifact_run_id }}
|
||||||
name: build-output
|
name: build-output-full
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
- name: Log in to container registry
|
- name: Log in to container registry
|
||||||
|
|||||||
22
.github/workflows/build-element-call.yaml
vendored
22
.github/workflows/build-element-call.yaml
vendored
@@ -5,6 +5,10 @@ on:
|
|||||||
vite_app_version:
|
vite_app_version:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
package:
|
||||||
|
type: string # This would ideally be a `choice` type, but that isn't supported yet
|
||||||
|
description: The package type to be built. Must be one of 'full' or 'embedded'
|
||||||
|
required: true
|
||||||
secrets:
|
secrets:
|
||||||
SENTRY_ORG:
|
SENTRY_ORG:
|
||||||
required: true
|
required: true
|
||||||
@@ -31,8 +35,20 @@ jobs:
|
|||||||
node-version-file: ".node-version"
|
node-version-file: ".node-version"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: "yarn install --frozen-lockfile"
|
run: "yarn install --frozen-lockfile"
|
||||||
- name: Build
|
- name: Build full version
|
||||||
run: "yarn run build"
|
if: ${{ inputs.package == 'full' }}
|
||||||
|
run: "yarn run build:full"
|
||||||
|
env:
|
||||||
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
|
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
VITE_APP_VERSION: ${{ inputs.vite_app_version }}
|
||||||
|
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||||
|
- name: Build embedded
|
||||||
|
if: ${{ inputs.package == 'embedded' }}
|
||||||
|
run: "yarn run build:embedded"
|
||||||
env:
|
env:
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
@@ -44,7 +60,7 @@ jobs:
|
|||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||||
with:
|
with:
|
||||||
name: build-output
|
name: build-output-${{ inputs.package }}
|
||||||
path: dist
|
path: dist
|
||||||
# We'll only use this in a triggered job, then we're done with it
|
# We'll only use this in a triggered job, then we're done with it
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|||||||
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
@@ -16,9 +16,11 @@ on:
|
|||||||
- ".github/CODEOWNERS"
|
- ".github/CODEOWNERS"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
jobs:
|
jobs:
|
||||||
build_element_call:
|
build_full_element_call:
|
||||||
|
# Use the full package vite build
|
||||||
uses: ./.github/workflows/build-element-call.yaml
|
uses: ./.github/workflows/build-element-call.yaml
|
||||||
with:
|
with:
|
||||||
|
package: full
|
||||||
vite_app_version: ${{ github.event.release.tag_name || github.sha }}
|
vite_app_version: ${{ github.event.release.tag_name || github.sha }}
|
||||||
secrets:
|
secrets:
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
@@ -29,7 +31,7 @@ jobs:
|
|||||||
deploy_develop:
|
deploy_develop:
|
||||||
# Deploy livekit branch to call.element.dev after build completes
|
# Deploy livekit branch to call.element.dev after build completes
|
||||||
if: github.ref == 'refs/heads/livekit'
|
if: github.ref == 'refs/heads/livekit'
|
||||||
needs: build_element_call
|
needs: build_full_element_call
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to call.element.dev
|
- name: Deploy to call.element.dev
|
||||||
@@ -50,7 +52,7 @@ jobs:
|
|||||||
docker_for_develop:
|
docker_for_develop:
|
||||||
# Build docker and publish docker for livekit branch after build completes
|
# Build docker and publish docker for livekit branch after build completes
|
||||||
if: github.ref == 'refs/heads/livekit'
|
if: github.ref == 'refs/heads/livekit'
|
||||||
needs: build_element_call
|
needs: build_full_element_call
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
@@ -61,3 +63,15 @@ jobs:
|
|||||||
type=sha,format=short,event=branch
|
type=sha,format=short,event=branch
|
||||||
type=raw,value=latest-ci
|
type=raw,value=latest-ci
|
||||||
type=raw,value=latest-ci_{{date 'X' }}
|
type=raw,value=latest-ci_{{date 'X' }}
|
||||||
|
build_embedded_element_call:
|
||||||
|
# Use the embedded package vite build
|
||||||
|
uses: ./.github/workflows/build-element-call.yaml
|
||||||
|
with:
|
||||||
|
package: embedded
|
||||||
|
vite_app_version: ${{ github.event.release.tag_name || github.sha }}
|
||||||
|
secrets:
|
||||||
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
|
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/deploy-to-netlify.yaml
vendored
2
.github/workflows/deploy-to-netlify.yaml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
run-id: ${{ inputs.artifact_run_id }}
|
run-id: ${{ inputs.artifact_run_id }}
|
||||||
name: build-output
|
name: build-output-full
|
||||||
path: webapp
|
path: webapp
|
||||||
|
|
||||||
- name: Add redirects file
|
- name: Add redirects file
|
||||||
|
|||||||
3
.github/workflows/publish.yaml
vendored
3
.github/workflows/publish.yaml
vendored
@@ -13,6 +13,7 @@ jobs:
|
|||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
uses: ./.github/workflows/build-element-call.yaml
|
uses: ./.github/workflows/build-element-call.yaml
|
||||||
with:
|
with:
|
||||||
|
package: full
|
||||||
vite_app_version: ${{ github.event.release.tag_name || github.sha }}
|
vite_app_version: ${{ github.event.release.tag_name || github.sha }}
|
||||||
secrets:
|
secrets:
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
@@ -38,7 +39,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id || github.run_id }}
|
run-id: ${{ github.event.workflow_run.id || github.run_id }}
|
||||||
name: build-output
|
name: build-output-full
|
||||||
path: dist
|
path: dist
|
||||||
- name: Create Tarball
|
- name: Create Tarball
|
||||||
env:
|
env:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
*.bkp
|
||||||
.idea/
|
.idea/
|
||||||
public/config.json
|
public/config.json
|
||||||
backend/synapse_tmp/*
|
backend/synapse_tmp/*
|
||||||
|
|||||||
238
README.md
238
README.md
@@ -2,122 +2,101 @@
|
|||||||
|
|
||||||
[](https://matrix.to/#/#webrtc:matrix.org)
|
[](https://matrix.to/#/#webrtc:matrix.org)
|
||||||
[](https://localazy.com/p/element-call)
|
[](https://localazy.com/p/element-call)
|
||||||
|
[](LICENSE-AGPL-3.0)
|
||||||
|
|
||||||
Group calls with WebRTC that leverage [Matrix](https://matrix.org) and an
|
[🎬 Live Demo 🎬](https://call.element.io)
|
||||||
open-source WebRTC toolkit from [LiveKit](https://livekit.io/).
|
|
||||||
|
|
||||||
For prior version of the Element Call that relied solely on full-mesh logic,
|
The world's first 🌐 decentralized and 🤝 federated video conferencing solution
|
||||||
check [`full-mesh`](https://github.com/element-hq/element-call/tree/full-mesh)
|
powered by **the Matrix protocol**.
|
||||||
branch.
|
|
||||||
|
## 📌 Overview
|
||||||
|
|
||||||
|
**Element Call** is a native Matrix video conferencing application developed by
|
||||||
|
[Element](https://element.io/), designed for **secure**, **scalable**,
|
||||||
|
**privacy-respecting**, and **decentralized** video and voice calls over the
|
||||||
|
Matrix protocol. Built on **MatrixRTC**
|
||||||
|
([MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)), it
|
||||||
|
utilizes
|
||||||
|
**[MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)**
|
||||||
|
with **[LiveKit](https://livekit.io/)** as its backend.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
To try it out, visit our hosted version at
|
You can find the latest development version continuously deployed to
|
||||||
[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/).
|
[call.element.dev](https://call.element.dev/).
|
||||||
|
|
||||||
## Host it yourself
|
> [!NOTE]
|
||||||
|
> For prior version of the Element Call that relied solely on full-mesh logic,
|
||||||
|
> check [`full-mesh`](https://github.com/element-hq/element-call/tree/full-mesh)
|
||||||
|
> branch.
|
||||||
|
|
||||||
Until prebuilt tarballs are available, you'll need to build Element Call from
|
## ✨ Key Features
|
||||||
source. First, clone and install the package:
|
|
||||||
|
|
||||||
```sh
|
✅ **Decentralized & Federated** – No central authority; works across Matrix
|
||||||
git clone https://github.com/element-hq/element-call.git
|
homeservers.
|
||||||
cd element-call
|
✅ **End-to-End Encrypted** – Secure and private calls.
|
||||||
yarn
|
✅ **Standalone & Widget Mode** – Use as an independent app or embed in Matrix
|
||||||
yarn build
|
clients.
|
||||||
```
|
✅ **WebRTC-based** – No additional software required.
|
||||||
|
✅ **Scalable with LiveKit** – Supports large meetings via SFU
|
||||||
|
([MSC4195: MatrixRTC using LiveKit backend](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)).
|
||||||
|
✅ **Raise Hand** – Participants can signal when they want to speak, helping to
|
||||||
|
organize the flow of the meeting.
|
||||||
|
✅ **Emoji Reactions** – Users can react with emojis 👍️ 🎉 👏 🤘, adding
|
||||||
|
engagement and interactivity to the conversation.
|
||||||
|
|
||||||
If all went well, you can now find the build output under `dist` as a series of
|
## 🚀 Deployment Options
|
||||||
static files. These can be hosted using any web server that can be configured
|
|
||||||
with custom routes (see below).
|
|
||||||
|
|
||||||
You also need to add a configuration file which goes in `public/config.json` -
|
Element Call can be packaged in two ways:
|
||||||
you can use the sample as a starting point:
|
|
||||||
|
|
||||||
```sh
|
**Full Package** – Supports both **Standalone** and **Widget** mode. Hosted as
|
||||||
cp config/config.sample.json public/config.json
|
a static web page and accessed via a URL when used as a widget.
|
||||||
# edit public/config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
The sample needs editing to contain the homeserver and LiveKit backend that you
|
**Embedded Package** – 🚧 **Coming [Soon](https://github.com/element-hq/element-call/issues/2994):** Designed for **Widget mode** only. Bundled with a
|
||||||
are using.
|
messenger app for seamless integration. This is the recommended method for
|
||||||
|
embedding Element Call into a messenger app.
|
||||||
|
|
||||||
Because Element Call uses client-side routing, your server must be able to route
|
### Standalone mode
|
||||||
any requests to non-existing paths back to `/index.html`. For example, in Nginx
|
|
||||||
you can achieve this with the `try_files` directive:
|
|
||||||
|
|
||||||
```jsonc
|

|
||||||
server {
|
|
||||||
...
|
|
||||||
location / {
|
|
||||||
...
|
|
||||||
try_files $uri /$uri /index.html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Element Call requires a homeserver with registration enabled without any 3pid or
|
In Standalone mode Element Call operates as an independent, full-featured video
|
||||||
token requirements, if you want it to be used by unregistered users.
|
conferencing web application, allowing users to join or host calls without
|
||||||
Furthermore, it is not recommended to use it with an existing homeserver where
|
requiring a separate Matrix client.
|
||||||
user accounts have joined normal rooms, as it may not be able to handle those
|
|
||||||
yet and it may behave unreliably.
|
|
||||||
|
|
||||||
Therefore, to use a self-hosted homeserver, this is recommended to be a new
|
### Widget mode embedded in Messenger Apps
|
||||||
server where any user account created has not joined any normal rooms anywhere
|
|
||||||
in the Matrix federated network. The homeserver used can be setup to disable
|
|
||||||
federation, so as to prevent spam registrations (if you keep registrations open)
|
|
||||||
and to ensure Element Call continues to work in case any user decides to log in
|
|
||||||
to their Element Call account using the standard Element app and joins normal
|
|
||||||
rooms that Element Call cannot handle.
|
|
||||||
|
|
||||||
## Configuration
|

|
||||||
|
|
||||||
There are currently two different config files. `.env` holds variables that are
|
Element Call can be embedded as a widget inside apps like
|
||||||
used at build time, while `public/config.json` holds variables that are used at
|
[**Element Web**](https://github.com/element-hq/element-web) or **Element X
|
||||||
runtime. Documentation and default values for `public/config.json` can be found
|
([iOS](https://github.com/element-hq/element-x-ios),
|
||||||
in [ConfigOptions.ts](src/config/ConfigOptions.ts).
|
[Android](https://github.com/element-hq/element-x-android))**, bringing
|
||||||
|
**MatrixRTC** capabilities to messenger apps for seamless decentralized video
|
||||||
|
and voice calls within Matrix rooms.
|
||||||
|
|
||||||
If you're using [Synapse](https://github.com/element-hq/synapse/), you'll need
|
> [!IMPORTANT]
|
||||||
to additionally add the following to `homeserver.yaml` or Element Call won't
|
> Embedded packaging is recommended for Element Call in widget mode!
|
||||||
work:
|
|
||||||
|
|
||||||
```yaml
|
## 🛠️ Self-Hosting
|
||||||
experimental_features:
|
|
||||||
# MSC3266: Room summary API. Used for knocking over federation
|
|
||||||
msc3266_enabled: true
|
|
||||||
# MSC4222 needed for syncv2 state_after. This allow clients to
|
|
||||||
# correctly track the state of the room.
|
|
||||||
msc4222_enabled: true
|
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
For operating and deploying Element Call on your own server, refer to the
|
||||||
# per MSC4140.
|
[**Self-Hosting Guide**](./docs/self-hosting.md).
|
||||||
max_event_delay_duration: 24h
|
|
||||||
|
|
||||||
rc_message:
|
## 🧭 MatrixRTC Backend Discovery and Selection
|
||||||
# This needs to match at least the heart-beat frequency plus a bit of headroom
|
|
||||||
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
|
||||||
per_second: 0.5
|
|
||||||
burst_count: 30
|
|
||||||
```
|
|
||||||
|
|
||||||
MSC3266 allows to request a room summary of rooms you are not joined. The
|
For proper Element Call operation each site deployment needs a MatrixRTC backend
|
||||||
summary contains the room join rules. We need that to decide if the user gets
|
setup as outlined in the [Self-Hosting](#self-hosting). A typical federated site
|
||||||
prompted with the option to knock ("Request to join call"), a cannot join error or the
|
deployment for three different sites A, B and C is depicted below.
|
||||||
join view.
|
|
||||||
|
|
||||||
MSC4222 allow clients to opt-in to a change of the sync v2 API that allows them
|

|
||||||
to correctly track the state of the room. This is required by Element Call to
|
|
||||||
track room state reliably.
|
|
||||||
|
|
||||||
Element Call requires a Livekit SFU alongside a [Livekit JWT
|
### Backend Discovery
|
||||||
service](https://github.com/element-hq/lk-jwt-service) to work. The url to the
|
|
||||||
Livekit JWT service can either be configured in the config of Element Call
|
|
||||||
(fallback/legacy configuration) or be configured by your homeserver via the
|
|
||||||
`.well-known/matrix/client`. This is the recommended method.
|
|
||||||
|
|
||||||
The configuration is a list of Foci configs:
|
MatrixRTC backend (according to
|
||||||
|
[MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143))
|
||||||
|
is announced by the homeserver's `.well-known/matrix/client` file and discovered
|
||||||
|
via the `org.matrix.msc4143.rtc_foci` key, e.g.:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
"org.matrix.msc4143.rtc_foci": [
|
||||||
@@ -125,25 +104,41 @@ The configuration is a list of Foci configs:
|
|||||||
"type": "livekit",
|
"type": "livekit",
|
||||||
"livekit_service_url": "https://someurl.com"
|
"livekit_service_url": "https://someurl.com"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "livekit",
|
|
||||||
"livekit_service_url": "https://livekit2.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "another_foci",
|
|
||||||
"props_for_another_foci": "val"
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Translation
|
where the format for MatrixRTC using LiveKit backend is defined in
|
||||||
|
[MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md).
|
||||||
|
In the example above Matrix clients do discover a focus of type `livekit` which
|
||||||
|
points them to a Matrix LiveKit JWT Auth Service via `livekit_service_url`.
|
||||||
|
|
||||||
|
### Backend Selection
|
||||||
|
|
||||||
|
- Each call participant proposes their discovered MatrixRTC backend from
|
||||||
|
`org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event.
|
||||||
|
- For **LiveKit** MatrixRTC backend
|
||||||
|
([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)),
|
||||||
|
the **first participant who joined the call** defines via the `foci_preferred`
|
||||||
|
key in their `org.matrix.msc3401.call.member` which actual MatrixRTC backend
|
||||||
|
will be used for this call.
|
||||||
|
- During the actual call join flow, the **LiveKit JWT Auth Service** provides
|
||||||
|
the client with the **LiveKit SFU WebSocket URL** and an **access JWT token**
|
||||||
|
in order to exchange media via WebRTC.
|
||||||
|
|
||||||
|
The example below illustrates how backend selection works across **Matrix
|
||||||
|
federation**, using the setup from sites A, B, and C. It demonstrates backend
|
||||||
|
selection for **Matrix rooms 123 and 456**, which include users from different
|
||||||
|
homeservers.
|
||||||
|

|
||||||
|
|
||||||
|
## 🌍 Translation
|
||||||
|
|
||||||
If you'd like to help translate Element Call, head over to
|
If you'd like to help translate Element Call, head over to
|
||||||
[Localazy](https://localazy.com/p/element-call). You're also encouraged to join
|
[Localazy](https://localazy.com/p/element-call). You're also encouraged to join
|
||||||
the [Element Translators](https://matrix.to/#/#translators:element.io) space to
|
the [Element Translators](https://matrix.to/#/#translators:element.io) space to
|
||||||
discuss and coordinate translation efforts.
|
discuss and coordinate translation efforts.
|
||||||
|
|
||||||
## Development
|
## 🛠️ Development
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
@@ -155,13 +150,15 @@ cd element-call
|
|||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
To use it, create a local config by, e.g., `cp ./config/config.devenv.json ./public/config.json`
|
To use it, create a local config by, e.g.,
|
||||||
and adapt it if necessary. The `config.devenv.json` config should work with the backend
|
`cp ./config/config.devenv.json ./public/config.json` and adapt it if necessary.
|
||||||
development environment as outlined in the next section out of box.
|
The `config.devenv.json` config should work with the backend development
|
||||||
|
environment as outlined in the next section out of box.
|
||||||
|
|
||||||
(Be aware, that this `config.devenv.json` is exposing a deprecated fallback
|
> [!NOTE]
|
||||||
LiveKit config key. If the homeserver advertises SFU backend via
|
> Be aware, that this `config.devenv.json` is exposing a deprecated fallback
|
||||||
`.well-known/matrix/client` this has precedence.)
|
> LiveKit config key. If the homeserver advertises SFU backend via
|
||||||
|
> `.well-known/matrix/client` this has precedence.
|
||||||
|
|
||||||
You're now ready to launch the development server:
|
You're now ready to launch the development server:
|
||||||
|
|
||||||
@@ -200,7 +197,8 @@ yarn backend
|
|||||||
|
|
||||||
To add a new translation key you can do these steps:
|
To add a new translation key you can do these steps:
|
||||||
|
|
||||||
1. Add the new key entry to the code where the new key is used: `t("some_new_key")`
|
1. Add the new key entry to the code where the new key is used:
|
||||||
|
`t("some_new_key")`
|
||||||
1. Run `yarn i18n` to extract the new key and update the translation files. This
|
1. Run `yarn i18n` to extract the new key and update the translation files. This
|
||||||
will add a skeleton entry to the `locales/en/app.json` file:
|
will add a skeleton entry to the `locales/en/app.json` file:
|
||||||
|
|
||||||
@@ -212,30 +210,38 @@ To add a new translation key you can do these steps:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Update the skeleton entry in the `locales/en/app.json` file with
|
1. Update the skeleton entry in the `locales/en/app.json` file with the English
|
||||||
the English translation:
|
translation:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
"some_new_key": "Some new key",
|
"some_new_key": "Some new key",
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## 📖 Documentation
|
||||||
|
|
||||||
Usage and other technical details about the project can be found here:
|
Usage and other technical details about the project can be found here:
|
||||||
|
|
||||||
[**Docs**](./docs/README.md)
|
[**Docs**](./docs/README.md)
|
||||||
|
|
||||||
## Copyright & License
|
## 📝 Copyright & License
|
||||||
|
|
||||||
Copyright 2021-2025 New Vector Ltd
|
Copyright 2021-2025 New Vector Ltd
|
||||||
|
|
||||||
This software is dual-licensed by New Vector Ltd (Element). It can be used either:
|
This software is dual-licensed by New Vector Ltd (Element). It can be used
|
||||||
|
either:
|
||||||
|
|
||||||
(1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR
|
(1) for free under the terms of the GNU Affero General Public License (as
|
||||||
|
published by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version); OR
|
||||||
|
|
||||||
(2) under the terms of a paid-for Element Commercial License agreement between you and Element (the terms of which may vary depending on what you and Element have agreed to).
|
(2) under the terms of a paid-for Element Commercial License agreement between
|
||||||
Unless required by applicable law or agreed to in writing, software distributed under the Licenses is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licenses for the specific language governing permissions and limitations under the Licenses.
|
you and Element (the terms of which may vary depending on what you and Element
|
||||||
|
have agreed to). Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the Licenses is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
Licenses for the specific language governing permissions and limitations under
|
||||||
|
the Licenses.
|
||||||
|
|||||||
BIN
docs/Federated_Setup.drawio.png
Normal file
BIN
docs/Federated_Setup.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
docs/MSC4195_setup.drawio.png
Normal file
BIN
docs/MSC4195_setup.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
@@ -5,3 +5,4 @@ This folder contains documentation for Element Call setup and usage.
|
|||||||
- [Embedded vs standalone mode](./embedded-standalone.md)
|
- [Embedded vs standalone mode](./embedded-standalone.md)
|
||||||
- [Url format and parameters](./url-params.md)
|
- [Url format and parameters](./url-params.md)
|
||||||
- [Global JS controls](./controls.md)
|
- [Global JS controls](./controls.md)
|
||||||
|
- [Self-Hosting](./self-hosting.md)
|
||||||
|
|||||||
BIN
docs/SFU_selection.drawio.png
Normal file
BIN
docs/SFU_selection.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/element_call_standalone.drawio.png
Normal file
BIN
docs/element_call_standalone.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 909 KiB |
BIN
docs/element_call_widget.drawio.png
Normal file
BIN
docs/element_call_widget.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 868 KiB |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Element call is developed using the js-sdk with matroska mode. This means the app can run either as a standalone app directly connected to a homeserver providing login interfaces or it can be used as a widget.
|
Element call is developed using the js-sdk with matroska mode. This means the app can run either as a standalone app directly connected to a homeserver providing login interfaces or it can be used as a widget.
|
||||||
|
|
||||||
As a widget the app only uses the core calling (matrixRTC) parts. The rest (authentication, sending events, getting room state updates about calls) is done by the hosting client.
|
As a widget the app only uses the core calling (MatrixRTC) parts. The rest (authentication, sending events, getting room state updates about calls) is done by the hosting client.
|
||||||
Element Call and the hosting client are connected via the widget api.
|
Element Call and the hosting client are connected via the widget api.
|
||||||
|
|
||||||
Element call detects that it is run as a widget if a widgetId is defined in the url parameters. If `widgetId` is present element call will try to connect to the client via the widget postMessage api using the parameters provided in [Url Format and parameters
|
Element call detects that it is run as a widget if a widgetId is defined in the url parameters. If `widgetId` is present element call will try to connect to the client via the widget postMessage api using the parameters provided in [Url Format and parameters
|
||||||
|
|||||||
161
docs/self-hosting.md
Normal file
161
docs/self-hosting.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Self-Hosting Element Call
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> This section covers the requirements for deploying a **Matrix site**
|
||||||
|
> compatible with MatrixRTC, the foundation of Element Call. These requirements
|
||||||
|
> apply to both Standalone as well as Widget mode operation of Element Call.
|
||||||
|
|
||||||
|
### A Matrix Homeserver
|
||||||
|
|
||||||
|
The following [MSCs](https://github.com/matrix-org/matrix-spec-proposals) are
|
||||||
|
required for Element Call to work properly:
|
||||||
|
|
||||||
|
- **[MSC3266](https://github.com/deepbluev7/matrix-doc/blob/room-summaries/proposals/3266-room-summary.md):
|
||||||
|
Room Summary API**: In Standalone mode Element Call is able to join rooms
|
||||||
|
over federation using knocking. In this context MSC3266 is required as it
|
||||||
|
allows to request a room summary of rooms you are not joined. The summary
|
||||||
|
contains the room join rules. We need that information to decide if the user
|
||||||
|
gets prompted with the option to knock ("Request to join call"), a "cannot
|
||||||
|
join error" or "the join view".
|
||||||
|
|
||||||
|
- **[MSC4140](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/expiring-events-keep-alive/proposals/4140-delayed-events-futures.md)
|
||||||
|
Delayed Events**: Delayed events are required for proper call participation
|
||||||
|
signalling. If disabled it is very likely that you end up with stuck calls in
|
||||||
|
Matrix rooms.
|
||||||
|
|
||||||
|
- **[MSC4222](https://github.com/matrix-org/matrix-spec-proposals/blob/erikj/sync_v2_state_after/proposals/4222-sync-v2-state-after.md)
|
||||||
|
Adding `state_after` to sync v2**: Allow clients to opt-in to a change of the
|
||||||
|
sync v2 API that allows them to correctly track the state of the room. This is
|
||||||
|
required by Element Call to track room state reliably.
|
||||||
|
|
||||||
|
If you're using [Synapse](https://github.com/element-hq/synapse/) as your homeserver, you'll need
|
||||||
|
to additionally add the following config items to `homeserver.yaml` to comply with Element Call:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
experimental_features:
|
||||||
|
# MSC3266: Room summary API. Used for knocking over federation
|
||||||
|
msc3266_enabled: true
|
||||||
|
# MSC4222 needed for syncv2 state_after. This allow clients to
|
||||||
|
# correctly track the state of the room.
|
||||||
|
msc4222_enabled: true
|
||||||
|
|
||||||
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
|
# per MSC4140.
|
||||||
|
max_event_delay_duration: 24h
|
||||||
|
|
||||||
|
rc_message:
|
||||||
|
# This needs to match at least the heart-beat frequency plus a bit of headroom
|
||||||
|
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
||||||
|
per_second: 0.5
|
||||||
|
burst_count: 30
|
||||||
|
```
|
||||||
|
|
||||||
|
### MatrixRTC Backend
|
||||||
|
|
||||||
|
In order to **guarantee smooth operation** of Element Call MatrixRTC backend is
|
||||||
|
required for each site deployment.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
As depicted above, Element Call requires a
|
||||||
|
[Livekit SFU](https://github.com/livekit/livekit) alongside a
|
||||||
|
[Matrix Livekit JWT auth service](https://github.com/element-hq/lk-jwt-service)
|
||||||
|
to implement
|
||||||
|
[MSC4195: MatrixRTC using LiveKit backend](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md).
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> As defined in
|
||||||
|
> [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)
|
||||||
|
> MatrixRTC backend must be announced to the client via your **homeserver's
|
||||||
|
> `.well-known/matrix/client`**. The configuration is a list of Foci configs:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"org.matrix.msc4143.rtc_foci": [
|
||||||
|
{
|
||||||
|
"type": "livekit",
|
||||||
|
"livekit_service_url": "https://someurl.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "livekit",
|
||||||
|
"livekit_service_url": "https://livekit2.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "another_foci",
|
||||||
|
"props_for_another_foci": "val"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building Element Call
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This step is only required if you want to deploy Element Call in Standalone
|
||||||
|
> mode.
|
||||||
|
|
||||||
|
Until prebuilt tarballs are available, you'll need to build Element Call from
|
||||||
|
source. First, clone and install the package:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/element-hq/element-call.git
|
||||||
|
cd element-call
|
||||||
|
yarn
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
If all went well, you can now find the build output under `dist` as a series of
|
||||||
|
static files. These can be hosted using any web server that can be configured
|
||||||
|
with custom routes (see below).
|
||||||
|
|
||||||
|
You also need to add a configuration file which goes in `public/config.json` -
|
||||||
|
you can use the sample as a starting point:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp config/config.sample.json public/config.json
|
||||||
|
# edit public/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The sample needs editing to contain the homeserver that you are using.
|
||||||
|
|
||||||
|
Because Element Call uses client-side routing, your server must be able to route
|
||||||
|
any requests to non-existing paths back to `/index.html`. For example, in Nginx
|
||||||
|
you can achieve this with the `try_files` directive:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
server {
|
||||||
|
...
|
||||||
|
location / {
|
||||||
|
...
|
||||||
|
try_files $uri /$uri /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
There are currently two different config files. `.env` holds variables that are
|
||||||
|
used at build time, while `public/config.json` holds variables that are used at
|
||||||
|
runtime. Documentation and default values for `public/config.json` can be found
|
||||||
|
in [ConfigOptions.ts](src/config/ConfigOptions.ts).
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Please note configuring MatrixRTC backend via `config.json` of
|
||||||
|
> Element Call is only available for developing and debug purposes. Relying on
|
||||||
|
> it might break Element Call going forward!
|
||||||
|
|
||||||
|
## A Note on Standalone Mode of Element Call
|
||||||
|
|
||||||
|
Element Call in Standalone mode requires a homeserver with registration enabled
|
||||||
|
without any 3pid or token requirements, if you want it to be used by
|
||||||
|
unregistered users. Furthermore, it is not recommended to use it with an
|
||||||
|
existing homeserver where user accounts have joined normal rooms, as it may not
|
||||||
|
be able to handle those yet and it may behave unreliably.
|
||||||
|
|
||||||
|
Therefore, to use a self-hosted homeserver, this is recommended to be a new
|
||||||
|
server where any user account created has not joined any normal rooms anywhere
|
||||||
|
in the Matrix federated network. The homeserver used can be setup to disable
|
||||||
|
federation, so as to prevent spam registrations (if you keep registrations open)
|
||||||
|
and to ensure Element Call continues to work in case any user decides to log in
|
||||||
|
to their Element Call account using the standard Element app and joins normal
|
||||||
|
rooms that Element Call cannot handle.
|
||||||
@@ -2,34 +2,40 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
||||||
|
<% if (packageType === "full") { %>
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.png" />
|
<link rel="icon" type="image/svg+xml" href="favicon.png" />
|
||||||
<link rel="preload" href="/config.json" as="fetch" />
|
<link rel="preload" href="/config.json" as="fetch" />
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
|
||||||
/>
|
/>
|
||||||
<title><%- title %></title>
|
<title><%- brand %></title>
|
||||||
<script>
|
<script>
|
||||||
window.global = window;
|
window.global = window;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<% if (packageType === "full") { %>
|
||||||
<!-- Open graph meta tags -->
|
<!-- Open graph meta tags -->
|
||||||
|
|
||||||
<meta property="og:title" content="<%- title %>" />
|
<meta property="og:title" content="<%- brand %>" />
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="You're invited to join a call on Element Call"
|
content="You're invited to join a call on <%- brand %>"
|
||||||
/>
|
/>
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:image" content="favicon.png" />
|
<meta property="og:image" content="favicon.png" />
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:title" content="<%- title %>" />
|
<meta name="twitter:title" content="<%- brand %>" />
|
||||||
<meta
|
<meta
|
||||||
name="twitter:description"
|
name="twitter:description"
|
||||||
content="You're invited to join a call on Element Call"
|
content="You're invited to join a call on <%- brand %>"
|
||||||
/>
|
/>
|
||||||
<meta name="twitter:image" content="favicon.png" />
|
<meta name="twitter:image" content="favicon.png" />
|
||||||
|
<% } %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<!-- The default class is: .no-theme {display: none}. It will be overwritten once the app is loaded. -->
|
<!-- The default class is: .no-theme {display: none}. It will be overwritten once the app is loaded. -->
|
||||||
3
knip.ts
3
knip.ts
@@ -1,6 +1,9 @@
|
|||||||
import { KnipConfig } from "knip";
|
import { KnipConfig } from "knip";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
vite: {
|
||||||
|
config: ["vite.config.js", "vite-embedded.config.js"],
|
||||||
|
},
|
||||||
entry: ["src/main.tsx", "i18next-parser.config.ts"],
|
entry: ["src/main.tsx", "i18next-parser.config.ts"],
|
||||||
ignoreBinaries: [
|
ignoreBinaries: [
|
||||||
// This is deprecated, so Knip doesn't actually recognize it as a globally
|
// This is deprecated, so Knip doesn't actually recognize it as a globally
|
||||||
|
|||||||
@@ -70,7 +70,8 @@
|
|||||||
"livekit_sfu": "LiveKit SFU: {{url}}",
|
"livekit_sfu": "LiveKit SFU: {{url}}",
|
||||||
"matrix_id": "Matrix-ID: {{id}}",
|
"matrix_id": "Matrix-ID: {{id}}",
|
||||||
"show_connection_stats": "Verbindungsstatistiken anzeigen",
|
"show_connection_stats": "Verbindungsstatistiken anzeigen",
|
||||||
"show_non_member_tiles": "Kacheln für Nicht-Mitgliedermedien anzeigen"
|
"show_non_member_tiles": "Kacheln für Nicht-Mitgliedermedien anzeigen",
|
||||||
|
"use_new_membership_manager": "Neuen MembershipManager verwenden"
|
||||||
},
|
},
|
||||||
"disconnected_banner": "Die Verbindung zum Server wurde getrennt.",
|
"disconnected_banner": "Die Verbindung zum Server wurde getrennt.",
|
||||||
"error": {
|
"error": {
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
"insufficient_capacity_description": "Der Server hat seine maximale Kapazität erreicht, daher ist ein Beitritt zum Anruf derzeit nicht möglich. Bitte später erneut versuchen oder den Serveradministrator kontaktieren, falls das Problem weiterhin besteht.",
|
"insufficient_capacity_description": "Der Server hat seine maximale Kapazität erreicht, daher ist ein Beitritt zum Anruf derzeit nicht möglich. Bitte später erneut versuchen oder den Serveradministrator kontaktieren, falls das Problem weiterhin besteht.",
|
||||||
"matrix_rtc_focus_missing": "Der Server ist nicht für die Verwendung mit {{brand}} konfiguriert. Bitte den Serveradministrator kontaktieren (Domain: {{domain}}, Fehlercode: {{ errorCode }}).",
|
"matrix_rtc_focus_missing": "Der Server ist nicht für die Verwendung mit {{brand}} konfiguriert. Bitte den Serveradministrator kontaktieren (Domain: {{domain}}, Fehlercode: {{ errorCode }}).",
|
||||||
"open_elsewhere": "In einem anderen Tab geöffnet",
|
"open_elsewhere": "In einem anderen Tab geöffnet",
|
||||||
"open_elsewhere_description": "{{brand}}wurde in einem anderen Tab geöffnet. Wenn das nicht richtig klingt, versuchen Sie, die Seite neu zu laden.",
|
"open_elsewhere_description": "{{brand}} wurde in einem anderen Tab geöffnet. Wenn das nicht richtig klingt, versuchen Sie, die Seite neu zu laden.",
|
||||||
"unexpected_ec_error": "Ein unerwarteter Fehler ist aufgetreten (<0>Fehlercode: </0> <1>{{ errorCode }}</1>). Bitte den Serveradministrator kontaktieren."
|
"unexpected_ec_error": "Ein unerwarteter Fehler ist aufgetreten (<0>Fehlercode: </0> <1>{{ errorCode }}</1>). Bitte den Serveradministrator kontaktieren."
|
||||||
},
|
},
|
||||||
"group_call_loader": {
|
"group_call_loader": {
|
||||||
|
|||||||
@@ -70,7 +70,8 @@
|
|||||||
"livekit_sfu": "LiveKit SFU: {{url}}",
|
"livekit_sfu": "LiveKit SFU: {{url}}",
|
||||||
"matrix_id": "Matrix ID: {{id}}",
|
"matrix_id": "Matrix ID: {{id}}",
|
||||||
"show_connection_stats": "Show connection statistics",
|
"show_connection_stats": "Show connection statistics",
|
||||||
"show_non_member_tiles": "Show tiles for non-member media"
|
"show_non_member_tiles": "Show tiles for non-member media",
|
||||||
|
"use_new_membership_manager": "Use the new implementation of the call MembershipManager"
|
||||||
},
|
},
|
||||||
"disconnected_banner": "Connectivity to the server has been lost.",
|
"disconnected_banner": "Connectivity to the server has been lost.",
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -3,8 +3,12 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "yarn dev:full",
|
||||||
"build": "NODE_OPTIONS=--max-old-space-size=16384 vite build",
|
"dev:full": "vite",
|
||||||
|
"dev:embedded": "vite --config vite-embedded.config.js",
|
||||||
|
"build": "yarn build:full",
|
||||||
|
"build:full": "NODE_OPTIONS=--max-old-space-size=16384 vite build",
|
||||||
|
"build:embedded": "yarn build:full --config vite-embedded.config.js",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"prettier:check": "prettier -c .",
|
"prettier:check": "prettier -c .",
|
||||||
"prettier:format": "prettier -w .",
|
"prettier:format": "prettier -w .",
|
||||||
@@ -91,7 +95,7 @@
|
|||||||
"livekit-client": "^2.5.7",
|
"livekit-client": "^2.5.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"loglevel": "^1.9.1",
|
"loglevel": "^1.9.1",
|
||||||
"matrix-js-sdk": "^36.1.0",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#9f9be701e7a8e85b5f749d0104138af36b0b82bd",
|
||||||
"matrix-widget-api": "1.11.0",
|
"matrix-widget-api": "1.11.0",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"observable-hooks": "^4.2.3",
|
"observable-hooks": "^4.2.3",
|
||||||
@@ -114,7 +118,8 @@
|
|||||||
"unique-names-generator": "^4.6.0",
|
"unique-names-generator": "^4.6.0",
|
||||||
"vaul": "^1.0.0",
|
"vaul": "^1.0.0",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
"vite-plugin-html-template": "^1.1.0",
|
"vite-plugin-generate-file": "^0.2.0",
|
||||||
|
"vite-plugin-html": "^3.2.2",
|
||||||
"vite-plugin-svgr": "^4.0.0",
|
"vite-plugin-svgr": "^4.0.0",
|
||||||
"vitest": "^3.0.0",
|
"vitest": "^3.0.0",
|
||||||
"vitest-axe": "^1.0.0-pre.3"
|
"vitest-axe": "^1.0.0-pre.3"
|
||||||
|
|||||||
3
src/@types/global.d.ts
vendored
3
src/@types/global.d.ts
vendored
@@ -6,6 +6,8 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "matrix-js-sdk/src/@types/global";
|
import "matrix-js-sdk/src/@types/global";
|
||||||
|
import { type setLogLevel as setLKLogLevel } from "livekit-client";
|
||||||
|
|
||||||
import type { DurationFormat as PolyfillDurationFormat } from "@formatjs/intl-durationformat";
|
import type { DurationFormat as PolyfillDurationFormat } from "@formatjs/intl-durationformat";
|
||||||
import { type Controls } from "../controls";
|
import { type Controls } from "../controls";
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ declare global {
|
|||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
controls: Controls;
|
controls: Controls;
|
||||||
|
setLKLogLevel: typeof setLKLogLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HTMLElement {
|
interface HTMLElement {
|
||||||
|
|||||||
@@ -72,7 +72,11 @@ export const App: FC = () => {
|
|||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<ClientProvider>
|
<ClientProvider>
|
||||||
<MediaDevicesProvider>
|
<MediaDevicesProvider>
|
||||||
<Sentry.ErrorBoundary fallback={ErrorPage}>
|
<Sentry.ErrorBoundary
|
||||||
|
fallback={(error) => (
|
||||||
|
<ErrorPage error={error} widget={widget} />
|
||||||
|
)}
|
||||||
|
>
|
||||||
<DisconnectedBanner />
|
<DisconnectedBanner />
|
||||||
<Routes>
|
<Routes>
|
||||||
<SentryRoute path="/" element={<HomePage />} />
|
<SentryRoute path="/" element={<HomePage />} />
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
}, [initClientState, onSync]);
|
}, [initClientState, onSync]);
|
||||||
|
|
||||||
if (alreadyOpenedErr) {
|
if (alreadyOpenedErr) {
|
||||||
return <ErrorPage error={alreadyOpenedErr} />;
|
return <ErrorPage widget={widget} error={alreadyOpenedErr} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -12,13 +12,16 @@ import {
|
|||||||
type FC,
|
type FC,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
type SVGAttributes,
|
type SVGAttributes,
|
||||||
|
type ReactElement,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { RageshakeButton } from "./settings/RageshakeButton";
|
import { RageshakeButton } from "./settings/RageshakeButton";
|
||||||
import styles from "./ErrorView.module.css";
|
import styles from "./ErrorView.module.css";
|
||||||
import { useUrlParams } from "./UrlParams";
|
import { useUrlParams } from "./UrlParams";
|
||||||
import { LinkButton } from "./button";
|
import { LinkButton } from "./button";
|
||||||
|
import { ElementWidgetActions, type WidgetHelpers } from "./widget.ts";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
Icon: ComponentType<SVGAttributes<SVGElement>>;
|
Icon: ComponentType<SVGAttributes<SVGElement>>;
|
||||||
@@ -35,6 +38,7 @@ interface Props {
|
|||||||
*/
|
*/
|
||||||
fatal?: boolean;
|
fatal?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
widget: WidgetHelpers | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ErrorView: FC<Props> = ({
|
export const ErrorView: FC<Props> = ({
|
||||||
@@ -43,6 +47,7 @@ export const ErrorView: FC<Props> = ({
|
|||||||
rageshake,
|
rageshake,
|
||||||
fatal,
|
fatal,
|
||||||
children,
|
children,
|
||||||
|
widget,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { confineToRoom } = useUrlParams();
|
const { confineToRoom } = useUrlParams();
|
||||||
@@ -51,6 +56,46 @@ export const ErrorView: FC<Props> = ({
|
|||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const CloseWidgetButton: FC<{ widget: WidgetHelpers }> = ({
|
||||||
|
widget,
|
||||||
|
}): ReactElement => {
|
||||||
|
// in widget mode we don't want to show the return home button but a close button
|
||||||
|
const closeWidget = (): void => {
|
||||||
|
widget.api.transport
|
||||||
|
.send(ElementWidgetActions.Close, {})
|
||||||
|
.catch((e) => {
|
||||||
|
// What to do here?
|
||||||
|
logger.error("Failed to send close action", e);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
widget.api.transport.stop();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Button kind="primary" onClick={closeWidget}>
|
||||||
|
{t("action.close")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Whether the error is considered fatal or pathname is `/` then reload the all app.
|
||||||
|
// If not then navigate to home page.
|
||||||
|
const ReturnToHomeButton = (): ReactElement => {
|
||||||
|
if (fatal || location.pathname === "/") {
|
||||||
|
return (
|
||||||
|
<Button kind="tertiary" className={styles.homeLink} onClick={onReload}>
|
||||||
|
{t("return_home_button")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<LinkButton kind="tertiary" className={styles.homeLink} to="/">
|
||||||
|
{t("return_home_button")}
|
||||||
|
</LinkButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.error}>
|
<div className={styles.error}>
|
||||||
<BigIcon className={styles.icon}>
|
<BigIcon className={styles.icon}>
|
||||||
@@ -63,20 +108,11 @@ export const ErrorView: FC<Props> = ({
|
|||||||
{rageshake && (
|
{rageshake && (
|
||||||
<RageshakeButton description={`***Error View***: ${title}`} />
|
<RageshakeButton description={`***Error View***: ${title}`} />
|
||||||
)}
|
)}
|
||||||
{!confineToRoom &&
|
{widget ? (
|
||||||
(fatal || location.pathname === "/" ? (
|
<CloseWidgetButton widget={widget} />
|
||||||
<Button
|
) : (
|
||||||
kind="tertiary"
|
!confineToRoom && <ReturnToHomeButton />
|
||||||
className={styles.homeLink}
|
)}
|
||||||
onClick={onReload}
|
|
||||||
>
|
|
||||||
{t("return_home_button")}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<LinkButton kind="tertiary" className={styles.homeLink} to="/">
|
|
||||||
{t("return_home_button")}
|
|
||||||
</LinkButton>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import styles from "./FullScreenView.module.css";
|
|||||||
import { useUrlParams } from "./UrlParams";
|
import { useUrlParams } from "./UrlParams";
|
||||||
import { RichError } from "./RichError";
|
import { RichError } from "./RichError";
|
||||||
import { ErrorView } from "./ErrorView";
|
import { ErrorView } from "./ErrorView";
|
||||||
|
import { type WidgetHelpers } from "./widget.ts";
|
||||||
|
|
||||||
interface FullScreenViewProps {
|
interface FullScreenViewProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -47,11 +48,12 @@ export const FullScreenView: FC<FullScreenViewProps> = ({
|
|||||||
|
|
||||||
interface ErrorPageProps {
|
interface ErrorPageProps {
|
||||||
error: Error | unknown;
|
error: Error | unknown;
|
||||||
|
widget: WidgetHelpers | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due to this component being used as the crash fallback for Sentry, which has
|
// Due to this component being used as the crash fallback for Sentry, which has
|
||||||
// weird type requirements, we can't just give this a type of FC<ErrorPageProps>
|
// weird type requirements, we can't just give this a type of FC<ErrorPageProps>
|
||||||
export const ErrorPage = ({ error }: ErrorPageProps): ReactElement => {
|
export const ErrorPage = ({ error, widget }: ErrorPageProps): ReactElement => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
@@ -63,7 +65,13 @@ export const ErrorPage = ({ error }: ErrorPageProps): ReactElement => {
|
|||||||
{error instanceof RichError ? (
|
{error instanceof RichError ? (
|
||||||
error.richMessage
|
error.richMessage
|
||||||
) : (
|
) : (
|
||||||
<ErrorView Icon={ErrorIcon} title={t("error.generic")} rageshake fatal>
|
<ErrorView
|
||||||
|
widget={widget}
|
||||||
|
Icon={ErrorIcon}
|
||||||
|
title={t("error.generic")}
|
||||||
|
rageshake
|
||||||
|
fatal
|
||||||
|
>
|
||||||
<p>{t("error.generic_description")}</p>
|
<p>{t("error.generic_description")}</p>
|
||||||
</ErrorView>
|
</ErrorView>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
|||||||
|
|
||||||
import type { FC, ReactNode } from "react";
|
import type { FC, ReactNode } from "react";
|
||||||
import { ErrorView } from "./ErrorView";
|
import { ErrorView } from "./ErrorView";
|
||||||
|
import { widget } from "./widget.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error consisting of a terse message to be logged to the console and a
|
* An error consisting of a terse message to be logged to the console and a
|
||||||
@@ -31,7 +32,11 @@ const OpenElsewhere: FC = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorView Icon={PopOutIcon} title={t("error.open_elsewhere")}>
|
<ErrorView
|
||||||
|
widget={widget}
|
||||||
|
Icon={PopOutIcon}
|
||||||
|
title={t("error.open_elsewhere")}
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
{t("error.open_elsewhere_description", {
|
{t("error.open_elsewhere_description", {
|
||||||
brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call",
|
brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call",
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ describe("UrlParams", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("returnToLobby", () => {
|
describe("returnToLobby", () => {
|
||||||
it("is true in SPA mode", () => {
|
it("is false in SPA mode", () => {
|
||||||
expect(getUrlParams("?returnToLobby=false").returnToLobby).toBe(true);
|
expect(getUrlParams("?returnToLobby=true").returnToLobby).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defaults to false in widget mode", () => {
|
it("defaults to false in widget mode", () => {
|
||||||
|
|||||||
@@ -264,7 +264,9 @@ export const getUrlParams = (
|
|||||||
"skipLobby",
|
"skipLobby",
|
||||||
isWidget && intent === UserIntent.StartNewCall,
|
isWidget && intent === UserIntent.StartNewCall,
|
||||||
),
|
),
|
||||||
returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : true,
|
// In SPA mode the user should always exit to the home screen when hanging
|
||||||
|
// up, rather than being sent back to the lobby
|
||||||
|
returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : false,
|
||||||
theme: parser.getParam("theme"),
|
theme: parser.getParam("theme"),
|
||||||
viaServers: !isWidget ? parser.getParam("viaServers") : null,
|
viaServers: !isWidget ? parser.getParam("viaServers") : null,
|
||||||
homeserver: !isWidget ? parser.getParam("homeserver") : null,
|
homeserver: !isWidget ? parser.getParam("homeserver") : null,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
type ConfigOptions,
|
type ConfigOptions,
|
||||||
type ResolvedConfigOptions,
|
type ResolvedConfigOptions,
|
||||||
} from "./ConfigOptions";
|
} from "./ConfigOptions";
|
||||||
|
import { isFailure } from "../utils/fetch";
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
private static internalInstance: Config | undefined;
|
private static internalInstance: Config | undefined;
|
||||||
@@ -28,7 +29,20 @@ export class Config {
|
|||||||
const internalInstance = new Config();
|
const internalInstance = new Config();
|
||||||
Config.internalInstance = internalInstance;
|
Config.internalInstance = internalInstance;
|
||||||
|
|
||||||
Config.internalInstance.initPromise = downloadConfig("/config.json").then(
|
let fetchTarget: string;
|
||||||
|
|
||||||
|
if (
|
||||||
|
window.location.pathname.endsWith("/room/") ||
|
||||||
|
window.location.pathname.endsWith("/room")
|
||||||
|
) {
|
||||||
|
// it looks like we are running in standalone mode so use the config at the root
|
||||||
|
fetchTarget = new URL("/config.json", window.location.href).href;
|
||||||
|
} else {
|
||||||
|
// otherwise we are probably running as a widget so use the config in the same directory
|
||||||
|
fetchTarget = "config.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.internalInstance.initPromise = downloadConfig(fetchTarget).then(
|
||||||
(config) => {
|
(config) => {
|
||||||
internalInstance.config = merge({}, DEFAULT_CONFIG, config);
|
internalInstance.config = merge({}, DEFAULT_CONFIG, config);
|
||||||
},
|
},
|
||||||
@@ -70,18 +84,15 @@ export class Config {
|
|||||||
private initPromise?: Promise<void>;
|
private initPromise?: Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadConfig(
|
async function downloadConfig(fetchTarget: string): Promise<ConfigOptions> {
|
||||||
configJsonFilename: string,
|
const response = await fetch(fetchTarget);
|
||||||
): Promise<ConfigOptions> {
|
|
||||||
const url = new URL(configJsonFilename, window.location.href);
|
|
||||||
const res = await fetch(url);
|
|
||||||
|
|
||||||
if (!res.ok || res.status === 404 || res.status === 0) {
|
if (isFailure(response)) {
|
||||||
// Lack of a config isn't an error, we should just use the defaults.
|
// Lack of a config isn't an error, we should just use the defaults.
|
||||||
// Also treat a blank config as no config, assuming the status code is 0, because we don't get 404s from file:
|
// Also treat a blank config as no config, assuming the status code is 0, because we don't get 404s from file:
|
||||||
// URIs so this is the only way we can not fail if the file doesn't exist when loading from a file:// URI.
|
// URIs so this is the only way we can not fail if the file doesn't exist when loading from a file:// URI.
|
||||||
return DEFAULT_CONFIG;
|
return DEFAULT_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export interface ConfigOptions {
|
|||||||
/**
|
/**
|
||||||
* A link to the end-user license agreement (EULA)
|
* A link to the end-user license agreement (EULA)
|
||||||
*/
|
*/
|
||||||
eula: string;
|
eula?: string;
|
||||||
|
|
||||||
media_devices?: {
|
media_devices?: {
|
||||||
/**
|
/**
|
||||||
@@ -131,6 +131,7 @@ export interface ResolvedConfigOptions extends ConfigOptions {
|
|||||||
server_name: string;
|
server_name: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
eula: string;
|
||||||
media_devices: {
|
media_devices: {
|
||||||
enable_audio: boolean;
|
enable_audio: boolean;
|
||||||
enable_video: boolean;
|
enable_video: boolean;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { ErrorPage, LoadingPage } from "../FullScreenView";
|
|||||||
import { UnauthenticatedView } from "./UnauthenticatedView";
|
import { UnauthenticatedView } from "./UnauthenticatedView";
|
||||||
import { RegisteredView } from "./RegisteredView";
|
import { RegisteredView } from "./RegisteredView";
|
||||||
import { usePageTitle } from "../usePageTitle";
|
import { usePageTitle } from "../usePageTitle";
|
||||||
|
import { widget } from "../widget.ts";
|
||||||
|
|
||||||
export const HomePage: FC = () => {
|
export const HomePage: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -23,7 +24,7 @@ export const HomePage: FC = () => {
|
|||||||
if (!clientState) {
|
if (!clientState) {
|
||||||
return <LoadingPage />;
|
return <LoadingPage />;
|
||||||
} else if (clientState.state === "error") {
|
} else if (clientState.state === "error") {
|
||||||
return <ErrorPage error={clientState.error} />;
|
return <ErrorPage widget={widget} error={clientState.error} />;
|
||||||
} else {
|
} else {
|
||||||
return clientState.authenticated ? (
|
return clientState.authenticated ? (
|
||||||
<RegisteredView client={clientState.authenticated.client} />
|
<RegisteredView client={clientState.authenticated.client} />
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { getUrlParams } from "./UrlParams";
|
|||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
import { ElementCallOpenTelemetry } from "./otel/otel";
|
import { ElementCallOpenTelemetry } from "./otel/otel";
|
||||||
import { platform } from "./Platform";
|
import { platform } from "./Platform";
|
||||||
|
import { isFailure } from "./utils/fetch";
|
||||||
|
|
||||||
// This generates a map of locale names to their URL (based on import.meta.url), which looks like this:
|
// This generates a map of locale names to their URL (based on import.meta.url), which looks like this:
|
||||||
// {
|
// {
|
||||||
@@ -79,7 +80,7 @@ const Backend = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (isFailure(response)) {
|
||||||
throw Error(`Failed to fetch ${url}`);
|
throw Error(`Failed to fetch ${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type FC, useCallback, useState } from "react";
|
import { type FC, useCallback, useState } from "react";
|
||||||
import { test } from "vitest";
|
import { test, vi } from "vitest";
|
||||||
import {
|
import {
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
ConnectionErrorReason,
|
ConnectionErrorReason,
|
||||||
@@ -60,7 +60,7 @@ test.each<[string, ConnectionError]>([
|
|||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<GroupCallErrorBoundary>
|
<GroupCallErrorBoundary recoveryActionHandler={vi.fn()} widget={null}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</GroupCallErrorBoundary>
|
</GroupCallErrorBoundary>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import { App } from "./App";
|
|||||||
import { init as initRageshake } from "./settings/rageshake";
|
import { init as initRageshake } from "./settings/rageshake";
|
||||||
import { Initializer } from "./initializer";
|
import { Initializer } from "./initializer";
|
||||||
|
|
||||||
|
window.setLKLogLevel = setLKLogLevel;
|
||||||
|
|
||||||
initRageshake().catch((e) => {
|
initRageshake().catch((e) => {
|
||||||
logger.error("Failed to initialize rageshake", e);
|
logger.error("Failed to initialize rageshake", e);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ import {
|
|||||||
type ReactElement,
|
type ReactElement,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx";
|
import {
|
||||||
|
type CallErrorRecoveryAction,
|
||||||
|
GroupCallErrorBoundary,
|
||||||
|
} from "./GroupCallErrorBoundary.tsx";
|
||||||
import {
|
import {
|
||||||
ConnectionLostError,
|
ConnectionLostError,
|
||||||
E2EENotSupportedError,
|
E2EENotSupportedError,
|
||||||
@@ -28,8 +30,7 @@ import {
|
|||||||
UnknownCallError,
|
UnknownCallError,
|
||||||
} from "../utils/errors.ts";
|
} from "../utils/errors.ts";
|
||||||
import { mockConfig } from "../utils/test.ts";
|
import { mockConfig } from "../utils/test.ts";
|
||||||
import { useGroupCallErrorBoundary } from "./useCallErrorBoundary.ts";
|
import { ElementWidgetActions, type WidgetHelpers } from "../widget.ts";
|
||||||
import { GroupCallErrorBoundaryContextProvider } from "./GroupCallErrorBoundaryContextProvider.tsx";
|
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
{
|
{
|
||||||
@@ -63,7 +64,11 @@ test.each([
|
|||||||
const onErrorMock = vi.fn();
|
const onErrorMock = vi.fn();
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<GroupCallErrorBoundary onError={onErrorMock}>
|
<GroupCallErrorBoundary
|
||||||
|
onError={onErrorMock}
|
||||||
|
recoveryActionHandler={vi.fn()}
|
||||||
|
widget={null}
|
||||||
|
>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</GroupCallErrorBoundary>
|
</GroupCallErrorBoundary>
|
||||||
</BrowserRouter>,
|
</BrowserRouter>,
|
||||||
@@ -88,14 +93,18 @@ test("should render the error page with link back to home", async () => {
|
|||||||
const onErrorMock = vi.fn();
|
const onErrorMock = vi.fn();
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<GroupCallErrorBoundary onError={onErrorMock}>
|
<GroupCallErrorBoundary
|
||||||
|
onError={onErrorMock}
|
||||||
|
recoveryActionHandler={vi.fn()}
|
||||||
|
widget={null}
|
||||||
|
>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</GroupCallErrorBoundary>
|
</GroupCallErrorBoundary>
|
||||||
</BrowserRouter>,
|
</BrowserRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await screen.findByText("Call is not supported");
|
await screen.findByText("Call is not supported");
|
||||||
expect(screen.getByText(/Domain: example.com/i)).toBeInTheDocument();
|
expect(screen.getByText(/Domain: example\.com/i)).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(/Error Code: MISSING_MATRIX_RTC_FOCUS/i),
|
screen.getByText(/Error Code: MISSING_MATRIX_RTC_FOCUS/i),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
@@ -108,39 +117,7 @@ test("should render the error page with link back to home", async () => {
|
|||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should have a reconnect button for ConnectionLostError", async () => {
|
test("ConnectionLostError: Action handling should reset error state", async () => {
|
||||||
const user = userEvent.setup();
|
|
||||||
|
|
||||||
const reconnectCallback = vi.fn();
|
|
||||||
|
|
||||||
const TestComponent = (): ReactNode => {
|
|
||||||
throw new ConnectionLostError();
|
|
||||||
};
|
|
||||||
|
|
||||||
const { asFragment } = render(
|
|
||||||
<BrowserRouter>
|
|
||||||
<GroupCallErrorBoundary
|
|
||||||
onError={vi.fn()}
|
|
||||||
recoveryActionHandler={reconnectCallback}
|
|
||||||
>
|
|
||||||
<TestComponent />
|
|
||||||
</GroupCallErrorBoundary>
|
|
||||||
</BrowserRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
await screen.findByText("Connection lost");
|
|
||||||
await screen.findByRole("button", { name: "Reconnect" });
|
|
||||||
await screen.findByRole("button", { name: "Return to home screen" });
|
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
|
|
||||||
await user.click(screen.getByRole("button", { name: "Reconnect" }));
|
|
||||||
|
|
||||||
expect(reconnectCallback).toHaveBeenCalledOnce();
|
|
||||||
expect(reconnectCallback).toHaveBeenCalledWith("reconnect");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Action handling should reset error state", async () => {
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
const TestComponent: FC<{ fail: boolean }> = ({ fail }): ReactNode => {
|
const TestComponent: FC<{ fail: boolean }> = ({ fail }): ReactNode => {
|
||||||
@@ -150,30 +127,46 @@ test("Action handling should reset error state", async () => {
|
|||||||
return <div>HELLO</div>;
|
return <div>HELLO</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reconnectCallbackSpy = vi.fn();
|
||||||
|
|
||||||
const WrapComponent = (): ReactNode => {
|
const WrapComponent = (): ReactNode => {
|
||||||
const [failState, setFailState] = useState(true);
|
const [failState, setFailState] = useState(true);
|
||||||
const reconnectCallback = useCallback(() => {
|
const reconnectCallback = useCallback(
|
||||||
setFailState(false);
|
(action: CallErrorRecoveryAction) => {
|
||||||
}, [setFailState]);
|
reconnectCallbackSpy(action);
|
||||||
|
setFailState(false);
|
||||||
|
},
|
||||||
|
[setFailState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<GroupCallErrorBoundary recoveryActionHandler={reconnectCallback}>
|
<GroupCallErrorBoundary
|
||||||
|
recoveryActionHandler={reconnectCallback}
|
||||||
|
widget={null}
|
||||||
|
>
|
||||||
<TestComponent fail={failState} />
|
<TestComponent fail={failState} />
|
||||||
</GroupCallErrorBoundary>
|
</GroupCallErrorBoundary>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<WrapComponent />);
|
const { asFragment } = render(<WrapComponent />);
|
||||||
|
|
||||||
// Should fail first
|
// Should fail first
|
||||||
await screen.findByText("Connection lost");
|
await screen.findByText("Connection lost");
|
||||||
|
await screen.findByRole("button", { name: "Reconnect" });
|
||||||
|
await screen.findByRole("button", { name: "Return to home screen" });
|
||||||
|
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
||||||
await user.click(screen.getByRole("button", { name: "Reconnect" }));
|
await user.click(screen.getByRole("button", { name: "Reconnect" }));
|
||||||
|
|
||||||
// reconnect should have reset the error, thus rendering should be ok
|
// reconnect should have reset the error, thus rendering should be ok
|
||||||
await screen.findByText("HELLO");
|
await screen.findByText("HELLO");
|
||||||
|
|
||||||
|
expect(reconnectCallbackSpy).toHaveBeenCalledOnce();
|
||||||
|
expect(reconnectCallbackSpy).toHaveBeenCalledWith("reconnect");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Rageshake button", () => {
|
describe("Rageshake button", () => {
|
||||||
@@ -190,7 +183,11 @@ describe("Rageshake button", () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<GroupCallErrorBoundary onError={vi.fn()}>
|
<GroupCallErrorBoundary
|
||||||
|
onError={vi.fn()}
|
||||||
|
recoveryActionHandler={vi.fn()}
|
||||||
|
widget={null}
|
||||||
|
>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</GroupCallErrorBoundary>
|
</GroupCallErrorBoundary>
|
||||||
</BrowserRouter>,
|
</BrowserRouter>,
|
||||||
@@ -214,29 +211,43 @@ describe("Rageshake button", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should show async error with useElementCallErrorContext", async () => {
|
test("should have a close button in widget mode", async () => {
|
||||||
// const error = new MatrixRTCFocusMissingError("example.com");
|
const error = new MatrixRTCFocusMissingError("example.com");
|
||||||
const TestComponent = (): ReactNode => {
|
const TestComponent = (): ReactNode => {
|
||||||
const { showGroupCallErrorBoundary } = useGroupCallErrorBoundary();
|
throw error;
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
showGroupCallErrorBoundary(new ConnectionLostError());
|
|
||||||
});
|
|
||||||
}, [showGroupCallErrorBoundary]);
|
|
||||||
|
|
||||||
return <div>Hello</div>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockWidget = {
|
||||||
|
api: {
|
||||||
|
transport: { send: vi.fn().mockResolvedValue(undefined), stop: vi.fn() },
|
||||||
|
},
|
||||||
|
} as unknown as WidgetHelpers;
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
const onErrorMock = vi.fn();
|
const onErrorMock = vi.fn();
|
||||||
render(
|
const { asFragment } = render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<GroupCallErrorBoundaryContextProvider>
|
<GroupCallErrorBoundary
|
||||||
<GroupCallErrorBoundary onError={onErrorMock}>
|
widget={mockWidget}
|
||||||
<TestComponent />
|
onError={onErrorMock}
|
||||||
</GroupCallErrorBoundary>
|
recoveryActionHandler={vi.fn()}
|
||||||
</GroupCallErrorBoundaryContextProvider>
|
>
|
||||||
|
<TestComponent />
|
||||||
|
</GroupCallErrorBoundary>
|
||||||
</BrowserRouter>,
|
</BrowserRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await screen.findByText("Connection lost");
|
await screen.findByText("Call is not supported");
|
||||||
|
|
||||||
|
await screen.findByRole("button", { name: "Close" });
|
||||||
|
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: "Close" }));
|
||||||
|
|
||||||
|
expect(mockWidget.api.transport.send).toHaveBeenCalledWith(
|
||||||
|
ElementWidgetActions.Close,
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
expect(mockWidget.api.transport.stop).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
} from "../utils/errors.ts";
|
} from "../utils/errors.ts";
|
||||||
import { FullScreenView } from "../FullScreenView.tsx";
|
import { FullScreenView } from "../FullScreenView.tsx";
|
||||||
import { ErrorView } from "../ErrorView.tsx";
|
import { ErrorView } from "../ErrorView.tsx";
|
||||||
|
import { type WidgetHelpers } from "../widget.ts";
|
||||||
|
|
||||||
export type CallErrorRecoveryAction = "reconnect"; // | "retry" ;
|
export type CallErrorRecoveryAction = "reconnect"; // | "retry" ;
|
||||||
|
|
||||||
@@ -38,17 +39,18 @@ export type RecoveryActionHandler = (action: CallErrorRecoveryAction) => void;
|
|||||||
|
|
||||||
interface ErrorPageProps {
|
interface ErrorPageProps {
|
||||||
error: ElementCallError;
|
error: ElementCallError;
|
||||||
recoveryActionHandler?: RecoveryActionHandler;
|
recoveryActionHandler: RecoveryActionHandler;
|
||||||
resetError: () => void;
|
resetError: () => void;
|
||||||
|
widget: WidgetHelpers | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrorPage: FC<ErrorPageProps> = ({
|
const ErrorPage: FC<ErrorPageProps> = ({
|
||||||
error,
|
error,
|
||||||
recoveryActionHandler,
|
recoveryActionHandler,
|
||||||
|
widget,
|
||||||
}: ErrorPageProps): ReactElement => {
|
}: ErrorPageProps): ReactElement => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// let title: string;
|
|
||||||
let icon: ComponentType<SVGAttributes<SVGElement>>;
|
let icon: ComponentType<SVGAttributes<SVGElement>>;
|
||||||
switch (error.category) {
|
switch (error.category) {
|
||||||
case ErrorCategory.CONFIGURATION_ISSUE:
|
case ErrorCategory.CONFIGURATION_ISSUE:
|
||||||
@@ -68,7 +70,7 @@ const ErrorPage: FC<ErrorPageProps> = ({
|
|||||||
if (error instanceof ConnectionLostError) {
|
if (error instanceof ConnectionLostError) {
|
||||||
actions.push({
|
actions.push({
|
||||||
label: t("call_ended_view.reconnect_button"),
|
label: t("call_ended_view.reconnect_button"),
|
||||||
onClick: () => recoveryActionHandler?.("reconnect"),
|
onClick: () => recoveryActionHandler("reconnect"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ const ErrorPage: FC<ErrorPageProps> = ({
|
|||||||
Icon={icon}
|
Icon={icon}
|
||||||
title={error.localisedTitle}
|
title={error.localisedTitle}
|
||||||
rageshake={error.code == ErrorCode.UNKNOWN_ERROR}
|
rageshake={error.code == ErrorCode.UNKNOWN_ERROR}
|
||||||
|
widget={widget}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{error.localisedMessage ?? (
|
{error.localisedMessage ?? (
|
||||||
@@ -101,38 +104,16 @@ const ErrorPage: FC<ErrorPageProps> = ({
|
|||||||
|
|
||||||
interface BoundaryProps {
|
interface BoundaryProps {
|
||||||
children: ReactNode | (() => ReactNode);
|
children: ReactNode | (() => ReactNode);
|
||||||
recoveryActionHandler?: RecoveryActionHandler;
|
recoveryActionHandler: RecoveryActionHandler;
|
||||||
onError?: (error: unknown) => void;
|
onError?: (error: unknown) => void;
|
||||||
|
widget: WidgetHelpers | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An ErrorBoundary component that handles ElementCalls errors that can occur during a group call.
|
|
||||||
* It is based on the sentry ErrorBoundary component, that will log the error to sentry.
|
|
||||||
*
|
|
||||||
* The error fallback will show an error page with:
|
|
||||||
* - a description of the error
|
|
||||||
* - a button to go back the home screen
|
|
||||||
* - optional call-to-action buttons (ex: reconnect for connection lost)
|
|
||||||
* - A rageshake button for unknown errors
|
|
||||||
*
|
|
||||||
* For async errors the `useCallErrorBoundary` hook should be used to show the error page
|
|
||||||
* ```
|
|
||||||
* const { showGroupCallErrorBoundary } = useCallErrorBoundary();
|
|
||||||
* ... some async code
|
|
||||||
* catch(error) {
|
|
||||||
* showGroupCallErrorBoundary(error);
|
|
||||||
* }
|
|
||||||
* ...
|
|
||||||
* ```
|
|
||||||
* @param recoveryActionHandler
|
|
||||||
* @param onError
|
|
||||||
* @param children
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export const GroupCallErrorBoundary = ({
|
export const GroupCallErrorBoundary = ({
|
||||||
recoveryActionHandler,
|
recoveryActionHandler,
|
||||||
onError,
|
onError,
|
||||||
children,
|
children,
|
||||||
|
widget,
|
||||||
}: BoundaryProps): ReactElement => {
|
}: BoundaryProps): ReactElement => {
|
||||||
const fallbackRenderer: FallbackRender = useCallback(
|
const fallbackRenderer: FallbackRender = useCallback(
|
||||||
({ error, resetError }): ReactElement => {
|
({ error, resetError }): ReactElement => {
|
||||||
@@ -142,16 +123,17 @@ export const GroupCallErrorBoundary = ({
|
|||||||
: new UnknownCallError(error instanceof Error ? error : new Error());
|
: new UnknownCallError(error instanceof Error ? error : new Error());
|
||||||
return (
|
return (
|
||||||
<ErrorPage
|
<ErrorPage
|
||||||
|
widget={widget ?? null}
|
||||||
error={callError}
|
error={callError}
|
||||||
resetError={resetError}
|
resetError={resetError}
|
||||||
recoveryActionHandler={(action: CallErrorRecoveryAction) => {
|
recoveryActionHandler={(action: CallErrorRecoveryAction) => {
|
||||||
resetError();
|
resetError();
|
||||||
recoveryActionHandler?.(action);
|
recoveryActionHandler(action);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[recoveryActionHandler],
|
[recoveryActionHandler, widget],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { it } from "vitest";
|
import { it, vi } from "vitest";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { type ReactElement, useCallback } from "react";
|
import { type ReactElement, useCallback } from "react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
@@ -37,7 +37,7 @@ it("should show async error", async () => {
|
|||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<GroupCallErrorBoundaryContextProvider>
|
<GroupCallErrorBoundaryContextProvider>
|
||||||
<GroupCallErrorBoundary>
|
<GroupCallErrorBoundary widget={null} recoveryActionHandler={vi.fn()}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</GroupCallErrorBoundary>
|
</GroupCallErrorBoundary>
|
||||||
</GroupCallErrorBoundaryContextProvider>
|
</GroupCallErrorBoundaryContextProvider>
|
||||||
|
|||||||
@@ -15,12 +15,16 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import {
|
import {
|
||||||
|
Room as LivekitRoom,
|
||||||
isE2EESupported as isE2EESupportedBrowser,
|
isE2EESupported as isE2EESupportedBrowser,
|
||||||
Room,
|
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
|
||||||
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
import {
|
||||||
|
MatrixRTCSessionEvent,
|
||||||
|
type MatrixRTCSession,
|
||||||
|
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
@@ -54,9 +58,16 @@ import { usePageTitle } from "../usePageTitle";
|
|||||||
import {
|
import {
|
||||||
E2EENotSupportedError,
|
E2EENotSupportedError,
|
||||||
ElementCallError,
|
ElementCallError,
|
||||||
|
ErrorCode,
|
||||||
|
RTCSessionError,
|
||||||
UnknownCallError,
|
UnknownCallError,
|
||||||
} from "../utils/errors.ts";
|
} from "../utils/errors.ts";
|
||||||
import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx";
|
import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx";
|
||||||
|
import {
|
||||||
|
useNewMembershipManagerSetting as useNewMembershipManagerSetting,
|
||||||
|
useSetting,
|
||||||
|
} from "../settings/settings";
|
||||||
|
import { useTypedEventEmitter } from "../useEvents";
|
||||||
import { GroupCallErrorBoundaryContextProvider } from "./GroupCallErrorBoundaryContextProvider.tsx";
|
import { GroupCallErrorBoundaryContextProvider } from "./GroupCallErrorBoundaryContextProvider.tsx";
|
||||||
import { useGroupCallErrorBoundary } from "./useCallErrorBoundary.ts";
|
import { useGroupCallErrorBoundary } from "./useCallErrorBoundary.ts";
|
||||||
|
|
||||||
@@ -120,6 +131,20 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
};
|
};
|
||||||
}, [rtcSession]);
|
}, [rtcSession]);
|
||||||
|
|
||||||
|
const { showGroupCallErrorBoundary } = useGroupCallErrorBoundary();
|
||||||
|
|
||||||
|
useTypedEventEmitter(
|
||||||
|
rtcSession,
|
||||||
|
MatrixRTCSessionEvent.MembershipManagerError,
|
||||||
|
(error) => {
|
||||||
|
showGroupCallErrorBoundary(
|
||||||
|
new RTCSessionError(
|
||||||
|
ErrorCode.MEMBERSHIP_MANAGER_UNRECOVERABLE,
|
||||||
|
error.message ?? error,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Sanity check the room object
|
// Sanity check the room object
|
||||||
if (client.getRoom(rtcSession.room.roomId) !== rtcSession.room)
|
if (client.getRoom(rtcSession.room.roomId) !== rtcSession.room)
|
||||||
@@ -128,11 +153,14 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}, [client, rtcSession.room]);
|
}, [client, rtcSession.room]);
|
||||||
|
|
||||||
|
const room = rtcSession.room as Room;
|
||||||
const { displayName, avatarUrl } = useProfile(client);
|
const { displayName, avatarUrl } = useProfile(client);
|
||||||
const roomName = useRoomName(rtcSession.room);
|
const roomName = useRoomName(room);
|
||||||
const roomAvatar = useRoomAvatar(rtcSession.room);
|
const roomAvatar = useRoomAvatar(room);
|
||||||
const { perParticipantE2EE, returnToLobby } = useUrlParams();
|
const { perParticipantE2EE, returnToLobby } = useUrlParams();
|
||||||
const e2eeSystem = useRoomEncryptionSystem(rtcSession.room.roomId);
|
const e2eeSystem = useRoomEncryptionSystem(room.roomId);
|
||||||
|
const [useNewMembershipManager] = useSetting(useNewMembershipManagerSetting);
|
||||||
|
|
||||||
usePageTitle(roomName);
|
usePageTitle(roomName);
|
||||||
|
|
||||||
const matrixInfo = useMemo((): MatrixInfo => {
|
const matrixInfo = useMemo((): MatrixInfo => {
|
||||||
@@ -140,21 +168,13 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
userId: client.getUserId()!,
|
userId: client.getUserId()!,
|
||||||
displayName: displayName!,
|
displayName: displayName!,
|
||||||
avatarUrl: avatarUrl!,
|
avatarUrl: avatarUrl!,
|
||||||
roomId: rtcSession.room.roomId,
|
roomId: room.roomId,
|
||||||
roomName,
|
roomName,
|
||||||
roomAlias: rtcSession.room.getCanonicalAlias(),
|
roomAlias: room.getCanonicalAlias(),
|
||||||
roomAvatar,
|
roomAvatar,
|
||||||
e2eeSystem,
|
e2eeSystem,
|
||||||
};
|
};
|
||||||
}, [
|
}, [client, displayName, avatarUrl, roomName, room, roomAvatar, e2eeSystem]);
|
||||||
client,
|
|
||||||
displayName,
|
|
||||||
avatarUrl,
|
|
||||||
rtcSession.room,
|
|
||||||
roomName,
|
|
||||||
roomAvatar,
|
|
||||||
e2eeSystem,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Count each member only once, regardless of how many devices they use
|
// Count each member only once, regardless of how many devices they use
|
||||||
const participantCount = useMemo(
|
const participantCount = useMemo(
|
||||||
@@ -166,15 +186,18 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
const latestDevices = useLatest(deviceContext);
|
const latestDevices = useLatest(deviceContext);
|
||||||
const latestMuteStates = useLatest(muteStates);
|
const latestMuteStates = useLatest(muteStates);
|
||||||
|
|
||||||
const { showGroupCallErrorBoundary } = useGroupCallErrorBoundary();
|
|
||||||
|
|
||||||
const enterRTCSessionOrError = useCallback(
|
const enterRTCSessionOrError = useCallback(
|
||||||
async (
|
async (
|
||||||
rtcSession: MatrixRTCSession,
|
rtcSession: MatrixRTCSession,
|
||||||
perParticipantE2EE: boolean,
|
perParticipantE2EE: boolean,
|
||||||
|
newMembershipManager: boolean,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
await enterRTCSession(
|
||||||
|
rtcSession,
|
||||||
|
perParticipantE2EE,
|
||||||
|
newMembershipManager,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ElementCallError) {
|
if (e instanceof ElementCallError) {
|
||||||
showGroupCallErrorBoundary(e);
|
showGroupCallErrorBoundary(e);
|
||||||
@@ -199,7 +222,7 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
// permissions and give you device names unless you specify a kind, but
|
// permissions and give you device names unless you specify a kind, but
|
||||||
// here we want all kinds of devices. This needs a fix in livekit-client
|
// here we want all kinds of devices. This needs a fix in livekit-client
|
||||||
// for the following name-matching logic to do anything useful.
|
// for the following name-matching logic to do anything useful.
|
||||||
const devices = await Room.getLocalDevices(undefined, true);
|
const devices = await LivekitRoom.getLocalDevices(undefined, true);
|
||||||
|
|
||||||
if (audioInput) {
|
if (audioInput) {
|
||||||
const deviceId = findDeviceByName(audioInput, "audioinput", devices);
|
const deviceId = findDeviceByName(audioInput, "audioinput", devices);
|
||||||
@@ -239,7 +262,11 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
await defaultDeviceSetup(
|
await defaultDeviceSetup(
|
||||||
ev.detail.data as unknown as JoinCallData,
|
ev.detail.data as unknown as JoinCallData,
|
||||||
);
|
);
|
||||||
await enterRTCSessionOrError(rtcSession, perParticipantE2EE);
|
await enterRTCSessionOrError(
|
||||||
|
rtcSession,
|
||||||
|
perParticipantE2EE,
|
||||||
|
useNewMembershipManager,
|
||||||
|
);
|
||||||
widget.api.transport.reply(ev.detail, {});
|
widget.api.transport.reply(ev.detail, {});
|
||||||
})().catch((e) => {
|
})().catch((e) => {
|
||||||
logger.error("Error joining RTC session", e);
|
logger.error("Error joining RTC session", e);
|
||||||
@@ -252,13 +279,21 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
} else {
|
} else {
|
||||||
// No lobby and no preload: we enter the rtc session right away
|
// No lobby and no preload: we enter the rtc session right away
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
await enterRTCSessionOrError(rtcSession, perParticipantE2EE);
|
await enterRTCSessionOrError(
|
||||||
|
rtcSession,
|
||||||
|
perParticipantE2EE,
|
||||||
|
useNewMembershipManager,
|
||||||
|
);
|
||||||
})().catch((e) => {
|
})().catch((e) => {
|
||||||
logger.error("Error joining RTC session", e);
|
logger.error("Error joining RTC session", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
void enterRTCSessionOrError(rtcSession, perParticipantE2EE);
|
void enterRTCSessionOrError(
|
||||||
|
rtcSession,
|
||||||
|
perParticipantE2EE,
|
||||||
|
useNewMembershipManager,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -270,6 +305,7 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
latestDevices,
|
latestDevices,
|
||||||
latestMuteStates,
|
latestMuteStates,
|
||||||
enterRTCSessionOrError,
|
enterRTCSessionOrError,
|
||||||
|
useNewMembershipManager,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [left, setLeft] = useState(false);
|
const [left, setLeft] = useState(false);
|
||||||
@@ -287,7 +323,7 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||||
const posthogRequest = new Promise((resolve) => {
|
const posthogRequest = new Promise((resolve) => {
|
||||||
PosthogAnalytics.instance.eventCallEnded.track(
|
PosthogAnalytics.instance.eventCallEnded.track(
|
||||||
rtcSession.room.roomId,
|
room.roomId,
|
||||||
rtcSession.memberships.length,
|
rtcSession.memberships.length,
|
||||||
sendInstantly,
|
sendInstantly,
|
||||||
rtcSession,
|
rtcSession,
|
||||||
@@ -316,11 +352,12 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
leaveSoundContext,
|
||||||
widget,
|
widget,
|
||||||
rtcSession,
|
rtcSession,
|
||||||
|
room.roomId,
|
||||||
isPasswordlessUser,
|
isPasswordlessUser,
|
||||||
confineToRoom,
|
confineToRoom,
|
||||||
leaveSoundContext,
|
|
||||||
navigate,
|
navigate,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -346,7 +383,7 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}, [widget, isJoined, rtcSession]);
|
}, [widget, isJoined, rtcSession]);
|
||||||
|
|
||||||
const joinRule = useJoinRule(rtcSession.room);
|
const joinRule = useJoinRule(room);
|
||||||
|
|
||||||
const [shareModalOpen, setInviteModalOpen] = useState(false);
|
const [shareModalOpen, setInviteModalOpen] = useState(false);
|
||||||
const onDismissInviteModal = useCallback(
|
const onDismissInviteModal = useCallback(
|
||||||
@@ -367,7 +404,7 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
|
|
||||||
const shareModal = (
|
const shareModal = (
|
||||||
<InviteModal
|
<InviteModal
|
||||||
room={rtcSession.room}
|
room={room}
|
||||||
open={shareModalOpen}
|
open={shareModalOpen}
|
||||||
onDismiss={onDismissInviteModal}
|
onDismiss={onDismissInviteModal}
|
||||||
/>
|
/>
|
||||||
@@ -380,7 +417,11 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
matrixInfo={matrixInfo}
|
matrixInfo={matrixInfo}
|
||||||
muteStates={muteStates}
|
muteStates={muteStates}
|
||||||
onEnter={() =>
|
onEnter={() =>
|
||||||
void enterRTCSessionOrError(rtcSession, perParticipantE2EE)
|
void enterRTCSessionOrError(
|
||||||
|
rtcSession,
|
||||||
|
perParticipantE2EE,
|
||||||
|
useNewMembershipManager,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
confineToRoom={confineToRoom}
|
confineToRoom={confineToRoom}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
@@ -438,9 +479,7 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
} else if (left && widget !== null) {
|
} else if (left && widget !== null) {
|
||||||
// Left in widget mode:
|
// Left in widget mode:
|
||||||
if (!returnToLobby) {
|
body = returnToLobby ? lobbyView : null;
|
||||||
body = null;
|
|
||||||
}
|
|
||||||
} else if (preload || skipLobby) {
|
} else if (preload || skipLobby) {
|
||||||
body = null;
|
body = null;
|
||||||
} else {
|
} else {
|
||||||
@@ -449,10 +488,15 @@ export const GroupCallViewInner: FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GroupCallErrorBoundary
|
<GroupCallErrorBoundary
|
||||||
|
widget={widget}
|
||||||
recoveryActionHandler={(action) => {
|
recoveryActionHandler={(action) => {
|
||||||
if (action == "reconnect") {
|
if (action == "reconnect") {
|
||||||
setLeft(false);
|
setLeft(false);
|
||||||
enterRTCSessionOrError(rtcSession, perParticipantE2EE).catch((e) => {
|
enterRTCSessionOrError(
|
||||||
|
rtcSession,
|
||||||
|
perParticipantE2EE,
|
||||||
|
useNewMembershipManager,
|
||||||
|
).catch((e) => {
|
||||||
logger.error("Error re-entering RTC session", e);
|
logger.error("Error re-entering RTC session", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ export const RoomPage: FC = () => {
|
|||||||
<ErrorView
|
<ErrorView
|
||||||
Icon={UnknownSolidIcon}
|
Icon={UnknownSolidIcon}
|
||||||
title={t("error.call_not_found")}
|
title={t("error.call_not_found")}
|
||||||
|
widget={widget}
|
||||||
>
|
>
|
||||||
<Trans i18nKey="error.call_not_found_description">
|
<Trans i18nKey="error.call_not_found_description">
|
||||||
<p>
|
<p>
|
||||||
@@ -199,6 +200,7 @@ export const RoomPage: FC = () => {
|
|||||||
<ErrorView
|
<ErrorView
|
||||||
Icon={groupCallState.error.icon}
|
Icon={groupCallState.error.icon}
|
||||||
title={groupCallState.error.message}
|
title={groupCallState.error.message}
|
||||||
|
widget={widget}
|
||||||
>
|
>
|
||||||
<p>{groupCallState.error.messageBody}</p>
|
<p>{groupCallState.error.messageBody}</p>
|
||||||
{groupCallState.error.reason && (
|
{groupCallState.error.reason && (
|
||||||
@@ -212,7 +214,7 @@ export const RoomPage: FC = () => {
|
|||||||
</FullScreenView>
|
</FullScreenView>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <ErrorPage error={groupCallState.error} />;
|
return <ErrorPage widget={widget} error={groupCallState.error} />;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return <> </>;
|
return <> </>;
|
||||||
@@ -223,7 +225,7 @@ export const RoomPage: FC = () => {
|
|||||||
if (loading || isRegistering) {
|
if (loading || isRegistering) {
|
||||||
content = <LoadingPage />;
|
content = <LoadingPage />;
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
content = <ErrorPage error={error} />;
|
content = <ErrorPage widget={widget} error={error} />;
|
||||||
} else if (!client) {
|
} else if (!client) {
|
||||||
content = <RoomAuthView />;
|
content = <RoomAuthView />;
|
||||||
} else if (!roomIdOrAlias) {
|
} else if (!roomIdOrAlias) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`should have a reconnect button for ConnectionLostError 1`] = `
|
exports[`ConnectionLostError: Action handling should reset error state 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="page"
|
class="page"
|
||||||
@@ -150,7 +150,7 @@ exports[`should have a reconnect button for ConnectionLostError 1`] = `
|
|||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`should render the error page 1`] = `
|
exports[`should have a close button in widget mode 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="page"
|
class="page"
|
||||||
@@ -287,13 +287,13 @@ exports[`should render the error page 1`] = `
|
|||||||
The server is not configured to work with Element Call. Please contact your server admin (Domain: example.com, Error Code: MISSING_MATRIX_RTC_FOCUS).
|
The server is not configured to work with Element Call. Please contact your server admin (Domain: example.com, Error Code: MISSING_MATRIX_RTC_FOCUS).
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
class="_button_i91xf_17 homeLink"
|
class="_button_i91xf_17"
|
||||||
data-kind="tertiary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
Return to home screen
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
import { expect, test, vi } from "vitest";
|
import { expect, onTestFinished, test, vi } from "vitest";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
|
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
|
|
||||||
@@ -15,11 +15,17 @@ import { mockConfig } from "./utils/test";
|
|||||||
import { ElementWidgetActions, widget } from "./widget";
|
import { ElementWidgetActions, widget } from "./widget";
|
||||||
import { ErrorCode } from "./utils/errors.ts";
|
import { ErrorCode } from "./utils/errors.ts";
|
||||||
|
|
||||||
|
const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
|
||||||
|
vi.mock("./UrlParams", () => ({ getUrlParams }));
|
||||||
|
|
||||||
const actualWidget = await vi.hoisted(async () => vi.importActual("./widget"));
|
const actualWidget = await vi.hoisted(async () => vi.importActual("./widget"));
|
||||||
vi.mock("./widget", () => ({
|
vi.mock("./widget", () => ({
|
||||||
...actualWidget,
|
...actualWidget,
|
||||||
widget: {
|
widget: {
|
||||||
api: { transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() } },
|
api: {
|
||||||
|
setAlwaysOnScreen: (): void => {},
|
||||||
|
transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() },
|
||||||
|
},
|
||||||
lazyActions: new EventEmitter(),
|
lazyActions: new EventEmitter(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -105,38 +111,50 @@ test("It joins the correct Session", async () => {
|
|||||||
{
|
{
|
||||||
manageMediaKeys: false,
|
manageMediaKeys: false,
|
||||||
useLegacyMemberEvents: false,
|
useLegacyMemberEvents: false,
|
||||||
|
useNewMembershipManager: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("leaveRTCSession closes the widget on a normal hangup", async () => {
|
async function testLeaveRTCSession(
|
||||||
|
cause: "user" | "error",
|
||||||
|
expectClose: boolean,
|
||||||
|
): Promise<void> {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession;
|
const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession;
|
||||||
await leaveRTCSession(session, "user");
|
await leaveRTCSession(session, cause);
|
||||||
expect(session.leaveRoomSession).toHaveBeenCalled();
|
expect(session.leaveRoomSession).toHaveBeenCalled();
|
||||||
expect(widget!.api.transport.send).toHaveBeenCalledWith(
|
expect(widget!.api.transport.send).toHaveBeenCalledWith(
|
||||||
ElementWidgetActions.HangupCall,
|
ElementWidgetActions.HangupCall,
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
);
|
);
|
||||||
expect(widget!.api.transport.send).toHaveBeenCalledWith(
|
if (expectClose) {
|
||||||
ElementWidgetActions.Close,
|
expect(widget!.api.transport.send).toHaveBeenCalledWith(
|
||||||
expect.anything(),
|
ElementWidgetActions.Close,
|
||||||
);
|
expect.anything(),
|
||||||
|
);
|
||||||
|
expect(widget!.api.transport.stop).toHaveBeenCalled();
|
||||||
|
} else {
|
||||||
|
expect(widget!.api.transport.send).not.toHaveBeenCalledWith(
|
||||||
|
ElementWidgetActions.Close,
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
expect(widget!.api.transport.stop).not.toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("leaveRTCSession closes the widget on a normal hangup", async () => {
|
||||||
|
await testLeaveRTCSession("user", true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("leaveRTCSession doesn't close the widget on a fatal error", async () => {
|
test("leaveRTCSession doesn't close the widget on a fatal error", async () => {
|
||||||
vi.clearAllMocks();
|
await testLeaveRTCSession("error", false);
|
||||||
const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession;
|
});
|
||||||
await leaveRTCSession(session, "error");
|
|
||||||
expect(session.leaveRoomSession).toHaveBeenCalled();
|
test("leaveRTCSession doesn't close the widget when returning to lobby", async () => {
|
||||||
expect(widget!.api.transport.send).toHaveBeenCalledWith(
|
getUrlParams.mockReturnValue({ returnToLobby: true });
|
||||||
ElementWidgetActions.HangupCall,
|
onTestFinished(() => void getUrlParams.mockReset());
|
||||||
expect.anything(),
|
await testLeaveRTCSession("user", false);
|
||||||
);
|
|
||||||
expect(widget!.api.transport.send).not.toHaveBeenCalledWith(
|
|
||||||
ElementWidgetActions.Close,
|
|
||||||
expect.anything(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("It fails with configuration error if no live kit url config is set in fallback", async () => {
|
test("It fails with configuration error if no live kit url config is set in fallback", async () => {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
|
|||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget";
|
import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget";
|
||||||
import { MatrixRTCFocusMissingError } from "./utils/errors.ts";
|
import { MatrixRTCFocusMissingError } from "./utils/errors.ts";
|
||||||
|
import { getUrlParams } from "./UrlParams.ts";
|
||||||
|
|
||||||
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
|
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ async function makePreferredLivekitFoci(
|
|||||||
export async function enterRTCSession(
|
export async function enterRTCSession(
|
||||||
rtcSession: MatrixRTCSession,
|
rtcSession: MatrixRTCSession,
|
||||||
encryptMedia: boolean,
|
encryptMedia: boolean,
|
||||||
|
useNewMembershipManager = true,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||||
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||||
@@ -113,6 +115,7 @@ export async function enterRTCSession(
|
|||||||
await makePreferredLivekitFoci(rtcSession, livekitAlias),
|
await makePreferredLivekitFoci(rtcSession, livekitAlias),
|
||||||
makeActiveFocus(),
|
makeActiveFocus(),
|
||||||
{
|
{
|
||||||
|
useNewMembershipManager,
|
||||||
manageMediaKeys: encryptMedia,
|
manageMediaKeys: encryptMedia,
|
||||||
...(useDeviceSessionMemberEvents !== undefined && {
|
...(useDeviceSessionMemberEvents !== undefined && {
|
||||||
useLegacyMemberEvents: !useDeviceSessionMemberEvents,
|
useLegacyMemberEvents: !useDeviceSessionMemberEvents,
|
||||||
@@ -124,6 +127,13 @@ export async function enterRTCSession(
|
|||||||
makeKeyDelay: matrixRtcSessionConfig?.key_rotation_on_leave_delay,
|
makeKeyDelay: matrixRtcSessionConfig?.key_rotation_on_leave_delay,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if (widget) {
|
||||||
|
try {
|
||||||
|
await widget.api.transport.send(ElementWidgetActions.JoinCall, {});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Failed to send join action", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const widgetPostHangupProcedure = async (
|
const widgetPostHangupProcedure = async (
|
||||||
@@ -149,7 +159,7 @@ const widgetPostHangupProcedure = async (
|
|||||||
}
|
}
|
||||||
// On a normal user hangup we can shut down and close the widget. But if an
|
// On a normal user hangup we can shut down and close the widget. But if an
|
||||||
// error occurs we should keep the widget open until the user reads it.
|
// error occurs we should keep the widget open until the user reads it.
|
||||||
if (cause === "user") {
|
if (cause === "user" && !getUrlParams().returnToLobby) {
|
||||||
try {
|
try {
|
||||||
await widget.api.transport.send(ElementWidgetActions.Close, {});
|
await widget.api.transport.send(ElementWidgetActions.Close, {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
debugTileLayout as debugTileLayoutSetting,
|
debugTileLayout as debugTileLayoutSetting,
|
||||||
showNonMemberTiles as showNonMemberTilesSetting,
|
showNonMemberTiles as showNonMemberTilesSetting,
|
||||||
showConnectionStats as showConnectionStatsSetting,
|
showConnectionStats as showConnectionStatsSetting,
|
||||||
|
useNewMembershipManagerSetting,
|
||||||
} from "./settings";
|
} from "./settings";
|
||||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import type { Room as LivekitRoom } from "livekit-client";
|
import type { Room as LivekitRoom } from "livekit-client";
|
||||||
@@ -38,6 +39,10 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRoom }) => {
|
|||||||
showConnectionStatsSetting,
|
showConnectionStatsSetting,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [useNewMembershipManager, setNewMembershipManager] = useSetting(
|
||||||
|
useNewMembershipManagerSetting,
|
||||||
|
);
|
||||||
|
|
||||||
const sfuUrl = useMemo((): URL | null => {
|
const sfuUrl = useMemo((): URL | null => {
|
||||||
if (livekitRoom?.engine.client.ws?.url) {
|
if (livekitRoom?.engine.client.ws?.url) {
|
||||||
// strip the URL params
|
// strip the URL params
|
||||||
@@ -134,6 +139,20 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRoom }) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="useNewMembershipManager"
|
||||||
|
type="checkbox"
|
||||||
|
label={t("developer_mode.use_new_membership_manager")}
|
||||||
|
checked={!!useNewMembershipManager}
|
||||||
|
onChange={useCallback(
|
||||||
|
(event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setNewMembershipManager(event.target.checked);
|
||||||
|
},
|
||||||
|
[setNewMembershipManager],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
{livekitRoom ? (
|
{livekitRoom ? (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -113,4 +113,8 @@ export const soundEffectVolumeSetting = new Setting<number>(
|
|||||||
0.5,
|
0.5,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const useNewMembershipManagerSetting = new Setting<boolean>(
|
||||||
|
"new-membership-manager",
|
||||||
|
true,
|
||||||
|
);
|
||||||
export const alwaysShowSelf = new Setting<boolean>("always-show-self", true);
|
export const alwaysShowSelf = new Setting<boolean>("always-show-self", true);
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import { type ComponentProps, useCallback, useEffect, useState } from "react";
|
|||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import {
|
import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
type Crypto,
|
|
||||||
type MatrixClient,
|
type MatrixClient,
|
||||||
type MatrixEvent,
|
type MatrixEvent,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||||
|
|
||||||
import { getLogsForReport } from "./rageshake";
|
import { getLogsForReport } from "./rageshake";
|
||||||
import { useClient } from "../ClientContext";
|
import { useClient } from "../ClientContext";
|
||||||
@@ -34,7 +34,7 @@ const gzip = async (text: string): Promise<Blob> => {
|
|||||||
* Collects crypto related information.
|
* Collects crypto related information.
|
||||||
*/
|
*/
|
||||||
async function collectCryptoInfo(
|
async function collectCryptoInfo(
|
||||||
cryptoApi: Crypto.CryptoApi,
|
cryptoApi: CryptoApi,
|
||||||
body: FormData,
|
body: FormData,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
body.append("crypto_version", cryptoApi.getVersion());
|
body.append("crypto_version", cryptoApi.getVersion());
|
||||||
@@ -82,7 +82,7 @@ async function collectCryptoInfo(
|
|||||||
*/
|
*/
|
||||||
async function collectRecoveryInfo(
|
async function collectRecoveryInfo(
|
||||||
client: MatrixClient,
|
client: MatrixClient,
|
||||||
cryptoApi: Crypto.CryptoApi,
|
cryptoApi: CryptoApi,
|
||||||
body: FormData,
|
body: FormData,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const secretStorage = client.secretStorage;
|
const secretStorage = client.secretStorage;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
import { isFailure } from "./utils/fetch";
|
||||||
|
|
||||||
type SoundDefinition = { mp3?: string; ogg: string };
|
type SoundDefinition = { mp3?: string; ogg: string };
|
||||||
|
|
||||||
export type PrefetchedSounds<S extends string> = Promise<
|
export type PrefetchedSounds<S extends string> = Promise<
|
||||||
@@ -49,7 +51,7 @@ export async function prefetchSounds<S extends string>(
|
|||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
preferredFormat === "ogg" ? ogg : (mp3 ?? ogg),
|
preferredFormat === "ogg" ? ogg : (mp3 ?? ogg),
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (isFailure(response)) {
|
||||||
// If the sound doesn't load, it's not the end of the world. We won't play
|
// If the sound doesn't load, it's not the end of the world. We won't play
|
||||||
// the sound when requested, but it's better than failing the whole application.
|
// the sound when requested, but it's better than failing the whole application.
|
||||||
logger.warn(`Could not load sound ${name}, response was not okay`);
|
logger.warn(`Could not load sound ${name}, response was not okay`);
|
||||||
|
|||||||
@@ -496,6 +496,10 @@ export class CallViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
return displaynameMap;
|
return displaynameMap;
|
||||||
}),
|
}),
|
||||||
|
// It turns out that doing the disambiguation above is rather expensive on Safari (10x slower
|
||||||
|
// than on Chrome/Firefox). This means it is important that we share() the result so that we
|
||||||
|
// don't do this work more times than we need to. This is achieve through the state() operator:
|
||||||
|
this.scope.state(),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
40
src/utils/displayname-integration.test.ts
Normal file
40
src/utils/displayname-integration.test.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { afterEach, beforeAll, describe, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
|
import { shouldDisambiguate } from "./displayname";
|
||||||
|
import { alice } from "./test-fixtures";
|
||||||
|
import { mockMatrixRoom } from "./test";
|
||||||
|
|
||||||
|
// Ideally these tests would be in ./displayname.test.ts but I can't figure out how to
|
||||||
|
// just spy on the removeHiddenChars() function without impacting the other tests.
|
||||||
|
// So, these tests are in this separate test file.
|
||||||
|
vi.mock("matrix-js-sdk/src/utils");
|
||||||
|
|
||||||
|
describe("shouldDisambiguate", () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||||
|
let jsUtils: typeof import("matrix-js-sdk/src/utils");
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jsUtils = await import("matrix-js-sdk/src/utils");
|
||||||
|
vi.spyOn(jsUtils, "removeHiddenChars").mockImplementation((str) => str);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should only call removeHiddenChars once for a single displayname", () => {
|
||||||
|
const room = mockMatrixRoom({});
|
||||||
|
shouldDisambiguate(alice, [], room);
|
||||||
|
expect(jsUtils.removeHiddenChars).toHaveBeenCalledTimes(1);
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
shouldDisambiguate(alice, [], room);
|
||||||
|
}
|
||||||
|
expect(jsUtils.removeHiddenChars).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,12 +7,36 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
removeDirectionOverrideChars,
|
removeDirectionOverrideChars,
|
||||||
removeHiddenChars,
|
removeHiddenChars as removeHiddenCharsUncached,
|
||||||
} from "matrix-js-sdk/src/utils";
|
} from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||||
import type { CallMembership } from "matrix-js-sdk/src/matrixrtc";
|
import type { CallMembership } from "matrix-js-sdk/src/matrixrtc";
|
||||||
|
|
||||||
|
// Calling removeHiddenChars() can be slow on Safari, so we cache the results.
|
||||||
|
// To illustrate a simple benchmark:
|
||||||
|
// Chrome: 10,000 calls took 2.599ms
|
||||||
|
// Safari: 10,000 calls took 242ms
|
||||||
|
// See: https://github.com/element-hq/element-call/issues/3065
|
||||||
|
|
||||||
|
const removeHiddenCharsCache = new Map<string, string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls removeHiddenCharsUncached and caches the result
|
||||||
|
*/
|
||||||
|
function removeHiddenChars(str: string): string {
|
||||||
|
if (removeHiddenCharsCache.has(str)) {
|
||||||
|
return removeHiddenCharsCache.get(str)!;
|
||||||
|
}
|
||||||
|
const result = removeHiddenCharsUncached(str);
|
||||||
|
// this is naive but should be good enough for our purposes
|
||||||
|
if (removeHiddenCharsCache.size > 500) {
|
||||||
|
removeHiddenCharsCache.clear();
|
||||||
|
}
|
||||||
|
removeHiddenCharsCache.set(str, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Borrowed from https://github.com/matrix-org/matrix-js-sdk/blob/f10deb5ef2e8f061ff005af0476034382ea128ca/src/models/room-member.ts#L409
|
// Borrowed from https://github.com/matrix-org/matrix-js-sdk/blob/f10deb5ef2e8f061ff005af0476034382ea128ca/src/models/room-member.ts#L409
|
||||||
export function shouldDisambiguate(
|
export function shouldDisambiguate(
|
||||||
member: { rawDisplayName?: string; userId: string },
|
member: { rawDisplayName?: string; userId: string },
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export enum ErrorCode {
|
|||||||
*/
|
*/
|
||||||
MISSING_MATRIX_RTC_FOCUS = "MISSING_MATRIX_RTC_FOCUS",
|
MISSING_MATRIX_RTC_FOCUS = "MISSING_MATRIX_RTC_FOCUS",
|
||||||
CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
|
CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
|
||||||
|
MEMBERSHIP_MANAGER_UNRECOVERABLE = "MEMBERSHIP_MANAGER_UNRECOVERABLE",
|
||||||
/** LiveKit indicates that the server has hit its track limits */
|
/** LiveKit indicates that the server has hit its track limits */
|
||||||
INSUFFICIENT_CAPACITY_ERROR = "INSUFFICIENT_CAPACITY_ERROR",
|
INSUFFICIENT_CAPACITY_ERROR = "INSUFFICIENT_CAPACITY_ERROR",
|
||||||
E2EE_NOT_SUPPORTED = "E2EE_NOT_SUPPORTED",
|
E2EE_NOT_SUPPORTED = "E2EE_NOT_SUPPORTED",
|
||||||
@@ -24,6 +25,7 @@ export enum ErrorCategory {
|
|||||||
/** Calling is not supported, server misconfigured (JWT service missing, no MSC support ...)*/
|
/** Calling is not supported, server misconfigured (JWT service missing, no MSC support ...)*/
|
||||||
CONFIGURATION_ISSUE = "CONFIGURATION_ISSUE",
|
CONFIGURATION_ISSUE = "CONFIGURATION_ISSUE",
|
||||||
NETWORK_CONNECTIVITY = "NETWORK_CONNECTIVITY",
|
NETWORK_CONNECTIVITY = "NETWORK_CONNECTIVITY",
|
||||||
|
RTC_SESSION_FAILURE = "RTC_SESSION_FAILURE",
|
||||||
CLIENT_CONFIGURATION = "CLIENT_CONFIGURATION",
|
CLIENT_CONFIGURATION = "CLIENT_CONFIGURATION",
|
||||||
UNKNOWN = "UNKNOWN",
|
UNKNOWN = "UNKNOWN",
|
||||||
// SYSTEM_FAILURE / FEDERATION_FAILURE ..
|
// SYSTEM_FAILURE / FEDERATION_FAILURE ..
|
||||||
@@ -82,6 +84,11 @@ export class ConnectionLostError extends ElementCallError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RTCSessionError extends ElementCallError {
|
||||||
|
public constructor(code: ErrorCode, message: string) {
|
||||||
|
super("RTCSession Error", code, ErrorCategory.RTC_SESSION_FAILURE, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
export class E2EENotSupportedError extends ElementCallError {
|
export class E2EENotSupportedError extends ElementCallError {
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super(
|
super(
|
||||||
|
|||||||
30
src/utils/fetch.test.ts
Normal file
30
src/utils/fetch.test.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, describe, it } from "vitest";
|
||||||
|
|
||||||
|
import { isFailure } from "./fetch";
|
||||||
|
|
||||||
|
describe("isFailure", () => {
|
||||||
|
it("returns false for a successful response", () => {
|
||||||
|
expect(isFailure({ ok: true, url: "https://foo.com" } as Response)).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns true for a failed response", () => {
|
||||||
|
expect(isFailure({ ok: false, url: "https://foo.com" } as Response)).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false for a file:// URL with status 0", () => {
|
||||||
|
expect(
|
||||||
|
isFailure({ ok: false, url: "file://foo", status: 0 } as Response),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
25
src/utils/fetch.ts
Normal file
25
src/utils/fetch.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a fetch response is a failure in a way that works with file:// URLs
|
||||||
|
* @param response the response to check
|
||||||
|
* @returns true if the response is a failure, false otherwise
|
||||||
|
*/
|
||||||
|
export function isFailure(response: Response): boolean {
|
||||||
|
// if response says it's okay, then it's not a failure
|
||||||
|
if (response.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch will return status === 0 for a success on a file:// URL, so we special case it
|
||||||
|
if (response.url.startsWith("file:") && response.status === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -71,7 +71,7 @@ async function waitForSync(client: MatrixClient): Promise<void> {
|
|||||||
* otherwise rust crypto will throw since it is not ready to initialize a new session.
|
* otherwise rust crypto will throw since it is not ready to initialize a new session.
|
||||||
* If another client is running make sure `.logout()` is called before executing this function.
|
* If another client is running make sure `.logout()` is called before executing this function.
|
||||||
* @param clientOptions Object of options passed through to the client
|
* @param clientOptions Object of options passed through to the client
|
||||||
* @param restore If the rust crypto should be reset before the cient initialization or
|
* @param restore If the rust crypto should be reset before the client initialization or
|
||||||
* if the initialization should try to restore the crypto state from the indexDB.
|
* if the initialization should try to restore the crypto state from the indexDB.
|
||||||
* @returns The MatrixClient instance
|
* @returns The MatrixClient instance
|
||||||
*/
|
*/
|
||||||
@@ -162,7 +162,6 @@ export async function initClient(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
client.setGlobalErrorOnUnknownDevices(false);
|
|
||||||
// Once startClient is called, syncs are run asynchronously.
|
// Once startClient is called, syncs are run asynchronously.
|
||||||
// Also, sync completion is communicated only via events.
|
// Also, sync completion is communicated only via events.
|
||||||
// So, apply the event listener *before* starting the client.
|
// So, apply the event listener *before* starting the client.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"noEmitOnError": true,
|
"noEmitOnError": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
|||||||
31
vite-embedded.config.js
Normal file
31
vite-embedded.config.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { defineConfig, mergeConfig } from "vite";
|
||||||
|
import fullConfig from "./vite.config";
|
||||||
|
import generateFile from "vite-plugin-generate-file";
|
||||||
|
|
||||||
|
const base = "./";
|
||||||
|
|
||||||
|
// Config for embedded deployments (possibly hosted under a non-root path)
|
||||||
|
export default defineConfig((env) =>
|
||||||
|
mergeConfig(
|
||||||
|
fullConfig({ ...env, packageType: "embedded" }),
|
||||||
|
defineConfig({
|
||||||
|
base, // Use relative URLs to allow the app to be hosted under any path
|
||||||
|
publicDir: false, // Don't serve the public directory which only contains the favicon
|
||||||
|
plugins: [
|
||||||
|
generateFile([
|
||||||
|
{
|
||||||
|
type: "json",
|
||||||
|
output: "./config.json",
|
||||||
|
data: {
|
||||||
|
matrix_rtc_session: {
|
||||||
|
key_rotation_on_leave_delay: 15000,
|
||||||
|
membership_keep_alive_period: 5000,
|
||||||
|
membership_server_side_expiry_timeout: 15000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
@@ -7,16 +7,15 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import svgrPlugin from "vite-plugin-svgr";
|
import svgrPlugin from "vite-plugin-svgr";
|
||||||
import htmlTemplate from "vite-plugin-html-template";
|
import { createHtmlPlugin } from "vite-plugin-html";
|
||||||
import { codecovVitePlugin } from "@codecov/vite-plugin";
|
import { codecovVitePlugin } from "@codecov/vite-plugin";
|
||||||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import basicSsl from "@vitejs/plugin-basic-ssl";
|
import basicSsl from "@vitejs/plugin-basic-ssl";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode, packageType }) => {
|
||||||
const env = loadEnv(mode, process.cwd());
|
const env = loadEnv(mode, process.cwd());
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
react(),
|
react(),
|
||||||
basicSsl(),
|
basicSsl(),
|
||||||
@@ -27,9 +26,14 @@ export default defineConfig(({ mode }) => {
|
|||||||
ref: true,
|
ref: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
htmlTemplate.default({
|
|
||||||
data: {
|
createHtmlPlugin({
|
||||||
title: env.VITE_PRODUCT_NAME || "Element Call",
|
entry: "src/main.tsx",
|
||||||
|
inject: {
|
||||||
|
data: {
|
||||||
|
brand: env.VITE_PRODUCT_NAME || "Element Call",
|
||||||
|
packageType: packageType ?? "full",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
369
yarn.lock
369
yarn.lock
@@ -1737,6 +1737,14 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
|
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
|
||||||
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
|
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
|
||||||
|
|
||||||
|
"@jridgewell/source-map@^0.3.3":
|
||||||
|
version "0.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a"
|
||||||
|
integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/gen-mapping" "^0.3.5"
|
||||||
|
"@jridgewell/trace-mapping" "^0.3.25"
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0":
|
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0":
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
|
||||||
@@ -1787,10 +1795,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@bufbuild/protobuf" "^1.10.0"
|
"@bufbuild/protobuf" "^1.10.0"
|
||||||
|
|
||||||
"@matrix-org/matrix-sdk-crypto-wasm@^12.1.0":
|
"@matrix-org/matrix-sdk-crypto-wasm@^14.0.1":
|
||||||
version "12.1.0"
|
version "14.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-12.1.0.tgz#2aef64eab2d30c0a1ace9c0fe876f53aa2949f14"
|
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-14.0.1.tgz#e258ef84bcc7889f0e7eb3a7dbecf0830a6dd606"
|
||||||
integrity sha512-NhJFu/8FOGjnW7mDssRUzaMSwXrYOcCqgAjZyAw9KQ9unNADKEi7KoIKe7GtrG2PWtm36y2bUf+hB8vhSY6Wdw==
|
integrity sha512-CgLpHs6nTw5pjSsMBi9xbQnBXf2l8YhImQP9cv8nbGSCYdYjFI0FilMXffzjWV5HThpNHri/3pF20ahZtuS3VA==
|
||||||
|
|
||||||
"@matrix-org/olm@3.2.15":
|
"@matrix-org/olm@3.2.15":
|
||||||
version "3.2.15"
|
version "3.2.15"
|
||||||
@@ -2589,6 +2597,14 @@
|
|||||||
"@react-spring/shared" "~9.7.5"
|
"@react-spring/shared" "~9.7.5"
|
||||||
"@react-spring/types" "~9.7.5"
|
"@react-spring/types" "~9.7.5"
|
||||||
|
|
||||||
|
"@rollup/pluginutils@^4.2.0":
|
||||||
|
version "4.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||||
|
integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
|
||||||
|
dependencies:
|
||||||
|
estree-walker "^2.0.1"
|
||||||
|
picomatch "^2.2.2"
|
||||||
|
|
||||||
"@rollup/pluginutils@^5.1.3":
|
"@rollup/pluginutils@^5.1.3":
|
||||||
version "5.1.3"
|
version "5.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.3.tgz#3001bf1a03f3ad24457591f2c259c8e514e0dbdf"
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.3.tgz#3001bf1a03f3ad24457591f2c259c8e514e0dbdf"
|
||||||
@@ -3444,7 +3460,7 @@ acorn-jsx@^5.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||||
|
|
||||||
acorn@^8.14.0, acorn@^8.8.1:
|
acorn@^8.14.0, acorn@^8.8.1, acorn@^8.8.2:
|
||||||
version "8.14.0"
|
version "8.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
||||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
||||||
@@ -3685,6 +3701,11 @@ ast-types-flow@^0.0.8:
|
|||||||
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
|
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
|
||||||
integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==
|
integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==
|
||||||
|
|
||||||
|
async@^3.2.3:
|
||||||
|
version "3.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
|
||||||
|
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
@@ -3880,6 +3901,11 @@ bs58@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
base-x "^5.0.0"
|
base-x "^5.0.0"
|
||||||
|
|
||||||
|
buffer-from@^1.0.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||||
|
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||||
|
|
||||||
buffer@^6.0.3:
|
buffer@^6.0.3:
|
||||||
version "6.0.3"
|
version "6.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||||
@@ -3934,6 +3960,14 @@ callsites@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||||
|
|
||||||
|
camel-case@^4.1.2:
|
||||||
|
version "4.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
|
||||||
|
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
|
||||||
|
dependencies:
|
||||||
|
pascal-case "^3.1.2"
|
||||||
|
tslib "^2.0.3"
|
||||||
|
|
||||||
camelcase@^5.0.0:
|
camelcase@^5.0.0:
|
||||||
version "5.3.1"
|
version "5.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||||
@@ -3965,7 +3999,7 @@ chai@^5.2.0:
|
|||||||
loupe "^3.1.0"
|
loupe "^3.1.0"
|
||||||
pathval "^2.0.0"
|
pathval "^2.0.0"
|
||||||
|
|
||||||
chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@~4.1.0:
|
chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@~4.1.0:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||||
@@ -4061,6 +4095,13 @@ classnames@^2.3.1, classnames@^2.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||||
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
||||||
|
|
||||||
|
clean-css@^5.2.2:
|
||||||
|
version "5.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd"
|
||||||
|
integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==
|
||||||
|
dependencies:
|
||||||
|
source-map "~0.6.0"
|
||||||
|
|
||||||
clean-regexp@^1.0.0:
|
clean-regexp@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7"
|
resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7"
|
||||||
@@ -4135,6 +4176,11 @@ color-name@~1.1.4:
|
|||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
|
colorette@^2.0.16:
|
||||||
|
version "2.0.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
|
||||||
|
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
|
||||||
|
|
||||||
colors@^1.4.0:
|
colors@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
||||||
@@ -4152,11 +4198,21 @@ commander@^12.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
|
||||||
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
|
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
|
||||||
|
|
||||||
|
commander@^2.20.0:
|
||||||
|
version "2.20.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
|
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||||
|
|
||||||
commander@^4.1.1:
|
commander@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||||
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||||
|
|
||||||
|
commander@^8.3.0:
|
||||||
|
version "8.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||||
|
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||||
|
|
||||||
common-tags@^1.8.0:
|
common-tags@^1.8.0:
|
||||||
version "1.8.2"
|
version "1.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
|
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
|
||||||
@@ -4167,6 +4223,16 @@ concat-map@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||||
|
|
||||||
|
connect-history-api-fallback@^1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
||||||
|
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
||||||
|
|
||||||
|
consola@^2.15.3:
|
||||||
|
version "2.15.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550"
|
||||||
|
integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==
|
||||||
|
|
||||||
content-type@^1.0.4:
|
content-type@^1.0.4:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
||||||
@@ -4257,6 +4323,17 @@ css-prefers-color-scheme@^10.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz#ba001b99b8105b8896ca26fc38309ddb2278bd3c"
|
resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz#ba001b99b8105b8896ca26fc38309ddb2278bd3c"
|
||||||
integrity sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==
|
integrity sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==
|
||||||
|
|
||||||
|
css-select@^4.2.1:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
|
||||||
|
integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
|
||||||
|
dependencies:
|
||||||
|
boolbase "^1.0.0"
|
||||||
|
css-what "^6.0.1"
|
||||||
|
domhandler "^4.3.1"
|
||||||
|
domutils "^2.8.0"
|
||||||
|
nth-check "^2.0.1"
|
||||||
|
|
||||||
css-select@^5.1.0:
|
css-select@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
|
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
|
||||||
@@ -4268,7 +4345,7 @@ css-select@^5.1.0:
|
|||||||
domutils "^3.0.1"
|
domutils "^3.0.1"
|
||||||
nth-check "^2.0.1"
|
nth-check "^2.0.1"
|
||||||
|
|
||||||
css-what@^6.1.0:
|
css-what@^6.0.1, css-what@^6.1.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
|
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
|
||||||
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
|
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
|
||||||
@@ -4485,6 +4562,15 @@ dom-accessibility-api@^0.6.3:
|
|||||||
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8"
|
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8"
|
||||||
integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==
|
integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==
|
||||||
|
|
||||||
|
dom-serializer@^1.0.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
|
||||||
|
integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.0.1"
|
||||||
|
domhandler "^4.2.0"
|
||||||
|
entities "^2.0.0"
|
||||||
|
|
||||||
dom-serializer@^2.0.0:
|
dom-serializer@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
|
||||||
@@ -4494,11 +4580,18 @@ dom-serializer@^2.0.0:
|
|||||||
domhandler "^5.0.2"
|
domhandler "^5.0.2"
|
||||||
entities "^4.2.0"
|
entities "^4.2.0"
|
||||||
|
|
||||||
domelementtype@^2.3.0:
|
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
|
||||||
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
|
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
|
||||||
|
|
||||||
|
domhandler@^4.2.0, domhandler@^4.3.1:
|
||||||
|
version "4.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
|
||||||
|
integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
domhandler@^5.0.2, domhandler@^5.0.3:
|
domhandler@^5.0.2, domhandler@^5.0.3:
|
||||||
version "5.0.3"
|
version "5.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
|
||||||
@@ -4506,6 +4599,15 @@ domhandler@^5.0.2, domhandler@^5.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "^2.3.0"
|
domelementtype "^2.3.0"
|
||||||
|
|
||||||
|
domutils@^2.8.0:
|
||||||
|
version "2.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||||
|
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
|
||||||
|
dependencies:
|
||||||
|
dom-serializer "^1.0.1"
|
||||||
|
domelementtype "^2.2.0"
|
||||||
|
domhandler "^4.2.0"
|
||||||
|
|
||||||
domutils@^3.0.1, domutils@^3.1.0:
|
domutils@^3.0.1, domutils@^3.1.0:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
|
||||||
@@ -4523,7 +4625,12 @@ dot-case@^3.0.4:
|
|||||||
no-case "^3.0.4"
|
no-case "^3.0.4"
|
||||||
tslib "^2.0.3"
|
tslib "^2.0.3"
|
||||||
|
|
||||||
dotenv@^16.3.1:
|
dotenv-expand@^8.0.2:
|
||||||
|
version "8.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e"
|
||||||
|
integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==
|
||||||
|
|
||||||
|
dotenv@^16.0.0, dotenv@^16.3.1:
|
||||||
version "16.4.7"
|
version "16.4.7"
|
||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26"
|
||||||
integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
|
integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
|
||||||
@@ -4551,6 +4658,13 @@ easy-table@1.2.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
wcwidth "^1.0.1"
|
wcwidth "^1.0.1"
|
||||||
|
|
||||||
|
ejs@^3.1.6, ejs@^3.1.9:
|
||||||
|
version "3.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
|
||||||
|
integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
|
||||||
|
dependencies:
|
||||||
|
jake "^10.8.5"
|
||||||
|
|
||||||
electron-to-chromium@^1.5.73:
|
electron-to-chromium@^1.5.73:
|
||||||
version "1.5.109"
|
version "1.5.109"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.109.tgz#905a573d2b4cbb31412a2de6267fb22cf45e097e"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.109.tgz#905a573d2b4cbb31412a2de6267fb22cf45e097e"
|
||||||
@@ -4587,6 +4701,11 @@ ensure-posix-path@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce"
|
resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce"
|
||||||
integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==
|
integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==
|
||||||
|
|
||||||
|
entities@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
|
||||||
|
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
|
||||||
|
|
||||||
entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
|
entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
||||||
@@ -5046,7 +5165,7 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||||
|
|
||||||
estree-walker@^2.0.2:
|
estree-walker@^2.0.1, estree-walker@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||||
@@ -5083,6 +5202,17 @@ fast-fifo@^1.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
|
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
|
||||||
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
|
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
|
||||||
|
|
||||||
|
fast-glob@^3.2.11, fast-glob@^3.3.2, fast-glob@^3.3.3:
|
||||||
|
version "3.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
|
||||||
|
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
|
||||||
|
dependencies:
|
||||||
|
"@nodelib/fs.stat" "^2.0.2"
|
||||||
|
"@nodelib/fs.walk" "^1.2.3"
|
||||||
|
glob-parent "^5.1.2"
|
||||||
|
merge2 "^1.3.0"
|
||||||
|
micromatch "^4.0.8"
|
||||||
|
|
||||||
fast-glob@^3.2.9:
|
fast-glob@^3.2.9:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
||||||
@@ -5094,17 +5224,6 @@ fast-glob@^3.2.9:
|
|||||||
merge2 "^1.3.0"
|
merge2 "^1.3.0"
|
||||||
micromatch "^4.0.4"
|
micromatch "^4.0.4"
|
||||||
|
|
||||||
fast-glob@^3.3.2, fast-glob@^3.3.3:
|
|
||||||
version "3.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
|
|
||||||
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
|
|
||||||
dependencies:
|
|
||||||
"@nodelib/fs.stat" "^2.0.2"
|
|
||||||
"@nodelib/fs.walk" "^1.2.3"
|
|
||||||
glob-parent "^5.1.2"
|
|
||||||
merge2 "^1.3.0"
|
|
||||||
micromatch "^4.0.8"
|
|
||||||
|
|
||||||
fast-json-stable-stringify@^2.0.0:
|
fast-json-stable-stringify@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||||
@@ -5134,6 +5253,13 @@ file-entry-cache@^6.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flat-cache "^3.0.4"
|
flat-cache "^3.0.4"
|
||||||
|
|
||||||
|
filelist@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
|
||||||
|
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
|
||||||
|
dependencies:
|
||||||
|
minimatch "^5.0.1"
|
||||||
|
|
||||||
fill-range@^7.1.1:
|
fill-range@^7.1.1:
|
||||||
version "7.1.1"
|
version "7.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||||
@@ -5200,6 +5326,15 @@ fraction.js@^4.3.7:
|
|||||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
||||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||||
|
|
||||||
|
fs-extra@^10.0.1:
|
||||||
|
version "10.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||||
|
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.2.0"
|
||||||
|
jsonfile "^6.0.1"
|
||||||
|
universalify "^2.0.0"
|
||||||
|
|
||||||
fs-extra@^11.2.0:
|
fs-extra@^11.2.0:
|
||||||
version "11.3.0"
|
version "11.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
|
||||||
@@ -5368,7 +5503,7 @@ glob@^10.4.1:
|
|||||||
package-json-from-dist "^1.0.0"
|
package-json-from-dist "^1.0.0"
|
||||||
path-scurry "^1.11.1"
|
path-scurry "^1.11.1"
|
||||||
|
|
||||||
glob@^7.0.0, glob@^7.1.3, glob@~7.2.0:
|
glob@^7.1.3, glob@~7.2.0:
|
||||||
version "7.2.3"
|
version "7.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||||
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
||||||
@@ -5502,6 +5637,11 @@ hasown@^2.0.0, hasown@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.2"
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
|
he@1.2.0, he@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
heimdalljs-logger@^0.1.10, heimdalljs-logger@^0.1.7:
|
heimdalljs-logger@^0.1.10, heimdalljs-logger@^0.1.7:
|
||||||
version "0.1.10"
|
version "0.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/heimdalljs-logger/-/heimdalljs-logger-0.1.10.tgz#90cad58aabb1590a3c7e640ddc6a4cd3a43faaf7"
|
resolved "https://registry.yarnpkg.com/heimdalljs-logger/-/heimdalljs-logger-0.1.10.tgz#90cad58aabb1590a3c7e640ddc6a4cd3a43faaf7"
|
||||||
@@ -5541,6 +5681,19 @@ html-escaper@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||||
|
|
||||||
|
html-minifier-terser@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab"
|
||||||
|
integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==
|
||||||
|
dependencies:
|
||||||
|
camel-case "^4.1.2"
|
||||||
|
clean-css "^5.2.2"
|
||||||
|
commander "^8.3.0"
|
||||||
|
he "^1.2.0"
|
||||||
|
param-case "^3.0.4"
|
||||||
|
relateurl "^0.2.7"
|
||||||
|
terser "^5.10.0"
|
||||||
|
|
||||||
html-parse-stringify@^3.0.1:
|
html-parse-stringify@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||||
@@ -5681,11 +5834,6 @@ internal-slot@^1.1.0:
|
|||||||
hasown "^2.0.2"
|
hasown "^2.0.2"
|
||||||
side-channel "^1.1.0"
|
side-channel "^1.1.0"
|
||||||
|
|
||||||
interpret@^1.0.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
|
||||||
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
|
|
||||||
|
|
||||||
is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
|
is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280"
|
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280"
|
||||||
@@ -5990,6 +6138,16 @@ jackspeak@^3.1.2:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@pkgjs/parseargs" "^0.11.0"
|
"@pkgjs/parseargs" "^0.11.0"
|
||||||
|
|
||||||
|
jake@^10.8.5:
|
||||||
|
version "10.9.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
|
||||||
|
integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==
|
||||||
|
dependencies:
|
||||||
|
async "^3.2.3"
|
||||||
|
chalk "^4.0.2"
|
||||||
|
filelist "^1.0.4"
|
||||||
|
minimatch "^3.1.2"
|
||||||
|
|
||||||
jiti@^2.4.2:
|
jiti@^2.4.2:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560"
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560"
|
||||||
@@ -6334,13 +6492,12 @@ matrix-events-sdk@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||||
|
|
||||||
matrix-js-sdk@^36.1.0:
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#9f9be701e7a8e85b5f749d0104138af36b0b82bd":
|
||||||
version "36.1.0"
|
version "37.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-36.1.0.tgz#3685a85c0c1adf4e2c3622bce76c11430963f23d"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/9f9be701e7a8e85b5f749d0104138af36b0b82bd"
|
||||||
integrity sha512-KNPswMSAGKDxBybJedxRpWadaRes9paxmjTCUsQT8t1Jg3ZENraAt6ynIaxh6PxazAH9D5ly6EYKHaLMLbZ1Dg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
"@matrix-org/matrix-sdk-crypto-wasm" "^12.1.0"
|
"@matrix-org/matrix-sdk-crypto-wasm" "^14.0.1"
|
||||||
"@matrix-org/olm" "3.2.15"
|
"@matrix-org/olm" "3.2.15"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
bs58 "^6.0.0"
|
bs58 "^6.0.0"
|
||||||
@@ -6381,7 +6538,7 @@ mime-db@1.52.0:
|
|||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||||
|
|
||||||
mime-types@^2.1.12:
|
mime-types@^2.1.12, mime-types@^2.1.35:
|
||||||
version "2.1.35"
|
version "2.1.35"
|
||||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||||
@@ -6400,6 +6557,13 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatc
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
|
minimatch@^5.0.1:
|
||||||
|
version "5.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||||
|
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimatch@^8.0.2:
|
minimatch@^8.0.2:
|
||||||
version "8.0.4"
|
version "8.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229"
|
||||||
@@ -6474,6 +6638,14 @@ node-fetch@^2.6.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
whatwg-url "^5.0.0"
|
||||||
|
|
||||||
|
node-html-parser@^5.3.3:
|
||||||
|
version "5.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.4.2.tgz#93e004038c17af80226c942336990a0eaed8136a"
|
||||||
|
integrity sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==
|
||||||
|
dependencies:
|
||||||
|
css-select "^4.2.1"
|
||||||
|
he "1.2.0"
|
||||||
|
|
||||||
node-releases@^2.0.19:
|
node-releases@^2.0.19:
|
||||||
version "2.0.19"
|
version "2.0.19"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
|
||||||
@@ -6695,6 +6867,14 @@ pako@^2.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
|
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
|
||||||
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
|
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
|
||||||
|
|
||||||
|
param-case@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
|
||||||
|
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
|
||||||
|
dependencies:
|
||||||
|
dot-case "^3.0.4"
|
||||||
|
tslib "^2.0.3"
|
||||||
|
|
||||||
parent-module@^1.0.0:
|
parent-module@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||||
@@ -6739,6 +6919,14 @@ parse5@^7.0.0, parse5@^7.1.2, parse5@^7.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
entities "^4.5.0"
|
entities "^4.5.0"
|
||||||
|
|
||||||
|
pascal-case@^3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
|
||||||
|
integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
|
||||||
|
dependencies:
|
||||||
|
no-case "^3.0.4"
|
||||||
|
tslib "^2.0.3"
|
||||||
|
|
||||||
path-exists@^4.0.0:
|
path-exists@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||||
@@ -6777,6 +6965,11 @@ path-type@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||||
|
|
||||||
|
pathe@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.2.0.tgz#30fd7bbe0a0d91f0e60bae621f5d19e9e225c339"
|
||||||
|
integrity sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==
|
||||||
|
|
||||||
pathe@^2.0.3:
|
pathe@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
|
resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
|
||||||
@@ -6792,7 +6985,7 @@ picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0, picocolors@^1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||||
|
|
||||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
@@ -7379,13 +7572,6 @@ readdirp@~3.6.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
rechoir@^0.6.2:
|
|
||||||
version "0.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
|
||||||
integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
|
|
||||||
dependencies:
|
|
||||||
resolve "^1.1.6"
|
|
||||||
|
|
||||||
redent@^3.0.0:
|
redent@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||||
@@ -7485,6 +7671,11 @@ regjsparser@^0.12.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jsesc "~3.0.2"
|
jsesc "~3.0.2"
|
||||||
|
|
||||||
|
relateurl@^0.2.7:
|
||||||
|
version "0.2.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
|
||||||
|
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
|
||||||
|
|
||||||
remove-trailing-separator@^1.1.0:
|
remove-trailing-separator@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||||
@@ -7522,7 +7713,7 @@ resolve-options@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
value-or-function "^4.0.0"
|
value-or-function "^4.0.0"
|
||||||
|
|
||||||
resolve@^1.1.6, resolve@^1.10.0, resolve@^1.22.4:
|
resolve@^1.10.0, resolve@^1.22.4:
|
||||||
version "1.22.8"
|
version "1.22.8"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
|
||||||
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
|
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
|
||||||
@@ -7794,15 +7985,6 @@ shebang-regex@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
shelljs@0.8.4:
|
|
||||||
version "0.8.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
|
|
||||||
integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
|
|
||||||
dependencies:
|
|
||||||
glob "^7.0.0"
|
|
||||||
interpret "^1.0.0"
|
|
||||||
rechoir "^0.6.2"
|
|
||||||
|
|
||||||
side-channel-list@^1.0.0:
|
side-channel-list@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
|
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
|
||||||
@@ -7888,6 +8070,19 @@ sort-keys@^5.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||||
|
|
||||||
|
source-map-support@~0.5.20:
|
||||||
|
version "0.5.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||||
|
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
|
||||||
|
dependencies:
|
||||||
|
buffer-from "^1.0.0"
|
||||||
|
source-map "^0.6.0"
|
||||||
|
|
||||||
|
source-map@^0.6.0, source-map@~0.6.0:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
|
|
||||||
spdx-correct@^3.0.0:
|
spdx-correct@^3.0.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c"
|
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c"
|
||||||
@@ -7946,16 +8141,7 @@ streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bare-events "^2.2.0"
|
bare-events "^2.2.0"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -8055,14 +8241,7 @@ string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@@ -8154,6 +8333,16 @@ teex@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
streamx "^2.12.5"
|
streamx "^2.12.5"
|
||||||
|
|
||||||
|
terser@^5.10.0:
|
||||||
|
version "5.39.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a"
|
||||||
|
integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/source-map" "^0.3.3"
|
||||||
|
acorn "^8.8.2"
|
||||||
|
commander "^2.20.0"
|
||||||
|
source-map-support "~0.5.20"
|
||||||
|
|
||||||
test-exclude@^7.0.1:
|
test-exclude@^7.0.1:
|
||||||
version "7.0.1"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2"
|
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2"
|
||||||
@@ -8637,12 +8826,33 @@ vite-node@3.0.7:
|
|||||||
pathe "^2.0.3"
|
pathe "^2.0.3"
|
||||||
vite "^5.0.0 || ^6.0.0"
|
vite "^5.0.0 || ^6.0.0"
|
||||||
|
|
||||||
vite-plugin-html-template@^1.1.0:
|
vite-plugin-generate-file@^0.2.0:
|
||||||
version "1.2.2"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/vite-plugin-html-template/-/vite-plugin-html-template-1.2.2.tgz#d263c18dcf5f5e54bc74894546fd0ed993191f2f"
|
resolved "https://registry.yarnpkg.com/vite-plugin-generate-file/-/vite-plugin-generate-file-0.2.0.tgz#fe143a80888c7320ffe1dad42925e8b5bf2b606c"
|
||||||
integrity sha512-xS9w453ropnzfYAQYPRorKa6qRKOr2kH09hSKBpUr5aSfOG0Q7KhdKg6YhrqWJ+itEpA5y4awFOfxwwVskKgYw==
|
integrity sha512-mAHqvyFWrqbYNmWmSjhfyxShEgVN9ZPSI/cRove86KEYQIJUZ74XajXpPMwIE1xll9piA5Cmhi8kt/ydQSzZ/Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
shelljs "0.8.4"
|
ejs "^3.1.9"
|
||||||
|
js-yaml "^4.1.0"
|
||||||
|
mime-types "^2.1.35"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
|
vite-plugin-html@^3.2.2:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite-plugin-html/-/vite-plugin-html-3.2.2.tgz#661834fa09015d3fda48ba694dbaa809396f5f7a"
|
||||||
|
integrity sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/pluginutils" "^4.2.0"
|
||||||
|
colorette "^2.0.16"
|
||||||
|
connect-history-api-fallback "^1.6.0"
|
||||||
|
consola "^2.15.3"
|
||||||
|
dotenv "^16.0.0"
|
||||||
|
dotenv-expand "^8.0.2"
|
||||||
|
ejs "^3.1.6"
|
||||||
|
fast-glob "^3.2.11"
|
||||||
|
fs-extra "^10.0.1"
|
||||||
|
html-minifier-terser "^6.1.0"
|
||||||
|
node-html-parser "^5.3.3"
|
||||||
|
pathe "^0.2.0"
|
||||||
|
|
||||||
vite-plugin-svgr@^4.0.0:
|
vite-plugin-svgr@^4.0.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
@@ -8879,7 +9089,7 @@ word-wrap@^1.2.5:
|
|||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@@ -8897,15 +9107,6 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
|||||||
Reference in New Issue
Block a user