diff --git a/locales/en/app.json b/locales/en/app.json index ca971fbc8..8656502cf 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -120,6 +120,8 @@ "peer_connection_timeout_description": "Connection to the media server timed out. Try switching to a different network or disabling your VPN. If the problem persists, see our <0>troubleshooting guide or contact your server administrator.", "room_creation_restricted": "Failed to create call", "room_creation_restricted_description": "Call creation might be restricted to authorized users only. Try again later, or contact your server admin if the problem persists.", + "sticky_events_required": "Homeserver does not support Matrix 2.0 calls", + "sticky_events_required_description": "This deployment is configured to use Matrix 2.0 call mode, but the homeserver does not advertise support for sticky events (MSC4354). Ask your server admin to upgrade, or switch the deployment to a compatible mode.", "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server admin." }, "group_call_loader": { diff --git a/package.json b/package.json index 2c49e2156..d16135e28 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "livekit-client": "^2.18.1", "lodash-es": "^4.17.21", "loglevel": "^1.9.1", - "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^1.16.1", "node-stdlib-browser": "^1.3.1", "normalize.css": "^8.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0bcc4542d..31c229479 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -241,8 +241,8 @@ importers: specifier: ^1.9.1 version: 1.9.2 matrix-js-sdk: - specifier: matrix-org/matrix-js-sdk#develop - version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a48c8fe8a1a5f18a517e9b27552c73b6a7d210ee + specifier: github:matrix-org/matrix-js-sdk#develop + version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/7675fb2e65c3b13910b745cd7e7067c5aad4e816 matrix-widget-api: specifier: ^1.16.1 version: 1.17.0 @@ -3829,6 +3829,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.61.0': + resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@5.62.0': resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3845,6 +3851,10 @@ packages: resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.61.0': + resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.58.2': resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3863,6 +3873,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/tsconfig-utils@8.61.0': + resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/type-utils@8.60.0': resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3886,6 +3902,10 @@ packages: resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.61.0': + resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@5.62.0': resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3913,6 +3933,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/typescript-estree@8.61.0': + resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@5.62.0': resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3940,6 +3966,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.61.0': + resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@5.62.0': resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3956,6 +3989,10 @@ packages: resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.61.0': + resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} deprecated: Potential CWE-502 - Update to 1.3.1 or higher @@ -5891,9 +5928,9 @@ packages: matrix-events-sdk@0.0.1: resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a48c8fe8a1a5f18a517e9b27552c73b6a7d210ee: - resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a48c8fe8a1a5f18a517e9b27552c73b6a7d210ee} - version: 41.6.0 + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/7675fb2e65c3b13910b745cd7e7067c5aad4e816: + resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/7675fb2e65c3b13910b745cd7e7067c5aad4e816} + version: 41.7.0 engines: {node: '>=22.0.0'} matrix-widget-api@1.17.0: @@ -6829,6 +6866,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -10958,6 +11000,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.61.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) + '@typescript-eslint/types': 8.61.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -10978,6 +11029,11 @@ snapshots: '@typescript-eslint/types': 8.60.1 '@typescript-eslint/visitor-keys': 8.60.1 + '@typescript-eslint/scope-manager@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@5.9.3)': dependencies: typescript: 5.9.3 @@ -10990,6 +11046,10 @@ snapshots: dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.61.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/type-utils@8.60.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.60.0 @@ -11010,6 +11070,8 @@ snapshots: '@typescript-eslint/types@8.60.1': {} + '@typescript-eslint/types@8.61.0': {} + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 5.62.0 @@ -11069,6 +11131,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.61.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.61.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.4 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) @@ -11117,6 +11194,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.61.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) + eslint: 8.57.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -11137,6 +11225,11 @@ snapshots: '@typescript-eslint/types': 8.60.1 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} '@use-gesture/core@10.3.1': {} @@ -12249,7 +12342,7 @@ snapshots: eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.60.1(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.61.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) @@ -13372,7 +13465,7 @@ snapshots: matrix-events-sdk@0.0.1: {} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a48c8fe8a1a5f18a517e9b27552c73b6a7d210ee: + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/7675fb2e65c3b13910b745cd7e7067c5aad4e816: dependencies: '@babel/runtime': 7.29.2 '@matrix-org/matrix-sdk-crypto-wasm': 18.3.1 @@ -14545,6 +14638,8 @@ snapshots: semver@7.8.1: {} + semver@7.8.4: {} + set-blocking@2.0.0: {} set-cookie-parser@2.7.2: {} diff --git a/src/room/GroupCallErrorBoundary.test.tsx b/src/room/GroupCallErrorBoundary.test.tsx index 891a9724a..e10044ae1 100644 --- a/src/room/GroupCallErrorBoundary.test.tsx +++ b/src/room/GroupCallErrorBoundary.test.tsx @@ -32,6 +32,7 @@ import { LivekitConnectionError, MatrixRTCTransportMissingError, PeerConnectionTimeoutError, + StickyEventsRequiredError, UnknownCallError, } from "../utils/errors.ts"; import { mockConfig } from "../utils/test.ts"; @@ -59,6 +60,12 @@ test.each([ expectedDescription: "The server has reached its maximum capacity and you cannot join the call at this time. Try again later, or contact your server admin if the problem persists.", }, + { + error: new StickyEventsRequiredError(), + expectedTitle: "Homeserver does not support Matrix 2.0 calls", + expectedDescription: + "This deployment is configured to use Matrix 2.0 call mode, but the homeserver does not advertise support for sticky events (MSC4354). Ask your server admin to upgrade, or switch the deployment to a compatible mode.", + }, ])( "should report correct error for $expectedTitle", async ({ error, expectedTitle, expectedDescription }) => { diff --git a/src/room/GroupCallView.test.tsx b/src/room/GroupCallView.test.tsx index 02fcd64bd..53b18cde0 100644 --- a/src/room/GroupCallView.test.tsx +++ b/src/room/GroupCallView.test.tsx @@ -19,7 +19,12 @@ import { vitest, } from "vitest"; import { render, waitFor, screen, act } from "@testing-library/react"; -import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk"; +import { + type MatrixClient, + JoinRule, + type RoomState, + UnsupportedStickyEventsEndpointError, +} from "matrix-js-sdk"; import { MatrixRTCSessionEvent, type MatrixRTCSession, @@ -46,6 +51,7 @@ import { MockRTCSession, } from "../utils/test"; import { GroupCallView } from "./GroupCallView"; +import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary"; import { ElementWidgetActions, type WidgetHelpers } from "../widget"; import { LazyEventEmitter } from "../LazyEventEmitter"; import { MatrixRTCTransportMissingError } from "../utils/errors"; @@ -130,6 +136,9 @@ beforeEach(() => { function createGroupCallView( widget: WidgetHelpers | null, joined = true, + options: { + withErrorBoundary?: boolean; + } = {}, ): { rtcSession: MatrixRTCSession; getByText: ReturnType["getByText"]; @@ -166,24 +175,36 @@ function createGroupCallView( video: { enabled: false }, // TODO-MULTI-SFU: This cast isn't valid, it's likely the cause of some current test failures } as unknown as MuteStates; + const groupCallView = ( + + ); const { getByText } = render( - + {options.withErrorBoundary ? ( + + {groupCallView} + + ) : ( + groupCallView + )} @@ -394,6 +415,46 @@ test.skip("GroupCallView shows errors that occur during joining", async () => { screen.getByText("Call is not supported"); }); +test("translates wrapped UnsupportedStickyEventsEndpointError to the StickyEventsRequiredError screen", async () => { + // Mirror the shape the SDK emits: the MembershipManager scheduler wraps + // the original UnsupportedStickyEventsEndpointError in a generic Error + // but preserves the original on `.cause`. + const stickyError = new UnsupportedStickyEventsEndpointError( + "Server does not support the sticky events", + "sendStickyEvent", + ); + const wrappedError = new Error( + "The MembershipManager shut down because of the end condition: " + + String(stickyError), + { cause: stickyError }, + ); + + const { rtcSession } = createGroupCallView(null, true, { + withErrorBoundary: true, + }); + + await act(() => + rtcSession.emit(MatrixRTCSessionEvent.MembershipManagerError, wrappedError), + ); + + await screen.findByText("Homeserver does not support Matrix 2.0 calls"); +}); + +test("falls back to ConnectionLostError for unrecognised membership manager errors", async () => { + const { rtcSession } = createGroupCallView(null, true, { + withErrorBoundary: true, + }); + + await act(() => + rtcSession.emit( + MatrixRTCSessionEvent.MembershipManagerError, + new Error("something else broke"), + ), + ); + + await screen.findByText("Connection lost"); +}); + test("user can reconnect after a membership manager error", async () => { const user = userEvent.setup(); const { rtcSession } = createGroupCallView(null, true); diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 7c9009fec..09ea15be3 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -13,7 +13,12 @@ import { useMemo, useState, } from "react"; -import { type MatrixClient, JoinRule, type Room } from "matrix-js-sdk"; +import { + type MatrixClient, + JoinRule, + type Room, + UnsupportedStickyEventsEndpointError, +} from "matrix-js-sdk"; import { Room as LivekitRoom, isE2EESupported as isE2EESupportedBrowser, @@ -67,6 +72,7 @@ import { ConnectionLostError, E2EENotSupportedError, ElementCallError, + StickyEventsRequiredError, UnknownCallError, } from "../utils/errors.ts"; import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx"; @@ -162,8 +168,22 @@ export const GroupCallView: FC = ({ useTypedEventEmitter( rtcSession, MatrixRTCSessionEvent.MembershipManagerError, - (error) => setExternalError(new ConnectionLostError()), + (error) => { + // When matrix_rtc_mode=matrix_2_0 is in effect but the homeserver does + // not advertise MSC4354 (sticky events), the SDK throws an + // `UnsupportedStickyEventsEndpointError`. The MembershipManager + // scheduler wraps it and exposes the original via `.cause`. + if ( + error instanceof Error && + error.cause instanceof UnsupportedStickyEventsEndpointError + ) { + setExternalError(new StickyEventsRequiredError()); + } else { + setExternalError(new ConnectionLostError()); + } + }, ); + useEffect(() => { // Sanity check the room object if (client.getRoom(rtcSession.room.roomId) !== rtcSession.room) diff --git a/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap b/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap index 5a687eb2a..810419428 100644 --- a/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap +++ b/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap @@ -1720,6 +1720,160 @@ exports[`should report correct error for 'Connection lost' 1`] = ` `; +exports[`should report correct error for 'Homeserver does not support Matrix 2.…' 1`] = ` + +
+
+ +
+
+
+
+
+ +
+

+ Homeserver does not support Matrix 2.0 calls +

+

+ This deployment is configured to use Matrix 2.0 call mode, but the homeserver does not advertise support for sticky events (MSC4354). Ask your server admin to upgrade, or switch the deployment to a compatible mode. +

+ +
+
+
+
+
+`; + exports[`should report correct error for 'Incompatible browser' 1`] = `