Compare commits

..

128 Commits

Author SHA1 Message Date
fkwp
3d52f1695d test livekit-client 2.9.1 2025-03-18 16:11:32 +01:00
fkwp
fd72fc0ccc test livekit-client 2.8.1 2025-03-18 10:06:42 +01:00
fkwp
5c563058a9 test livekit-client 2.9.8 2025-03-18 10:03:15 +01:00
fkwp
5cb1970c41 wip 2025-03-18 09:55:09 +01:00
fkwp
ef2fee708e Merge pull request #3099 from element-hq/fkwp/update_readme
Update readme with new Element Call screenshot
2025-03-17 10:33:23 +01:00
fkwp
ef8e894c0b update Element Call screenshot 2025-03-17 09:55:04 +01:00
fkwp
d01a0de568 fix JSON blob 2025-03-17 09:54:37 +01:00
Valere Fedronic
a204dbe1d4 Merge pull request #3098 from element-hq/valere/playwright_dev_backend_no_ratelimit
Keep rate limits for dev backend, disable only for playwright tests
2025-03-17 09:51:30 +01:00
Valere
bd38a56ccc Keep rate limits for dev backend, disable only for playwright tests 2025-03-17 09:45:43 +01:00
Valere Fedronic
af58161f21 Merge pull request #3097 from element-hq/valere/playwright_initial_setup
Integration Test: Add test for register, login, logout and guest create and join call
2025-03-17 09:43:21 +01:00
Valere Fedronic
ad8ad218f1 Merge pull request #3096 from element-hq/valere/playwright_access_tests
Integration Test: Add test for register, login, logout and guest create and join call
2025-03-17 09:31:44 +01:00
fkwp
c4747dd177 Merge pull request #3026 from element-hq/fkwp/add_rc_delayed_event_mgmt_to_readme
README.md add information about rc_delayed_event_mgmt
2025-03-14 16:18:55 +01:00
Valere Fedronic
39c64afb77 Merge pull request #3091 from element-hq/valere/playwright_initial_setup
First integration tests with playwright
2025-03-14 16:02:46 +01:00
Valere
b9700b9986 code review 2025-03-14 14:50:47 +01:00
Valere
83198c061a Disable rate limiting on dev homeserver to avoid flaky tests 2025-03-14 11:01:25 +01:00
Valere
b5fff42adc Test: Add more integrations test
Testing access, register, login, logout. Create and join as guest
2025-03-14 11:01:19 +01:00
Valere
9dc515f45d Fixup: bad copy in the text 2025-03-13 20:04:26 +01:00
Valere
4c477bc4b1 fix: github action, missing config file for dev env 2025-03-13 20:04:26 +01:00
Valere
45d3a5e332 playwright add ignoreHTTPSErrors option 2025-03-13 20:04:26 +01:00
Valere
4efc32f8e2 fix: local element-call not running 2025-03-13 20:04:26 +01:00
Valere
a7af470549 yml: try docker compose instead of dash compose 2025-03-13 20:04:26 +01:00
Valere
102aaa80e2 typo in playwright.yml timeout 2025-03-13 20:04:26 +01:00
Valere
6eb1a02937 fix: vitest --coverage was running playwright tests 2025-03-13 20:04:26 +01:00
Valere
687dafab3e First integration tests with playwright 2025-03-13 20:04:26 +01:00
Timo
46e7e8e9cd Fix: js-sdk version that uses expected value for isJoined (isJoined will now be false before we left as expected by EC) (#3092) 2025-03-13 19:09:18 +01:00
Hugh Nimmo-Smith
a924c7ffbb Separate widget-only URL parameters in the documentation (#3088) 2025-03-13 10:28:38 +00:00
Valere Fedronic
90a282b46c Merge pull request #3063 from element-hq/valere/fix_add_close_button_in_widget_mode
Error: use a close button instead of go to home when in widget mode
2025-03-13 10:16:26 +01:00
Valere Fedronic
79e294755c Merge pull request #3058 from element-hq/valere/refactor_error_handling
refactor: Centralize group call errors in custom GroupCallErrorBoundary
2025-03-13 10:12:11 +01:00
Hugh Nimmo-Smith
7aac56aac0 Vite embedded build of Element Call (#3077)
* Embedded package build of Element Call

Part of https://github.com/element-hq/element-call/issues/2994

This creates a new "embedded" build (vs "full" build) at the vite level. It will be used by a later PR that actually provides platform specific packages.

Embedded build:

- Uses relative URLs
- Uses relative config.json path and other resource loading
- Has a config.json built in
- Doesn't include the public folder (e.g. favicon)

Out of scope:

- this doesn't attempt to exclude SPA functionality, so technically the build could be used in SPA
- the above means that the crypto-wasm binary is included in the build

* CI artifact name based on type of build

* Update src/config/Config.ts
2025-03-12 17:00:44 +00:00
Hugh Nimmo-Smith
520c0f4925 Fix typing on config: EULA is optional as it has a default (#3074) 2025-03-12 13:52:13 +01:00
Valere
b02ad8871e review: Make widget prop mandatory 2025-03-12 10:27:45 +01:00
Valere
b0a4a954f8 Error: use a close button instead of go to home when in widget mode 2025-03-12 10:12:34 +01:00
Robin
5547e838d3 Merge pull request #3080 from element-hq/robin/pin-matrix-js-sdk
Pin matrix-js-sdk to a specific commit
2025-03-12 05:06:10 -04:00
Valere
612ace137a code review: recoveryActionHandler should not be optional 2025-03-12 09:44:41 +01:00
Robin
3cf0ce3dcb Pin matrix-js-sdk to a specific commit
Whenever we use a Git dependency, we should reference a specific tag or commit rather than a branch name, because Yarn Classic is bad at recognizing when a Git dependency needs to be re-installed due to it resolving to a new version. (It seems to simply use the version from package.json as a cache key.)

This is relevant only for developers who run 'yarn install' on top of a previously up-to-date working copy, not for CI which re-installs the project on every run.
2025-03-11 16:25:03 -04:00
Valere
8584521ade fixup: post rebase fix 2025-03-11 20:19:14 +01:00
Timo
93ae2aed98 New Membership Manager js-sdk develop (#3067)
* fix experimentalDecorators like we do in the js-sdk

* bump-js-sdk

* update to develop branch of js-sdk (new membership manager)
2025-03-11 19:48:02 +01:00
Valere
c0ef6e5e4d fix: Error recover/retry buttons should reset error state 2025-03-11 16:54:19 +01:00
Valere
4c27551014 refactor: Centralize group call errors in custom GroupCallErrorBoundary 2025-03-11 16:54:19 +01:00
ElementRobot
a97c36a335 Merge pull request #3078 from element-hq/actions/localazy-download
Localazy Download
2025-03-11 15:40:53 +00:00
fkwp
1db3e3cbc4 Translations updates 2025-03-11 15:39:33 +00:00
Hugh Nimmo-Smith
1a692b983a Use fetch() in a way that works for file URLs (#3071)
fetch returns a response code of 0 when it successfully loads a `file://` resource.

This means we can't just rely on `response.ok`.

Required for https://github.com/element-hq/element-call/issues/2994
2025-03-11 10:39:51 +01:00
fkwp
f9ced9f10d typo 2025-03-10 20:58:12 +01:00
fkwp
e39fe3bc7f add information about rc_delayed_event_mgmt 2025-03-10 20:55:59 +01:00
fkwp
2885e7e42e Merge pull request #3059 from element-hq/fkwp/doc_update
Refactor README.md
2025-03-10 20:31:09 +01:00
Hugh Nimmo-Smith
88b862b5fd Update README.md 2025-03-10 18:10:25 +00:00
Hugh Nimmo-Smith
30b5bcdfb6 Cache calls to removeHiddenChars() to fix performance bottleneck in Safari (#3066)
* Cache calls to removeHiddenChars() as very slow on Safari

Fixes #3065

* Test

* Split testing for removeHiddenChars
2025-03-10 13:55:03 +00:00
Timo
b635b00b0c Expose setLKLogLevel to window for debugging. (#3064)
* Expose setLKLogLevel to window for debugging.

* fix es lint
2025-03-10 08:59:27 +00:00
Timo
750db09156 Use the New MatrixRTCSession MembershipManager (#3015)
* provide option to use the New MembershipManager

* fix cryptoApi import change

* add error screen

* bump js-sdk

* rename to `setUnrecoverableError` and remove onLeave call because that will be handled by an effect.

* this was doing nothing (it is a fragment back when there was no deprecated `rtcSession.room`)

* rename to error

* Update src/utils/errors.ts

Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>

* Update src/utils/errors.ts

Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>

* review

* bump js-sdk

* expose lk log level changing in `window`

* bump js-sdk
 - always log "Missing own membership: force re-join"
 - also check insertions queue

* change lk log level to warn

* Bump js-sdk

* Bump js-sdk

* .

* Bump js-sdk

* show user count based on meberships not users.

Signed-off-by: Timo K <toger5@hotmail.de>

* bump js-sdk

* rename setting name

* remove unused import

* js sdk bump

* remove `window.setLKLogLevel`

* bump js sdk with reverted incompatible change

* bump js-sdk with one less merge

---------

Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>
Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
2025-03-07 16:27:04 +00:00
Timo
c9f2a1c943 Reduce redundant calculations of display name map (#3062)
* Use share() on fromEvent() so that we multiplex subscribers onto the event emitter

* .

* .

* Comment

* Comment

---------

Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
2025-03-07 10:18:28 +00:00
fkwp
1958b658c3 make colours dark theme compatible 2025-03-05 21:53:41 +01:00
fkwp
542d36f893 prettier 2025-03-05 21:43:09 +01:00
fkwp
ad10b6c2fa add self-hosting section 2025-03-05 21:20:08 +01:00
fkwp
c29eeff450 indentation 2025-03-05 21:19:56 +01:00
fkwp
af57834c87 format Copyright & License 2025-03-05 21:19:12 +01:00
fkwp
e87e1a8f25 Update documentation 2025-03-05 21:18:17 +01:00
fkwp
5583f621ba Update Development Section 2025-03-05 21:17:54 +01:00
fkwp
1df5872215 update Translation section 2025-03-05 21:16:49 +01:00
fkwp
32a3c9ea5d Add section about MatrixRTC Backend Discovery and Selection 2025-03-05 21:15:57 +01:00
fkwp
93fb299770 add self-hosting section 2025-03-05 21:13:55 +01:00
fkwp
f79d7cfaea Add deployment options section 2025-03-05 21:12:23 +01:00
fkwp
1c233b88f0 add feature section 2025-03-05 21:10:45 +01:00
fkwp
52b40d3116 add Overview section 2025-03-05 19:35:02 +01:00
fkwp
f7379b5683 Add short description of Element Call 2025-03-05 19:34:39 +01:00
fkwp
2c21423f1a add License shield 2025-03-05 19:33:39 +01:00
Robin
b5f5ab329a Merge pull request #3051 from element-hq/robin/return-to-lobby
Respect the returnToLobby flag
2025-03-05 12:17:53 -05:00
Robin
308d42980f Merge pull request #3054 from element-hq/robin/og-brand
Use the configured brand name in OpenGraph tags
2025-03-05 12:09:25 -05:00
Robin
0ca2627bd4 Merge pull request #3055 from element-hq/robin/join-action
Send a 'join' action when entering the call
2025-03-05 12:09:16 -05:00
Robin
359812d8b1 Explain why returnToLobby is false in SPA 2025-03-05 10:40:37 -05:00
Robin
5b1ea4501d Send a 'join' action when entering the call
Following a75952cf77, this is one more upgrade to the widget communication that I'd like to make within this release cycle.

The motivating issue is https://github.com/element-hq/element-web/issues/29429. Fundamentally, without a 'join' action, the only info Element Web can use to determine whether it's joined the call is whether a MatrixRTC membership exists. But membership state events can inaccurately represent the client's actual state (whether because delayed events aren't supported, or because the delayed event hasn't timed out yet), so I suggest we send a 'join' action here just as we do in the Element Web Jitsi wrapper (e9a3625bd6/src/vector/jitsi/index.ts (L503)) to let Element Web tap directly into the widget's local state. (This will need additional Element Web changes, but is certainly backwards compatible.)
2025-03-05 09:25:52 -05:00
Robin
ec73e7fa86 Use the configured brand name in OpenGraph tags
Overlooked some of these when reviewing https://github.com/element-hq/element-call/pull/3006.
2025-03-05 09:18:31 -05:00
fkwp
c579904f29 added rule for ignoring draw.io backup files 2025-03-05 13:37:04 +01:00
Robin
28c45c6107 Avoid closing the widget in returnToLobby mode
If returnToLobby is enabled then we obviously want to keep the widget open once the user leaves the call.
2025-03-05 07:32:25 -05:00
Robin
b5f5edba09 Fix the control flow of GroupCallView render function
2bb5b020e6 refactored the end of the GroupCallView render function to not use any early returns, and clumsily failed to account for the fall-through case that makes returnToLobby work (as opposed to sitting on a blank screen).
2025-03-05 07:29:56 -05:00
Robin
771397389c Prevent PRs with the X-Blocked label from being merged (#3041)
Copied from matrix-js-sdk (https://github.com/matrix-org/matrix-js-sdk/blob/develop/.github/workflows/pull_request.yaml#L31)
2025-03-05 10:44:10 +00:00
Timo
939eac5a66 change lk log level to warn (#3049) 2025-03-04 19:37:51 +01:00
Robin
e783339557 Merge pull request #3035 from element-hq/renovate/all-minor-patch
Update all non-major dependencies
2025-03-04 00:14:56 -05:00
Robin
36203c2017 Work around LiveKit's overly constrained dependencies
We need compatible versions of RxJS in our dependencies and LiveKit's dependencies, but LiveKit has pinned it to a very specific version which is now holding us back from updating. See https://github.com/livekit/components-js/issues/1101 for a request for a proper solution.
2025-03-04 00:13:00 -05:00
Robin
db9f255a4a Update formatting 2025-03-03 23:41:29 -05:00
Robin
6d6852d5b0 Merge pull request #3006 from Harshal-Taori-11/livekit
Added OpenGraph and Twitter meta tags for better link previews
2025-03-03 23:37:10 -05:00
Valere Fedronic
f80e9452ec Merge pull request #3037 from element-hq/valere/fix_i18n_test_not_configured
Fix: i18n not properly configured in vitest
2025-03-03 17:17:53 +01:00
fkwp
815b013d53 Merge pull request #3034 from element-hq/renovate/github-actions
Update GitHub Actions
2025-03-03 17:04:57 +01:00
renovate[bot]
518f1f7bee Update all non-major dependencies 2025-03-03 15:55:00 +00:00
renovate[bot]
8aa95d1ddd Update GitHub Actions 2025-03-03 15:54:20 +00:00
Valere
4c91326edb fix, testing: i18n not properly configured 2025-03-03 14:41:31 +01:00
ElementRobot
05e81a8143 Merge pull request #3030 from element-hq/actions/localazy-download
Localazy Download
2025-02-28 13:40:30 +00:00
fkwp
4fa1b26827 Translations updates 2025-02-28 13:38:53 +00:00
Valere Fedronic
66899f33e2 Merge pull request #3027 from element-hq/valere/missing_livekit_url_config
Error Handling: gracefully handle missing MatrixRTC focus configuration
2025-02-28 13:43:01 +01:00
Valere
f38adf12d9 review: remove not yet used case ErrorCategory in rich error 2025-02-28 12:25:35 +01:00
Valere
5dfc89d36c fixup: lint error 2025-02-28 12:24:43 +01:00
Valere
1fbf8e2686 review: Report unknown exceptions when entering RTC session 2025-02-28 12:17:28 +01:00
Valere
2b35573672 review: Upate error.unexpected_ec_error for consistency 2025-02-28 12:06:31 +01:00
Valere
f8a5de604f review: rename ec-error file to error 2025-02-28 12:04:49 +01:00
fkwp
b355615236 Merge pull request #3029 from element-hq/renovate/livekit-client
Update dependency livekit-client to v2.9.5
2025-02-28 11:58:24 +01:00
renovate[bot]
4ebbb3382a Update dependency livekit-client to v2.9.5 2025-02-28 10:56:09 +00:00
Hugh Nimmo-Smith
7761e042fb Update readme to reflect latest config.json and matrix-js-sdk dependency (#3028) 2025-02-28 10:05:36 +00:00
Strac Consulting Engineers Pty Ltd
acc0b64968 Update config.sample.json (#3001)
Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>
Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
2025-02-27 18:25:24 +00:00
Valere
4701e413fd fixup: MissingRTCFocus error, fix confusion between domain/brand 2025-02-27 10:09:26 +01:00
Valere
b43defedfd fixup: i18n incorrect ordering of keys 2025-02-27 09:32:43 +01:00
Valere
2ba803fcef review: Improve error structure + better RTCFocus error message 2025-02-27 09:26:38 +01:00
Valere
109809182f fixup: Unused (yet) ErrorCode enum value 2025-02-26 15:37:28 +01:00
Valere
3c23e5a4e6 Error Handling: gracefully handle missing livekit service url in config 2025-02-26 15:19:08 +01:00
Valere
7423dfa527 refactor: Introduce specific ElementCall error type with code 2025-02-26 14:58:31 +01:00
Robin
31577d7263 Show an error screen when the SFU is at capacity (#3022)
Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>
Co-authored-by: fkwp <fkwp@users.noreply.github.com>
2025-02-26 12:00:56 +00:00
Robin
2bb5b020e6 Leave session when error occurs and show error screens in widget mode (#3021)
Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>
2025-02-26 10:20:30 +00:00
Robin
cd05df3e33 Merge pull request #3020 from element-hq/robin/clarify-use-latest
Clarify what useLatest is for
2025-02-24 18:22:46 +07:00
Robin
9b59544db2 Clarify what useLatest is for
I was touching this file and noticed a chance to clean up this little TODO comment by swapping in our useLatest hook and giving it better documentation.
2025-02-24 18:18:15 +07:00
Robin
91671d9ff1 Merge pull request #3019 from element-hq/robin/actually-hide-header
Actually hide the header on error pages
2025-02-24 17:47:22 +07:00
Robin
e4d7baf7bb Actually hide the header on error pages
Currently, if you set hideHeader to true, you get a big empty <header> on error pages which serves no purpose except to push the screen's content off center.
2025-02-24 17:37:11 +07:00
Robin
832a5aaf57 Merge pull request #3011 from element-hq/robin/close-action
Send a 'close' action when the widget is ready to close
2025-02-24 17:23:33 +07:00
fkwp
5e8395262c Merge pull request #3017 from element-hq/renovate/livekit-client
Update dependency livekit-client to v2.9.3
2025-02-24 08:28:40 +01:00
Robin
518c8eadca Finish the hangup procedure even if widget API throws errors 2025-02-24 11:48:05 +07:00
renovate[bot]
ad461a7643 Update dependency livekit-client to v2.9.3 2025-02-24 02:54:51 +00:00
Robin
028b3104e4 Merge pull request #3009 from element-hq/renovate/livekit-client
Update dependency livekit-client to v2.9.1
2025-02-20 13:24:17 +07:00
Robin
a75952cf77 Send a 'close' action when the widget is ready to close
By keeping 'hangup' and 'close' as separate actions, we can allow Element Call widgets to stay on an error screen after the user has been disconnected without the widget completely disappearing from the host's UI. We don't have to request any additional capabilities to use a custom widget action like this one.
2025-02-20 11:17:17 +07:00
Timo
4015efa6c6 fix lk usage in test 2025-02-20 06:33:01 +03:00
Harshal Taori
c276af7006 Merge branch 'livekit' of https://github.com/Harshal-Taori-11/element-call into livekit 2025-02-19 14:25:05 +05:30
Harshal-Taori-11
64d33426e4 Merge branch 'element-hq:livekit' into livekit 2025-02-19 14:19:02 +05:30
Harshal Taori
aafae258ac Changed the title to env variable and removed url and domain tags. 2025-02-19 14:17:33 +05:30
Hugh Nimmo-Smith
0f5dc33918 Fix copyright header to say dual license not just AGPL (#3013)
This probably should have been part of https://github.com/element-hq/element-call/pull/2984
2025-02-18 17:59:58 +00:00
renovate[bot]
f111c64264 Update dependency livekit-client to v2.9.1 2025-02-18 15:52:41 +00:00
Harshal Taori
292095abf0 Added OpenGraph and Twitter meta tags for better link previews 2025-02-13 21:29:08 +05:30
Hugh Nimmo-Smith
294e67a5ba Show current SFU and Server Info in developer tab (#3000)
* Show current SFU and Server Info in developer tab

* Lint
2025-02-10 18:22:19 +01:00
Hugh Nimmo-Smith
6e3fd10268 Explicitly build and publish docker for develop/livekit branch (#2996)
We don't need the tarball artifact and we have already done a build
2025-02-07 16:31:44 +00:00
Hugh Nimmo-Smith
0128f2f258 Don't produce compressed assets at build time and instead do it as part of the nginx build (#2995)
The core vite build shouldn't be responsible for implementing optimisations that are specific to a particular distribution mechanism. e.g. static compressed files for nginx.

This way the core build is "clean".

Instead, the packager/distributor of the build should be responsible for it. e.g. the nginx docker distribution should do any optimisations that it chooses.
2025-02-06 15:16:55 +01:00
Hugh Nimmo-Smith
e23d8828ad GitHub Action workflows cannot be in a sub folder 2025-02-06 12:55:28 +00:00
Hugh Nimmo-Smith
613675553f Fix typo in license filename 2025-02-06 12:52:11 +00:00
Hugh Nimmo-Smith
6dcfdf8d39 Cleanup GitHub Actions (#2983)
This is mostly just about naming. Plus removing an unused end-to-end testing workflow.
2025-02-06 13:13:08 +01:00
326 changed files with 5297 additions and 2162 deletions

View File

@@ -1,7 +1,7 @@
const COPYRIGHT_HEADER = `/*
Copyright %%CURRENT_YEAR%% New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

17
.github/workflows/blocked.yaml vendored Normal file
View 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.");

View File

@@ -1,4 +1,4 @@
name: Docker - Deploy
name: Build and publish docker image
on:
workflow_call:
inputs:
@@ -26,11 +26,11 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: 📥 Download artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ inputs.artifact_run_id }}
name: build-output
name: build-output-full
path: dist
- name: Log in to container registry
@@ -42,16 +42,16 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: ${{ inputs.docker_tags}}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Build and push Docker image
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
platforms: linux/amd64,linux/arm64

View File

@@ -1,10 +1,14 @@
name: Element Call - Build
name: Build Element Call
on:
workflow_call:
inputs:
vite_app_version:
required: true
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:
SENTRY_ORG:
required: true
@@ -25,14 +29,26 @@ jobs:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Yarn cache
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
with:
cache: "yarn"
node-version-file: ".node-version"
- name: Install dependencies
run: "yarn install --frozen-lockfile"
- name: Build
run: "yarn run build"
- name: Build full version
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:
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
@@ -42,9 +58,9 @@ jobs:
VITE_APP_VERSION: ${{ inputs.vite_app_version }}
NODE_OPTIONS: "--max-old-space-size=4096"
- name: Upload Artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
with:
name: build-output
name: build-output-${{ inputs.package }}
path: dist
# We'll only use this in a triggered job, then we're done with it
retention-days: 1

View File

@@ -16,9 +16,11 @@ on:
- ".github/CODEOWNERS"
- "docs/**"
jobs:
build_element_call:
uses: ./.github/workflows/element-call.yaml
build_full_element_call:
# Use the full package vite build
uses: ./.github/workflows/build-element-call.yaml
with:
package: full
vite_app_version: ${{ github.event.release.tag_name || github.sha }}
secrets:
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
@@ -29,11 +31,11 @@ jobs:
deploy_develop:
# Deploy livekit branch to call.element.dev after build completes
if: github.ref == 'refs/heads/livekit'
needs: build_element_call
needs: build_full_element_call
runs-on: ubuntu-latest
steps:
- name: Deploy to call.element.dev
uses: actions/github-script@v6
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
with:
github-token: ${{ secrets.DEVELOP_DEPLOYMENT_TOKEN }}
script: |
@@ -47,3 +49,29 @@ jobs:
version: '${{ github.sha }}'
}
})
docker_for_develop:
# Build docker and publish docker for livekit branch after build completes
if: github.ref == 'refs/heads/livekit'
needs: build_full_element_call
permissions:
contents: write
packages: write
uses: ./.github/workflows/build-and-publish-docker.yaml
with:
artifact_run_id: ${{ github.run_id }}
docker_tags: |
type=sha,format=short,event=branch
type=raw,value=latest-ci
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 }}

View File

@@ -1,4 +1,4 @@
name: Netlify - Deploy
name: Deploy to Netlify
on:
workflow_call:
inputs:
@@ -46,11 +46,11 @@ jobs:
Exercise caution. Use test accounts.
- name: 📥 Download artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
with:
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
run-id: ${{ inputs.artifact_run_id }}
name: build-output
name: build-output-full
path: webapp
- name: Add redirects file

View File

@@ -1,24 +0,0 @@
name: Run E2E tests
on:
workflow_run:
workflows: ["deploy"]
types:
- completed
branches-ignore:
- "livekit"
jobs:
e2e:
name: E2E tests runs on Element Call
runs-on: ubuntu-latest
steps:
- name: Check out test private repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
repository: element-hq/static-call-participant
ref: refs/heads/main
path: static-call-participant
token: ${{ secrets.GH_E2E_TEST_TOKEN }}
- name: Build E2E Image
run: "cd static-call-participant && docker build --no-cache --tag matrixdotorg/chrome-node-static-call-participant:latest ."
- name: Run E2E tests in container
run: "docker run --rm -v '${{ github.workspace }}/static-call-participant/callemshost-users.txt:/opt/app/callemshost-users.txt' matrixdotorg/chrome-node-static-call-participant:latest ./e2e.sh"

View File

@@ -9,7 +9,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Yarn cache
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
with:
cache: "yarn"
node-version-file: ".node-version"

33
.github/workflows/playwright.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Playwright Tests
on:
pull_request: {}
push:
branches: [livekit, full-mesh]
jobs:
test:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: ".node-version"
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Run backend components
run: |
docker compose -f playwright-backend-docker-compose.yml up -d
docker ps
- name: Copy config file
run: cp config/config.devenv.json public/config.json
- name: Run Playwright tests
run: yarn playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 3

View File

@@ -1,4 +1,4 @@
name: PR Preview Deployments
name: Deploy previews for PRs
on:
workflow_run:
workflows: ["Build"]
@@ -24,7 +24,7 @@ jobs:
needs: prdetails
permissions:
deployments: write
uses: ./.github/workflows/netlify.yaml
uses: ./.github/workflows/deploy-to-netlify.yaml
with:
artifact_run_id: ${{ github.event.workflow_run.id || github.run_id }}
pr_number: ${{ needs.prdetails.outputs.pr_number }}
@@ -42,7 +42,7 @@ jobs:
permissions:
contents: write
packages: write
uses: ./.github/workflows/docker.yaml
uses: ./.github/workflows/build-and-publish-docker.yaml
with:
artifact_run_id: ${{ github.event.workflow_run.id || github.run_id }}
docker_tags: |

View File

@@ -1,13 +1,8 @@
name: Build & publish images to the package registry for tags
name: Build & publish images to the package registry for releases
on:
release:
types: [published]
workflow_run:
workflows: ["Build"]
branches: [livekit]
types:
- completed
env:
REGISTRY: ghcr.io
@@ -16,8 +11,9 @@ env:
jobs:
build_element_call:
if: ${{ github.event_name == 'release' }}
uses: ./.github/workflows/element-call.yaml
uses: ./.github/workflows/build-element-call.yaml
with:
package: full
vite_app_version: ${{ github.event.release.tag_name || github.sha }}
secrets:
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
@@ -39,11 +35,11 @@ jobs:
id: current-time
run: echo "unix_time=$(date +'%s')" >> $GITHUB_OUTPUT
- name: 📥 Download artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id || github.run_id }}
name: build-output
name: build-output-full
path: dist
- name: Create Tarball
env:
@@ -51,7 +47,7 @@ jobs:
run: |
tar --numeric-owner --transform "s/dist/element-call-${TARBALL_VERSION}/" -cvzf element-call-${TARBALL_VERSION}.tar.gz dist
- name: Upload
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
@@ -62,11 +58,9 @@ jobs:
permissions:
contents: write
packages: write
uses: ./.github/workflows/docker.yaml
uses: ./.github/workflows/build-and-publish-docker.yaml
with:
artifact_run_id: ${{ github.event.workflow_run.id || github.run_id }}
docker_tags: |
type=sha,format=short,event=branch
type=semver,pattern=v{{version}}
type=raw,value=latest-ci,enable={{is_default_branch}}
type=raw,value=latest-ci_${{needs.publish_tarball.outputs.unix_time}},enable={{is_default_branch}}

View File

@@ -11,7 +11,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Yarn cache
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
with:
cache: "yarn"
node-version-file: ".node-version"
@@ -20,7 +20,7 @@ jobs:
- name: Vitest
run: "yarn run test:coverage"
- name: Upload to codecov
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5
uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:

View File

@@ -15,7 +15,7 @@ jobs:
- name: Checkout the code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
with:
cache: "yarn"
node-version-file: ".node-version"
@@ -39,7 +39,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # v7.0.7
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/localazy-download

7
.gitignore vendored
View File

@@ -4,8 +4,15 @@ node_modules
dist
dist-ssr
*.local
*.bkp
.idea/
public/config.json
backend/synapse_tmp/*
/coverage
yarn-error.log
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

332
README.md
View File

@@ -2,127 +2,101 @@
[![Chat](https://img.shields.io/matrix/webrtc:matrix.org)](https://matrix.to/#/#webrtc:matrix.org)
[![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement-call%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element-call)
[![License](https://img.shields.io/github/license/element-hq/element-call)](LICENSE-AGPL-3.0)
Group calls with WebRTC that leverage [Matrix](https://matrix.org) and an
open-source WebRTC toolkit from [LiveKit](https://livekit.io/).
[🎬 Live Demo 🎬](https://call.element.io)
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.
The world's first 🌐 decentralized and 🤝 federated video conferencing solution
powered by **the Matrix protocol**.
![A demo of Element Call with six people](demo.jpg)
## 📌 Overview
To try it out, visit our hosted version at
[call.element.io](https://call.element.io). You can also find the latest
development version continuously deployed to
**Element Call** 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.
![A demo of Element Call with six people](demo.gif)
You can find the latest development version continuously deployed to
[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
source. First, clone and install the package:
## ✨ Key Features
```
git clone https://github.com/element-hq/element-call.git
cd element-call
yarn
yarn build
```
**Decentralized & Federated** No central authority; works across Matrix
homeservers.
**End-to-End Encrypted** Secure and private calls.
**Standalone & Widget Mode** Use as an independent app or embed in Matrix
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
static files. These can be hosted using any web server that can be configured
with custom routes (see below).
## 🚀 Deployment Options
You may also wish to add a configuration file (Element Call uses the domain it's
hosted on as a Homeserver URL by default, but you can change this in the config
file). This goes in `public/config.json` - you can use the sample as a starting
point:
Element Call can be packaged in two ways:
```
cp config/config.sample.json public/config.json
# edit public/config.json
```
**Full Package** Supports both **Standalone** and **Widget** mode. Hosted as
a static web page and accessed via a URL when used as a widget.
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:
**Embedded Package** 🚧 **Coming [Soon](https://github.com/element-hq/element-call/issues/2994):** Designed for **Widget mode** only. Bundled with a
messenger app for seamless integration. This is the recommended method for
embedding Element Call into a messenger app.
```
server {
...
location / {
...
try_files $uri /$uri /index.html;
}
}
```
### Standalone mode
By default, the app expects you to have a Matrix homeserver (such as
[Synapse](https://element-hq.github.io/synapse/latest/setup/installation.html))
installed locally and running on port 8008. If you wish to use a homeserver on a
different URL or one that is hosted on a different server, you can add a config
file as above, and include the homeserver URL that you'd like to use.
![Element Call in Standalone Mode](./docs/element_call_standalone.drawio.png)
Element Call 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.
In Standalone mode Element Call operates as an independent, full-featured video
conferencing web application, allowing users to join or host calls without
requiring a separate Matrix client.
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.
### Widget mode embedded in Messenger Apps
## Configuration
![Element Call in Widget Mode](./docs/element_call_widget.drawio.png)
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).
Element Call can be embedded as a widget inside apps like
[**Element Web**](https://github.com/element-hq/element-web) or **Element X
([iOS](https://github.com/element-hq/element-x-ios),
[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
to additionally add the following to `homeserver.yaml` or Element Call won't
work:
> [!IMPORTANT]
> Embedded packaging is recommended for Element Call in widget mode!
```
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
## 🛠️ Self-Hosting
# The maximum allowed duration by which sent events can be delayed, as
# per MSC4140.
max_event_delay_duration: 24h
For operating and deploying Element Call on your own server, refer to the
[**Self-Hosting Guide**](./docs/self-hosting.md).
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 Discovery and Selection
MSC3266 allows to request a room summary of rooms you are not joined. The
summary contains the room join rules. We need that to decide if the user gets
prompted with the option to knock ("Request to join call"), a cannot join error or the
join view.
For proper Element Call operation each site deployment needs a MatrixRTC backend
setup as outlined in the [Self-Hosting](#self-hosting). A typical federated site
deployment for three different sites A, B and C is depicted below.
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 federated setup](./docs/Federated_Setup.drawio.png)
Element Call requires a Livekit SFU alongside a [Livekit JWT
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.
### Backend Discovery
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
"org.matrix.msc4143.rtc_foci": [
@@ -130,60 +104,65 @@ The configuration is a list of Foci configs:
"type": "livekit",
"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.
![Element Call SFU selection over Matrix federation](./docs/SFU_selection.drawio.png)
## 🌍 Translation
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
the [Element Translators](https://matrix.to/#/#translators:element.io) space to
discuss and coordinate translation efforts.
## Development
## 🛠️ Development
### Frontend
Element Call is built against
[matrix-js-sdk](https://github.com/matrix-org/matrix-js-sdk/pull/2553). To get
started, clone, install, and link the package:
To get started clone and set up this project:
```
git clone https://github.com/matrix-org/matrix-js-sdk.git
cd matrix-js-sdk
yarn
yarn link
```
Next, we can set up this project:
```
```sh
git clone https://github.com/element-hq/element-call.git
cd element-call
yarn
yarn link matrix-js-sdk
```
To use it, create a local config by, e.g., `cp ./config/config.devenv.json
./public/config.json` and adapt it if necessary. The `config.devenv.json` config
should work with the backend development environment as outlined in the next
section out of box.
To use it, create a local config by, e.g.,
`cp ./config/config.devenv.json ./public/config.json` and adapt it if necessary.
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
LiveKit config key. If the homeserver advertises SFU backend via
`.well-known/matrix/client` this has precedence.)
> [!NOTE]
> Be aware, that this `config.devenv.json` is exposing a deprecated fallback
> 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:
```
```sh
yarn dev
```
@@ -192,24 +171,82 @@ yarn dev
A docker compose file `dev-backend-docker-compose.yml` is provided to start the
whole stack of components which is required for a local development environment:
- Minimum Synapse Setup (servername: synapse.localhost)
- Minimum Synapse Setup (servername: `synapse.localhost`)
- LiveKit JWT Service (Note requires Federation API and hence a TLS reverse proxy)
- Minimum TLS reverse proxy (servername: synapse.localhost) Note certificates
- Minimum TLS reverse proxy (servername: `synapse.localhost`) Note certificates
are valid for at least 10 years from now
- Minimum LiveKit SFU Setup using dev defaults for config
- Redis db for completness
- Redis db for completeness
These use a test 'secret' published in this repository, so this must be used
only for local development and **_never be exposed to the public Internet._**
Run backend components:
```
```sh
yarn backend
# or for podman-compose
# podman-compose -f dev-backend-docker-compose.yml up
```
### Playwright tests
Our Playwright tests run automatically as part of our CI along with our other tests,
on every pull request.
You may need to follow instructions to set up your development environment for running
Playwright by following <https://playwright.dev/docs/browsers#install-browsers> and
<https://playwright.dev/docs/browsers#install-system-dependencies>.
However the Playwright tests are run, an element-call instance must be running on
https://localhost:3000 (this is configured in `playwright.config.ts`) - this is what will
be tested.
The local backend environment should be running for the test to work:
`yarn backend`
There are a few different ways to run the tests yourself. The simplest is to run:
```shell
yarn run test:playwright
```
This will run the Playwright tests once, non-interactively.
There is a more user-friendly way to run the tests in interactive mode:
```shell
yarn run test:playwright:open
```
The easiest way to develop new test is to use the codegen feature of Playwright:
```shell
npx playwright codegen
```
This will record your action and write the test code for you. Use the tool bar to test visibility, text content,
clicking.
##### Investigate a failed test from the CI
In the failed action page, click on the failed job, then scroll down to the `upload-artifact` step.
You will find a link to download the zip report, as per:
```
Artifact playwright-report has been successfully uploaded! Final size is 1360358 bytes. Artifact ID is 2746265841
Artifact download URL: https://github.com/element-hq/element-call/actions/runs/13837660687/artifacts/2746265841
```
Unzip the report then use this command to open the report in your browser:
```shell
npx playwright show-report ~/Downloads/playwright-report/
```
Under the failed test there is a small icon looking like "3 columns" (next to test name file name),
click on it to see the live screenshots/console output.
### Test Coverage
<img src="https://codecov.io/github/element-hq/element-call/graphs/tree.svg?token=O6CFVKK6I1"></img>
@@ -218,9 +255,11 @@ yarn backend
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
will add a skeleton entry to the `locales/en/app.json` file:
```jsonc
{
...
@@ -228,30 +267,39 @@ To add a new translation key you can do these steps:
...
}
```
1. Update the skeleton entry in the `locales/en/app.json` file with
the English translation:
```jsonc
1. Update the skeleton entry in the `locales/en/app.json` file with the English
translation:
```jsonc
{
...
"some_new_key": "Some new key",
...
}
```
```
## Documentation
## 📖 Documentation
Usage and other technical details about the project can be found here:
[**Docs**](./docs/README.md)
## Copyright & License
## 📝 Copyright & License
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).
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.
(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). 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.

View File

@@ -0,0 +1,67 @@
server_name: "synapse.localhost"
public_baseurl: http://synapse.localhost:8008/
pid_file: /data/homeserver.pid
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation, openid]
compress: false
database:
name: sqlite3
args:
database: /data/homeserver.db
media_store_path: /data/media_store
signing_key_path: "/data/SERVERNAME.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
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. Must be a positive value if set. Defaults to no
# duration (null), which disallows sending delayed events.
max_event_delay_duration: 24h
# Ratelimiting settings for client actions (registration, login, messaging).
#
# Each ratelimiting configuration is made of two parameters:
# - per_second: number of requests a client can send per second.
# - burst_count: number of requests a client can send before being throttled.
rc_message:
per_second: 10000
burst_count: 10000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
# Required for Element Call in Single Page Mode due to on-the-fly user registration
enable_registration: true
enable_registration_without_verification: true
report_stats: false
serve_server_wellknown: true

View File

@@ -1,10 +1,13 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "http://synapse.localhost:8008",
"server_name": "synapse.localhost"
"base_url": "https://mydomain.com",
"server_name": "mydomain.com"
}
},
"livekit": {
"livekit_service_url": "https://livekit-jwt.mydomain.com"
},
"features": {
"feature_use_device_session_member_events": true
},

BIN
demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -5,3 +5,4 @@ This folder contains documentation for Element Call setup and usage.
- [Embedded vs standalone mode](./embedded-standalone.md)
- [Url format and parameters](./url-params.md)
- [Global JS controls](./controls.md)
- [Self-Hosting](./self-hosting.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 KiB

View File

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

166
docs/self-hosting.md Normal file
View File

@@ -0,0 +1,166 @@
# 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 e2ee key sharing frequency plus a bit of headroom
# Note key sharing events are bursty
per_second: 0.5
burst_count: 30
# 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
rc_delayed_event_mgmt:
per_second: 1
burst_count: 20
```
### MatrixRTC Backend
In order to **guarantee smooth operation** of Element Call MatrixRTC backend is
required for each site deployment.
![MSC4195 compatible setup](MSC4195_setup.drawio.png)
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.

View File

@@ -32,14 +32,16 @@ There are two formats for Element Call urls.
## Parameters
### Common Parameters
These parameters are relevant to both widget and standalone modes:
| Name | Values | Required for widget | Required for SPA | Description |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowIceFallback` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Allows use of fallback STUN servers for ICE if the user's homeserver doesnt provide any. |
| `analyticsID` | Posthog analytics ID | No | No | Available only with user's consent for sharing telemetry in Element Web. |
| `appPrompt` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Prompts the user to launch the native mobile app upon entering a room, applicable only on Android and iOS, and must be enabled in config. |
| `baseUrl` | | Yes | Not applicable | The base URL of the homeserver to use for media lookups. |
| `confineToRoom` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Keeps the user confined to the current call/room. |
| `deviceId` | Matrix device ID | Yes | Not applicable | The Matrix device ID for the widget host. |
| `displayName` | | No | No | Display name used for auto-registration. |
| `enableE2EE` (deprecated) | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Legacy flag to enable end-to-end encryption, not used in the `livekit` branch. |
| `fontScale` | A decimal number such as `0.9` | No, defaults to `1.0` | No, defaults to `1.0` | Factor by which to scale the interface's font size. |
@@ -49,15 +51,24 @@ There are two formats for Element Call urls.
| `homeserver` | | Not applicable | No | Homeserver for registering a new (guest) user, configures non-default guest user server when creating a spa link. |
| `intent` | `start_call` or `join_existing` | No, defaults to `start_call` | No, defaults to `start_call` | The intent of the user with respect to the call. e.g. if they clicked a Start Call button, this would be `start_call`. If it was a Join Call button, it would be `join_existing`. |
| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. |
| `parentUrl` | | Yes | Not applicable | The url used to send widget action postMessages. This should be the domain of the client or the webview the widget is hosted in. (in case the widget is not in an Iframe but in a dedicated webview we send the postMessages same WebView the widget lives in. Filtering is done in the widget so it ignores the messages it receives from itself) |
| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) |
| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. |
| `preload` | `true` or `false` | No, defaults to `false` | Not applicable | Pauses app before joining a call until an `io.element.join` widget action is seen, allowing preloading. |
| `returnToLobby` | `true` or `false` | No, defaults to `false` | Not applicable | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. |
| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. |
| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. |
| `skipLobby` (deprecated: use `intent` instead) | `true` or `false` | No. If `intent` is explicitly `start_call` then defaults to `true`. Otherwise defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) |
| `theme` | One of: `light`, `dark`, `light-high-contrast`, `dark-high-contrast` | No, defaults to `dark` | No, defaults to `dark` | UI theme to use. |
| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | Not applicable | The Matrix user ID. |
| `viaServers` | Comma separated list of [Matrix Server Names](https://spec.matrix.org/v1.12/appendices/#server-name) | Not applicable | No | Homeserver for joining a room, non-empty value required for rooms not on the users default homeserver. |
| `widgetId` | [MSC2774](https://github.com/matrix-org/matrix-spec-proposals/pull/2774) format widget ID | Yes | Not applicable | The id used by the widget. The presence of this parameter implies that element call will not connect to a homeserver directly and instead tries to establish postMessage communication via the `parentUrl`. |
### Widget-only parameters
These parameters are only available in widget mode.
| Name | Values | Required | Description |
| --------------- | ----------------------------------------------------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl` | | Yes | The base URL of the homeserver to use for media lookups. |
| `deviceId` | Matrix device ID | Yes | The Matrix device ID for the widget host. |
| `parentUrl` | | Yes | The url used to send widget action postMessages. This should be the domain of the client or the webview the widget is hosted in. (in case the widget is not in an Iframe but in a dedicated webview we send the postMessages same WebView the widget lives in. Filtering is done in the widget so it ignores the messages it receives from itself) |
| `preload` | `true` or `false` | No, defaults to `false` | Pauses app before joining a call until an `io.element.join` widget action is seen, allowing preloading. |
| `returnToLobby` | `true` or `false` | No, defaults to `false` | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. |
| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | The Matrix user ID. |
| `widgetId` | [MSC2774](https://github.com/matrix-org/matrix-spec-proposals/pull/2774) format widget ID | Yes | The id used by the widget. The presence of this parameter implies that element call will not connect to a homeserver directly and instead tries to establish postMessage communication via the `parentUrl`. |

45
index.html Normal file
View File

@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<% if (packageType === "full") { %>
<link rel="icon" type="image/svg+xml" href="favicon.png" />
<link rel="preload" href="/config.json" as="fetch" />
<% } %>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
/>
<title><%- brand %></title>
<script>
window.global = window;
</script>
<% if (packageType === "full") { %>
<!-- Open graph meta tags -->
<meta property="og:title" content="<%- brand %>" />
<meta
property="og:description"
content="You're invited to join a call on <%- brand %>"
/>
<meta property="og:type" content="website" />
<meta property="og:image" content="favicon.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="<%- brand %>" />
<meta
name="twitter:description"
content="You're invited to join a call on <%- brand %>"
/>
<meta name="twitter:image" content="favicon.png" />
<% } %>
</head>
<!-- The default class is: .no-theme {display: none}. It will be overwritten once the app is loaded. -->
<body class="no-theme">
<div id="root"></div>
</body>
</html>

View File

@@ -1,6 +1,9 @@
import { KnipConfig } from "knip";
export default {
vite: {
config: ["vite.config.js", "vite-embedded.config.js"],
},
entry: ["src/main.tsx", "i18next-parser.config.ts"],
ignoreBinaries: [
// This is deprecated, so Knip doesn't actually recognize it as a globally

View File

@@ -19,11 +19,9 @@
"common": {
"audio": "Звук",
"avatar": "Аватар",
"camera": "Камера",
"display_name": "Име/псевдоним",
"home": "Начало",
"loading": "Зареждане…",
"microphone": "Микрофон",
"password": "Парола",
"profile": "Профил",
"settings": "Настройки",
@@ -61,8 +59,7 @@
"settings": {
"developer_tab_title": "Разработчик",
"feedback_tab_h4": "Изпрати обратна връзка",
"feedback_tab_send_logs_label": "Включи debug логове",
"speaker_device_selection_label": "Говорител"
"feedback_tab_send_logs_label": "Включи debug логове"
},
"unauthenticated_view_body": "Все още не сте регистрирани? <2>Създайте акаунт</2>",
"unauthenticated_view_login_button": "Влезте в акаунта си",

View File

@@ -17,20 +17,14 @@
"not_now_button": "Teď ne, vrátit se na domovskou obrazovku"
},
"common": {
"camera": "Kamera",
"display_name": "Zobrazované jméno",
"home": "Domov",
"loading": "Načítání…",
"microphone": "Mikrofon",
"password": "Heslo",
"profile": "Profil",
"settings": "Nastavení",
"username": "Uživatelské jméno"
},
"error": {
"generic_description": "Odeslání ladících záznamů nám pomůže diagnostikovat problém."
},
"full_screen_view_h1": "<0>Oops, něco se pokazilo.</0>",
"header_label": "Domov Element Call",
"join_existing_call_modal": {
"join_button": "Ano, připojit se",
@@ -64,8 +58,7 @@
"settings": {
"developer_tab_title": "Vývojář",
"feedback_tab_h4": "Dát feedback",
"feedback_tab_send_logs_label": "Zahrnout ladící záznamy",
"speaker_device_selection_label": "Reproduktor"
"feedback_tab_send_logs_label": "Zahrnout ladící záznamy"
},
"unauthenticated_view_body": "Nejste registrovaní? <2>Vytvořit účet</2>",
"unauthenticated_view_login_button": "Přihlásit se ke svému účtu",

View File

@@ -28,9 +28,6 @@
"text": "Bereit, beizutreten?",
"title": "App auswählen"
},
"application_opened_another_tab": "Diese Anwendung wurde in einem anderen Tab geöffnet.",
"browser_media_e2ee_unsupported": "Dein Webbrowser unterstützt keine Medien-Ende-zu-Ende-Verschlüsselung. Unterstützte Browser sind Chrome, Safari, Firefox >=117",
"browser_media_e2ee_unsupported_heading": "Inkompatibler Browser",
"call_ended_view": {
"create_account_button": "Konto erstellen",
"create_account_prompt": "<0>Warum vergibst du nicht abschließend ein Passwort, um dein Konto zu erhalten?</0><1>Du kannst deinen Namen behalten und ein Profilbild für zukünftige Anrufe festlegen.</1>",
@@ -47,13 +44,10 @@
"audio": "Audio",
"avatar": "Profilbild",
"back": "Zurück",
"camera": "Kamera",
"display_name": "Anzeigename",
"encrypted": "Verschlüsselt",
"error": "Fehler",
"home": "Startseite",
"loading": "Lade …",
"microphone": "Mikrofon",
"next": "Weiter",
"options": "Optionen",
"password": "Passwort",
@@ -62,34 +56,49 @@
"reaction": "Reaktion",
"reactions": "Reaktionen",
"settings": "Einstellungen",
"something_went_wrong": "Etwas ist schief gelaufen",
"unencrypted": "Nicht verschlüsselt",
"username": "Benutzername",
"video": "Video"
},
"developer_mode": {
"crypto_version": "Krypto-Version: {{version}}",
"debug_tile_layout_label": "Kachel-Layout debuggen",
"device_id": "Geräte-ID: {{id}}",
"duplicate_tiles_label": "Anzahl zusätzlicher Kachelkopien pro Teilnehmer",
"hostname": "Hostname: {{hostname}}",
"matrix_id": "Matrix-ID: {{id}}"
"livekit_server_info": "LiveKit-Server Informationen",
"livekit_sfu": "LiveKit SFU: {{url}}",
"matrix_id": "Matrix-ID: {{id}}",
"show_connection_stats": "Verbindungsstatistiken 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.",
"error": {
"connection_lost_description": "Deine Verbindung wurde getrennt",
"generic_description": "Übermittelte Problemberichte helfen uns, Fehler zu beheben."
"call_is_not_supported": "Anrufe werden nicht unterstützt",
"call_not_found": "Anruf nicht gefunden",
"call_not_found_description": "<0>Dieser Link scheint zu keinem bestehenden Anruf zu gehören. Vergewissern Sie sich, dass Sie den richtigen Link haben, oder <1> erstellen Sie einen neuen</1>. </0>",
"connection_lost": "Verbindung verloren",
"connection_lost_description": "Ihre Verbindung zum Anruf wurde unterbrochen.",
"e2ee_unsupported": "Inkompatibler Browser",
"e2ee_unsupported_description": "Ihr Webbrowser unterstützt keine verschlüsselten Anrufe. Zu den unterstützten Browsern gehören Chrome, Safari und Firefox 117+.",
"generic": "Etwas ist schief gelaufen",
"generic_description": "Durch das Senden von Debugprotokollen können wir das Problem leichter eingrenzen.",
"insufficient_capacity": "Unzureichende Kapazität",
"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 }}).",
"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.",
"unexpected_ec_error": "Ein unerwarteter Fehler ist aufgetreten (<0>Fehlercode: </0> <1>{{ errorCode }}</1>). Bitte den Serveradministrator kontaktieren."
},
"full_screen_view_h1": "<0>Hoppla, etwas ist schiefgelaufen.</0>",
"group_call_loader": {
"banned_body": "Du wurdest aus dem Raum verbannt.",
"banned_heading": "Verbannt",
"call_ended_body": "Du wurdest aus dem Anruf entfernt.",
"call_ended_heading": "Anruf beendet",
"failed_heading": "Beitreten fehlgeschlagen",
"failed_text": "Anruf nicht gefunden oder Beitritt nicht erlaubt",
"knock_reject_body": "Die Teilnahmeanfrage wurde abgelehnt.",
"knock_reject_heading": "Zugriff verweigert",
"reason": "Grund"
"reason": "Grund: {{reason}}"
},
"hangup_button_label": "Anruf beenden",
"header_label": "Element Call-Startseite",
@@ -149,10 +158,20 @@
"screenshare_button_label": "Bildschirm teilen",
"settings": {
"audio_tab": {
"effect_volume_description": "Lautstärke anpassen, mit der Reaktionen und Handmeldungen abgespielt werden",
"effect_volume_description": "Lautstärke anpassen, mit der Reaktionen und Handmeldungen abgespielt werden.",
"effect_volume_label": "Lautstärke der Soundeffekte"
},
"developer_tab_title": "Entwickler",
"devices": {
"camera": "Kamera",
"camera_numbered": "Kamera {{n}}",
"default": "Standard",
"default_named": "Standard<2> ({{name}} )</2>",
"microphone": "Mikrofon",
"microphone_numbered": "Mikrofon{{n}}",
"speaker": "Lautsprecher",
"speaker_numbered": "Lautsprecher{{n}}"
},
"feedback_tab_body": "Falls du auf Probleme stößt oder einfach nur eine Rückmeldung geben möchtest, sende uns bitte eine kurze Beschreibung.",
"feedback_tab_description_label": "Deine Rückmeldung",
"feedback_tab_h4": "Rückmeldung geben",
@@ -161,12 +180,16 @@
"feedback_tab_title": "Rückmeldung",
"opt_in_description": "<0></0><1></1>Du kannst deine Zustimmung durch Abwählen dieses Kästchens zurückziehen. Falls du dich aktuell in einem Anruf befindest, wird diese Einstellung nach dem Ende des Anrufs wirksam.",
"preferences_tab": {
"reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet",
"developer_mode_label": "Entwickler-Modus",
"developer_mode_label_description": "Aktivieren Sie den Entwicklermodus und zeigen Sie die Registerkarte mit den Entwicklereinstellungen an.",
"introduction": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden.",
"reactions_play_sound_description": "Spielen Sie einen Soundeffekt ab, wenn jemand eine Reaktion auf einen Anruf sendet.",
"reactions_play_sound_label": "Reaktionstöne abspielen",
"reactions_show_description": "Zeige eine Animation, wenn jemand eine Reaktion sendet.",
"reactions_show_label": "Reaktionen anzeigen"
},
"speaker_device_selection_label": "Lautsprecher"
"reactions_show_label": "Reaktionen anzeigen",
"show_hand_raised_timer_description": "Einen Timer zur Handmeldung anzeigen",
"show_hand_raised_timer_label": "Dauer der Handmeldung anzeigen"
}
},
"star_rating_input_label_one": "{{count}} Stern",
"star_rating_input_label_other": "{{count}} Sterne",
@@ -183,6 +206,7 @@
"version": "{{productName}} Version: {{version}}",
"video_tile": {
"always_show": "Immer anzeigen",
"camera_starting": "Video wird geladen...",
"change_fit_contain": "An Fenster anpassen",
"collapse": "Minimieren",
"expand": "Erweitern",

View File

@@ -24,21 +24,15 @@
},
"common": {
"audio": "Ήχος",
"camera": "Κάμερα",
"display_name": "Εμφανιζόμενο όνομα",
"home": "Αρχική",
"loading": "Φόρτωση…",
"microphone": "Μικρόφωνο",
"password": "Κωδικός",
"profile": "Προφίλ",
"settings": "Ρυθμίσεις",
"username": "Όνομα χρήστη",
"video": "Βίντεο"
},
"error": {
"generic_description": "Η υποβολή αρχείων καταγραφής σφαλμάτων θα μας βοηθήσει να εντοπίσουμε το πρόβλημα."
},
"full_screen_view_h1": "<0>Ωχ, κάτι πήγε στραβά.</0>",
"header_label": "Element Κεντρική Οθόνη Κλήσεων",
"join_existing_call_modal": {
"join_button": "Ναι, συμμετοχή στην κλήση",
@@ -76,8 +70,7 @@
"feedback_tab_send_logs_label": "Να συμπεριληφθούν αρχεία καταγραφής",
"feedback_tab_thank_you": "Ευχαριστούμε, λάβαμε τα σχόλιά σας!",
"feedback_tab_title": "Ανατροφοδότηση",
"opt_in_description": "<0></0><1></1>Μπορείτε να ανακαλέσετε τη συγκατάθεσή σας αποεπιλέγοντας αυτό το πλαίσιο. Εάν βρίσκεστε σε κλήση, η ρύθμιση αυτή θα τεθεί σε ισχύ στο τέλος της.",
"speaker_device_selection_label": "Ηχείο"
"opt_in_description": "<0></0><1></1>Μπορείτε να ανακαλέσετε τη συγκατάθεσή σας αποεπιλέγοντας αυτό το πλαίσιο. Εάν βρίσκεστε σε κλήση, η ρύθμιση αυτή θα τεθεί σε ισχύ στο τέλος της."
},
"star_rating_input_label_one": "{{count}} αστέρι",
"star_rating_input_label_other": "{{count}} αστέρια",

View File

@@ -66,12 +66,16 @@
"device_id": "Device ID: {{id}}",
"duplicate_tiles_label": "Number of additional tile copies per participant",
"hostname": "Hostname: {{hostname}}",
"livekit_server_info": "LiveKit Server Info",
"livekit_sfu": "LiveKit SFU: {{url}}",
"matrix_id": "Matrix ID: {{id}}",
"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.",
"error": {
"call_is_not_supported": "Call is not supported",
"call_not_found": "Call not found",
"call_not_found_description": "<0>That link doesn't appear to belong to any existing call. Check that you have the right link, or <1>create a new one</1>.</0>",
"connection_lost": "Connection lost",
@@ -80,8 +84,12 @@
"e2ee_unsupported_description": "Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+.",
"generic": "Something went wrong",
"generic_description": "Submitting debug logs will help us track down the problem.",
"insufficient_capacity": "Insufficient capacity",
"insufficient_capacity_description": "The server has reached its maximum capacity and you cannot join the call at this time. Try again later, or contact your server admin if the problem persists.",
"matrix_rtc_focus_missing": "The server is not configured to work with {{brand}}. Please contact your server admin (Domain: {{domain}}, Error Code: {{ errorCode }}).",
"open_elsewhere": "Opened in another tab",
"open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page."
"open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page.",
"unexpected_ec_error": "An unexpected error occurred (<0>Error Code:</0> <1>{{ errorCode }}</1>). Please contact your server admin."
},
"group_call_loader": {
"banned_body": "You have been banned from the room.",

View File

@@ -22,20 +22,14 @@
"survey_prompt": "¿Cómo ha ido?"
},
"common": {
"camera": "Cámara",
"display_name": "Nombre a mostrar",
"home": "Inicio",
"loading": "Cargando…",
"microphone": "Micrófono",
"password": "Contraseña",
"profile": "Perfil",
"settings": "Ajustes",
"username": "Nombre de usuario"
},
"error": {
"generic_description": "Subir los registros de depuración nos ayudará a encontrar el problema."
},
"full_screen_view_h1": "<0>Ups, algo ha salido mal.</0>",
"header_label": "Inicio de Element Call",
"join_existing_call_modal": {
"join_button": "Si, unirse a la llamada",
@@ -76,8 +70,7 @@
"feedback_tab_send_logs_label": "Incluir registros de depuración",
"feedback_tab_thank_you": "¡Gracias, hemos recibido tus comentarios!",
"feedback_tab_title": "Danos tu opinión",
"opt_in_description": "<0></0><1></1>Puedes retirar tu consentimiento desmarcando esta casilla. Si estás en una llamada, este ajuste se aplicará al final de esta.",
"speaker_device_selection_label": "Altavoz"
"opt_in_description": "<0></0><1></1>Puedes retirar tu consentimiento desmarcando esta casilla. Si estás en una llamada, este ajuste se aplicará al final de esta."
},
"star_rating_input_label_one": "{{count}} estrella",
"star_rating_input_label_other": "{{count}} estrellas",

View File

@@ -21,7 +21,6 @@
"text": "Oled valmis liituma?",
"title": "Vali rakendus"
},
"browser_media_e2ee_unsupported": "Sinu veebibrauser ei toeta meedia läbivat krüptimist. Toetatud brauserid on Chrome, Chromium, Safari ja Firefox >=117",
"call_ended_view": {
"create_account_button": "Loo konto",
"create_account_prompt": "<0>Kas soovid salasõna seadistada ja sellega oma kasutajakonto alles jätta?</0><1>Nii saad säilitada oma nime ja määrata profiilipildi, mida saad kasutada tulevastes kõnedes</1>",
@@ -36,12 +35,10 @@
"common": {
"audio": "Heli",
"avatar": "Tunnuspilt",
"camera": "Kaamera",
"display_name": "Kuvatav nimi",
"encrypted": "Krüptitud",
"home": "Avavaatesse",
"loading": "Laadimine …",
"microphone": "Mikrofon",
"password": "Salasõna",
"profile": "Profiil",
"settings": "Seadistused",
@@ -49,11 +46,6 @@
"username": "Kasutajanimi"
},
"disconnected_banner": "Võrguühendus serveriga on katkenud.",
"error": {
"connection_lost_description": "Sinu ühendus kõnega katkes",
"generic_description": "Kui saadad meile vealogid, siis on lihtsam vea põhjust otsida."
},
"full_screen_view_h1": "<0>Ohoo, midagi on nüüd katki.</0>",
"hangup_button_label": "Lõpeta kõne",
"header_participants_label": "Osalejad",
"invite_modal": {
@@ -106,8 +98,7 @@
"feedback_tab_send_logs_label": "Lisa veatuvastuslogid",
"feedback_tab_thank_you": "Tänud, me oleme sinu tagasiside kätte saanud!",
"feedback_tab_title": "Tagasiside",
"opt_in_description": "<0></0><1></1>Sa võid selle valiku eelmaldamisega alati oma nõusoleku tagasi võtta. Kui sul parasjagu on kõne pooleli, siis seadistuste muudatus jõustub pärast kõne lõppu.",
"speaker_device_selection_label": "Kõlar"
"opt_in_description": "<0></0><1></1>Sa võid selle valiku eelmaldamisega alati oma nõusoleku tagasi võtta. Kui sul parasjagu on kõne pooleli, siis seadistuste muudatus jõustub pärast kõne lõppu."
},
"star_rating_input_label_one": "{{count}} tärni",
"star_rating_input_label_other": "{{count}} tärni",

View File

@@ -19,11 +19,9 @@
"common": {
"audio": "صدا",
"avatar": "آواتار",
"camera": "دوربین",
"display_name": "نام نمایشی",
"home": "خانه",
"loading": "بارگزاری…",
"microphone": "میکروفون",
"password": "رمز عبور",
"profile": "پروفایل",
"settings": "تنظیمات",
@@ -63,8 +61,7 @@
"settings": {
"developer_tab_title": "توسعه دهنده",
"feedback_tab_h4": "بازخورد ارائه دهید",
"feedback_tab_send_logs_label": "شامل لاگ‌های عیب‌یابی",
"speaker_device_selection_label": "بلندگو"
"feedback_tab_send_logs_label": "شامل لاگ‌های عیب‌یابی"
},
"unauthenticated_view_body": "هنوز ثبت‌نام نکرده‌اید؟ <2>ساخت حساب کاربری</2>",
"unauthenticated_view_login_button": "به حساب کاربری خود وارد شوید",

View File

@@ -21,7 +21,6 @@
"text": "Prêt à rejoindre ?",
"title": "Choisissez lapplication"
},
"browser_media_e2ee_unsupported": "Votre navigateur web ne prend pas en charge le chiffrement de bout-en-bout des médias. Les navigateurs pris en charge sont Chrome, Safari, Firefox >= 117",
"call_ended_view": {
"create_account_button": "Créer un compte",
"create_account_prompt": "<0>Pourquoi ne pas créer un mot de passe pour conserver votre compte ?</0><1>Vous pourrez garder votre nom et définir un avatar pour vos futurs appels</1>",
@@ -34,7 +33,6 @@
},
"call_name": "Nom de lappel",
"common": {
"camera": "Caméra",
"display_name": "Nom daffichage",
"encrypted": "Chiffré",
"home": "Accueil",
@@ -47,11 +45,6 @@
"video": "Vidéo"
},
"disconnected_banner": "La connexion avec le serveur a été perdue.",
"error": {
"connection_lost_description": "Vous avez été déconnecté de lappel",
"generic_description": "Soumettre les journaux de débogage nous aidera à déterminer le problème."
},
"full_screen_view_h1": "<0>Oups, quelque chose sest mal passé.</0>",
"hangup_button_label": "Terminer lappel",
"header_label": "Accueil Element Call",
"invite_modal": {
@@ -104,8 +97,7 @@
"feedback_tab_send_logs_label": "Inclure les journaux de débogage",
"feedback_tab_thank_you": "Merci, nous avons reçu vos commentaires !",
"feedback_tab_title": "Commentaires",
"opt_in_description": "<0></0><1></1>Vous pouvez retirer votre consentement en décochant cette case. Si vous êtes actuellement en communication, ce paramètre prendra effet à la fin de lappel.",
"speaker_device_selection_label": "Intervenant"
"opt_in_description": "<0></0><1></1>Vous pouvez retirer votre consentement en décochant cette case. Si vous êtes actuellement en communication, ce paramètre prendra effet à la fin de lappel."
},
"star_rating_input_label_one": "{{count}} favori",
"star_rating_input_label_other": "{{count}} favoris",

View File

@@ -21,7 +21,6 @@
"text": "Siap untuk bergabung?",
"title": "Pilih plikasi"
},
"browser_media_e2ee_unsupported": "Peramban web Anda tidak mendukung enkripsi media ujung ke ujung. Peramban yang didukung adalah Chrome, Safari, dan Firefox >=117",
"call_ended_view": {
"create_account_button": "Buat akun",
"create_account_prompt": "<0>Kenapa tidak selesaikan dengan mengatur sebuah kata sandi untuk menjaga akun Anda?</0><1>Anda akan dapat tetap menggunakan nama Anda dan atur sebuah avatar untuk digunakan dalam panggilan di masa mendatang</1>",
@@ -34,12 +33,10 @@
},
"call_name": "Nama panggilan",
"common": {
"camera": "Kamera",
"display_name": "Nama tampilan",
"encrypted": "Terenkripsi",
"home": "Beranda",
"loading": "Memuat…",
"microphone": "Mikrofon",
"password": "Kata sandi",
"profile": "Profil",
"settings": "Pengaturan",
@@ -47,11 +44,6 @@
"username": "Nama pengguna"
},
"disconnected_banner": "Koneksi ke server telah hilang.",
"error": {
"connection_lost_description": "Anda terputus dari panggilan",
"generic_description": "Mengirim catatan pengawakutuan akan membantu kami melacak masalahnya."
},
"full_screen_view_h1": "<0>Aduh, ada yang salah.</0>",
"hangup_button_label": "Akhiri panggilan",
"header_label": "Beranda Element Call",
"header_participants_label": "Peserta",
@@ -105,8 +97,7 @@
"feedback_tab_send_logs_label": "Termasuk catatan pengawakutuan",
"feedback_tab_thank_you": "Terima kasih, kami telah menerima masukan Anda!",
"feedback_tab_title": "Masukan",
"opt_in_description": "<0></0><1></1>Anda dapat mengurungkan kembali izin dengan mencentang kotak ini. Jika Anda saat ini dalam panggilan, pengaturan ini akan diterapkan di akhir panggilan.",
"speaker_device_selection_label": "Pembicara"
"opt_in_description": "<0></0><1></1>Anda dapat mengurungkan kembali izin dengan mencentang kotak ini. Jika Anda saat ini dalam panggilan, pengaturan ini akan diterapkan di akhir panggilan."
},
"star_rating_input_label_one": "{{count}} bintang",
"star_rating_input_label_other": "{{count}} bintang",

View File

@@ -20,7 +20,6 @@
"text": "Tutto pronto per entrare?",
"title": "Seleziona app"
},
"browser_media_e2ee_unsupported": "Il tuo browser non supporta la crittografia end-to-end dei media. I browser supportati sono Chrome, Safari, Firefox >=117",
"call_ended_view": {
"create_account_button": "Crea profilo",
"create_account_prompt": "<0>Ti va di terminare impostando una password per mantenere il profilo?</0><1>Potrai mantenere il tuo nome e impostare un avatar da usare in chiamate future</1>",
@@ -33,23 +32,16 @@
},
"call_name": "Nome della chiamata",
"common": {
"camera": "Fotocamera",
"display_name": "Il tuo nome",
"encrypted": "Cifrata",
"home": "Pagina iniziale",
"loading": "Caricamento…",
"microphone": "Microfono",
"profile": "Profilo",
"settings": "Impostazioni",
"unencrypted": "Non cifrata",
"username": "Nome utente"
},
"disconnected_banner": "La connessione al server è stata persa.",
"error": {
"connection_lost_description": "Sei stato disconnesso dalla chiamata",
"generic_description": "L'invio di registri di debug ci aiuterà ad individuare il problema."
},
"full_screen_view_h1": "<0>Ops, qualcosa è andato storto.</0>",
"hangup_button_label": "Termina chiamata",
"header_label": "Inizio di Element Call",
"header_participants_label": "Partecipanti",
@@ -102,8 +94,7 @@
"feedback_tab_h4": "Invia commento",
"feedback_tab_send_logs_label": "Includi registri di debug",
"feedback_tab_thank_you": "Grazie, abbiamo ricevuto il tuo commento!",
"opt_in_description": "<0></0><1></1>Puoi revocare il consenso deselezionando questa casella. Se attualmente sei in una chiamata, avrà effetto al termine di essa.",
"speaker_device_selection_label": "Altoparlante"
"opt_in_description": "<0></0><1></1>Puoi revocare il consenso deselezionando questa casella. Se attualmente sei in una chiamata, avrà effetto al termine di essa."
},
"star_rating_input_label_one": "{{count}} stelle",
"star_rating_input_label_other": "{{count}} stelle",

View File

@@ -17,20 +17,15 @@
"common": {
"audio": "音声",
"avatar": "アバター",
"camera": "カメラ",
"display_name": "表示名",
"home": "ホーム",
"loading": "読み込んでいます…",
"microphone": "マイク",
"password": "パスワード",
"profile": "プロフィール",
"settings": "設定",
"username": "ユーザー名",
"video": "ビデオ"
},
"error": {
"generic_description": "何かがうまく行きませんでした。"
},
"header_label": "Element Call ホーム",
"join_existing_call_modal": {
"join_button": "はい、通話に参加",
@@ -61,8 +56,7 @@
"settings": {
"developer_tab_title": "開発者",
"feedback_tab_h4": "フィードバックを送信",
"feedback_tab_send_logs_label": "デバッグログを含める",
"speaker_device_selection_label": "スピーカー"
"feedback_tab_send_logs_label": "デバッグログを含める"
},
"unauthenticated_view_body": "アカウントがありませんか? <2>アカウントを作成</2>",
"unauthenticated_view_login_button": "アカウントにログイン",

View File

@@ -26,22 +26,15 @@
"common": {
"audio": "Skaņa",
"avatar": "Attēls",
"camera": "Kamera",
"display_name": "Attēlojamais vārds",
"home": "Sākums",
"loading": "Lādējas…",
"microphone": "Mikrofons",
"password": "Parole",
"profile": "Profils",
"settings": "Iestatījumi",
"username": "Lietotājvārds"
},
"disconnected_banner": "Ir zaudēts savienojums ar serveri.",
"error": {
"connection_lost_description": "Tu tiki atvienots no zvana",
"generic_description": "Atkļūdošanas žurnāla ierakstu iesūtīšana palīdzēs mums atklāt nepilnību."
},
"full_screen_view_h1": "<0>Ak vai, kaut kas nogāja greizi!</0>",
"header_label": "Element Call sākums",
"join_existing_call_modal": {
"join_button": "Jā, pievienoties zvanam",
@@ -84,8 +77,7 @@
"feedback_tab_send_logs_label": "Iekļaut atkļūdošanas žurnāla ierakstus",
"feedback_tab_thank_you": "Paldies, mēs saņēmām atsauksmi!",
"feedback_tab_title": "Atsauksmes",
"opt_in_description": "<0></0><1></1>Savu piekrišanu var atsaukt ar atzīmes noņemšanu no šīs rūtiņas. Ja pašreiz atrodies zvanā, šis iestatījums stāsies spēkā zvana beigās.",
"speaker_device_selection_label": "Runātājs"
"opt_in_description": "<0></0><1></1>Savu piekrišanu var atsaukt ar atzīmes noņemšanu no šīs rūtiņas. Ja pašreiz atrodies zvanā, šis iestatījums stāsies spēkā zvana beigās."
},
"star_rating_input_label_one": "{{count}} zvaigzne",
"star_rating_input_label_other": "{{count}} zvaigznes",

View File

@@ -21,7 +21,6 @@
"text": "Gotowy, by dołączyć?",
"title": "Wybierz aplikację"
},
"browser_media_e2ee_unsupported": "Twoja przeglądarka nie wspiera szyfrowania end-to-end. Wspierane przeglądarki to Chrome, Safari, Firefox >=117",
"call_ended_view": {
"create_account_button": "Utwórz konto",
"create_account_prompt": "<0>Może zechcesz ustawić hasło, aby zachować swoje konto?</0><1>Będziesz w stanie utrzymać swoją nazwę i ustawić awatar do wyświetlania podczas połączeń w przyszłości</1>",
@@ -36,12 +35,10 @@
"common": {
"audio": "Dźwięk",
"avatar": "Awatar",
"camera": "Kamera",
"display_name": "Nazwa wyświetlana",
"encrypted": "Szyfrowane",
"home": "Strona domowa",
"loading": "Ładowanie…",
"microphone": "Mikrofon",
"password": "Hasło",
"profile": "Profil",
"settings": "Ustawienia",
@@ -50,11 +47,6 @@
"video": "Wideo"
},
"disconnected_banner": "Utracono połączenie z serwerem.",
"error": {
"connection_lost_description": "Rozłączono Cię z połączenia",
"generic_description": "Wysłanie dzienników debuggowania pomoże nam ustalić przyczynę problemu."
},
"full_screen_view_h1": "<0>Ojej, coś poszło nie tak.</0>",
"hangup_button_label": "Zakończ połączenie",
"header_label": "Strona główna Element Call",
"header_participants_label": "Uczestnicy",
@@ -108,8 +100,7 @@
"feedback_tab_send_logs_label": "Dołącz dzienniki debugowania",
"feedback_tab_thank_you": "Dziękujemy, otrzymaliśmy Twoją opinię!",
"feedback_tab_title": "Opinia użytkownika",
"opt_in_description": "<0></0><1></1>Możesz wycofać swoją zgodę poprzez odznaczenie tego pola. Jeśli już jesteś w trakcie rozmowy, opcja zostanie zastosowana po jej zakończeniu.",
"speaker_device_selection_label": "Głośnik"
"opt_in_description": "<0></0><1></1>Możesz wycofać swoją zgodę poprzez odznaczenie tego pola. Jeśli już jesteś w trakcie rozmowy, opcja zostanie zastosowana po jej zakończeniu."
},
"star_rating_input_label_one": "{{count}} gwiazdki",
"star_rating_input_label_other": "{{count}} gwiazdki",

View File

@@ -28,9 +28,6 @@
"text": "Sunteți gata să vă alăturați?",
"title": "Selectați aplicația"
},
"application_opened_another_tab": "Această aplicație a fost deschisă într-o altă filă.",
"browser_media_e2ee_unsupported": "Browserul dvs. web nu acceptă criptarea media end-to-end. Browserele acceptate sunt Chrome, Safari, Firefox > = 117",
"browser_media_e2ee_unsupported_heading": "Browser incompatibil",
"call_ended_view": {
"create_account_button": "Creează cont",
"create_account_prompt": "<0>De ce să nu terminați prin configurarea unei parole pentru a vă păstra contul? </0><1>Veți putea să vă păstrați numele și să setați un avatar pentru a fi utilizat la apelurile viitoare </1>",
@@ -47,13 +44,10 @@
"audio": "Audio",
"avatar": "avatar",
"back": "Înapoi",
"camera": "Aparat foto",
"display_name": "Nume afișat",
"encrypted": "Criptat",
"error": "Eroare",
"home": "Acasa",
"loading": "Se încarcă...",
"microphone": "Microfon",
"next": "Urmator\n",
"options": "Opțiuni",
"password": "Parolă",
@@ -62,7 +56,6 @@
"reaction": "Reacție",
"reactions": "Reacții",
"settings": "Settings",
"something_went_wrong": "Ceva nu a mers bine",
"unencrypted": "Nu este criptat",
"username": "Nume utilizator",
"video": "Videoclip"
@@ -75,18 +68,11 @@
"matrix_id": "ID-ul matricei: {{id}}"
},
"disconnected_banner": "Conectivitatea la server a fost pierdută.",
"error": {
"connection_lost_description": "Ai fost deconectat de la apel",
"generic_description": "Trimiterea jurnalelor de depanare ne va ajuta să urmărim problema."
},
"full_screen_view_h1": "<0>Hopa, ceva nu a mers bine. </0>",
"group_call_loader": {
"banned_body": "Ai fost interzis să ieși din cameră.",
"banned_heading": "Interzis",
"call_ended_body": "Ați fost eliminat din apel.",
"call_ended_heading": "Apel încheiat",
"failed_heading": "Nu s-a putut alătura",
"failed_text": "Apelul nu a fost găsit sau nu este accesibil.",
"knock_reject_body": "Cererea dvs. de a vă alătura a fost respinsă.",
"knock_reject_heading": "Acces refuzat",
"reason": "Motivul"
@@ -163,8 +149,7 @@
"reactions_play_sound_label": "Redați sunete de reacție",
"reactions_show_description": "Afișați o animație atunci când cineva trimite o reacție.",
"reactions_show_label": "Afișați reacțiile"
},
"speaker_device_selection_label": "vorbitor"
}
},
"start_new_call": "Începe un nou apel",
"start_video_button_label": "Începeți videoclipul",

View File

@@ -25,21 +25,15 @@
"common": {
"audio": "Аудио",
"avatar": "Аватар",
"camera": "Камера",
"display_name": "Видимое имя",
"home": "Начало",
"loading": "Загрузка…",
"microphone": "Микрофон",
"password": "Пароль",
"profile": "Профиль",
"settings": "Настройки",
"username": "Имя пользователя",
"video": "Видео"
},
"error": {
"generic_description": "Отправка журналов поможет нам найти и устранить проблему."
},
"full_screen_view_h1": "<0>Упс, что-то пошло не так.</0>",
"header_label": "Главная Element Call",
"join_existing_call_modal": {
"join_button": "Да, присоединиться",
@@ -78,8 +72,7 @@
"feedback_tab_send_logs_label": "Приложить журнал отладки",
"feedback_tab_thank_you": "Спасибо. Мы получили ваш отзыв!",
"feedback_tab_title": "Отзыв",
"opt_in_description": "<0></0><1></1>Вы можете отозвать согласие, сняв этот флажок. Если вы в данный момент находитесь в разговоре, эта настройка вступит в силу по окончании разговора.",
"speaker_device_selection_label": "Динамик"
"opt_in_description": "<0></0><1></1>Вы можете отозвать согласие, сняв этот флажок. Если вы в данный момент находитесь в разговоре, эта настройка вступит в силу по окончании разговора."
},
"star_rating_input_label_one": "{{count}} отмечен",
"star_rating_input_label_other": "{{count}} отмеченных",

View File

@@ -21,7 +21,6 @@
"text": "Ste pripravení sa pridať?",
"title": "Vybrať aplikáciu"
},
"browser_media_e2ee_unsupported": "Váš webový prehliadač nepodporuje end-to-end šifrovanie médií. Podporované prehliadače sú Chrome, Safari, Firefox >=117",
"call_ended_view": {
"create_account_button": "Vytvoriť účet",
"create_account_prompt": "<0>Prečo neskončiť nastavením hesla, aby ste si zachovali svoj účet? </0><1>Budete si môcť ponechať svoje meno a nastaviť obrázok, ktorý sa bude používať pri budúcich hovoroch</1>",
@@ -35,12 +34,10 @@
"call_name": "Názov hovoru",
"common": {
"avatar": "Obrázok",
"camera": "Kamera",
"display_name": "Zobrazované meno",
"encrypted": "Šifrované",
"home": "Domov",
"loading": "Načítanie…",
"microphone": "Mikrofón",
"password": "Heslo",
"profile": "Profil",
"settings": "Nastavenia",
@@ -48,11 +45,6 @@
"username": "Meno používateľa"
},
"disconnected_banner": "Spojenie so serverom sa stratilo.",
"error": {
"connection_lost_description": "Boli ste odpojení z hovoru",
"generic_description": "Odoslanie záznamov ladenia nám pomôže nájsť problém."
},
"full_screen_view_h1": "<0>Hups, niečo sa pokazilo.</0>",
"hangup_button_label": "Ukončiť hovor",
"header_label": "Domov Element Call",
"header_participants_label": "Účastníci",
@@ -106,8 +98,7 @@
"feedback_tab_send_logs_label": "Zahrnúť záznamy o ladení",
"feedback_tab_thank_you": "Ďakujeme, dostali sme vašu spätnú väzbu!",
"feedback_tab_title": "Spätná väzba",
"opt_in_description": "<0></0><1></1>Súhlas môžete odvolať zrušením označenia tohto políčka. Ak práve prebieha hovor, toto nastavenie nadobudne platnosť po skončení hovoru.",
"speaker_device_selection_label": "Reproduktor"
"opt_in_description": "<0></0><1></1>Súhlas môžete odvolať zrušením označenia tohto políčka. Ak práve prebieha hovor, toto nastavenie nadobudne platnosť po skončení hovoru."
},
"star_rating_input_label_one": "{{count}} hviezdička",
"star_rating_input_label_other": "{{count}} hviezdičiek",

View File

@@ -15,11 +15,9 @@
},
"common": {
"audio": "Ses",
"camera": "Kamera",
"display_name": "Ekran adı",
"home": "Ev",
"loading": "Yükleniyor…",
"microphone": "Mikrofon",
"password": "Parola",
"settings": "Ayarlar"
},

View File

@@ -21,7 +21,6 @@
"text": "Готові приєднатися?",
"title": "Вибрати застосунок"
},
"browser_media_e2ee_unsupported": "Ваш браузер не підтримує наскрізне шифрування мультимедійних даних. Підтримувані браузери: Chrome, Safari, Firefox >=117",
"call_ended_view": {
"create_account_button": "Створити обліковий запис",
"create_account_prompt": "<0>Чому б не завершити, налаштувавши пароль для збереження свого облікового запису?</0><1>Ви зможете зберегти своє ім'я та встановити аватарку для подальшого користування під час майбутніх викликів</1>",
@@ -36,12 +35,10 @@
"common": {
"audio": "Звук",
"avatar": "Аватар",
"camera": "Камера",
"display_name": "Псевдонім",
"encrypted": "Зашифровано",
"home": "Домівка",
"loading": "Завантаження…",
"microphone": "Мікрофон",
"password": "Пароль",
"profile": "Профіль",
"settings": "Налаштування",
@@ -50,11 +47,6 @@
"video": "Відео"
},
"disconnected_banner": "Втрачено зв'язок з сервером.",
"error": {
"connection_lost_description": "Вас від'єднано від виклику",
"generic_description": "Надсилання журналів налагодження допоможе нам виявити проблему."
},
"full_screen_view_h1": "<0>Йой, щось пішло не за планом.</0>",
"hangup_button_label": "Завершити виклик",
"header_label": "Домівка Element Call",
"header_participants_label": "Учасники",
@@ -108,8 +100,7 @@
"feedback_tab_send_logs_label": "Долучити журнали налагодження",
"feedback_tab_thank_you": "Дякуємо, ми отримали ваш відгук!",
"feedback_tab_title": "Відгук",
"opt_in_description": "<0></0><1></1>Ви можете відкликати згоду, прибравши цей прапорець. Якщо ви зараз розмовляєте, це налаштування застосується після завершення виклику.",
"speaker_device_selection_label": "Динамік"
"opt_in_description": "<0></0><1></1>Ви можете відкликати згоду, прибравши цей прапорець. Якщо ви зараз розмовляєте, це налаштування застосується після завершення виклику."
},
"star_rating_input_label_one": "{{count}} зірок",
"star_rating_input_label_other": "{{count}} зірок",

View File

@@ -17,20 +17,14 @@
"common": {
"audio": "Âm thanh",
"avatar": "Ảnh đại diện",
"camera": "Máy quay",
"display_name": "Tên hiển thị",
"loading": "Đang tải…",
"microphone": "Micrô",
"password": "Mật khẩu",
"profile": "Hồ sơ",
"settings": "Cài đặt",
"username": "Tên người dùng",
"video": "Truyền hình"
},
"error": {
"generic_description": "Gửi nhật ký gỡ lỗi sẽ giúp chúng tôi theo dõi vấn đề."
},
"full_screen_view_h1": "<0>Ối, có cái gì đó sai.</0>",
"join_existing_call_modal": {
"join_button": "Vâng, tham gia cuộc gọi",
"text": "Cuộc gọi đã tồn tại, bạn có muốn tham gia không?",
@@ -62,8 +56,7 @@
"feedback_tab_h4": "Gửi phản hồi",
"feedback_tab_send_logs_label": "Kèm theo nhật ký gỡ lỗi",
"feedback_tab_thank_you": "Cảm ơn, chúng tôi đã nhận được phản hồi!",
"feedback_tab_title": "Phản hồi",
"speaker_device_selection_label": "Loa"
"feedback_tab_title": "Phản hồi"
},
"submitting": "Đang gửi…",
"unauthenticated_view_body": "Chưa đăng ký? <2>Tạo tài khoản</2>",

View File

@@ -19,9 +19,7 @@
"text": "准备好加入了吗?",
"title": "选择应用程序"
},
"browser_media_e2ee_unsupported": "您的浏览器不支持媒体端对端加密。支持的浏览器有 Chrome、Safari、Firefox >=117",
"call_ended_view": {
"body": "通话已中断",
"create_account_button": "创建账户",
"create_account_prompt": "<0>为何不设置密码来保留你的账户?</0><1>保留昵称并设置头像,以便在未来的通话中使用。</1>",
"feedback_done": "<0>感谢反馈!</0>",
@@ -35,12 +33,10 @@
"common": {
"audio": "音频",
"avatar": "头像",
"camera": "摄像头",
"display_name": "显示名称",
"encrypted": "已加密",
"home": "主页",
"loading": "加载中……",
"microphone": "麦克风",
"password": "密码",
"profile": "个人信息",
"settings": "设置",
@@ -49,8 +45,6 @@
"video": "视频"
},
"disconnected_banner": "与服务器的连接中断。",
"full_screen_view_description": "<0>提交日志以帮助我们修复问题。</0>",
"full_screen_view_h1": "<0>哎哟,出问题了。</0>",
"hangup_button_label": "通话结束",
"header_label": "Element Call主页",
"join_existing_call_modal": {
@@ -99,8 +93,7 @@
"feedback_tab_send_logs_label": "包含调试日志",
"feedback_tab_thank_you": "谢谢,我们收到了反馈!",
"feedback_tab_title": "反馈",
"opt_in_description": "<0></0><1></1>您可以取消选中复选框来撤回同意。如果正在通话中,此设置将在通话结束时生效。",
"speaker_device_selection_label": "发言人"
"opt_in_description": "<0></0><1></1>您可以取消选中复选框来撤回同意。如果正在通话中,此设置将在通话结束时生效。"
},
"star_rating_input_label_one": "{{count}} 个星",
"star_rating_input_label_other": "{{count}} 个星",

View File

@@ -21,9 +21,7 @@
"text": "準備好加入了?",
"title": "選取應用程式"
},
"browser_media_e2ee_unsupported": "您的網路瀏覽器不支援媒體端到端加密。支援的瀏覽器包含了 Chrome、Safari、Firefox >=117",
"call_ended_view": {
"body": "您已從通話斷線",
"create_account_button": "建立帳號",
"create_account_prompt": "<0>何不設定密碼以保留此帳號?</0><1>您可以保留暱稱並設定頭像,以便未來通話時使用</1>",
"feedback_done": "<0>感謝您的回饋!</0>",
@@ -37,12 +35,10 @@
"common": {
"audio": "語音",
"avatar": "大頭照",
"camera": "相機",
"display_name": "顯示名稱",
"encrypted": "已加密",
"home": "首頁",
"loading": "載入中…",
"microphone": "麥克風",
"password": "密碼",
"profile": "個人檔案",
"settings": "設定",
@@ -51,8 +47,6 @@
"video": "視訊"
},
"disconnected_banner": "到伺服器的連線已遺失。",
"full_screen_view_description": "<0>送出除錯紀錄,可幫助我們修正問題。</0>",
"full_screen_view_h1": "<0>喔喔,有些地方怪怪的。</0>",
"hangup_button_label": "結束通話",
"header_label": "Element Call 首頁",
"header_participants_label": "參與者",
@@ -106,8 +100,7 @@
"feedback_tab_send_logs_label": "包含除錯紀錄",
"feedback_tab_thank_you": "感謝,我們已經收到您的回饋了!",
"feedback_tab_title": "回饋",
"opt_in_description": "<0></0><1></1>您可以透過取消核取此方塊來撤回同意。若您目前正在通話中,此設定將在通話結束時生效。",
"speaker_device_selection_label": "發言者"
"opt_in_description": "<0></0><1></1>您可以透過取消核取此方塊來撤回同意。若您目前正在通話中,此設定將在通話結束時生效。"
},
"star_rating_input_label_one": "{{count}} 個星星",
"star_rating_input_label_other": "{{count}} 個星星",

View File

@@ -3,8 +3,12 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "NODE_OPTIONS=--max-old-space-size=16384 vite build",
"dev": "yarn dev:full",
"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",
"prettier:check": "prettier -c .",
"prettier:format": "prettier -w .",
@@ -17,7 +21,9 @@
"i18n:check": "i18next --fail-on-warnings --fail-on-update",
"test": "vitest",
"test:coverage": "vitest --coverage",
"backend": "docker-compose -f dev-backend-docker-compose.yml up"
"backend": "docker-compose -f dev-backend-docker-compose.yml up",
"test:playwright": "playwright test",
"test:playwright:open": "yarn test:playwright --ui"
},
"devDependencies": {
"@babel/core": "^7.16.5",
@@ -31,6 +37,7 @@
"@formatjs/intl-segmenter": "^11.7.3",
"@livekit/components-core": "^0.12.0",
"@livekit/components-react": "^2.0.0",
"@livekit/protocol": "^1.33.0",
"@opentelemetry/api": "^1.4.0",
"@opentelemetry/core": "^1.25.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
@@ -38,6 +45,7 @@
"@opentelemetry/sdk-trace-base": "^1.25.1",
"@opentelemetry/sdk-trace-web": "^1.9.1",
"@opentelemetry/semantic-conventions": "^1.25.1",
"@playwright/test": "^1.51.0",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-visually-hidden": "^1.0.3",
@@ -87,10 +95,10 @@
"i18next-parser": "^9.1.0",
"jsdom": "^26.0.0",
"knip": "^5.27.2",
"livekit-client": "^2.5.7",
"livekit-client": "2.9.1",
"lodash-es": "^4.17.21",
"loglevel": "^1.9.1",
"matrix-js-sdk": "^36.1.0",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#8395919f0fd1af7cab1e793d736f2cdf18ef7686",
"matrix-widget-api": "1.11.0",
"normalize.css": "^8.0.1",
"observable-hooks": "^4.2.3",
@@ -113,12 +121,14 @@
"unique-names-generator": "^4.6.0",
"vaul": "^1.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",
"vitest": "^3.0.0",
"vitest-axe": "^1.0.0-pre.3"
},
"resolutions": {
"@livekit/components-core/rxjs": "^7.8.1",
"matrix-widget-api": "1.11.0"
}
}

View File

@@ -0,0 +1,86 @@
networks:
ecbackend:
services:
auth-service:
image: ghcr.io/element-hq/lk-jwt-service:latest-ci
hostname: auth-server
environment:
- LK_JWT_PORT=8080
- LIVEKIT_URL=ws://localhost:7880
- LIVEKIT_KEY=devkey
- LIVEKIT_SECRET=secret
# If the configured homeserver runs on localhost, it'll probably be using
# a self-signed certificate
- LIVEKIT_INSECURE_SKIP_VERIFY_TLS=YES_I_KNOW_WHAT_I_AM_DOING
deploy:
restart_policy:
condition: on-failure
ports:
# HOST_PORT:CONTAINER_PORT
- 8009:8080
networks:
- ecbackend
livekit:
image: livekit/livekit-server:latest
command: --dev --config /etc/livekit.yaml
restart: unless-stopped
# The SFU seems to work far more reliably when we let it share the host
# network rather than opening specific ports (but why?? we're not missing
# any…)
ports:
# HOST_PORT:CONTAINER_PORT
- 7880:7880/tcp
- 7881:7881/tcp
- 7882:7882/tcp
- 50100-50200:50100-50200/udp
volumes:
- ./backend/dev_livekit.yaml:/etc/livekit.yaml:Z
networks:
- ecbackend
redis:
image: redis:6-alpine
command: redis-server /etc/redis.conf
ports:
# HOST_PORT:CONTAINER_PORT
- 6379:6379
volumes:
- ./backend/redis.conf:/etc/redis.conf:Z
networks:
- ecbackend
synapse:
hostname: homeserver
image: docker.io/matrixdotorg/synapse:latest
environment:
- SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml
# Needed for rootless podman-compose such that the uid/gid mapping does
# fit local user uid. If the container runs as root (uid 0) it is fine as
# it actually maps to your non-root user on the host (e.g. 1000).
# Otherwise uid mapping will not match your non-root user.
- UID=0
- GID=0
volumes:
- ./backend/synapse_tmp:/data:Z
- ./backend/playwright_homeserver.yaml:/data/cfg/homeserver.yaml:Z
networks:
- ecbackend
nginx:
# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls_localhost_key.pem -out tls_localhost_cert.pem -subj "/C=GB/ST=London/L=London/O=Alros/OU=IT Department/CN=localhost"
hostname: synapse.localhost
image: nginx:latest
volumes:
- ./backend/tls_localhost_nginx.conf:/etc/nginx/conf.d/default.conf:Z
- ./backend/tls_localhost_key.pem:/root/ssl/key.pem:Z
- ./backend/tls_localhost_cert.pem:/root/ssl/cert.pem:Z
ports:
# HOST_PORT:CONTAINER_PORT
- "8008:80"
- "4443:443"
depends_on:
- synapse
networks:
- ecbackend

81
playwright.config.ts Normal file
View File

@@ -0,0 +1,81 @@
/*
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 { defineConfig, devices } from "@playwright/test";
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./playwright",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "https://localhost:3000",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
permissions: [
"clipboard-write",
"clipboard-read",
"microphone",
"camera",
],
ignoreHTTPSErrors: true,
launchOptions: {
args: [
"--use-fake-ui-for-media-stream",
"--use-fake-device-for-media-stream",
"--mute-audio",
],
},
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
ignoreHTTPSErrors: true,
launchOptions: {
firefoxUserPrefs: {
"permissions.default.microphone": 1,
"permissions.default.camera": 1,
},
},
},
},
// No safari for now, until I find a solution to fix `Not allowed to request resource` due to calling
// clear http to the homeserver
],
/* Run your local dev server before starting the tests */
webServer: {
command: "yarn dev",
url: "https://localhost:3000",
reuseExistingServer: !process.env.CI,
ignoreHTTPSErrors: true,
},
});

131
playwright/access.spec.ts Normal file
View File

@@ -0,0 +1,131 @@
/*
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, test } from "@playwright/test";
test("Sign up a new account, then login, then logout", async ({ browser }) => {
const userId = `test_user-id_${Date.now()}`;
const newUserContext = await browser.newContext();
const newUserPage = await newUserContext.newPage();
await newUserPage.goto("/");
await expect(newUserPage.getByTestId("home_register")).toBeVisible();
await newUserPage.getByTestId("home_register").click();
await newUserPage.getByTestId("register_username").click();
await newUserPage.getByTestId("register_username").fill(userId);
await newUserPage.getByTestId("register_password").click();
await newUserPage.getByTestId("register_password").fill("password1!");
await newUserPage.getByTestId("register_confirm_password").click();
await newUserPage.getByTestId("register_confirm_password").fill("password1!");
await newUserPage.getByTestId("register_register").click();
await expect(
newUserPage.getByRole("heading", { name: "Start new call" }),
).toBeVisible();
// Now use a new page to login this account
const returningUserContext = await browser.newContext();
const returningUserPage = await returningUserContext.newPage();
await returningUserPage.goto("/");
await expect(returningUserPage.getByTestId("home_login")).toBeVisible();
await returningUserPage.getByTestId("home_login").click();
await returningUserPage.getByTestId("login_username").click();
await returningUserPage.getByTestId("login_username").fill(userId);
await returningUserPage.getByTestId("login_password").click();
await returningUserPage.getByTestId("login_password").fill("password1!");
await returningUserPage.getByTestId("login_login").click();
await expect(
returningUserPage.getByRole("heading", { name: "Start new call" }),
).toBeVisible();
// logout
await returningUserPage.getByTestId("usermenu_open").click();
await returningUserPage.locator('[data-test-id="usermenu_logout"]').click();
await expect(
returningUserPage.getByRole("link", { name: "Log In" }),
).toBeVisible();
await expect(returningUserPage.getByTestId("home_login")).toBeVisible();
});
test("As a guest, create a call, share link and other join", async ({
browser,
}) => {
// Use reduce motion to disable animations that are making the tests a bit flaky
const creatorContext = await browser.newContext({ reducedMotion: "reduce" });
const creatorPage = await creatorContext.newPage();
await creatorPage.goto("/");
// ========
// ARRANGE: The first user creates a call as guest, join it, then click the invite button to copy the invite link
// ========
await creatorPage.getByTestId("home_callName").click();
await creatorPage.getByTestId("home_callName").fill("Welcome");
await creatorPage.getByTestId("home_displayName").click();
await creatorPage.getByTestId("home_displayName").fill("Inviter");
await creatorPage.getByTestId("home_go").click();
await expect(creatorPage.locator("video")).toBeVisible();
// join
await creatorPage.getByTestId("lobby_joinCall").click();
// Spotlight mode to make checking the test visually clearer
await creatorPage.getByRole("radio", { name: "Spotlight" }).check();
// Get the invite link
await creatorPage.getByRole("button", { name: "Invite" }).click();
await expect(
creatorPage.getByRole("heading", { name: "Invite to this call" }),
).toBeVisible();
await expect(creatorPage.getByRole("img", { name: "QR Code" })).toBeVisible();
await expect(creatorPage.getByTestId("modal_inviteLink")).toBeVisible();
await expect(creatorPage.getByTestId("modal_inviteLink")).toBeVisible();
await creatorPage.getByTestId("modal_inviteLink").click();
let inviteLink = (await creatorPage.evaluate(
"navigator.clipboard.readText()",
)) as string;
expect(inviteLink).toContain("room/#/");
// ========
// ACT: The other user use the invite link to join the call as a guest
// ========
const guestInviteeContext = await browser.newContext({
reducedMotion: "reduce",
});
const guestPage = await guestInviteeContext.newPage();
await guestPage.goto(inviteLink);
await guestPage.getByTestId("joincall_displayName").fill("Invitee");
await expect(guestPage.getByTestId("joincall_joincall")).toBeVisible();
await guestPage.getByTestId("joincall_joincall").click();
await guestPage.getByTestId("lobby_joinCall").click();
await guestPage.getByRole("radio", { name: "Spotlight" }).check();
// ========
// ASSERT: check that there are two members in the call
// ========
// There should be two participants now
await expect(
guestPage.getByTestId("roomHeader_participants_count"),
).toContainText("2");
expect(await guestPage.getByTestId("videoTile").count()).toBe(2);
// Same in creator page
await expect(
creatorPage.getByTestId("roomHeader_participants_count"),
).toContainText("2");
expect(await creatorPage.getByTestId("videoTile").count()).toBe(2);
// XXX check the display names on the video tiles
});

View File

@@ -0,0 +1,55 @@
/*
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, test } from "@playwright/test";
test("Start a new call then leave and show the feedback screen", async ({
page,
}) => {
await page.goto("/");
await page.getByTestId("home_callName").click();
await page.getByTestId("home_callName").fill("HelloCall");
await page.getByTestId("home_displayName").click();
await page.getByTestId("home_displayName").fill("John Doe");
await page.getByTestId("home_go").click();
await expect(page.locator("video")).toBeVisible();
await expect(page.getByTestId("lobby_joinCall")).toBeVisible();
// Check the button toolbar
// await expect(page.getByRole('button', { name: 'Mute microphone' })).toBeVisible();
// await expect(page.getByRole('button', { name: 'Stop video' })).toBeVisible();
await expect(page.getByRole("button", { name: "Settings" })).toBeVisible();
await expect(page.getByRole("button", { name: "End call" })).toBeVisible();
// Join the call
await page.getByTestId("lobby_joinCall").click();
// Ensure that the call is connected
await page
.locator("div")
.filter({ hasText: /^HelloCall$/ })
.click();
// Check the number of participants
await expect(page.locator("div").filter({ hasText: /^1$/ })).toBeVisible();
// The tooltip with the name should be visible
await expect(page.getByTestId("name_tag")).toContainText("John Doe");
// leave the call
await page.getByTestId("incall_leave").click();
await expect(page.getByRole("heading")).toContainText(
"John Doe, your call has ended. How did it go?",
);
await expect(page.getByRole("main")).toContainText(
"Why not finish by setting up a password to keep your account?",
);
await expect(
page.getByRole("link", { name: "Not now, return to home screen" }),
).toBeVisible();
});

View 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 { test, expect } from "@playwright/test";
test("has title", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/Element Call/);
});
test("Landing page", async ({ page }) => {
await page.goto("/");
// There should be a login button in the header
await expect(page.getByRole("link", { name: "Log In" })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Start new call" }),
).toBeVisible();
await expect(page.getByTestId("home_callName")).toBeVisible();
await expect(page.getByTestId("home_displayName")).toBeVisible();
await expect(page.getByTestId("home_go")).toBeVisible();
});

View File

@@ -1,21 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.png" />
<link rel="preload" href="/config.json" as="fetch" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
/>
<title><%- title %></title>
<script>
window.global = window;
</script>
</head>
<!-- The default class is: .no-theme {display: none}. It will be overwritten once the app is loaded. -->
<body class="no-theme">
<div id="root"></div>
</body>
</html>

View File

@@ -1,11 +1,13 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
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 Controls } from "../controls";
@@ -18,6 +20,7 @@ declare global {
interface Window {
controls: Controls;
setLKLogLevel: typeof setLKLogLevel;
}
interface HTMLElement {

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2021-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -72,7 +72,11 @@ export const App: FC = () => {
<Suspense fallback={null}>
<ClientProvider>
<MediaDevicesProvider>
<Sentry.ErrorBoundary fallback={ErrorPage}>
<Sentry.ErrorBoundary
fallback={(error) => (
<ErrorPage error={error} widget={widget} />
)}
>
<DisconnectedBanner />
<Routes>
<SentryRoute path="/" element={<HomePage />} />

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2021-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -351,7 +351,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
}, [initClientState, onSync]);
if (alreadyOpenedErr) {
return <ErrorPage error={alreadyOpenedErr} />;
return <ErrorPage widget={widget} error={alreadyOpenedErr} />;
}
return (

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -12,13 +12,16 @@ import {
type FC,
type ReactNode,
type SVGAttributes,
type ReactElement,
} from "react";
import { useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger";
import { RageshakeButton } from "./settings/RageshakeButton";
import styles from "./ErrorView.module.css";
import { useUrlParams } from "./UrlParams";
import { LinkButton } from "./button";
import { ElementWidgetActions, type WidgetHelpers } from "./widget.ts";
interface Props {
Icon: ComponentType<SVGAttributes<SVGElement>>;
@@ -35,6 +38,7 @@ interface Props {
*/
fatal?: boolean;
children: ReactNode;
widget: WidgetHelpers | null;
}
export const ErrorView: FC<Props> = ({
@@ -43,6 +47,7 @@ export const ErrorView: FC<Props> = ({
rageshake,
fatal,
children,
widget,
}) => {
const { t } = useTranslation();
const { confineToRoom } = useUrlParams();
@@ -51,6 +56,46 @@ export const ErrorView: FC<Props> = ({
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 (
<div className={styles.error}>
<BigIcon className={styles.icon}>
@@ -63,20 +108,11 @@ export const ErrorView: FC<Props> = ({
{rageshake && (
<RageshakeButton description={`***Error View***: ${title}`} />
)}
{!confineToRoom &&
(fatal || location.pathname === "/" ? (
<Button
kind="tertiary"
className={styles.homeLink}
onClick={onReload}
>
{t("return_home_button")}
</Button>
) : (
<LinkButton kind="tertiary" className={styles.homeLink} to="/">
{t("return_home_button")}
</LinkButton>
))}
{widget ? (
<CloseWidgetButton widget={widget} />
) : (
!confineToRoom && <ReturnToHomeButton />
)}
</div>
);
};

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -17,6 +17,7 @@ import styles from "./FullScreenView.module.css";
import { useUrlParams } from "./UrlParams";
import { RichError } from "./RichError";
import { ErrorView } from "./ErrorView";
import { type WidgetHelpers } from "./widget.ts";
interface FullScreenViewProps {
className?: string;
@@ -30,10 +31,14 @@ export const FullScreenView: FC<FullScreenViewProps> = ({
const { hideHeader } = useUrlParams();
return (
<div className={classNames(styles.page, className)}>
<Header>
<LeftNav>{!hideHeader && <HeaderLogo />}</LeftNav>
<RightNav />
</Header>
{!hideHeader && (
<Header>
<LeftNav>
<HeaderLogo />
</LeftNav>
<RightNav />
</Header>
)}
<div className={styles.container}>
<div className={styles.content}>{children}</div>
</div>
@@ -43,11 +48,12 @@ export const FullScreenView: FC<FullScreenViewProps> = ({
interface ErrorPageProps {
error: Error | unknown;
widget: WidgetHelpers | null;
}
// 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>
export const ErrorPage = ({ error }: ErrorPageProps): ReactElement => {
export const ErrorPage = ({ error, widget }: ErrorPageProps): ReactElement => {
const { t } = useTranslation();
useEffect(() => {
logger.error(error);
@@ -59,7 +65,13 @@ export const ErrorPage = ({ error }: ErrorPageProps): ReactElement => {
{error instanceof RichError ? (
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>
</ErrorView>
)}

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -161,7 +161,12 @@ export const RoomHeaderInfo: FC<RoomHeaderInfoProps> = ({
height={20}
aria-label={t("header_participants_label")}
/>
<Text as="span" size="sm" weight="medium">
<Text
as="span"
size="sm"
weight="medium"
data-testid="roomHeader_participants_count"
>
{t("participant_count", { count: participantCount ?? 0 })}
</Text>
</div>

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -46,7 +46,7 @@ test("the modal can be closed by clicking the close button", async () => {
}
const user = userEvent.setup();
const { queryByRole, getByRole } = render(<ModalFn />);
await user.click(getByRole("button", { name: "action.close" }));
await user.click(getByRole("button", { name: "Close" }));
expect(queryByRole("dialog")).toBeNull();
});

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,15 +1,16 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { type FC, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import type { FC, ReactNode } from "react";
import { ErrorView } from "./ErrorView";
import { widget } from "./widget.ts";
/**
* 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();
return (
<ErrorView Icon={PopOutIcon} title={t("error.open_elsewhere")}>
<ErrorView
widget={widget}
Icon={PopOutIcon}
title={t("error.open_elsewhere")}
>
<p>
{t("error.open_elsewhere_description", {
brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call",

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -110,8 +110,8 @@ describe("UrlParams", () => {
});
describe("returnToLobby", () => {
it("is true in SPA mode", () => {
expect(getUrlParams("?returnToLobby=false").returnToLobby).toBe(true);
it("is false in SPA mode", () => {
expect(getUrlParams("?returnToLobby=true").returnToLobby).toBe(false);
});
it("defaults to false in widget mode", () => {

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -264,7 +264,9 @@ export const getUrlParams = (
"skipLobby",
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"),
viaServers: !isWidget ? parser.getParam("viaServers") : null,
homeserver: !isWidget ? parser.getParam("homeserver") : null,

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -1,7 +1,7 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

View File

@@ -5,7 +5,7 @@ exports[`QrCode > renders 1`] = `
class="qrCode bar"
>
<img
alt="qr_code"
alt="QR Code"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAYAAABUmhYnAAAAAklEQVR4AewaftIAAALBSURBVO3BQW7kQAwEwSxC//9yro88NSBI4/UQjIg/WGMUa5RijVKsUYo1SrFGKdYoxRqlWKMUa5RijVKsUYo1SrFGKdYoxRrl4qEk/CaVkyR0Kl0STlS6JPwmlSeKNUqxRinWKBcvU3lTEk6S8ITKHSpvSsKbijVKsUYp1igXH5aEO1Q+SaVLQqdyRxLuUPmkYo1SrFGKNcrFl1PpknCShE5lkmKNUqxRijXKxZdLQqdyotIloVP5ZsUapVijFGuUiw9T+UuS8CaVv6RYoxRrlGKNcvGyJPwlSehUuiTckYS/rFijFGuUYo0Sf/DFkvAmlW9WrFGKNUqxRrl4KAknKl0SOpWTJJyodEk4UbkjCXeodEk4UXlTsUYp1ijFGuXiIZUuCXck4USlS0KXhE7lk1TelIRO5YlijVKsUYo1ysXLVLok3KHSJaFT6ZLQJaFTOUnCicodSehUTpLwpmKNUqxRijXKxUNJ6FSeSEKn0iXhROUkCZ3Kb0pCp/KmYo1SrFGKNcrFh6mcJKFT6ZJwotIloVPpVLokdCpdEjqVLgmdyh1J6FSeKNYoxRqlWKPEH3yxJHQqJ0noVO5IwolKl4ROpUtCp/JEsUYp1ijFGuXioST8JpU7ktCpnCShUzlROVHpktCpvKlYoxRrlGKNcvEylTcl4USlS8JJEt6UhBOVTqVLQqfyRLFGKdYoxRrl4sOScIfKEyonSehUTpJwh0qXhN9UrFGKNUqxRrn4ckn4JJU7kvA/FWuUYo1SrFEuvpxKl4QTlS4JncodSehU7kjCm4o1SrFGKdYoFx+m8klJOFE5UemScKJyRxI6lU7lTcUapVijFGuUi5cl4X9SOUnCicoTSThJQqfypmKNUqxRijVK/MEao1ijFGuUYo1SrFGKNUqxRinWKMUapVijFGuUYo1SrFGKNUqxRinWKP8AKoQP/lIBoMIAAAAASUVORK5CYII="
/>
</div>

Some files were not shown because too many files have changed in this diff Show More