Compare commits

..

58 Commits

Author SHA1 Message Date
David Baker
d575ea4117 Merge pull request #1722 from vector-im/dbkr/dont_use_sender
Don't use event.sender
2023-10-10 17:20:35 +01:00
David Baker
fbb2dc2afd Update to merged js-sdk commit 2023-10-10 17:17:16 +01:00
Timo
d5edcce470 Fix mute button not being in sync with actual video/audio feed. (#1721)
* Fix mute button not being in sync with actual video/audio feed.
This happens if we toggle the button while waiting for updating the stream.
It is prohibited by checking if the stream state is in sync after the update
is done.
Signed-off-by: Timo K <toger5@hotmail.de>


---------

Signed-off-by: Timo K <toger5@hotmail.de>
2023-10-10 14:14:39 +02:00
David Baker
7ab69435e5 Merge pull request #1717 from vector-im/dbkr/fix_url_password_param
Use base64url encoding for the password param
2023-10-10 11:07:45 +01:00
David Baker
07cde7ee4d Don't use event.sender
Pull in a js-sdk change to avoid using event.sender (see js-sdk PR
for details).

Fixes https://github.com/vector-im/element-call/issues/1697
2023-10-09 20:49:03 +01:00
David Baker
df93fb4a3f Add comment 2023-10-09 16:35:27 +01:00
David Baker
6faceb07cd Log if password needed url encoding 2023-10-09 16:28:48 +01:00
David Baker
0892edc432 Use base64url encoding for the password param
As base64 is fairly obviously not sensible for URLs and we were not
URL encoding it so we were ending up with spaces in the URL.

Also base 64 encode the password in case, as per comment.
2023-10-09 10:08:10 +01:00
David Baker
e2abeba194 Merge pull request #1705 from vector-im/dbkr/use_secure_random
Generate call passwords with secure RNG
2023-10-06 16:20:50 +01:00
David Baker
e9798441f7 Merge remote-tracking branch 'origin/livekit' into dbkr/use_secure_random 2023-10-06 16:18:53 +01:00
David Baker
bc36acafc8 Merge pull request #1704 from vector-im/dbkr/refactor_room_create
Refactor room creation code a little
2023-10-06 16:18:18 +01:00
David Baker
f2435f1c31 More consistent variable naming 2023-10-06 16:15:16 +01:00
David Baker
715c5c73ca Merge remote-tracking branch 'origin/livekit' into dbkr/refactor_room_create 2023-10-06 15:15:30 +01:00
David Baker
be4afaeb7e Merge pull request #1687 from vector-im/dbkr/update_default_device
Switch capture devices if the default device changes
2023-10-06 12:01:46 +01:00
David Baker
44e604aaa1 Merge pull request #1703 from vector-im/dbkr/keep_password_in_url
Keep the password in the URL
2023-10-06 10:55:12 +01:00
David Baker
87d5062d34 Don't use js-sdk's base64 encode function
It uses the NodeJS Buffer global which presumably is provided by
Webpack in element-web but isn't here, apparently.
2023-10-05 17:57:23 +01:00
David Baker
d373081db1 Generate call passwords with secure RNG 2023-10-05 17:32:43 +01:00
David Baker
6481b2f67e Merge branch 'dbkr/keep_password_in_url' into dbkr/refactor_room_create 2023-10-05 17:27:03 +01:00
David Baker
b646b0ae56 Remove extra function
that was now doing exactly the same thing as the one above it.
2023-10-05 17:25:06 +01:00
David Baker
e63721acea Refactor room creation code a little
We c+ped the code to create room passwords between two places, but we
already had a createRoom utility function that knew about e2ee.
2023-10-05 16:44:31 +01:00
David Baker
4984bd630e Keep the password in the URL
We changed our minds: people do copy the URL from the bar and
give that to people and expect it to work: it doesn't make sense
to prioritise shorter URLs over this. There's no security advantage
unless we think there's a risk someone might steal your key by taking
a photo of your monitor over your shoulder and decrypting the calls
they can't already hear by standing behind you.
2023-10-05 16:13:56 +01:00
renovate[bot]
847789dcda Update dependency @sentry/vite-plugin to v2.8.0 (#1701)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-05 17:13:26 +02:00
renovate[bot]
d1cb6ee889 Update dependency vaul to ^0.7.0 (#1692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-05 16:25:27 +02:00
renovate[bot]
7fbd84a63c Update dependency vite to v4.4.11 (#1699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-05 16:24:29 +02:00
Timo
63a00eef2f await leave rtc session (#1648)
so that the widget is only getting the hangup even,
once the call has been cleaned up

Signed-off-by: Timo K <toger5@hotmail.de>
2023-10-04 18:27:07 +02:00
Timo
c18dce3617 Make sure roomAlias = null in widget mode (#1676)
Signed-off-by: Timo K <toger5@hotmail.de>
2023-10-04 15:56:57 +02:00
Stefan Ceriu
1eb2302060 Move apple-app-site-association to .well-known
https://developer.apple.com/videos/play/wwdc2019/717/

```
This file should be located at HTTPS://your domain name/.well-known/apple-app-site-association

Other paths are deprecated.
```
2023-10-04 16:40:49 +03:00
Stefan Ceriu
ad462f3d8e Fix apple-app-site-assoctiation no_universal_link query matching.
https://developer.apple.com/videos/play/wwdc2019/717/

```
You'll notice that I specify a question mark and an asterisk as the pattern from the query items value. A pattern consisting of a single asterisk matches any string, including the empty string. And a missing query item has a value equivalent to the empty string. So to match against the string that's at least one character long, I specify a question mark and then any additional characters are matched by the asterisk.
```
2023-10-04 16:40:49 +03:00
Robin
a3eb58f9fe Merge pull request #1688 from vector-im/renovate/vite-4.x-lockfile
Update dependency vite to v4.4.10
2023-10-03 16:29:34 -04:00
Robin
50b4d61fbd Merge pull request #1684 from RiotTranslateBot/weblate-element-call-element-call
Translations update from Weblate
2023-10-03 16:29:10 -04:00
Robin
d0eda79f27 Merge pull request #1691 from vector-im/renovate/vector-im-compound-web-0.x
Update dependency @vector-im/compound-web to ^0.5.0
2023-10-03 16:28:44 -04:00
Robin
a0cc7686b3 Merge pull request #1678 from vector-im/renovate/posthog-js-1.x-lockfile
Update dependency posthog-js to v1.81.3
2023-10-03 16:25:41 -04:00
renovate[bot]
20f96f17e4 Update dependency @vector-im/compound-web to ^0.5.0 2023-10-03 20:25:25 +00:00
random
1b109e1b3a Translated using Weblate (Italian)
Currently translated at 100.0% (121 of 121 strings)

Translation: Element Call/element-call
Translate-URL: https://translate.element.io/projects/element-call/element-call/it/
2023-10-03 19:43:00 +00:00
Jeff Huang
daa1fed0c0 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (121 of 121 strings)

Translation: Element Call/element-call
Translate-URL: https://translate.element.io/projects/element-call/element-call/zh_Hant/
2023-10-03 19:43:00 +00:00
Timo
01b2367f38 allow widget related params in the fragment (#1675)
Signed-off-by: Timo K <toger5@hotmail.de>
2023-10-03 21:35:28 +02:00
renovate[bot]
2961d588b6 Update dependency vite to v4.4.10 2023-10-03 19:29:34 +00:00
David Baker
c37b2924af Comment 2023-10-03 18:27:10 +01:00
David Baker
e0cabbc514 Switch capture devices if the default device changes
This is a bit of a hack, but is the only way I can see that we can
update to using the new default device when the OS-level default
changes. Hopefully the comments explain everything.
2023-10-03 18:22:56 +01:00
Robin
e54a1274bb Merge pull request #1679 from vector-im/renovate/livekit-components-react-1.x-lockfile
Update dependency @livekit/components-react to v1.3.0
2023-10-03 07:54:25 -04:00
Robin
e246f3f66b Merge pull request #1667 from vector-im/renovate/sentry-javascript-monorepo
Update sentry-javascript monorepo to v7.73.0
2023-10-03 07:52:31 -04:00
Robin
c769a1b86b Merge pull request #1671 from vector-im/renovate/typescript-eslint-monorepo
Update typescript-eslint monorepo to v6.7.4
2023-10-03 07:51:40 -04:00
renovate[bot]
bbc58502da Update dependency @livekit/components-react to v1.3.0 2023-10-03 11:51:28 +00:00
renovate[bot]
72ab839eff Update dependency posthog-js to v1.81.3 2023-10-03 11:49:11 +00:00
Robin
aea404588a Merge pull request #1677 from vector-im/renovate/node-18.x-lockfile
Update dependency @types/node to v18.18.3
2023-10-03 07:48:37 -04:00
renovate[bot]
b3c0a01429 Update dependency @types/node to v18.18.3 2023-10-02 21:25:51 +00:00
renovate[bot]
27fa35cbab Update typescript-eslint monorepo to v6.7.4 2023-10-02 17:32:32 +00:00
Robin
f779bc26cd Merge pull request #1666 from vector-im/renovate/i18next-parser-8.x-lockfile
Update dependency i18next-parser to v8.8.0
2023-10-02 10:36:30 -04:00
Robin
6b94e3553c Merge pull request #1665 from RiotTranslateBot/weblate-element-call-element-call
Translations update from Weblate
2023-10-02 10:35:33 -04:00
renovate[bot]
13579d5972 Update sentry-javascript monorepo to v7.73.0 2023-10-02 14:00:44 +00:00
renovate[bot]
47c1740504 Update dependency i18next-parser to v8.8.0 2023-10-01 17:49:06 +00:00
Ihor Hordiichuk
21789f7d22 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (121 of 121 strings)

Translation: Element Call/element-call
Translate-URL: https://translate.element.io/projects/element-call/element-call/uk/
2023-10-01 04:26:55 +00:00
Robin
67ea390847 Merge pull request #1664 from vector-im/renovate/vite-plugin-svgr-4.x-lockfile
Update dependency vite-plugin-svgr to v4.1.0
2023-09-29 22:27:41 -04:00
Robin
e501c5305f Merge pull request #1662 from vector-im/renovate/node-18.x-lockfile
Update dependency @types/node to v18.18.1
2023-09-29 22:26:50 -04:00
renovate[bot]
d3704dab33 Update dependency vite-plugin-svgr to v4.1.0 2023-09-29 21:28:22 +00:00
renovate[bot]
a7a2adaf6b Update dependency @types/node to v18.18.1 2023-09-29 17:01:31 +00:00
Robin
516d365511 Merge pull request #1660 from RiotTranslateBot/weblate-element-call-element-call
Translations update from Weblate
2023-09-29 13:01:03 -04:00
Vri
4343ae588e Translated using Weblate (German)
Currently translated at 100.0% (121 of 121 strings)

Translation: Element Call/element-call
Translate-URL: https://translate.element.io/projects/element-call/element-call/de/
2023-09-29 14:22:14 +00:00
17 changed files with 579 additions and 509 deletions

View File

@@ -47,7 +47,7 @@
"@types/lodash": "^4.14.199",
"@use-gesture/react": "^10.2.11",
"@vector-im/compound-design-tokens": "^0.0.6",
"@vector-im/compound-web": "^0.4.0",
"@vector-im/compound-web": "^0.5.0",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"@vitejs/plugin-react": "^4.0.1",
"buffer": "^6.0.3",
@@ -58,7 +58,7 @@
"i18next-http-backend": "^2.0.0",
"livekit-client": "^1.12.3",
"lodash": "^4.17.21",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#6385c9c0dab8fe67bd3a8992a4777f243fdd1b68",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#c8f8fb587d29dce22d314bfc16bf25a76b04e8bb",
"matrix-widget-api": "^1.3.1",
"normalize.css": "^8.0.1",
"pako": "^2.0.4",
@@ -74,7 +74,7 @@
"tinyqueue": "^2.0.3",
"unique-names-generator": "^4.6.0",
"uuid": "9",
"vaul": "^0.6.1"
"vaul": "^0.7.0"
},
"devDependencies": {
"@babel/core": "^7.16.5",

View File

@@ -10,7 +10,7 @@
"components": [
{
"?": {
"no_universal_links": "*"
"no_universal_links": "?*"
},
"exclude": true,
"comment": "Opt out of universal links"

View File

@@ -114,5 +114,10 @@
"Start new call": "Neuen Anruf beginnen",
"Call not found": "Anruf nicht gefunden",
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Anrufe sind nun Ende-zu-Ende-verschlüsselt und müssen auf der Startseite erstellt werden. Damit stellen wir sicher, dass alle denselben Schlüssel verwenden.",
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Dein Webbrowser unterstützt keine Medien-Ende-zu-Ende-Verschlüsselung. Unterstützte Browser sind Chrome, Safari, Firefox >=117"
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Dein Webbrowser unterstützt keine Medien-Ende-zu-Ende-Verschlüsselung. Unterstützte Browser sind Chrome, Safari, Firefox >=117",
"Copy link": "Link kopieren",
"Invite": "Einladen",
"Invite to this call": "Zu diesem Anruf einladen",
"Link copied to clipboard": "Link in Zwischenablage kopiert",
"Participants": "Teilnehmende"
}

View File

@@ -23,7 +23,7 @@
"Debug log request": "Richiesta registro di debug",
"Developer": "Sviluppatore",
"Developer Settings": "Impostazioni per sviluppatori",
"Display name": "Nome da mostrare",
"Display name": "Il tuo nome",
"Element Call Home": "Inizio di Element Call",
"Enable end-to-end encryption (password protected calls)": "Attiva crittografia end-to-end (chiamate protette da password)",
"Encrypted": "Cifrata",

View File

@@ -114,5 +114,10 @@
"Back to recents": "Повернутися до недавніх",
"Call not found": "Виклик не знайдено",
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Відтепер виклики захищено наскрізним шифруванням, і їх потрібно створювати з домашньої сторінки. Це допомагає переконатися, що всі користувачі використовують один і той самий ключ шифрування.",
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Ваш браузер не підтримує наскрізне шифрування мультимедійних даних. Підтримувані браузери: Chrome, Safari, Firefox >=117"
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Ваш браузер не підтримує наскрізне шифрування мультимедійних даних. Підтримувані браузери: Chrome, Safari, Firefox >=117",
"Invite": "Запросити",
"Link copied to clipboard": "Посилання скопійовано до буфера обміну",
"Participants": "Учасники",
"Copy link": "Скопіювати посилання",
"Invite to this call": "Запросити до цього виклику"
}

View File

@@ -114,5 +114,10 @@
"Unmute microphone": "將麥克風取消靜音",
"Call not found": "找不到通話",
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "通話現在是端對端加密的,必須從首頁建立。這有助於確保每個人都使用相同的加密金鑰。",
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "您的網路瀏覽器不支援媒體端到端加密。支援的瀏覽器包含了 Chrome、Safari、Firefox >=117"
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "您的網路瀏覽器不支援媒體端到端加密。支援的瀏覽器包含了 Chrome、Safari、Firefox >=117",
"Copy link": "複製連結",
"Invite": "邀請",
"Invite to this call": "邀請到此通話",
"Link copied to clipboard": "連結已複製到剪貼簿",
"Participants": "參與者"
}

View File

@@ -33,6 +33,9 @@ interface RoomIdentifier {
// clearer what each flag means, and helps us avoid coupling Element Call's
// behavior to the needs of specific consumers.
interface UrlParams {
// Widget api related params
widgetId: string | null;
parentUrl: string | null;
/**
* 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
@@ -178,6 +181,9 @@ export const getUrlParams = (
const fontScale = parseFloat(parser.getParam("fontScale") ?? "");
return {
widgetId: parser.getParam("widgetId"),
parentUrl: parser.getParam("parentUrl"),
// NB. we don't validate roomId here as we do in getRoomIdentifierFromUrl:
// what would we do if it were invalid? If the widget API says that's what
// the room ID is, then that's what it is.
@@ -218,33 +224,39 @@ export function getRoomIdentifierFromUrl(
hash: string
): RoomIdentifier {
let roomAlias: string | null = null;
pathname = pathname.substring(1); // Strip the "/"
const pathComponents = pathname.split("/");
const pathHasRoom = pathComponents[0] == "room";
const hasRoomAlias = pathComponents.length > 1;
// Here we handle the beginning of the alias and make sure it starts with a "#"
// What type is our url: roomAlias in hash, room alias as the search path, roomAlias after /room/
if (hash === "" || hash.startsWith("#?")) {
roomAlias = pathname.substring(1); // Strip the "/"
// Delete "/room/", if present
if (roomAlias.startsWith("room/")) {
roomAlias = roomAlias.substring("room/".length);
if (hasRoomAlias && pathHasRoom) {
roomAlias = pathComponents[1];
}
// Add "#", if not present
if (!roomAlias.startsWith("#")) {
roomAlias = `#${roomAlias}`;
if (!pathHasRoom) {
roomAlias = pathComponents[0];
}
} else {
roomAlias = hash;
}
// Delete "?" and what comes afterwards
roomAlias = roomAlias.split("?")[0];
roomAlias = roomAlias?.split("?")[0] ?? null;
if (roomAlias.length <= 1) {
if (roomAlias) {
// Make roomAlias is null, if it only is a "#"
roomAlias = null;
} else {
// Add server part, if not present
if (!roomAlias.includes(":")) {
roomAlias = `${roomAlias}:${Config.defaultServerName()}`;
if (roomAlias.length <= 1) {
roomAlias = null;
} else {
// Add "#", if not present
if (!roomAlias.startsWith("#")) {
roomAlias = `#${roomAlias}`;
}
// Add server part, if not present
if (!roomAlias.includes(":")) {
roomAlias = `${roomAlias}:${Config.defaultServerName()}`;
}
}
}

View File

@@ -19,7 +19,7 @@ import { useEffect, useMemo } from "react";
import { useEnableE2EE } from "../settings/useSetting";
import { useLocalStorage } from "../useLocalStorage";
import { useClient } from "../ClientContext";
import { PASSWORD_STRING, useUrlParams } from "../UrlParams";
import { useUrlParams } from "../UrlParams";
import { widget } from "../widget";
export const getRoomSharedKeyLocalStorageKey = (roomId: string): string =>
@@ -60,29 +60,6 @@ export const useRoomSharedKey = (roomId: string): string | null => {
return useInternalRoomSharedKey(roomId)[0] ?? passwordFormUrl;
};
export const useManageRoomSharedKey = (roomId: string): string | null => {
const urlParams = useUrlParams();
const urlPassword = useKeyFromUrl(roomId);
const [e2eeSharedKey] = useInternalRoomSharedKey(roomId);
useEffect(() => {
const hash = location.hash;
if (!hash.includes("?")) return;
if (!hash.includes(PASSWORD_STRING)) return;
if (urlParams.password !== e2eeSharedKey) return;
const [hashStart, passwordStart] = hash.split(PASSWORD_STRING);
const hashEnd = passwordStart.split("&").slice(1).join("&");
location.replace((hashStart ?? "") + (hashEnd ?? ""));
}, [urlParams, e2eeSharedKey]);
return e2eeSharedKey ?? urlPassword;
};
export const useIsRoomE2EE = (roomId: string): boolean | null => {
const { client } = useClient();
const room = useMemo(() => client?.getRoom(roomId) ?? null, [roomId, client]);

View File

@@ -68,9 +68,11 @@ function CallTile({ name, avatarUrl, room }: CallTileProps) {
return (
<div className={styles.callTile}>
<Link
// note we explicitly omit the password here as we don't want it on this link because
// it's just for the user to navigate around and not for sharing
to={getRelativeRoomUrl(room.roomId, room.name)}
to={getRelativeRoomUrl(
room.roomId,
room.name,
roomSharedKey ?? undefined
)}
className={styles.callTileLink}
>
<Avatar id={room.roomId} name={name} size={Size.LG} src={avatarUrl} />

View File

@@ -17,7 +17,6 @@ limitations under the License.
import { useState, useCallback, FormEvent, FormEventHandler } from "react";
import { useHistory } from "react-router-dom";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { randomString } from "matrix-js-sdk/src/randomstring";
import { useTranslation } from "react-i18next";
import { Heading } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger";
@@ -42,8 +41,6 @@ import { Form } from "../form/Form";
import { useEnableE2EE, useOptInAnalytics } from "../settings/useSetting";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { E2EEBanner } from "../E2EEBanner";
import { setLocalStorageItem } from "../useLocalStorage";
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
interface Props {
client: MatrixClient;
@@ -77,18 +74,19 @@ export function RegisteredView({ client }: Props) {
setError(undefined);
setLoading(true);
const roomId = (
await createRoom(client, roomName, e2eeEnabled ?? false)
)[1];
const createRoomResult = await createRoom(
client,
roomName,
e2eeEnabled ?? false
);
if (e2eeEnabled) {
setLocalStorageItem(
getRoomSharedKeyLocalStorageKey(roomId),
randomString(32)
);
}
history.push(getRelativeRoomUrl(roomId, roomName));
history.push(
getRelativeRoomUrl(
createRoomResult.roomId,
roomName,
createRoomResult.password
)
);
}
submit().catch((error) => {

View File

@@ -44,8 +44,6 @@ import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { useEnableE2EE, useOptInAnalytics } from "../settings/useSetting";
import { Config } from "../config/Config";
import { E2EEBanner } from "../E2EEBanner";
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
import { setLocalStorageItem } from "../useLocalStorage";
export const UnauthenticatedView: FC = () => {
const { setClient } = useClient();
@@ -87,18 +85,13 @@ export const UnauthenticatedView: FC = () => {
true
);
let roomId: string;
let createRoomResult;
try {
roomId = (
await createRoom(client, roomName, e2eeEnabled ?? false)
)[1];
if (e2eeEnabled) {
setLocalStorageItem(
getRoomSharedKeyLocalStorageKey(roomId),
randomString(32)
);
}
createRoomResult = await createRoom(
client,
roomName,
e2eeEnabled ?? false
);
} catch (error) {
if (!setClient) {
throw error;
@@ -127,7 +120,13 @@ export const UnauthenticatedView: FC = () => {
}
setClient({ client, session });
history.push(getRelativeRoomUrl(roomId, roomName));
history.push(
getRelativeRoomUrl(
createRoomResult.roomId,
roomName,
createRoomResult.password
)
);
}
submit().catch((error) => {

View File

@@ -20,6 +20,7 @@ import {
ExternalE2EEKeyProvider,
Room,
RoomOptions,
Track,
} from "livekit-client";
import { useLiveKitRoom } from "@livekit/components-react";
import { useEffect, useMemo, useRef, useState } from "react";
@@ -100,6 +101,17 @@ export function useLiveKit(
// block audio from being enabled until the connection is finished.
const [blockAudio, setBlockAudio] = useState(true);
// Store if audio/video are currently updating. If to prohibit unnecessary calls
// to setMicrophoneEnabled/setCameraEnabled
const audioMuteUpdating = useRef(false);
const videoMuteUpdating = useRef(false);
// Store the current button mute state that gets passed to this hook via props.
// We need to store it for awaited code that relies on the current value.
const buttonEnabled = useRef({
audio: initialMuteStates.current.audio.enabled,
video: initialMuteStates.current.video.enabled,
});
// We have to create the room manually here due to a bug inside
// @livekit/components-react. JSON.stringify() is used in deps of a
// useEffect() with an argument that references itself, if E2EE is enabled
@@ -136,20 +148,50 @@ export function useLiveKit(
// and setting tracks to be enabled during this time causes errors.
if (room !== undefined && connectionState === ConnectionState.Connected) {
const participant = room.localParticipant;
if (participant.isMicrophoneEnabled !== muteStates.audio.enabled) {
participant
.setMicrophoneEnabled(muteStates.audio.enabled)
.catch((e) =>
logger.error("Failed to sync audio mute state with LiveKit", e)
);
}
if (participant.isCameraEnabled !== muteStates.video.enabled) {
participant
.setCameraEnabled(muteStates.video.enabled)
.catch((e) =>
logger.error("Failed to sync video mute state with LiveKit", e)
);
}
// Always update the muteButtonState Ref so that we can read the current
// state in awaited blocks.
buttonEnabled.current = {
audio: muteStates.audio.enabled,
video: muteStates.video.enabled,
};
const syncMuteStateAudio = async () => {
if (
participant.isMicrophoneEnabled !== buttonEnabled.current.audio &&
!audioMuteUpdating.current
) {
audioMuteUpdating.current = true;
try {
await participant.setMicrophoneEnabled(buttonEnabled.current.audio);
} catch (e) {
logger.error("Failed to sync audio mute state with LiveKit", e);
}
audioMuteUpdating.current = false;
// Run the check again after the change is done. Because the user
// can update the state (presses mute button) while the device is enabling
// itself we need might need to update the mute state right away.
// This async recursion makes sure that setCamera/MicrophoneEnabled is
// called as little times as possible.
syncMuteStateAudio();
}
};
const syncMuteStateVideo = async () => {
if (
participant.isCameraEnabled !== buttonEnabled.current.video &&
!videoMuteUpdating.current
) {
videoMuteUpdating.current = true;
try {
await participant.setCameraEnabled(buttonEnabled.current.video);
} catch (e) {
logger.error("Failed to sync audio mute state with LiveKit", e);
}
videoMuteUpdating.current = false;
// see above
syncMuteStateVideo();
}
};
syncMuteStateAudio();
syncMuteStateVideo();
}
}, [room, muteStates, connectionState]);
@@ -158,12 +200,54 @@ export function useLiveKit(
if (room !== undefined && connectionState === ConnectionState.Connected) {
const syncDevice = (kind: MediaDeviceKind, device: MediaDevice) => {
const id = device.selectedId;
if (id !== undefined && room.getActiveDevice(kind) !== id) {
room
.switchActiveDevice(kind, id)
.catch((e) =>
logger.error(`Failed to sync ${kind} device with LiveKit`, e)
);
// Detect if we're trying to use chrome's default device, in which case
// we need to to see if the default device has changed to a different device
// by comparing the group ID of the device we're using against the group ID
// of what the default device is *now*.
// This is special-cased for only audio inputs because we need to dig around
// in the LocalParticipant object for the track object and there's not a nice
// way to do that generically. There is usually no OS-level default video capture
// device anyway, and audio outputs work differently.
if (
id === "default" &&
kind === "audioinput" &&
room.options.audioCaptureDefaults?.deviceId === "default"
) {
const activeMicTrack = Array.from(
room.localParticipant.audioTracks.values()
).find((d) => d.source === Track.Source.Microphone)?.track;
const defaultDevice = device.available.find(
(d) => d.deviceId === "default"
);
if (
defaultDevice &&
activeMicTrack &&
// only restart if the stream is still running: LiveKit will detect
// when a track stops & restart appropriately, so this is not our job.
// Plus, we need to avoid restarting again if the track is already in
// the process of being restarted.
activeMicTrack.mediaStreamTrack.readyState !== "ended" &&
defaultDevice.groupId !==
activeMicTrack.mediaStreamTrack.getSettings().groupId
) {
// It's different, so restart the track, ie. cause Livekit to do another
// getUserMedia() call with deviceId: default to get the *new* default device.
// Note that room.switchActiveDevice() won't work: Livekit will ignore it because
// the deviceId hasn't changed (was & still is default).
room.localParticipant
.getTrack(Track.Source.Microphone)
?.audioTrack?.restartTrack();
}
} else {
if (id !== undefined && room.getActiveDevice(kind) !== id) {
room
.switchActiveDevice(kind, id)
.catch((e) =>
logger.error(`Failed to sync ${kind} device with LiveKit`, e)
);
}
}
};

View File

@@ -1,5 +1,5 @@
/*
Copyright 2022 New Vector Ltd
Copyright 2022-2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -35,6 +35,8 @@ import IndexedDBWorker from "./IndexedDBWorker?worker";
import { getUrlParams, PASSWORD_STRING } from "./UrlParams";
import { loadOlm } from "./olm";
import { Config } from "./config/Config";
import { setLocalStorageItem } from "./useLocalStorage";
import { getRoomSharedKeyLocalStorageKey } from "./e2ee/sharedKeyManagement";
export const fallbackICEServerAllowed =
import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true";
@@ -71,6 +73,23 @@ function waitForSync(client: MatrixClient) {
});
}
function secureRandomString(entropyBytes: number): string {
const key = new Uint8Array(entropyBytes);
crypto.getRandomValues(key);
// encode to base64url as this value goes into URLs
// base64url is just base64 with thw two non-alphanum characters swapped out for
// ones that can be put in a URL without encoding. Browser JS has a native impl
// for base64 encoding but only a string (there isn't one that takes a UInt8Array
// yet) so just use the built-in one and convert, replace the chars and strip the
// padding from the end (otherwise we'd need to pull in another dependency).
return btoa(
key.reduce((acc, current) => acc + String.fromCharCode(current), "")
)
.replace("+", "-")
.replace("/", "_")
.replace(/=*$/, "");
}
/**
* Initialises and returns a new standalone Matrix Client.
* If true is passed for the 'restore' parameter, a check will be made
@@ -269,11 +288,17 @@ export function isLocalRoomId(roomId: string, client: MatrixClient): boolean {
return parts[1] === client.getDomain();
}
interface CreateRoomResult {
roomId: string;
alias?: string;
password?: string;
}
export async function createRoom(
client: MatrixClient,
name: string,
e2ee: boolean
): Promise<[string, string]> {
): Promise<CreateRoomResult> {
logger.log(`Creating room for group call`);
const createPromise = client.createRoom({
visibility: Visibility.Private,
@@ -336,7 +361,20 @@ export async function createRoom(
true
);
return [fullAliasFromRoomName(name, client), result.room_id];
let password;
if (e2ee) {
password = secureRandomString(16);
setLocalStorageItem(
getRoomSharedKeyLocalStorageKey(result.room_id),
password
);
}
return {
roomId: result.room_id,
alias: e2ee ? undefined : fullAliasFromRoomName(name, client),
password,
};
}
/**
@@ -366,9 +404,16 @@ export function getRelativeRoomUrl(
roomName?: string,
password?: string
): string {
// The password shouldn't need URL encoding here (we generate URL-safe ones) but encode
// it in case it came from another client that generated a non url-safe one
const encodedPassword = password ? encodeURIComponent(password) : undefined;
if (password && encodedPassword !== password) {
logger.info("Encoded call password used non URL-safe chars: buggy client?");
}
return `/room/#${
roomName ? "/" + roomAliasLocalpartFromRoomName(roomName) : ""
}?roomId=${roomId}${password ? "&" + PASSWORD_STRING + password : ""}`;
}?roomId=${roomId}${password ? "&" + PASSWORD_STRING + encodedPassword : ""}`;
}
export function getAvatarUrl(

View File

@@ -39,10 +39,7 @@ import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext";
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers";
import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState";
import {
useManageRoomSharedKey,
useIsRoomE2EE,
} from "../e2ee/sharedKeyManagement";
import { useIsRoomE2EE, useRoomSharedKey } from "../e2ee/sharedKeyManagement";
import { useEnableE2EE } from "../settings/useSetting";
import { useRoomAvatar } from "./useRoomAvatar";
import { useRoomName } from "./useRoomName";
@@ -75,7 +72,7 @@ export function GroupCallView({
const memberships = useMatrixRTCSessionMemberships(rtcSession);
const isJoined = useMatrixRTCSessionJoinState(rtcSession);
const e2eeSharedKey = useManageRoomSharedKey(rtcSession.room.roomId);
const e2eeSharedKey = useRoomSharedKey(rtcSession.room.roomId);
const isRoomE2EE = useIsRoomE2EE(rtcSession.room.roomId);
useEffect(() => {
@@ -113,7 +110,7 @@ export function GroupCallView({
// Count each member only once, regardless of how many devices they use
const participantCount = useMemo(
() => new Set<string>(memberships.map((m) => m.member.userId)).size,
() => new Set<string>(memberships.map((m) => m.sender!)).size,
[memberships]
);
@@ -217,13 +214,16 @@ export function GroupCallView({
sendInstantly
);
leaveRTCSession(rtcSession);
await leaveRTCSession(rtcSession);
if (widget) {
// we need to wait until the callEnded event is tracked on posthog.
// Otherwise the iFrame gets killed before the callEnded event got tracked.
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
widget.api.setAlwaysOnScreen(false);
PosthogAnalytics.instance.logout();
// we will always send the hangup event after the memberships have been updated
// calling leaveRTCSession.
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
}

View File

@@ -41,13 +41,15 @@ export function enterRTCSession(rtcSession: MatrixRTCSession) {
// have started tracking by the time calls start getting created.
//groupCallOTelMembership?.onJoinCall();
// right now we asume everything is a room-scoped call
// right now we assume everything is a room-scoped call
const livekitAlias = rtcSession.room.roomId;
rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
}
export function leaveRTCSession(rtcSession: MatrixRTCSession) {
export async function leaveRTCSession(
rtcSession: MatrixRTCSession
): Promise<void> {
//groupCallOTelMembership?.onLeaveCall();
rtcSession.leaveRoomSession();
await rtcSession.leaveRoomSession();
}

View File

@@ -70,9 +70,7 @@ interface WidgetHelpers {
*/
export const widget: WidgetHelpers | null = (() => {
try {
const query = new URLSearchParams(window.location.search);
const widgetId = query.get("widgetId");
const parentUrl = query.get("parentUrl");
const { widgetId, parentUrl } = getUrlParams();
if (widgetId && parentUrl) {
const parentOrigin = new URL(parentUrl).origin;

724
yarn.lock

File diff suppressed because it is too large Load Diff