From 750db09156edd96c8d366cfc44834eeff0b594ab Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:27:04 +0100 Subject: [PATCH] Use the New MatrixRTCSession MembershipManager (#3015) * provide option to use the New MembershipManager * fix cryptoApi import change * add error screen * bump js-sdk * rename to `setUnrecoverableError` and remove onLeave call because that will be handled by an effect. * this was doing nothing (it is a fragment back when there was no deprecated `rtcSession.room`) * rename to error * Update src/utils/errors.ts Co-authored-by: Hugh Nimmo-Smith * Update src/utils/errors.ts Co-authored-by: Hugh Nimmo-Smith * review * bump js-sdk * expose lk log level changing in `window` * bump js-sdk - always log "Missing own membership: force re-join" - also check insertions queue * change lk log level to warn * Bump js-sdk * Bump js-sdk * . * Bump js-sdk * show user count based on meberships not users. Signed-off-by: Timo K * bump js-sdk * rename setting name * remove unused import * js sdk bump * remove `window.setLKLogLevel` * bump js sdk with reverted incompatible change * bump js-sdk with one less merge --------- Signed-off-by: Timo K Co-authored-by: Hugh Nimmo-Smith Co-authored-by: Hugh Nimmo-Smith --- locales/en/app.json | 3 +- package.json | 2 +- src/room/GroupCallView.tsx | 115 +++++++++++++++++--------- src/rtcSessionHelpers.test.ts | 1 + src/rtcSessionHelpers.ts | 2 + src/settings/DeveloperSettingsTab.tsx | 19 +++++ src/settings/settings.ts | 4 + src/settings/submit-rageshake.ts | 6 +- src/utils/errors.ts | 8 ++ src/utils/matrix.ts | 3 +- yarn.lock | 17 ++-- 11 files changed, 127 insertions(+), 53 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 30c134bf..d80a8502 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -70,7 +70,8 @@ "livekit_sfu": "LiveKit SFU: {{url}}", "matrix_id": "Matrix ID: {{id}}", "show_connection_stats": "Show connection statistics", - "show_non_member_tiles": "Show tiles for non-member media" + "show_non_member_tiles": "Show tiles for non-member media", + "use_new_membership_manager": "Use the new implementation of the call MembershipManager" }, "disconnected_banner": "Connectivity to the server has been lost.", "error": { diff --git a/package.json b/package.json index 2a7209be..bddb8082 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "livekit-client": "^2.5.7", "lodash-es": "^4.17.21", "loglevel": "^1.9.1", - "matrix-js-sdk": "^36.1.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#0cdb07544d9926cf0855a76ca5cc7dab253bdb24", "matrix-widget-api": "1.11.0", "normalize.css": "^8.0.1", "observable-hooks": "^4.2.3", diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 66f14821..6ae2b415 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -16,12 +16,15 @@ import { } from "react"; import { type MatrixClient } from "matrix-js-sdk/src/client"; import { + Room as LivekitRoom, isE2EESupported as isE2EESupportedBrowser, - Room, } from "livekit-client"; import { logger } from "matrix-js-sdk/src/logger"; -import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; -import { JoinRule } from "matrix-js-sdk/src/matrix"; +import { + MatrixRTCSessionEvent, + type MatrixRTCSession, +} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { JoinRule, type Room } from "matrix-js-sdk/src/matrix"; import { OfflineIcon, WebBrowserIcon, @@ -66,8 +69,14 @@ import { ElementCallError, ErrorCategory, ErrorCode, + RTCSessionError, } from "../utils/errors.ts"; import { ElementCallRichError } from "../RichError.tsx"; +import { + useNewMembershipManagerSetting as useNewMembershipManagerSetting, + useSetting, +} from "../settings/settings"; +import { useTypedEventEmitter } from "../useEvents"; declare global { interface Window { @@ -126,6 +135,18 @@ export const GroupCallView: FC = ({ }; }, [rtcSession]); + useTypedEventEmitter( + rtcSession, + MatrixRTCSessionEvent.MembershipManagerError, + (error) => { + setError( + new RTCSessionError( + ErrorCode.MEMBERSHIP_MANAGER_UNRECOVERABLE, + error.message ?? error, + ), + ); + }, + ); useEffect(() => { // Sanity check the room object if (client.getRoom(rtcSession.room.roomId) !== rtcSession.room) @@ -134,11 +155,14 @@ export const GroupCallView: FC = ({ ); }, [client, rtcSession.room]); + const room = rtcSession.room as Room; const { displayName, avatarUrl } = useProfile(client); - const roomName = useRoomName(rtcSession.room); - const roomAvatar = useRoomAvatar(rtcSession.room); + const roomName = useRoomName(room); + const roomAvatar = useRoomAvatar(room); const { perParticipantE2EE, returnToLobby } = useUrlParams(); - const e2eeSystem = useRoomEncryptionSystem(rtcSession.room.roomId); + const e2eeSystem = useRoomEncryptionSystem(room.roomId); + const [useNewMembershipManager] = useSetting(useNewMembershipManagerSetting); + usePageTitle(roomName); const matrixInfo = useMemo((): MatrixInfo => { @@ -146,21 +170,13 @@ export const GroupCallView: FC = ({ userId: client.getUserId()!, displayName: displayName!, avatarUrl: avatarUrl!, - roomId: rtcSession.room.roomId, + roomId: room.roomId, roomName, - roomAlias: rtcSession.room.getCanonicalAlias(), + roomAlias: room.getCanonicalAlias(), roomAvatar, e2eeSystem, }; - }, [ - client, - displayName, - avatarUrl, - rtcSession.room, - roomName, - roomAvatar, - e2eeSystem, - ]); + }, [client, displayName, avatarUrl, roomName, room, roomAvatar, e2eeSystem]); // Count each member only once, regardless of how many devices they use const participantCount = useMemo( @@ -175,13 +191,18 @@ export const GroupCallView: FC = ({ const enterRTCSessionOrError = async ( rtcSession: MatrixRTCSession, perParticipantE2EE: boolean, + newMembershipManager: boolean, ): Promise => { try { - await enterRTCSession(rtcSession, perParticipantE2EE); + await enterRTCSession( + rtcSession, + perParticipantE2EE, + newMembershipManager, + ); } catch (e) { if (e instanceof ElementCallError) { // e.code === ErrorCode.MISSING_LIVE_KIT_SERVICE_URL) - setEnterRTCError(e); + setError(e); } else { logger.error(`Unknown Error while entering RTC session`, e); const error = new ElementCallError( @@ -189,7 +210,7 @@ export const GroupCallView: FC = ({ ErrorCode.UNKNOWN_ERROR, ErrorCategory.UNKNOWN, ); - setEnterRTCError(error); + setError(error); } } }; @@ -203,7 +224,7 @@ export const GroupCallView: FC = ({ // permissions and give you device names unless you specify a kind, but // here we want all kinds of devices. This needs a fix in livekit-client // for the following name-matching logic to do anything useful. - const devices = await Room.getLocalDevices(undefined, true); + const devices = await LivekitRoom.getLocalDevices(undefined, true); if (audioInput) { const deviceId = findDeviceByName(audioInput, "audioinput", devices); @@ -243,7 +264,11 @@ export const GroupCallView: FC = ({ await defaultDeviceSetup( ev.detail.data as unknown as JoinCallData, ); - await enterRTCSessionOrError(rtcSession, perParticipantE2EE); + await enterRTCSessionOrError( + rtcSession, + perParticipantE2EE, + useNewMembershipManager, + ); widget.api.transport.reply(ev.detail, {}); })().catch((e) => { logger.error("Error joining RTC session", e); @@ -256,13 +281,21 @@ export const GroupCallView: FC = ({ } else { // No lobby and no preload: we enter the rtc session right away (async (): Promise => { - await enterRTCSessionOrError(rtcSession, perParticipantE2EE); + await enterRTCSessionOrError( + rtcSession, + perParticipantE2EE, + useNewMembershipManager, + ); })().catch((e) => { logger.error("Error joining RTC session", e); }); } } else { - void enterRTCSessionOrError(rtcSession, perParticipantE2EE); + void enterRTCSessionOrError( + rtcSession, + perParticipantE2EE, + useNewMembershipManager, + ); } } }, [ @@ -273,12 +306,11 @@ export const GroupCallView: FC = ({ perParticipantE2EE, latestDevices, latestMuteStates, + useNewMembershipManager, ]); const [left, setLeft] = useState(false); - const [enterRTCError, setEnterRTCError] = useState( - null, - ); + const [error, setError] = useState(null); const navigate = useNavigate(); const onLeave = useCallback( @@ -292,7 +324,7 @@ export const GroupCallView: FC = ({ // Otherwise the iFrame gets killed before the callEnded event got tracked. const posthogRequest = new Promise((resolve) => { PosthogAnalytics.instance.eventCallEnded.track( - rtcSession.room.roomId, + room.roomId, rtcSession.memberships.length, sendInstantly, rtcSession, @@ -321,11 +353,12 @@ export const GroupCallView: FC = ({ }); }, [ + leaveSoundContext, widget, rtcSession, + room.roomId, isPasswordlessUser, confineToRoom, - leaveSoundContext, navigate, ], ); @@ -351,7 +384,7 @@ export const GroupCallView: FC = ({ } }, [widget, isJoined, rtcSession]); - const joinRule = useJoinRule(rtcSession.room); + const joinRule = useJoinRule(room); const [shareModalOpen, setInviteModalOpen] = useState(false); const onDismissInviteModal = useCallback( @@ -379,8 +412,12 @@ export const GroupCallView: FC = ({ const onReconnect = useCallback(() => { setLeft(false); resetError(); - enterRTCSessionOrError(rtcSession, perParticipantE2EE).catch((e) => { - logger.error("Error re-entering RTC session", e); + enterRTCSessionOrError( + rtcSession, + perParticipantE2EE, + useNewMembershipManager, + ).catch((e) => { + logger.error("Error re-entering RTC session on reconnect", e); }); }, [resetError]); @@ -402,7 +439,7 @@ export const GroupCallView: FC = ({ ); } return GroupCallErrorPage; - }, [onLeave, rtcSession, perParticipantE2EE, t]); + }, [t, rtcSession, onLeave, perParticipantE2EE, useNewMembershipManager]); if (!isE2EESupportedBrowser() && e2eeSystem.kind !== E2eeType.NONE) { // If we have a encryption system but the browser does not support it. @@ -417,7 +454,7 @@ export const GroupCallView: FC = ({ const shareModal = ( @@ -430,7 +467,11 @@ export const GroupCallView: FC = ({ matrixInfo={matrixInfo} muteStates={muteStates} onEnter={() => - void enterRTCSessionOrError(rtcSession, perParticipantE2EE) + void enterRTCSessionOrError( + rtcSession, + perParticipantE2EE, + useNewMembershipManager, + ) } confineToRoom={confineToRoom} hideHeader={hideHeader} @@ -441,11 +482,11 @@ export const GroupCallView: FC = ({ ); let body: ReactNode; - if (enterRTCError) { + if (error) { // If an ElementCallError was recorded, then create a component that will fail to render and throw // an ElementCallRichError error. This will then be handled by the ErrorBoundary component. const ErrorComponent = (): ReactNode => { - throw new ElementCallRichError(enterRTCError); + throw new ElementCallRichError(error); }; body = ; } else if (isJoined) { diff --git a/src/rtcSessionHelpers.test.ts b/src/rtcSessionHelpers.test.ts index 972d6e75..8d0b95d3 100644 --- a/src/rtcSessionHelpers.test.ts +++ b/src/rtcSessionHelpers.test.ts @@ -111,6 +111,7 @@ test("It joins the correct Session", async () => { { manageMediaKeys: false, useLegacyMemberEvents: false, + useNewMembershipManager: true, }, ); }); diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 31149d51..0f43fd90 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -97,6 +97,7 @@ async function makePreferredLivekitFoci( export async function enterRTCSession( rtcSession: MatrixRTCSession, encryptMedia: boolean, + useNewMembershipManager = true, ): Promise { PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId); @@ -114,6 +115,7 @@ export async function enterRTCSession( await makePreferredLivekitFoci(rtcSession, livekitAlias), makeActiveFocus(), { + useNewMembershipManager, manageMediaKeys: encryptMedia, ...(useDeviceSessionMemberEvents !== undefined && { useLegacyMemberEvents: !useDeviceSessionMemberEvents, diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index c7c88f5b..7f5e236b 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -15,6 +15,7 @@ import { debugTileLayout as debugTileLayoutSetting, showNonMemberTiles as showNonMemberTilesSetting, showConnectionStats as showConnectionStatsSetting, + useNewMembershipManagerSetting, } from "./settings"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room as LivekitRoom } from "livekit-client"; @@ -38,6 +39,10 @@ export const DeveloperSettingsTab: FC = ({ client, livekitRoom }) => { showConnectionStatsSetting, ); + const [useNewMembershipManager, setNewMembershipManager] = useSetting( + useNewMembershipManagerSetting, + ); + const sfuUrl = useMemo((): URL | null => { if (livekitRoom?.engine.client.ws?.url) { // strip the URL params @@ -134,6 +139,20 @@ export const DeveloperSettingsTab: FC = ({ client, livekitRoom }) => { )} /> + + ): void => { + setNewMembershipManager(event.target.checked); + }, + [setNewMembershipManager], + )} + /> + {livekitRoom ? ( <>

diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 4ec0143f..ee43acc6 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -113,4 +113,8 @@ export const soundEffectVolumeSetting = new Setting( 0.5, ); +export const useNewMembershipManagerSetting = new Setting( + "new-membership-manager", + true, +); export const alwaysShowSelf = new Setting("always-show-self", true); diff --git a/src/settings/submit-rageshake.ts b/src/settings/submit-rageshake.ts index 58056ef3..9f38deb7 100644 --- a/src/settings/submit-rageshake.ts +++ b/src/settings/submit-rageshake.ts @@ -9,10 +9,10 @@ import { type ComponentProps, useCallback, useEffect, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { ClientEvent, - type Crypto, type MatrixClient, type MatrixEvent, } from "matrix-js-sdk/src/matrix"; +import { type CryptoApi } from "matrix-js-sdk/src/crypto-api"; import { getLogsForReport } from "./rageshake"; import { useClient } from "../ClientContext"; @@ -34,7 +34,7 @@ const gzip = async (text: string): Promise => { * Collects crypto related information. */ async function collectCryptoInfo( - cryptoApi: Crypto.CryptoApi, + cryptoApi: CryptoApi, body: FormData, ): Promise { body.append("crypto_version", cryptoApi.getVersion()); @@ -82,7 +82,7 @@ async function collectCryptoInfo( */ async function collectRecoveryInfo( client: MatrixClient, - cryptoApi: Crypto.CryptoApi, + cryptoApi: CryptoApi, body: FormData, ): Promise { const secretStorage = client.secretStorage; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index c87bdee7..05b7dedc 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -13,6 +13,7 @@ export enum ErrorCode { */ MISSING_MATRIX_RTC_FOCUS = "MISSING_MATRIX_RTC_FOCUS", CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR", + MEMBERSHIP_MANAGER_UNRECOVERABLE = "MEMBERSHIP_MANAGER_UNRECOVERABLE", UNKNOWN_ERROR = "UNKNOWN_ERROR", } @@ -20,6 +21,7 @@ export enum ErrorCategory { /** Calling is not supported, server misconfigured (JWT service missing, no MSC support ...)*/ CONFIGURATION_ISSUE = "CONFIGURATION_ISSUE", NETWORK_CONNECTIVITY = "NETWORK_CONNECTIVITY", + RTC_SESSION_FAILURE = "RTC_SESSION_FAILURE", UNKNOWN = "UNKNOWN", // SYSTEM_FAILURE / FEDERATION_FAILURE .. } @@ -72,3 +74,9 @@ export class ConnectionLostError extends ElementCallError { ); } } + +export class RTCSessionError extends ElementCallError { + public constructor(code: ErrorCode, message: string) { + super("RTCSession Error", code, ErrorCategory.RTC_SESSION_FAILURE, message); + } +} diff --git a/src/utils/matrix.ts b/src/utils/matrix.ts index c80fa7d9..f1801b97 100644 --- a/src/utils/matrix.ts +++ b/src/utils/matrix.ts @@ -69,7 +69,7 @@ async function waitForSync(client: MatrixClient): Promise { * otherwise rust crypto will throw since it is not ready to initialize a new session. * If another client is running make sure `.logout()` is called before executing this function. * @param clientOptions Object of options passed through to the client - * @param restore If the rust crypto should be reset before the cient initialization or + * @param restore If the rust crypto should be reset before the client initialization or * if the initialization should try to restore the crypto state from the indexDB. * @returns The MatrixClient instance */ @@ -160,7 +160,6 @@ export async function initClient( ); } - client.setGlobalErrorOnUnknownDevices(false); // 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. diff --git a/yarn.lock b/yarn.lock index bbe4b1db..e9ee21bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1787,10 +1787,10 @@ dependencies: "@bufbuild/protobuf" "^1.10.0" -"@matrix-org/matrix-sdk-crypto-wasm@^12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-12.1.0.tgz#2aef64eab2d30c0a1ace9c0fe876f53aa2949f14" - integrity sha512-NhJFu/8FOGjnW7mDssRUzaMSwXrYOcCqgAjZyAw9KQ9unNADKEi7KoIKe7GtrG2PWtm36y2bUf+hB8vhSY6Wdw== +"@matrix-org/matrix-sdk-crypto-wasm@^14.0.1": + version "14.0.1" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-14.0.1.tgz#e258ef84bcc7889f0e7eb3a7dbecf0830a6dd606" + integrity sha512-CgLpHs6nTw5pjSsMBi9xbQnBXf2l8YhImQP9cv8nbGSCYdYjFI0FilMXffzjWV5HThpNHri/3pF20ahZtuS3VA== "@matrix-org/olm@3.2.15": version "3.2.15" @@ -6334,13 +6334,12 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@^36.1.0: - version "36.1.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-36.1.0.tgz#3685a85c0c1adf4e2c3622bce76c11430963f23d" - integrity sha512-KNPswMSAGKDxBybJedxRpWadaRes9paxmjTCUsQT8t1Jg3ZENraAt6ynIaxh6PxazAH9D5ly6EYKHaLMLbZ1Dg== +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#0cdb07544d9926cf0855a76ca5cc7dab253bdb24": + version "37.0.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/0cdb07544d9926cf0855a76ca5cc7dab253bdb24" dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^12.1.0" + "@matrix-org/matrix-sdk-crypto-wasm" "^14.0.1" "@matrix-org/olm" "3.2.15" another-json "^0.2.0" bs58 "^6.0.0"