Compare commits

...

150 Commits

Author SHA1 Message Date
Hugh Nimmo-Smith
7b31c7ec1e Lint 2024-07-19 09:55:38 +01:00
Hugh Nimmo-Smith
9b68539719 Use Node 20 2024-07-19 09:07:01 +01:00
Hugh Nimmo-Smith
2c5b1de6b4 Use prototype of matrix-js-sdk to send heartbeats and show offline status in UI 2024-07-17 11:56:45 +01:00
Hugh Nimmo-Smith
76a494464e Prototyping of showing video tile for every call membership
...even if they don't have a LK participant
2024-07-17 11:08:46 +01:00
Robin
486430d1f0 Merge pull request #2478 from element-hq/renovate/compound
Update dependency @vector-im/compound-design-tokens to v1.6.0
2024-07-12 14:19:29 -04:00
renovate[bot]
e6ddf40b1b Update dependency @vector-im/compound-design-tokens to v1.6.0 2024-07-12 16:55:33 +00:00
fkwp
9f521a79f7 Merge pull request #2477 from element-hq/renovate/livekit-client
Update dependency livekit-client to v2.3.2
2024-07-11 10:22:54 +02:00
fkwp
83784a717a Merge pull request #2476 from element-hq/renovate/livekit-components
Update dependency @livekit/components-react to v2.3.6
2024-07-11 10:22:18 +02:00
renovate[bot]
0729deee79 Update dependency livekit-client to v2.3.2 2024-07-11 08:18:46 +00:00
renovate[bot]
77c3114cf8 Update dependency @livekit/components-react to v2.3.6 2024-07-11 08:18:27 +00:00
Robin
82a56c8204 Merge pull request #2471 from element-hq/renovate/major-vitest-monorepo
Update dependency vitest to v2
2024-07-09 10:42:23 -04:00
renovate[bot]
b39896d8c6 Update dependency @vector-im/compound-design-tokens to v1.4.0 (#2472)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 11:34:44 +02:00
renovate[bot]
79b3fdb645 Update dependency vitest to v2 2024-07-08 13:01:11 +00:00
renovate[bot]
0f877cd021 Update dependency tinyqueue to v3 (#2468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-08 09:12:26 +02:00
renovate[bot]
db2acc75b2 Update LiveKit components (#2469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-08 09:11:56 +02:00
renovate[bot]
a5dbfbf2c1 Update all non-major dependencies (#2470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-08 09:11:25 +02:00
Andrew Ferrazzutti
34c7d02de2 Add config to send session membership state events (#2460)
If not set, legacy call membership state events are sent instead.
Even if set, legacy events are sent in rooms with active legacy calls.

---------

Co-authored-by: Timo <16718859+toger5@users.noreply.github.com>
2024-07-05 21:10:16 +09:00
Andrew Ferrazzutti
ca45067158 Let Element Call widget set session memberships (#2459)
Make Element Call widgets request permission to set device-specific
session membership state events.
2024-07-05 04:59:48 +09:00
Andrew Ferrazzutti
5a6eb7c573 Make widgets request the room creation event (#2457)
This allows the widget to check the room version, so it can know about
version-specific auth rules (namely MSC3779).
2024-07-05 04:57:45 +09:00
Robin
5ad2a27a92 Merge pull request #2462 from element-hq/renovate/github-actions
Update docker/build-push-action action to v6.3.0
2024-07-03 15:25:13 -04:00
renovate[bot]
68daaa45f9 Update docker/build-push-action action to v6.3.0 2024-07-03 11:29:17 +00:00
renovate[bot]
c40ea35937 Update all non-major dependencies (#2461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 10:41:19 +02:00
Timo
d27f433175 Skip error screen for the issue, that the homeserver does not support the room summary endpoint. (#2453)
* Add try inner try block to the room summary fetching and only throw after fetching and a "blind join" fails.
(blind join: call room.join without knowing if the room is public)

Co-authored-by: Robin <robin@robin.town>

---------

Co-authored-by: Robin <robin@robin.town>
2024-06-25 08:44:02 +00:00
Robin
8a6101cd14 Merge pull request #2456 from element-hq/renovate/all-minor-patch
Update all non-major dependencies
2024-06-24 10:37:18 -04:00
Robin
4db7c2bc68 Fix type errors 2024-06-24 10:31:50 -04:00
renovate[bot]
18740fc686 Update all non-major dependencies 2024-06-24 13:04:28 +00:00
Robin
0c39398493 Merge pull request #2447 from element-hq/renovate/compound
Update dependency @vector-im/compound-design-tokens to v1.3.0
2024-06-21 10:39:03 -04:00
Robin
949145f04b Merge pull request #2446 from element-hq/renovate/major-testing-library-monorepo
Update dependency @testing-library/react to v16
2024-06-21 10:32:30 -04:00
Robin
8578dcadf2 Add missing peer dependencies 2024-06-21 10:30:39 -04:00
renovate[bot]
959db44eca Update dependency @testing-library/react to v16 2024-06-21 10:30:28 -04:00
Robin
a031c0e128 Merge pull request #2445 from element-hq/renovate/major-sentry-javascript-monorepo
Update dependency @sentry/react to v8
2024-06-21 10:25:04 -04:00
Robin
591833505f Adapt to breaking changes 2024-06-21 10:23:30 -04:00
Robin
f7ad5074d8 Merge pull request #2452 from element-hq/renovate/i18next-parser-9.x
Update dependency i18next-parser to v9
2024-06-21 10:14:39 -04:00
Robin
e0aef74bf5 Merge pull request #2454 from element-hq/renovate/uuid-10.x
Update dependency uuid to v10
2024-06-21 10:13:01 -04:00
renovate[bot]
b2378bf899 Update dependency i18next-parser to v9 2024-06-21 14:12:34 +00:00
Robin
255f6b1814 Merge pull request #2451 from element-hq/renovate/i18next-browser-languagedetector-8.x
Update dependency i18next-browser-languagedetector to v8
2024-06-21 10:11:59 -04:00
Robin
4c491b5363 Merge pull request #2450 from element-hq/renovate/eslint-plugin-unicorn-54.x
Update dependency eslint-plugin-unicorn to v54
2024-06-21 10:11:23 -04:00
Robin
61c808d4cf Merge pull request #2455 from element-hq/renovate/github-actions
Pin dependencies
2024-06-21 10:00:40 -04:00
Robin
13ef3183e2 Tell Renovate that we're trying to pin actions to specific tags
It thought that we were just trying to follow the latest commit on these actions, when in reality we want to follow the latest tag and pin its commit hash.
2024-06-21 09:57:48 -04:00
renovate[bot]
afd4fdcea2 Pin dependencies 2024-06-21 13:21:08 +00:00
Robin
982181ccd4 Merge pull request #2444 from robintown/more-renovate
Refine Renovate config further
2024-06-21 09:10:16 -04:00
renovate[bot]
30629ebba2 Update dependency uuid to v10 2024-06-21 10:07:33 +00:00
renovate[bot]
7f6a32d21a Update dependency i18next-browser-languagedetector to v8 2024-06-21 08:46:24 +00:00
renovate[bot]
320ade0a50 Update dependency eslint-plugin-unicorn to v54 2024-06-21 04:03:53 +00:00
renovate[bot]
8c6fee3150 Update dependency @vector-im/compound-design-tokens to v1.3.0 2024-06-21 00:35:09 +00:00
renovate[bot]
5c6acaf915 Update dependency @sentry/react to v8 2024-06-20 21:46:07 +00:00
Robin
c46549b2b6 Refine Renovate config further
By getting it to pin GitHub Actions to specific commits, which hardens our workflows, and fixing a warning about matchPackageNames
2024-06-20 16:00:52 -04:00
fkwp
97a58f6db7 Merge pull request #2442 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to 31159d4
2024-06-20 21:48:42 +02:00
fkwp
b6288579c9 Merge pull request #2443 from element-hq/renovate/docker-setup-buildx-action-digest
Update docker/setup-buildx-action digest to abe89fb
2024-06-20 21:48:25 +02:00
renovate[bot]
44bf987cdc Update docker/setup-buildx-action digest to abe89fb 2024-06-20 19:45:16 +00:00
renovate[bot]
a7d55824bb Update docker/build-push-action digest to 31159d4 2024-06-20 19:45:13 +00:00
Robin
8fa038c61f Merge pull request #2441 from robintown/renovate-noise
Reduce noise coming from Renovate updates
2024-06-20 15:44:38 -04:00
Robin
869d9b43cb Reduce noise coming from Renovate updates
What I've tried to do here is to group most dependency updates together and put them on a weekly schedule. Some of our more sensitive dependencies such as LiveKit and Compound have been put into separate groups, so we still receive frequent updates for them.
2024-06-20 15:43:21 -04:00
Robin
974a2fe49b Merge pull request #2438 from element-hq/dependabot/npm_and_yarn/ws-8.17.1
Bump ws from 8.16.0 to 8.17.1
2024-06-20 11:00:16 -04:00
dependabot[bot]
022497c8e5 Bump ws from 8.16.0 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 8.16.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.16.0...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-20 14:58:51 +00:00
Robin
207a5f047c Merge pull request #2430 from robintown/update-js-sdk
Update matrix-js-sdk
2024-06-20 10:54:27 -04:00
Robin
d3c63f9314 Update matrix-js-sdk
To pull in https://github.com/matrix-org/matrix-js-sdk/pull/4252
2024-06-20 10:52:06 -04:00
Robin
f9ef037cea Merge pull request #2435 from element-hq/hughns/visibilitychange
Use correct DOM event name for visibility changes in useWakeLock
2024-06-20 10:19:05 -04:00
fkwp
8666ffec81 Merge pull request #2437 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to f6010ea
2024-06-20 16:17:42 +02:00
fkwp
4b85879891 Merge pull request #2436 from element-hq/renovate/livekit-client-2.x-lockfile
Update dependency livekit-client to v2.3.1
2024-06-20 16:07:06 +02:00
renovate[bot]
f376291f50 Update docker/build-push-action digest to f6010ea 2024-06-20 14:05:57 +00:00
renovate[bot]
f0f56bf101 Update dependency livekit-client to v2.3.1 2024-06-20 14:05:24 +00:00
Hugh Nimmo-Smith
ba647780e8 Use correct DOM event name for visibility changes in useWakeLock 2024-06-19 17:54:15 +01:00
Timo
812ae2ce89 Load focus information from well known and use client config only as a fallback. (#2358)
* Load focus information from well known and use client config only as a fallback.

Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
2024-06-19 16:41:52 +02:00
renovate[bot]
09ca3b4dc0 Update typescript-eslint monorepo to v7.13.1 (#2424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 13:33:22 +02:00
renovate[bot]
86afde8612 Update dependency @vector-im/compound-web to v3.3.1 (#2408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 13:30:36 +02:00
renovate[bot]
469f0b5983 Update dependency eslint-plugin-react to v7.34.3 (#2410)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 13:26:40 +02:00
fkwp
69d1beaf28 Merge pull request #2431 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to 94f8f8c
2024-06-18 20:18:56 +02:00
renovate[bot]
7f22f442b1 Update docker/build-push-action digest to 94f8f8c 2024-06-18 18:08:03 +00:00
fkwp
ab0f8fa2e3 Merge pull request #2427 from element-hq/renovate/livekit-components-react-2.x-lockfile
Update dependency @livekit/components-react to v2.3.3
2024-06-18 10:10:43 +02:00
renovate[bot]
f5abbb1e5e Update dependency @livekit/components-react to v2.3.3 2024-06-18 08:08:31 +00:00
fkwp
a80c96d187 Merge pull request #2426 from element-hq/renovate/livekit-components-core-0.x-lockfile
Update dependency @livekit/components-core to v0.10.3
2024-06-18 10:06:44 +02:00
renovate[bot]
fb0c8fb92b Update dependency @livekit/components-core to v0.10.3 2024-06-18 08:05:01 +00:00
fkwp
438a6c2a42 Merge pull request #2425 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to 4367da9
2024-06-18 10:03:30 +02:00
fkwp
0c8cd0842a Merge pull request #2428 from element-hq/renovate/livekit-client-2.x-lockfile
Update dependency livekit-client to v2.3.0
2024-06-18 10:02:52 +02:00
renovate[bot]
b39d35d5d1 Update dependency livekit-client to v2.3.0 2024-06-18 08:00:45 +00:00
renovate[bot]
6f00a961c9 Update docker/build-push-action digest to 4367da9 2024-06-18 07:59:46 +00:00
Timo
0ed7853958 Remove EventType.GroupCallPrefix from widget capabilities (#2399)
Its not used anymore.
2024-06-17 20:04:49 +02:00
renovate[bot]
4acf279f32 Update dependency posthog-js to v1.139.2 (#2414)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 14:19:18 +02:00
fkwp
a7c065f300 Merge pull request #2422 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to cfed4e9
2024-06-13 20:47:39 +02:00
renovate[bot]
a0d248065d Update docker/build-push-action digest to cfed4e9 2024-06-13 07:42:06 +00:00
fkwp
c18008e039 Merge pull request #2419 from element-hq/renovate/livekit-components-react-2.x-lockfile
Update dependency @livekit/components-react to v2.3.2
2024-06-10 15:24:45 +02:00
renovate[bot]
0bab898c25 Update dependency @livekit/components-react to v2.3.2 2024-06-06 09:54:12 +00:00
Robin
53d1f717c9 Merge pull request #2406 from element-hq/renovate/testing-library-monorepo
Update dependency @testing-library/react to v14.3.1
2024-06-04 12:11:35 -04:00
Robin
0a69664186 Merge pull request #2405 from element-hq/renovate/sentry-vite-plugin-2.x-lockfile
Update dependency @sentry/vite-plugin to v2.18.0
2024-06-04 12:10:26 -04:00
renovate[bot]
d1307e61b9 Update dependency @testing-library/react to v14.3.1 2024-06-04 16:00:57 +00:00
renovate[bot]
a267eca78d Update dependency @sentry/vite-plugin to v2.18.0 2024-06-04 16:00:43 +00:00
Robin
51a8d2b718 Merge pull request #2404 from element-hq/renovate/opentelemetry-instrumentation-user-interaction-0.x
Update dependency @opentelemetry/instrumentation-user-interaction to ^0.38.0
2024-06-04 12:00:35 -04:00
Robin
2d76a3780d Merge pull request #2402 from element-hq/renovate/vite-5.x-lockfile
Update dependency vite to v5.2.12
2024-06-04 11:59:30 -04:00
renovate[bot]
e70818b2da Update dependency @opentelemetry/instrumentation-user-interaction to ^0.38.0 2024-06-04 15:59:12 +00:00
Robin
03144783ac Merge pull request #2401 from element-hq/renovate/prettier-3.x-lockfile
Update dependency prettier to v3.3.0
2024-06-04 11:59:03 -04:00
Robin
b9ab2fdf1b Merge pull request #2293 from element-hq/renovate/opentelemetry-instrumentation-document-load-0.x
Update dependency @opentelemetry/instrumentation-document-load to ^0.38.0
2024-06-04 11:58:28 -04:00
Robin
183a1845bb Merge pull request #2292 from element-hq/renovate/i18next-browser-languagedetector-7.x-lockfile
Update dependency i18next-browser-languagedetector to v7.2.1
2024-06-04 11:58:08 -04:00
Robin
e423fc0ace Merge pull request #2231 from element-hq/renovate/grecaptcha-3.x-lockfile
Update dependency @types/grecaptcha to v3.0.9
2024-06-04 11:57:39 -04:00
Robin
40cbe5408e Merge pull request #2403 from element-hq/renovate/react-monorepo
Update react monorepo
2024-06-04 11:56:52 -04:00
renovate[bot]
2c3e95f401 Update react monorepo 2024-06-04 15:23:02 +00:00
renovate[bot]
1fdff9bbd4 Update dependency @types/grecaptcha to v3.0.9 2024-06-04 15:22:52 +00:00
Robin
bd7209cd40 Merge pull request #2250 from element-hq/renovate/typescript-eslint-monorepo
Update typescript-eslint monorepo to v7.12.0
2024-06-04 11:21:59 -04:00
Robin
70fdc68b13 Fix lints 2024-06-04 11:20:25 -04:00
renovate[bot]
f9bed2c2a9 Update dependency vite to v5.2.12 2024-06-04 14:35:42 +00:00
renovate[bot]
33437d9743 Update dependency i18next-browser-languagedetector to v7.2.1 2024-06-04 14:35:27 +00:00
Robin
1f4139ae0a Merge pull request #2271 from element-hq/renovate/lodash-4.x-lockfile
Update dependency @types/lodash to v4.17.4
2024-06-04 10:35:10 -04:00
Robin
b8ad8baf9d Merge pull request #2335 from element-hq/renovate/i18next-http-backend-2.x-lockfile
Update dependency i18next-http-backend to v2.5.2
2024-06-04 10:34:44 -04:00
Robin
694048dd7f Merge pull request #2391 from element-hq/renovate/react-i18next-14.x-lockfile
Update dependency react-i18next to v14.1.2
2024-06-04 10:34:14 -04:00
Robin
a5088d4ae9 Merge pull request #2392 from element-hq/renovate/vaul-0.x-lockfile
Update dependency vaul to v0.9.1
2024-06-04 10:33:45 -04:00
Robin
d2bdaf7049 Merge pull request #2397 from dreamflasher/patch-1
Remove excessive footer space
2024-06-04 10:11:13 -04:00
Robin
0aeb68b445 Reduce footer space while preserving visual balance 2024-06-04 10:05:21 -04:00
fkwp
f40740edd3 Merge pull request #2400 from element-hq/renovate/livekit-client-2.x-lockfile
Update dependency livekit-client to v2.2.0
2024-06-04 15:46:30 +02:00
renovate[bot]
cd47b63d29 Update dependency livekit-client to v2.2.0 2024-06-04 13:35:35 +00:00
renovate[bot]
07ce272e9f Update typescript-eslint monorepo to v7.12.0 2024-06-03 19:47:09 +00:00
renovate[bot]
a3c4e3e2a5 Update dependency prettier to v3.3.0 2024-06-02 17:36:19 +00:00
Marcel Ackermann
6b6ad16306 remove excessive footer space 2024-06-01 13:10:15 +02:00
Robin
0f38445fdd Merge pull request #2395 from robintown/participants-color
Update participant count color
2024-05-31 08:41:14 -04:00
Robin
b9b53ec251 Update participant count color
It has changed in recent designs.
2024-05-30 21:36:50 -04:00
fkwp
b1a5c8c120 Merge pull request #2393 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to ca052bb
2024-05-29 17:07:35 +02:00
fkwp
b44680149d Merge pull request #2394 from element-hq/renovate/docker-login-action-digest
Update docker/login-action digest to 0d4c9c5
2024-05-29 17:07:17 +02:00
renovate[bot]
cd0cec32b5 Update docker/login-action digest to 0d4c9c5 2024-05-29 14:58:52 +00:00
renovate[bot]
2faa9c9d50 Update docker/build-push-action digest to ca052bb 2024-05-29 14:58:48 +00:00
renovate[bot]
caab45ee7f Update dependency vaul to v0.9.1 2024-05-27 13:03:40 +00:00
renovate[bot]
d3687298e0 Update dependency react-i18next to v14.1.2 2024-05-27 13:02:31 +00:00
fkwp
42612476b8 Merge pull request #2389 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to ef6cba3
2024-05-27 15:01:54 +02:00
fkwp
7021ea6a5c Merge pull request #2390 from element-hq/renovate/docker-login-action-digest
Update docker/login-action digest to db77945
2024-05-27 15:01:38 +02:00
renovate[bot]
ff09631546 Update docker/login-action digest to db77945 2024-05-27 12:31:53 +00:00
renovate[bot]
28a3dfef23 Update dependency @sentry/vite-plugin to v2.17.0 (#2255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 14:31:15 +02:00
renovate[bot]
2b5561a88c Update docker/build-push-action digest to ef6cba3 2024-05-27 12:30:42 +00:00
renovate[bot]
9dc740c2de Update dependency @types/node to v20.12.12 (#2388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 14:30:20 +02:00
Daniel Kilimnik
0b93374e86 package.json add name field (#2386) 2024-05-27 14:29:15 +02:00
renovate[bot]
d36af0cae6 Update dependency eslint-plugin-deprecate to v0.8.5 (#2387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 12:31:52 +02:00
fkwp
7fae106da2 Merge pull request #2383 from element-hq/renovate/livekit-components-core-0.x-lockfile
Update dependency @livekit/components-core to v0.10.2
2024-05-27 11:02:19 +02:00
renovate[bot]
a84b692f20 Update dependency @livekit/components-core to v0.10.2 2024-05-27 08:57:34 +00:00
fkwp
86a5c24750 Merge pull request #2384 from element-hq/renovate/livekit-components-react-2.x-lockfile
Update dependency @livekit/components-react to v2.3.1
2024-05-27 10:56:35 +02:00
fkwp
ed6f02ac56 add manually import resolution for livekit-client/dist/src/room/track/TrackPublication 2024-05-27 08:54:50 +00:00
renovate[bot]
f701886aa9 Update babel monorepo to v7.24.6 (#2229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 17:13:50 +02:00
renovate[bot]
d298e3438c Update dependency @livekit/components-react to v2.3.1 2024-05-22 16:01:37 +00:00
Andrew Ferrazzutti
c405b61c66 Wait for a full sync on startup (#2372)
This avoids missing any state not included in the latest cached sync.
2024-05-23 01:00:17 +09:00
renovate[bot]
6be67aa145 Update dependency i18next-http-backend to v2.5.2 2024-05-17 15:12:40 +00:00
renovate[bot]
603c658949 Update dependency @types/lodash to v4.17.4 2024-05-17 04:37:35 +00:00
fkwp
ebc33e003d Merge pull request #2379 from element-hq/renovate/livekit-client-2.x-lockfile
Update dependency livekit-client to v2.1.5
2024-05-16 14:42:42 +02:00
renovate[bot]
023ab9fc47 Update dependency livekit-client to v2.1.5 2024-05-16 12:40:49 +00:00
fkwp
417faf795d Merge pull request #2375 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to 2a53c6c
2024-05-15 15:39:34 +02:00
fkwp
c8fe393fcf Merge pull request #2376 from element-hq/renovate/docker-login-action-digest
Update docker/login-action digest to 70fccc7
2024-05-15 15:39:15 +02:00
fkwp
e6a9555a91 Merge pull request #2377 from element-hq/renovate/docker-metadata-action-digest
Update docker/metadata-action digest to f7b4ed1
2024-05-15 15:38:54 +02:00
fkwp
fd65baed58 Merge pull request #2378 from element-hq/renovate/docker-setup-buildx-action-digest
Update docker/setup-buildx-action digest to 5138f76
2024-05-15 15:38:33 +02:00
renovate[bot]
d079bee5e0 Update docker/setup-buildx-action digest to 5138f76 2024-05-15 13:25:12 +00:00
renovate[bot]
1918478069 Update docker/metadata-action digest to f7b4ed1 2024-05-15 13:25:08 +00:00
renovate[bot]
c6d8d5e137 Update docker/login-action digest to 70fccc7 2024-05-15 13:25:04 +00:00
renovate[bot]
7a4583dcb0 Update docker/build-push-action digest to 2a53c6c 2024-05-15 13:06:40 +00:00
fkwp
929175d826 Merge pull request #2374 from element-hq/renovate/docker-build-push-action-digest
Update docker/build-push-action digest to df19a79
2024-05-15 15:05:58 +02:00
renovate[bot]
49ce642c2d Update docker/build-push-action digest to df19a79 2024-05-14 18:32:46 +00:00
renovate[bot]
43f7f9b76c Update dependency @opentelemetry/instrumentation-document-load to ^0.38.0 2024-04-25 20:18:38 +00:00
55 changed files with 3716 additions and 2891 deletions

View File

@@ -23,10 +23,10 @@ jobs:
packages: write
steps:
- name: Check it out
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: 📥 Download artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ inputs.artifact_run_id }}
@@ -34,7 +34,7 @@ jobs:
path: dist
- name: Log in to container registry
uses: docker/login-action@7840e6ddd4a9223910798f6a315544257fccd96e
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -42,16 +42,16 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@2ee3d3070bb41b40bf7305d15233321e12c1dc5c
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: ${{ inputs.docker_tags}}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@be3701b2116d2f723573ca9e8cdb4ca85d3cdaf0
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Build and push Docker image
uses: docker/build-push-action@6003d3266a923de06063d23a19bd1c719af997c0
uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0
with:
context: .
platforms: linux/amd64,linux/arm64

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out test private repo
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
repository: element-hq/static-call-participant
ref: refs/heads/main

View File

@@ -21,11 +21,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Yarn cache
uses: actions/setup-node@v4
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
cache: "yarn"
node-version-file: ".nvmrc"
- name: Install dependencies
run: "yarn install"
- name: Build
@@ -38,7 +39,7 @@ jobs:
VITE_APP_VERSION: ${{ inputs.vite_app_version }}
NODE_OPTIONS: "--max-old-space-size=4096"
- name: Upload Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
with:
name: build-output
path: dist

View File

@@ -7,11 +7,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Yarn cache
uses: actions/setup-node@v4
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
cache: "yarn"
node-version-file: ".nvmrc"
- name: Install dependencies
run: "yarn install"
- name: Prettier

View File

@@ -34,7 +34,7 @@ jobs:
environment: Netlify
steps:
- name: 📝 Create Deployment
uses: bobheadxi/deployments@v1
uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8 # v1
id: deployment
with:
step: start
@@ -46,7 +46,7 @@ jobs:
Exercise caution. Use test accounts.
- name: 📥 Download artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4
with:
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
run-id: ${{ inputs.artifact_run_id }}
@@ -62,7 +62,7 @@ jobs:
- name: ☁️ Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v3.0
uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0
with:
publish-dir: webapp
deploy-message: "Deploy from GitHub Actions"
@@ -73,7 +73,7 @@ jobs:
timeout-minutes: 1
- name: 🚦 Update deployment status
uses: bobheadxi/deployments@v1
uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8 # v1
if: always()
with:
step: finish

View File

@@ -14,7 +14,7 @@ jobs:
pr_data_json: ${{ steps.prdetails.outputs.data }}
steps:
- id: prdetails
uses: matrix-org/pr-details-action@v1.3
uses: matrix-org/pr-details-action@15bde5285d7850ba276cc3bd8a03733e3f24622a # v1.3
continue-on-error: true
with:
owner: ${{ github.event.workflow_run.head_repository.owner.login }}

View File

@@ -39,7 +39,7 @@ jobs:
id: current-time
run: echo "unix_time=$(date +'%s')" >> $GITHUB_OUTPUT
- name: 📥 Download artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id || github.run_id }}
@@ -51,7 +51,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@552bf3722c16e81001aea7db72d8cedf64eb5f68
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
env:
GITHUB_TOKEN: ${{ github.token }}
with:

View File

@@ -9,16 +9,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Yarn cache
uses: actions/setup-node@v4
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
cache: "yarn"
node-version-file: ".nvmrc"
- name: Install dependencies
run: "yarn install"
- name: Vitest
run: "yarn run test"
- name: Upload to codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4
with:
flags: unittests

View File

@@ -13,11 +13,12 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
cache: "yarn"
node-version-file: ".nvmrc"
- name: Install Deps
run: "yarn install --frozen-lockfile"
@@ -26,7 +27,7 @@ jobs:
run: "rm -R public/locales"
- name: Download translation files
uses: localazy/download@v1.1.0
uses: localazy/download@0a79880fb66150601e3b43606fab69c88123c087 # v1.1.0
with:
groups: "-p includeSourceLang:true"
@@ -38,7 +39,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v6.0.5
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/localazy-download

View File

@@ -14,9 +14,9 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Upload
uses: localazy/upload@v1
uses: localazy/upload@27e6b5c0fddf4551596b42226b1c24124335d24a # v1
with:
write_key: ${{ secrets.LOCALAZY_WRITE_KEY }}

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
20

View File

@@ -64,6 +64,28 @@ experimental_features:
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 ("ask to join"), a cannot join error or the join view.
Element Call requires a Livekit SFU behind a Livekit 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`.
This is the recommended method.
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"
},
]
```
## 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.
@@ -103,7 +125,9 @@ service for development. These use a test 'secret' published in this
repository, so this must be used only for local development and
**_never be exposed to the public Internet._**
To use it, add SFU parameter in your local config `./public/config.json`:
To use it, add a SFU parameter in your local config `./public/config.json`:
(Be aware, that this is only the fallback Livekit SFU. If the homeserver
advertises one in the client well-known, this will not be used.)
```json
"livekit": {

View File

@@ -1,4 +1,5 @@
{
"name": "element-call",
"version": "0.0.0",
"type": "module",
"scripts": {
@@ -22,12 +23,13 @@
"@livekit/components-core": "^0.10.0",
"@livekit/components-react": "^2.0.0",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
"@nkzw/use-relative-time": "^1.1.0",
"@opentelemetry/api": "^1.4.0",
"@opentelemetry/context-zone": "^1.9.1",
"@opentelemetry/exporter-jaeger": "^1.9.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.48.0",
"@opentelemetry/instrumentation-document-load": "^0.36.0",
"@opentelemetry/instrumentation-user-interaction": "^0.36.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.52.0",
"@opentelemetry/instrumentation-document-load": "^0.39.0",
"@opentelemetry/instrumentation-user-interaction": "^0.39.0",
"@opentelemetry/sdk-trace-web": "^1.9.1",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-slider": "^1.1.2",
@@ -46,7 +48,7 @@
"@react-stately/select": "^3.1.3",
"@react-stately/tooltip": "^3.0.5",
"@react-stately/tree": "^3.2.0",
"@sentry/react": "^7.0.0",
"@sentry/react": "^8.0.0",
"@sentry/tracing": "^7.0.0",
"@types/lodash": "^4.14.199",
"@use-gesture/react": "^10.2.11",
@@ -58,11 +60,11 @@
"classnames": "^2.3.1",
"events": "^3.3.0",
"i18next": "^23.0.0",
"i18next-browser-languagedetector": "^7.0.0",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.0.0",
"livekit-client": "^2.0.2",
"lodash": "^4.17.21",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#e874468ba3e84819cf4b342d2e66af67ab4cf804",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#c795c7eb656f2f6136ac4c6b17015c0f000df9a6",
"matrix-widget-api": "^1.3.1",
"normalize.css": "^8.0.1",
"pako": "^2.0.4",
@@ -76,9 +78,9 @@
"react-use-measure": "^2.1.1",
"rxjs": "^7.8.1",
"sdp-transform": "^2.14.1",
"tinyqueue": "^2.0.3",
"tinyqueue": "^3.0.0",
"unique-names-generator": "^4.6.0",
"uuid": "9",
"uuid": "10",
"vaul": "^0.9.0"
},
"devDependencies": {
@@ -89,17 +91,19 @@
"@react-spring/rafz": "^9.7.3",
"@react-types/dialog": "^3.5.5",
"@sentry/vite-plugin": "^2.0.0",
"@testing-library/react": "^14.0.0",
"@testing-library/dom": "^10.1.0",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/content-type": "^1.1.5",
"@types/dom-screen-wake-lock": "^1.0.1",
"@types/dompurify": "^3.0.2",
"@types/grecaptcha": "^3.0.4",
"@types/node": "^20.0.0",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"@types/request": "^2.48.8",
"@types/sdp-transform": "^2.4.5",
"@types/uuid": "9",
"@types/uuid": "10",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"babel-loader": "^9.0.0",
@@ -113,8 +117,8 @@
"eslint-plugin-matrix-org": "^1.2.1",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-unicorn": "^51.0.0",
"i18next-parser": "^8.0.0",
"eslint-plugin-unicorn": "^54.0.0",
"i18next-parser": "^9.0.0",
"jsdom": "^24.0.0",
"prettier": "^3.0.0",
"sass": "^1.42.1",
@@ -123,6 +127,6 @@
"vite": "^5.0.0",
"vite-plugin-html-template": "^1.1.0",
"vite-plugin-svgr": "^4.0.0",
"vitest": "^1.2.2"
"vitest": "^2.0.0"
}
}

View File

@@ -3,9 +3,42 @@
"extends": ["config:base"],
"packageRules": [
{
"description": "Disable renoavte for packages we want to monitor ourselves",
"matchPackagePatterns": ["matrix-js-sdk"],
"extends": ["group:allNonMajor", "schedule:weekly"]
},
{
"groupName": "GitHub Actions",
"matchDepTypes": ["action"],
"pinDigests": true,
"extends": ["schedule:monthly"]
},
{
"description": "Disable Renovate for packages we want to monitor ourselves",
"groupName": "manually updated packages",
"matchDepNames": ["matrix-js-sdk"],
"enabled": false
},
{
"groupName": "matrix-widget-api",
"matchDepNames": ["matrix-widget-api"]
},
{
"groupName": "Compound",
"matchPackagePrefixes": ["@vector-im/compound-"],
"schedule": "before 5am on Tuesday and Friday"
},
{
"groupName": "LiveKit client",
"matchDepNames": ["livekit-client"]
},
{
"groupName": "LiveKit components",
"matchPackagePrefixes": ["@livekit/components-"]
},
{
"groupName": "Vaul",
"matchDepNames": ["vaul"],
"extends": ["schedule:monthly"],
"prHeader": "Please review modals on mobile for visual regressions."
}
],
"semanticCommits": "disabled",

View File

@@ -28,12 +28,6 @@ declare global {
OLM_OPTIONS: Record<string, string>;
}
// TypeScript doesn't know about the experimental setSinkId method, so we
// declare it ourselves
interface MediaElement extends HTMLVideoElement {
setSinkId: (id: string) => void;
}
interface HTMLElement {
// Safari only supports this prefixed, so tell the type system about it
webkitRequestFullscreen: () => void;

View File

@@ -25,7 +25,11 @@ import {
useMemo,
} from "react";
import { useHistory } from "react-router-dom";
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
import {
ClientEvent,
ICreateClientOpts,
MatrixClient,
} from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import { useTranslation } from "react-i18next";
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
@@ -317,7 +321,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
initClientState.client.on(ClientEvent.Sync, onSync);
}
return () => {
return (): void => {
if (initClientState.client) {
initClientState.client.removeListener(ClientEvent.Sync, onSync);
}
@@ -360,13 +364,13 @@ async function loadClient(): Promise<InitResult | null> {
/* eslint-disable camelcase */
const { user_id, device_id, access_token, passwordlessUser } = session;
const initClientParams = {
const initClientParams: ICreateClientOpts = {
baseUrl: Config.defaultHomeserverUrl()!,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
fallbackICEServerAllowed: fallbackICEServerAllowed,
livekitServiceURL: Config.get().livekit!.livekit_service_url,
livekitServiceURL: Config.get().livekit?.livekit_service_url,
};
try {

View File

@@ -109,6 +109,7 @@ limitations under the License.
.participantsLine {
grid-area: participants;
color: var(--cpd-color-text-secondary);
display: flex;
align-items: center;
gap: var(--cpd-space-1-5x);

View File

@@ -76,7 +76,7 @@ export const Toast: FC<Props> = ({
useEffect(() => {
if (open && autoDismiss !== undefined) {
const timeout = setTimeout(onDismiss, autoDismiss);
return () => clearTimeout(timeout);
return (): void => clearTimeout(timeout);
}
}, [open, autoDismiss, onDismiss]);

View File

@@ -16,7 +16,11 @@ limitations under the License.
import { useCallback } from "react";
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import {
createClient,
LoginResponse,
MatrixClient,
} from "matrix-js-sdk/src/matrix";
import { initClient } from "../matrix-utils";
import { Session } from "../ClientContext";
@@ -37,7 +41,7 @@ export function useInteractiveLogin(): (
const interactiveAuth = new InteractiveAuth({
matrixClient: authClient,
doRequest: () =>
doRequest: (): Promise<LoginResponse> =>
authClient.login("m.login.password", {
identifier: {
type: "m.id.user",

View File

@@ -16,7 +16,11 @@ limitations under the License.
import { useState, useEffect, useCallback, useRef } from "react";
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import {
createClient,
MatrixClient,
RegisterResponse,
} from "matrix-js-sdk/src/matrix";
import { initClient } from "../matrix-utils";
import { Session } from "../ClientContext";
@@ -69,7 +73,7 @@ export const useInteractiveRegistration = (): {
): Promise<[MatrixClient, Session]> => {
const interactiveAuth = new InteractiveAuth({
matrixClient: authClient.current!,
doRequest: (auth) =>
doRequest: (auth): Promise<RegisterResponse> =>
authClient.current!.registerRequest({
username,
password,

View File

@@ -55,16 +55,31 @@ export interface ConfigOptions {
// Describes the LiveKit configuration to be used.
livekit?: {
// The link to the service that returns a livekit url and token to use it
// The link to the service that returns a livekit url and token to use it.
// This is a fallback link in case the homeserver in use does not advertise
// a livekit service url in the client well-known.
// The well known needs to be formatted like so:
// {"type":"livekit", "livekit_service_url":"https://livekit.example.com"}
// and stored under the key: "livekit_focus"
livekit_service_url: string;
};
/**
* Allow to join a group calls without audio and video.
* TEMPORARY: Is a feature that's not proved and experimental
* TEMPORARY experimental features.
*/
features?: {
feature_group_calls_without_video_and_audio: boolean;
/**
* Allow to join group calls without audio and video.
*/
feature_group_calls_without_video_and_audio?: boolean;
/**
* Send device-specific call session membership state events instead of
* legacy user-specific call membership state events.
* This setting has no effect when the user joins an active call with
* legacy state events. For compatibility, Element Call will always join
* active legacy calls with legacy state events.
*/
feature_use_device_session_member_events?: boolean;
};
/**

View File

@@ -166,7 +166,7 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
updateRooms,
);
client.on(RoomEvent.MyMembership, updateRooms);
return () => {
return (): void => {
client.matrixRTC.off(
MatrixRTCSessionManagerEvents.SessionStarted,
updateRooms,

View File

@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { BrowserTracing } from "@sentry/browser";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
@@ -160,10 +159,7 @@ export class Initializer {
dsn: Config.get().sentry?.DSN,
environment: Config.get().sentry?.environment,
integrations: [
new BrowserTracing({
routingInstrumentation:
Sentry.reactRouterV5Instrumentation(history),
}),
Sentry.reactRouterV5BrowserTracingIntegration({ history }),
],
tracesSampleRate: 1.0,
});

View File

@@ -1,23 +0,0 @@
/*
Copyright 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Focus } from "matrix-js-sdk/src/matrixrtc/focus";
export interface LivekitFocus extends Focus {
type: "livekit";
livekit_service_url: string;
livekit_alias: string;
}

View File

@@ -59,7 +59,7 @@ function useObservableState<T>(
// observable state doesn't run in SSR
if (typeof window === "undefined" || !observable) return;
const subscription = observable.subscribe(setState);
return () => subscription.unsubscribe();
return (): void => subscription.unsubscribe();
}, [observable]);
return state;
}

View File

@@ -18,9 +18,9 @@ import { IOpenIDToken, MatrixClient } from "matrix-js-sdk";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { useEffect, useState } from "react";
import { LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
import { LivekitFocus } from "./LivekitFocus";
import { useActiveFocus } from "../room/useActiveFocus";
import { useActiveLivekitFocus } from "../room/useActiveFocus";
export interface SFUConfig {
url: string;
@@ -46,7 +46,7 @@ export function useOpenIDSFU(
): SFUConfig | undefined {
const [sfuConfig, setSFUConfig] = useState<SFUConfig | undefined>(undefined);
const activeFocus = useActiveFocus(rtcSession);
const activeFocus = useActiveLivekitFocus(rtcSession);
useEffect(() => {
(async (): Promise<void> => {

View File

@@ -192,7 +192,7 @@ export function useECConnectionState(
livekitRoom.on(RoomEvent.ConnectionStateChanged, onConnStateChanged);
}
return () => {
return (): void => {
if (oldRoom)
oldRoom.off(RoomEvent.ConnectionStateChanged, onConnStateChanged);
};

View File

@@ -55,7 +55,13 @@ const SYNC_STORE_NAME = "element-call-sync";
// (It's a good opportunity to make the database names consistent.)
const CRYPTO_STORE_NAME = "element-call-crypto";
function waitForSync(client: MatrixClient): Promise<void> {
async function waitForSync(client: MatrixClient): Promise<void> {
// If there is a saved sync, the client will fire an additional sync event
// for restoring it before it runs the first network sync.
// However, the sync we want to wait for is the network sync,
// as the saved sync may be missing some state.
// Thus, don't resolve on the first sync when we know it's for the saved sync.
let waitForSavedSync = !!(await client.store.getSavedSyncToken());
return new Promise<void>((resolve, reject) => {
const onSync = (
state: SyncState,
@@ -63,8 +69,12 @@ function waitForSync(client: MatrixClient): Promise<void> {
data?: ISyncStateData,
): void => {
if (state === "PREPARED") {
client.removeListener(ClientEvent.Sync, onSync);
resolve();
if (waitForSavedSync) {
waitForSavedSync = false;
} else {
client.removeListener(ClientEvent.Sync, onSync);
resolve();
}
} else if (state === "ERROR") {
client.removeListener(ClientEvent.Sync, onSync);
reject(data?.error);
@@ -190,8 +200,14 @@ export async function initClient(
await client.initCrypto();
client.setGlobalErrorOnUnknownDevices(false);
await client.startClient();
await waitForSync(client);
// Once startClient is called, syncs are run asynchronously.
// Also, sync completion is communicated only via events.
// So, apply the event listener *before* starting the client.
// Otherwise, a sync may complete before the listener gets applied,
// and we will miss it.
const syncPromise = waitForSync(client);
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
await syncPromise;
return client;
}

View File

@@ -40,7 +40,6 @@ import {
SummaryStatsReport,
CallFeedReport,
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils";
import { ElementCallOpenTelemetry } from "./otel";
import { ObjectFlattener } from "./ObjectFlattener";
@@ -446,7 +445,7 @@ export class OTelGroupCallMembership {
const type = OTelStatsReportType.SummaryReport;
const data = ObjectFlattener.flattenSummaryStatsReportObject(statsReport);
if (this.statsReportSpan.span === undefined && this.callMembershipSpan) {
const ctx = setSpan(
const ctx = opentelemetry.trace.setSpan(
opentelemetry.context.active(),
this.callMembershipSpan,
);

View File

@@ -82,7 +82,7 @@ export function useProfile(client: MatrixClient | undefined): UseProfile {
user?.on(UserEvent.AvatarUrl, onChangeUser);
}
return () => {
return (): void => {
if (user) {
user.removeListener(UserEvent.DisplayName, onChangeUser);
user.removeListener(UserEvent.AvatarUrl, onChangeUser);

View File

@@ -94,7 +94,7 @@ export const GroupCallView: FC<Props> = ({
useEffect(() => {
window.rtcSession = rtcSession;
return () => {
return (): void => {
delete window.rtcSession;
};
}, [rtcSession]);
@@ -196,11 +196,11 @@ export const GroupCallView: FC<Props> = ({
ev: CustomEvent<IWidgetApiRequest>,
): Promise<void> => {
defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
enterRTCSession(rtcSession, perParticipantE2EE);
await enterRTCSession(rtcSession, perParticipantE2EE);
await widget!.api.transport.reply(ev.detail, {});
};
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
return () => {
return (): void => {
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
};
} else if (widget && !preload && skipLobby) {
@@ -255,7 +255,7 @@ export const GroupCallView: FC<Props> = ({
await leaveRTCSession(rtcSession);
};
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
return () => {
return (): void => {
widget!.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
};
}
@@ -318,7 +318,7 @@ export const GroupCallView: FC<Props> = ({
client={client}
matrixInfo={matrixInfo}
muteStates={muteStates}
onEnter={(): void => enterRTCSession(rtcSession, perParticipantE2EE)}
onEnter={() => void enterRTCSession(rtcSession, perParticipantE2EE)}
confineToRoom={confineToRoom}
hideHeader={hideHeader}
participantCount={participantCount}

View File

@@ -85,13 +85,13 @@ limitations under the License.
@media (min-height: 400px) {
.footer {
padding-block: var(--cpd-space-10x);
padding-block: var(--cpd-space-8x);
}
}
@media (min-height: 800px) {
.footer {
padding-block: var(--cpd-space-15x);
padding-block: var(--cpd-space-10x);
}
}

View File

@@ -97,7 +97,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
);
useEffect(() => {
return () => {
return (): void => {
livekitRoom?.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -220,7 +220,7 @@ export const InCallView: FC<InCallViewProps> = subscribe(
onSpotlightLayout,
);
return () => {
return (): void => {
widget!.lazyActions.off(
ElementWidgetActions.TileLayout,
onTileLayout,
@@ -238,7 +238,7 @@ export const InCallView: FC<InCallViewProps> = subscribe(
const noControls = reducedControls && bounds.height <= 400;
const vm = useCallViewModel(
rtcSession.room,
rtcSession,
livekitRoom,
matrixInfo.e2eeSystem.kind !== E2eeType.NONE,
connState,

View File

@@ -110,7 +110,7 @@ export const VideoPreview: FC<Props> = ({
if (videoEl.current) {
videoTrack?.attach(videoEl.current);
}
return () => {
return (): void => {
videoTrack?.detach();
};
}, [videoTrack]);

View File

@@ -21,33 +21,28 @@ import {
import { useCallback, useEffect, useState } from "react";
import { deepCompare } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { LivekitFocus } from "../livekit/LivekitFocus";
function getActiveFocus(
rtcSession: MatrixRTCSession,
): LivekitFocus | undefined {
const oldestMembership = rtcSession.getOldestMembership();
const focus = oldestMembership?.getActiveFoci()[0] as LivekitFocus;
return focus;
}
import {
LivekitFocus,
isLivekitFocus,
} from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
/**
* Gets the currently active (livekit) focus for a MatrixRTC session
* This logic is specific to livekit foci where the whole call must use one
* and the same focus.
*/
export function useActiveFocus(
export function useActiveLivekitFocus(
rtcSession: MatrixRTCSession,
): LivekitFocus | undefined {
const [activeFocus, setActiveFocus] = useState(() =>
getActiveFocus(rtcSession),
);
const [activeFocus, setActiveFocus] = useState(() => {
const f = rtcSession.getActiveFocus();
// Only handle foci with type="livekit" for now.
return !!f && isLivekitFocus(f) ? f : undefined;
});
const onMembershipsChanged = useCallback(() => {
const newActiveFocus = getActiveFocus(rtcSession);
const newActiveFocus = rtcSession.getActiveFocus();
if (!!newActiveFocus && !isLivekitFocus(newActiveFocus)) return;
if (!deepCompare(activeFocus, newActiveFocus)) {
const oldestMembership = rtcSession.getOldestMembership();
logger.warn(
@@ -64,7 +59,7 @@ export function useActiveFocus(
onMembershipsChanged,
);
return () => {
return (): void => {
rtcSession.off(
MatrixRTCSessionEvent.MembershipsChanged,
onMembershipsChanged,

View File

@@ -217,8 +217,6 @@ export const useLoadGroupCall = (
"Room not found. The widget-api did not pass over the relevant room events/information.",
);
// If the room does not exist we first search for it with viaServers
const roomSummary = await client.getRoomSummary(roomId, viaServers);
if (membership === KnownMembership.Ban) {
throw bannedError();
} else if (membership === KnownMembership.Invite) {
@@ -226,15 +224,39 @@ export const useLoadGroupCall = (
viaServers,
});
} else {
if (roomSummary.join_rule === JoinRule.Public) {
room = await client.joinRoom(roomSummary.room_id, {
// If the room does not exist we first search for it with viaServers
let roomSummary: RoomSummary | undefined = undefined;
try {
roomSummary = await client.getRoomSummary(roomId, viaServers);
} catch (error) {
// If the room summary endpoint is not supported we let it be undefined and treat this case like
// `JoinRule.Public`.
// This is how the logic was done before: "we expect any room id passed to EC
// to be for a public call" Which is definitely not ideal but worth a try if fetching
// the summary crashes.
logger.warn(
`Could not load room summary to decide whether we want to join or knock.
EC will fallback to join as if this would be a public room.
Reach out to your homeserver admin to ask them about supporting the \`/summary\` endpoint (im.nheko.summary):`,
error,
);
}
if (
roomSummary === undefined ||
roomSummary.join_rule === JoinRule.Public
) {
room = await client.joinRoom(roomId, {
viaServers,
});
} else if (roomSummary.join_rule === JoinRule.Knock) {
// bind room summary in this scope so we have it stored in a binding of type `RoomSummary`
// instead of `RoomSummary | undefined`. Because we use it in a promise the linter does not accept
// the type check from the if condition above.
const _roomSummary = roomSummary;
let knock: () => void = () => {};
const userPressedAskToJoinPromise: Promise<void> = new Promise(
(resolve) => {
if (roomSummary.membership !== KnownMembership.Knock) {
if (_roomSummary.membership !== KnownMembership.Knock) {
knock = resolve;
} else {
// resolve immediately if the user already knocked
@@ -242,12 +264,13 @@ export const useLoadGroupCall = (
}
},
);
setState({ kind: "canKnock", roomSummary, knock });
setState({ kind: "canKnock", roomSummary: _roomSummary, knock });
await userPressedAskToJoinPromise;
room = await getRoomByKnocking(
roomSummary.room_id,
viaServers,
() => setState({ kind: "waitForInvite", roomSummary }),
() =>
setState({ kind: "waitForInvite", roomSummary: _roomSummary }),
);
} else {
throw new Error(

View File

@@ -50,7 +50,7 @@ export function usePageUnload(callback: () => void): void {
// @ts-ignore
window.addEventListener("beforeunload", onBeforeUnload);
return () => {
return (): void => {
window.removeEventListener("pagehide", onBeforeUnload);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

View File

@@ -15,29 +15,90 @@ limitations under the License.
*/
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { logger } from "matrix-js-sdk/src/logger";
import {
LivekitFocus,
LivekitFocusActive,
isLivekitFocus,
isLivekitFocusConfig,
} from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
import { LivekitFocus } from "./livekit/LivekitFocus";
import { Config } from "./config/Config";
import { ElementWidgetActions, WidgetHelpers, widget } from "./widget";
function makeFocus(livekitAlias: string): LivekitFocus {
const urlFromConf = Config.get().livekit!.livekit_service_url;
if (!urlFromConf) {
throw new Error("No livekit_service_url is configured!");
}
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
export function makeActiveFocus(): LivekitFocusActive {
return {
type: "livekit",
livekit_service_url: urlFromConf,
livekit_alias: livekitAlias,
focus_selection: "oldest_membership",
};
}
export function enterRTCSession(
async function makePreferredLivekitFoci(
rtcSession: MatrixRTCSession,
livekitAlias: string,
): Promise<LivekitFocus[]> {
logger.log("Start building foci_preferred list: ", rtcSession.room.roomId);
const preferredFoci: LivekitFocus[] = [];
// Make the Focus from the running rtc session the highest priority one
// This minimizes how often we need to switch foci during a call.
const focusInUse = rtcSession.getFocusInUse();
if (focusInUse && isLivekitFocus(focusInUse)) {
logger.log("Adding livekit focus from oldest member: ", focusInUse);
preferredFoci.push(focusInUse);
}
// Prioritize the client well known over the configured sfu.
const wellKnownFoci =
rtcSession.room.client.getClientWellKnown()?.[FOCI_WK_KEY];
if (Array.isArray(wellKnownFoci)) {
preferredFoci.push(
...wellKnownFoci
.filter((f) => !!f)
.filter(isLivekitFocusConfig)
.map((wellKnownFocus) => {
logger.log("Adding livekit focus from well known: ", wellKnownFocus);
return { ...wellKnownFocus, livekit_alias: livekitAlias };
}),
);
}
const urlFromConf = Config.get().livekit?.livekit_service_url;
if (urlFromConf) {
const focusFormConf: LivekitFocus = {
type: "livekit",
livekit_service_url: urlFromConf,
livekit_alias: livekitAlias,
};
logger.log("Adding livekit focus from config: ", focusFormConf);
preferredFoci.push(focusFormConf);
}
if (preferredFoci.length === 0)
throw new Error(
`No livekit_service_url is configured so we could not create a focus.
Currently we skip computing a focus based on other users in the room.`,
);
return preferredFoci;
// TODO: we want to do something like this:
//
// const focusOtherMembers = await focusFromOtherMembers(
// rtcSession,
// livekitAlias,
// );
// if (focusOtherMembers) preferredFoci.push(focusOtherMembers);
}
export async function enterRTCSession(
rtcSession: MatrixRTCSession,
encryptMedia: boolean,
): void {
): Promise<void> {
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
@@ -47,8 +108,18 @@ export function enterRTCSession(
// right now we assume everything is a room-scoped call
const livekitAlias = rtcSession.room.roomId;
rtcSession.joinRoomSession([makeFocus(livekitAlias)], encryptMedia);
const useDeviceSessionMemberEvents =
Config.get().features?.feature_use_device_session_member_events;
rtcSession.joinRoomSession(
await makePreferredLivekitFoci(rtcSession, livekitAlias),
makeActiveFocus(),
{
manageMediaKeys: encryptMedia,
...(useDeviceSessionMemberEvents !== undefined && {
useLegacyMemberEvents: !useDeviceSessionMemberEvents,
}),
},
);
}
const widgetPostHangupProcedure = async (

View File

@@ -47,7 +47,7 @@ export const ProfileSettingsTab: FC<Props> = ({ client }) => {
useEffect(() => {
const form = formRef.current!;
// Auto-save when the user dismisses this component
return () => {
return (): void => {
if (formChanged.current) {
const data = new FormData(form);
const displayNameDataEntry = data.get("displayName");

View File

@@ -334,7 +334,7 @@ export function useRageshakeRequestModal(
client.on(ClientEvent.Event, onEvent);
return () => {
return (): void => {
client.removeListener(ClientEvent.Event, onEvent);
};
}, [setOpen, roomId, client]);

View File

@@ -52,6 +52,8 @@ import {
} from "rxjs";
import { StateObservable, state } from "@react-rxjs/core";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership";
import { ViewModel } from "./ViewModel";
import { useObservable } from "./useObservable";
@@ -64,6 +66,7 @@ import {
MediaViewModel,
UserMediaViewModel,
ScreenShareViewModel,
MembershipOnlyViewModel,
} from "./MediaViewModel";
import { finalizeValue } from "../observable-utils";
import { ObservableScope } from "./ObservableScope";
@@ -204,25 +207,45 @@ class ScreenShare {
}
}
type MediaItem = UserMedia | ScreenShare;
class MembershipOnly {
public readonly vm: MembershipOnlyViewModel;
function findMatrixMember(
room: MatrixRoom,
id: string,
): RoomMember | undefined {
public constructor(member: RoomMember) {
this.vm = new MembershipOnlyViewModel(member);
}
public destroy(): void {
this.vm.destroy();
}
}
type MediaItem = UserMedia | ScreenShare | MembershipOnly;
function matrixUserIdFromParticipantId(id: string): string | undefined {
if (!id) return undefined;
const parts = id.split(":");
// must be at least 3 parts because we know the first part is a userId which must necessarily contain a colon
if (parts.length < 3) {
logger.warn(
"Livekit participants ID doesn't look like a userId:deviceId combination",
`Livekit participants ID doesn't look like a userId:deviceId combination: ${id}`,
);
return undefined;
}
parts.pop();
const userId = parts.join(":");
return userId;
}
function findMatrixMember(
room: MatrixRoom,
id: string,
): RoomMember | undefined {
const userId = matrixUserIdFromParticipantId(id);
if (!userId) {
return undefined;
}
return room.getMember(userId) ?? undefined;
}
@@ -304,6 +327,24 @@ export class CallViewModel extends ViewModel {
},
);
private readonly membershipsWithoutParticipant = combineLatest([
of(this.rtcSession.memberships),
this.remoteParticipants,
of(this.livekitRoom.localParticipant),
]).pipe(
scan((prev, [memberships, remoteParticipants, localParticipant]) => {
const participantIds = new Set(
remoteParticipants.map((p) =>
matrixUserIdFromParticipantId(p.identity),
),
);
participantIds.add(
matrixUserIdFromParticipantId(localParticipant.identity),
);
return memberships.filter((m) => !participantIds.has(m.sender ?? ""));
}, [] as CallMembership[]),
);
private readonly mediaItems: StateObservable<MediaItem[]> = state(
combineLatest([
this.remoteParticipants,
@@ -495,90 +536,136 @@ export class CallViewModel extends ViewModel {
combineLatest([
this.remoteParticipants,
observeParticipantMedia(this.livekitRoom.localParticipant),
this.membershipsWithoutParticipant,
]).pipe(
scan((ts, [remoteParticipants, { participant: localParticipant }]) => {
const ps = [localParticipant, ...remoteParticipants];
const tilesById = new Map(ts.map((t) => [t.id, t]));
const now = Date.now();
let allGhosts = true;
scan(
(
ts,
[
remoteParticipants,
{ participant: localParticipant },
membershipsWithoutParticipant,
],
) => {
const ps = [
localParticipant,
...remoteParticipants,
...membershipsWithoutParticipant,
];
const tilesById = new Map(ts.map((t) => [t.id, t]));
const now = Date.now();
let allGhosts = true;
const newTiles = ps.flatMap((p) => {
const userMediaId = p.identity;
const member = findMatrixMember(this.matrixRoom, userMediaId);
allGhosts &&= member === undefined;
const spokeRecently =
p.lastSpokeAt !== undefined && now - +p.lastSpokeAt <= 10000;
const newTiles = ps.flatMap((p) => {
if (p instanceof CallMembership) {
const userId = p.sender ?? "";
const member = this.matrixRoom.getMember(userId);
if (!member) {
logger.warn(
`Ruh, roh! No matrix member found for call membership '${userId}': ignoring`,
);
return [];
}
const membershipOnlyVm =
tilesById.get(userId)?.data ??
new MembershipOnlyViewModel(member);
tilesById.delete(userId);
// We always start with a local participant with the empty string as
// their ID before we're connected, this is fine and we'll be in
// "all ghosts" mode.
if (userMediaId !== "" && member === undefined) {
logger.warn(
`Ruh, roh! No matrix member found for SFU participant '${userMediaId}': creating g-g-g-ghost!`,
);
}
const membershipOnlyTile: TileDescriptor<MediaViewModel> = {
id: userId,
focused: false,
isPresenter: false,
isSpeaker: false,
hasVideo: false,
local: false,
largeBaseSize: false,
data: membershipOnlyVm,
};
return [membershipOnlyTile];
}
const userMediaVm =
tilesById.get(userMediaId)?.data ??
new UserMediaViewModel(userMediaId, member, p, this.encrypted);
tilesById.delete(userMediaId);
const userMediaId = p.identity;
const member = findMatrixMember(this.matrixRoom, userMediaId);
allGhosts &&= member === undefined;
const spokeRecently =
p.lastSpokeAt !== undefined && now - +p.lastSpokeAt <= 10000;
const userMediaTile: TileDescriptor<MediaViewModel> = {
id: userMediaId,
focused: false,
isPresenter: p.isScreenShareEnabled,
isSpeaker: (p.isSpeaking || spokeRecently) && !p.isLocal,
hasVideo: p.isCameraEnabled,
local: p.isLocal,
largeBaseSize: false,
data: userMediaVm,
};
if (p.isScreenShareEnabled) {
const screenShareId = `${userMediaId}:screen-share`;
const screenShareVm =
tilesById.get(screenShareId)?.data ??
new ScreenShareViewModel(
screenShareId,
member,
p,
this.encrypted,
// We always start with a local participant with the empty string as
// their ID before we're connected, this is fine and we'll be in
// "all ghosts" mode.
if (userMediaId !== "" && member === undefined) {
logger.warn(
`Ruh, roh! No matrix member found for SFU participant '${userMediaId}': creating g-g-g-ghost!`,
);
tilesById.delete(screenShareId);
}
const screenShareTile: TileDescriptor<MediaViewModel> = {
id: screenShareId,
focused: true,
isPresenter: false,
isSpeaker: false,
hasVideo: true,
const userMediaVm =
tilesById.get(userMediaId)?.data ??
new UserMediaViewModel(userMediaId, member, p, this.encrypted);
tilesById.delete(userMediaId);
const userMediaTile: TileDescriptor<MediaViewModel> = {
id: userMediaId,
focused: false,
isPresenter: p.isScreenShareEnabled,
isSpeaker: (p.isSpeaking || spokeRecently) && !p.isLocal,
hasVideo: p.isCameraEnabled,
local: p.isLocal,
largeBaseSize: true,
placeNear: userMediaId,
data: screenShareVm,
largeBaseSize: false,
data: userMediaVm,
};
return [userMediaTile, screenShareTile];
} else {
return [userMediaTile];
}
});
// Any tiles left in the map are unused and should be destroyed
for (const t of tilesById.values()) t.data.destroy();
if (p.isScreenShareEnabled) {
const screenShareId = `${userMediaId}:screen-share`;
const screenShareVm =
tilesById.get(screenShareId)?.data ??
new ScreenShareViewModel(
screenShareId,
member,
p,
this.encrypted,
);
tilesById.delete(screenShareId);
// If every item is a ghost, that probably means we're still connecting
// and shouldn't bother showing anything yet
return allGhosts ? [] : newTiles;
}, [] as TileDescriptor<MediaViewModel>[]),
const screenShareTile: TileDescriptor<MediaViewModel> = {
id: screenShareId,
focused: true,
isPresenter: false,
isSpeaker: false,
hasVideo: true,
local: p.isLocal,
largeBaseSize: true,
placeNear: userMediaId,
data: screenShareVm,
};
return [userMediaTile, screenShareTile];
} else {
return [userMediaTile];
}
});
// Any tiles left in the map are unused and should be destroyed
for (const t of tilesById.values()) t.data.destroy();
// If every item is a ghost, that probably means we're still connecting
// and shouldn't bother showing anything yet
return allGhosts ? [] : newTiles;
},
[] as TileDescriptor<MediaViewModel>[],
),
finalizeValue((ts) => {
for (const t of ts) t.data.destroy();
}),
),
);
private get matrixRoom(): MatrixRoom {
return this.rtcSession.room;
}
public constructor(
// A call is permanently tied to a single Matrix room and LiveKit room
private readonly matrixRoom: MatrixRoom,
private readonly rtcSession: MatrixRTCSession,
private readonly livekitRoom: LivekitRoom,
private readonly encrypted: boolean,
private readonly connectionState: Observable<ECConnectionState>,
@@ -588,25 +675,25 @@ export class CallViewModel extends ViewModel {
}
export function useCallViewModel(
matrixRoom: MatrixRoom,
rtcSession: MatrixRTCSession,
livekitRoom: LivekitRoom,
encrypted: boolean,
connectionState: ECConnectionState,
): CallViewModel {
const prevMatrixRoom = usePrevious(matrixRoom);
const prevRTCSession = usePrevious(rtcSession);
const prevLivekitRoom = usePrevious(livekitRoom);
const prevEncrypted = usePrevious(encrypted);
const connectionStateObservable = useObservable(connectionState);
const vm = useRef<CallViewModel>();
if (
matrixRoom !== prevMatrixRoom ||
rtcSession !== prevRTCSession ||
livekitRoom !== prevLivekitRoom ||
encrypted !== prevEncrypted
) {
vm.current?.destroy();
vm.current = new CallViewModel(
matrixRoom,
rtcSession,
livekitRoom,
encrypted,
connectionStateObservable,

View File

@@ -110,7 +110,10 @@ abstract class BaseMediaViewModel extends ViewModel {
/**
* Some participant's media.
*/
export type MediaViewModel = UserMediaViewModel | ScreenShareViewModel;
export type MediaViewModel =
| UserMediaViewModel
| ScreenShareViewModel
| MembershipOnlyViewModel;
/**
* Some participant's user media.
@@ -239,3 +242,15 @@ export class ScreenShareViewModel extends BaseMediaViewModel {
);
}
}
/**
* Placeholder for a call membership that does not have a LiveKit participant associated with it.
*/
export class MembershipOnlyViewModel extends ViewModel {
public id: string;
public local = false;
public constructor(public readonly member: RoomMember) {
super();
this.id = member.userId;
}
}

View File

@@ -33,7 +33,7 @@ export function useEventTarget<T extends Event>(
useEffect(() => {
if (target) {
target.addEventListener(eventType, listener as EventListener, options);
return () =>
return (): void =>
target.removeEventListener(
eventType,
listener as EventListener,
@@ -55,7 +55,7 @@ export function useTypedEventEmitter<
): void {
useEffect(() => {
emitter.on(eventType, listener);
return () => {
return (): void => {
emitter.off(eventType, listener);
};
}, [emitter, eventType, listener]);
@@ -72,7 +72,7 @@ export function useEventEmitterThree<
): void {
useEffect(() => {
emitter.on(eventType, listener);
return () => {
return (): void => {
emitter.off(eventType, listener);
};
}, [emitter, eventType, listener]);

View File

@@ -32,7 +32,7 @@ export const useLocalStorage = (
useEffect(() => {
localStorageBus.on(key, setValue);
return () => {
return (): void => {
localStorageBus.off(key, setValue);
};
}, [key, setValue]);

View File

@@ -34,7 +34,7 @@ export function useLocationNavigation(enabled = false): void {
});
}
return () => {
return (): void => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (unblock) {

View File

@@ -26,19 +26,22 @@ export function useMatrixRTCSessionJoinState(
): boolean {
const [isJoined, setJoined] = useState(rtcSession.isJoined());
const onJoinStateChanged = useCallback(() => {
logger.info(
`Session in room ${rtcSession.room.roomId} changed to ${
rtcSession.isJoined() ? "joined" : "left"
}`,
);
setJoined(rtcSession.isJoined());
}, [rtcSession]);
const onJoinStateChanged = useCallback(
(isJoined: boolean) => {
logger.info(
`Session in room ${rtcSession.room.roomId} changed to ${
isJoined ? "joined" : "left"
}`,
);
setJoined(isJoined);
},
[rtcSession],
);
useEffect(() => {
rtcSession.on(MatrixRTCSessionEvent.JoinStateChanged, onJoinStateChanged);
return () => {
return (): void => {
rtcSession.off(
MatrixRTCSessionEvent.JoinStateChanged,
onJoinStateChanged,

View File

@@ -40,7 +40,7 @@ export function useMatrixRTCSessionMemberships(
onMembershipsChanged,
);
return () => {
return (): void => {
rtcSession.off(
MatrixRTCSessionEvent.MembershipsChanged,
onMembershipsChanged,

View File

@@ -27,8 +27,8 @@ export function useWakeLock(): void {
let lock: WakeLockSentinel | null = null;
// The lock is automatically released whenever the window goes invisible,
// so we need to reacquire it on visiblity changes
const onVisiblityChange = async (): Promise<void> => {
// so we need to reacquire it on visibility changes
const onVisibilityChange = async (): Promise<void> => {
if (document.visibilityState === "visible") {
try {
lock = await navigator.wakeLock.request("screen");
@@ -44,16 +44,16 @@ export function useWakeLock(): void {
}
};
onVisiblityChange();
document.addEventListener("visiblitychange", onVisiblityChange);
onVisibilityChange();
document.addEventListener("visibilitychange", onVisibilityChange);
return () => {
return (): void => {
mounted = false;
if (lock !== null)
lock
.release()
.catch((e) => logger.warn("Can't release wake lock", e));
document.removeEventListener("visiblitychange", onVisiblityChange);
document.removeEventListener("visibilitychange", onVisibilityChange);
};
}
}, []);

View File

@@ -33,6 +33,7 @@ import {
VideoGridProps as Props,
TileSpring,
ChildrenProperties,
TileSpringUpdate,
} from "./VideoGrid";
import { useReactiveState } from "../useReactiveState";
import { useMergedRefs } from "../useMergedRefs";
@@ -110,7 +111,7 @@ export function NewVideoGrid<T>({
});
observer.observe(slotsRoot, { attributes: true });
return () => observer.disconnect();
return (): void => observer.disconnect();
}
}, [slotsRoot, setRenderedGeneration]);
@@ -174,8 +175,8 @@ export function NewVideoGrid<T>({
const [tileTransitions, springRef] = useTransition(
tiles,
() => ({
key: ({ item }: Tile<T>) => item.id,
from: ({ x, y, width, height }: Tile<T>) => ({
key: ({ item }: Tile<T>): string => item.id,
from: ({ x, y, width, height }: Tile<T>): TileSpringUpdate => ({
opacity: 0,
scale: 0,
shadow: 0,
@@ -188,7 +189,13 @@ export function NewVideoGrid<T>({
immediate: disableAnimations,
}),
enter: { opacity: 1, scale: 1, immediate: disableAnimations },
update: ({ item, x, y, width, height }: Tile<T>) =>
update: ({
item,
x,
y,
width,
height,
}: Tile<T>): TileSpringUpdate | null =>
item.id === dragState.current?.tileId
? null
: {
@@ -230,7 +237,7 @@ export function NewVideoGrid<T>({
disableAnimations || ((key): boolean => key === "zIndex"),
// Allow the tile's position to settle before pushing its
// z-index back down
delay: (key) => (key === "zIndex" ? 500 : 0),
delay: (key): number => (key === "zIndex" ? 500 : 0),
}
: {
scale: 1.1,

View File

@@ -77,6 +77,13 @@ export interface TileSpring {
height: number;
}
export interface TileSpringUpdate extends Partial<TileSpring> {
from?: Partial<TileSpring>;
reset?: boolean;
immediate?: boolean | ((key: string) => boolean);
delay?: (key: string) => number;
}
type LayoutDirection = "vertical" | "horizontal";
export function useVideoGridLayout(hasScreenshareFeeds: boolean): {
@@ -120,7 +127,7 @@ function useIsMounted(): MutableRefObject<boolean> {
useEffect(() => {
isMountedRef.current = true;
return () => {
return (): void => {
isMountedRef.current = false;
};
}, []);
@@ -1026,7 +1033,7 @@ export function VideoGrid<T>({
const oneOnOneLayout =
tiles.length === 2 && !tiles.some((t) => t.focused);
return (tileIndex: number) => {
return (tileIndex: number): TileSpringUpdate => {
const tile = tiles[tileIndex];
const tilePosition = tilePositions[tile.order];
const draggingTile = draggingTileRef.current;
@@ -1045,7 +1052,7 @@ export function VideoGrid<T>({
zIndex: 2,
shadow: 15,
shadowSpread: 0,
immediate: (key: string) =>
immediate: (key: string): boolean =>
disableAnimations ||
key === "zIndex" ||
key === "x" ||
@@ -1112,14 +1119,14 @@ export function VideoGrid<T>({
shadowSpread: oneOnOneLayout && tile.item.local ? 1 : 0,
from,
reset,
immediate: (key: string) =>
immediate: (key: string): boolean =>
disableAnimations ||
key === "zIndex" ||
key === "shadow" ||
key === "shadowSpread",
// If we just stopped dragging a tile, give it time for the
// animation to settle before pushing its z-index back down
delay: (key: string) => (key === "zIndex" ? 500 : 0),
delay: (key: string): number => (key === "zIndex" ? 500 : 0),
};
}
};

View File

@@ -53,6 +53,7 @@ import {
Menu,
} from "@vector-im/compound-web";
import { useStateObservable } from "@react-rxjs/core";
import useRelativeTime from "@nkzw/use-relative-time";
import { Avatar } from "../Avatar";
import styles from "./VideoTile.module.css";
@@ -61,6 +62,7 @@ import {
ScreenShareViewModel,
MediaViewModel,
UserMediaViewModel,
MembershipOnlyViewModel,
} from "../state/MediaViewModel";
import { subscribe } from "../state/subscribe";
import { useMergedRefs } from "../useMergedRefs";
@@ -72,7 +74,7 @@ interface TileProps {
style?: ComponentProps<typeof animated.div>["style"];
targetWidth: number;
targetHeight: number;
video: TrackReferenceOrPlaceholder;
video?: TrackReferenceOrPlaceholder;
member: RoomMember | undefined;
videoEnabled: boolean;
maximised: boolean;
@@ -110,6 +112,29 @@ const Tile = forwardRef<HTMLDivElement, TileProps>(
const { t } = useTranslation();
const mergedRef = useMergedRefs(tileRef, ref);
const [lastActiveTime, setLastActiveTime] = useState(
member?.getLastTypingTime() ?? 0,
);
const [online, setOnline] = useState(member?.typing ?? false);
useEffect(() => {
const onTyping = (): void => {
setOnline(member?.typing ?? false);
setLastActiveTime(member?.getLastTypingTime() ?? 0);
};
member?.on(RoomMemberEvent.Typing, onTyping);
return (): void => {
member?.off(RoomMemberEvent.Typing, onTyping);
};
}, [member]);
const joinedCallTime = member?.events.member?.getTs() ?? 0;
const lastActiveAgo = useRelativeTime(lastActiveTime ?? 0);
const joinedCallAgo = useRelativeTime(joinedCallTime ?? 0);
return (
<animated.div
className={classNames(styles.videoTile, className, {
@@ -129,7 +154,7 @@ const Tile = forwardRef<HTMLDivElement, TileProps>(
src={member?.getMxcAvatarUrl()}
className={styles.avatar}
/>
{video.publication !== undefined && (
{video?.publication !== undefined && (
<VideoTrack
trackRef={video}
// There's no reason for this to be focusable
@@ -137,12 +162,38 @@ const Tile = forwardRef<HTMLDivElement, TileProps>(
disablePictureInPicture
/>
)}
{!video && member && joinedCallTime > 0 && (
<div style={{ textAlign: "center" }}>
<span>
{nameTag} ({member.userId})
</span>{" "}
joined the call{" "}
<span title={new Date(joinedCallTime).toLocaleString()}>
{joinedCallAgo}
</span>{" "}
but is currently unreachable.
{lastActiveTime > 0 && (
<span>
They were last reachable{" "}
<span title={new Date(lastActiveTime).toLocaleString()}>
{lastActiveAgo}
</span>
.
</span>
)}{" "}
Are they having connection problems?
</div>
)}
</div>
<div className={styles.fg}>
<div className={styles.nameTag}>
{nameTagLeadingIcon}
<Text as="span" size="sm" weight="medium" className={styles.name}>
{nameTag}
<span
title={`${member?.userId}${lastActiveTime > 0 ? ` last reachable ${online ? "now" : lastActiveAgo}` : ""}`}
>
{nameTag}
</span>{" "}
</Text>
{unencryptedWarning && (
<Tooltip
@@ -402,6 +453,75 @@ const ScreenShareTile = subscribe<ScreenShareTileProps, HTMLDivElement>(
ScreenShareTile.displayName = "ScreenShareTile";
interface MembershipOnlyTileProps {
vm: MembershipOnlyViewModel;
className?: string;
style?: ComponentProps<typeof animated.div>["style"];
targetWidth: number;
targetHeight: number;
nameTag: string;
displayName: string;
maximised: boolean;
fullscreen: boolean;
onToggleFullscreen: (itemId: string) => void;
}
const MembershipOnlyTile = subscribe<MembershipOnlyTileProps, HTMLDivElement>(
(
{
vm,
className,
style,
targetWidth,
targetHeight,
nameTag,
displayName,
maximised,
fullscreen,
onToggleFullscreen,
},
ref,
) => {
const { t } = useTranslation();
const onClickFullScreen = useCallback(
() => onToggleFullscreen(vm.id),
[onToggleFullscreen, vm],
);
const FullScreenIcon = fullscreen ? CollapseIcon : ExpandIcon;
return (
<Tile
ref={ref}
className={classNames(className, styles.membershipOnly)}
style={style}
targetWidth={targetWidth}
targetHeight={targetHeight}
member={vm.member}
videoEnabled={true}
maximised={maximised}
unencryptedWarning={false}
nameTag={nameTag}
displayName={displayName}
primaryButton={
<button
aria-label={
fullscreen
? t("video_tile.full_screen")
: t("video_tile.exit_full_screen")
}
onClick={onClickFullScreen}
>
<FullScreenIcon aria-hidden width={20} height={20} />
</button>
}
/>
);
},
);
MembershipOnlyTile.displayName = "MembershipOnlyTile";
interface Props {
vm: MediaViewModel;
maximised: boolean;
@@ -471,7 +591,7 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
showSpeakingIndicator={showSpeakingIndicator}
/>
);
} else {
} else if (vm instanceof ScreenShareViewModel) {
return (
<ScreenShareTile
ref={ref}
@@ -487,6 +607,22 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
onToggleFullscreen={onToggleFullscreen}
/>
);
} else if (vm instanceof MembershipOnlyViewModel) {
return (
<MembershipOnlyTile
ref={ref}
className={className}
style={style}
vm={vm}
targetWidth={targetWidth}
targetHeight={targetHeight}
nameTag={nameTag}
displayName={displayName}
maximised={maximised}
fullscreen={fullscreen}
onToggleFullscreen={onToggleFullscreen}
/>
);
}
},
);

View File

@@ -77,8 +77,6 @@ export const widget = ((): WidgetHelpers | null => {
logger.info("Widget API is available");
const api = new WidgetApi(widgetId, parentOrigin);
api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
api.requestCapabilityToSendEvent(EventType.CallEncryptionKeysPrefix);
api.requestCapabilityToReceiveEvent(EventType.CallEncryptionKeysPrefix);
// Set up the lazy action emitter, but only for select actions that we
// intend for the app to handle
@@ -116,14 +114,23 @@ export const widget = ((): WidgetHelpers | null => {
if (!baseUrl) throw new Error("Base URL must be supplied");
// These are all the event types the app uses
const sendRecvEvent = ["org.matrix.rageshake_request"];
const sendState = [
{ eventType: EventType.GroupCallMemberPrefix, stateKey: userId },
const sendRecvEvent = [
"org.matrix.rageshake_request",
EventType.CallEncryptionKeysPrefix,
];
const sendState = [
userId, // legacy call membership events
`_${userId}_${deviceId}`, // session membership events
`${userId}_${deviceId}`, // MSC3779 session membership events
].map((stateKey) => ({
eventType: EventType.GroupCallMemberPrefix,
stateKey,
}));
const receiveState = [
{ eventType: EventType.RoomCreate },
{ eventType: EventType.RoomMember },
{ eventType: EventType.RoomEncryption },
{ eventType: EventType.GroupCallPrefix },
{ eventType: EventType.GroupCallMemberPrefix },
];
const sendRecvToDevice = [
@@ -168,7 +175,7 @@ export const widget = ((): WidgetHelpers | null => {
// wait for the config file to be ready (we load very early on so it might not
// be otherwise)
await Config.init();
await client.startClient();
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
resolve(client);
})();
});

View File

@@ -0,0 +1,93 @@
/*
Copyright 2024 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { enterRTCSession } from "../src/rtcSessionHelpers";
import { Config } from "../src/config/Config";
test("It joins the correct Session", async () => {
const focusFromOlderMembership = {
type: "livekit",
livekit_service_url: "http://my-oldest-member-service-url.com",
livekit_alias: "my-oldest-member-service-alias",
};
const focusConfigFromWellKnown = {
type: "livekit",
livekit_service_url: "http://my-well-known-service-url.com",
};
const focusConfigFromWellKnown2 = {
type: "livekit",
livekit_service_url: "http://my-well-known-service-url2.com",
};
const clientWellKnown = {
"org.matrix.msc4143.rtc_foci": [
focusConfigFromWellKnown,
focusConfigFromWellKnown2,
],
};
vi.spyOn(Config, "get").mockReturnValue({
livekit: { livekit_service_url: "http://my-default-service-url.com" },
eula: "",
});
const mockedSession = vi.mocked({
room: {
roomId: "roomId",
client: {
getClientWellKnown: vi.fn().mockReturnValue(clientWellKnown),
},
},
memberships: [],
getFocusInUse: vi.fn().mockReturnValue(focusFromOlderMembership),
getOldestMembership: vi.fn().mockReturnValue({
getPreferredFoci: vi.fn().mockReturnValue([focusFromOlderMembership]),
}),
joinRoomSession: vi.fn(),
}) as unknown as MatrixRTCSession;
await enterRTCSession(mockedSession, false);
expect(mockedSession.joinRoomSession).toHaveBeenLastCalledWith(
[
{
livekit_alias: "my-oldest-member-service-alias",
livekit_service_url: "http://my-oldest-member-service-url.com",
type: "livekit",
},
{
livekit_alias: "roomId",
livekit_service_url: "http://my-well-known-service-url.com",
type: "livekit",
},
{
livekit_alias: "roomId",
livekit_service_url: "http://my-well-known-service-url2.com",
type: "livekit",
},
{
livekit_alias: "roomId",
livekit_service_url: "http://my-default-service-url.com",
type: "livekit",
},
],
{
focus_selection: "oldest_membership",
type: "livekit",
},
{ manageMediaKeys: false },
);
});

View File

@@ -22,6 +22,9 @@
"livekit-client/dist/src/room/Room": [
"./node_modules/livekit-client/dist/src/room/Room.d.ts"
],
"livekit-client/dist/src/room/track/TrackPublication": [
"./node_modules/livekit-client/dist/src/room/track/TrackPublication.d.ts"
],
"livekit-client/dist/src/room/participant/Participant": [
"./node_modules/livekit-client/dist/src/room/participant/Participant.d.ts"
],

5534
yarn.lock

File diff suppressed because it is too large Load Diff