Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
909d980
still with broken tests...
toger5 Dec 15, 2025
5bc6ed5
small refactor to make it testable.
toger5 Dec 15, 2025
3d8d0ae
Merge branch 'livekit' into toger5/pseudonomous-identities
toger5 Dec 15, 2025
6fe6dab
switch synapse docker image to one with sticky event support
fkwp Dec 16, 2025
ff3d6bd
enable sticky events
fkwp Dec 16, 2025
9bd51fd
Merge branch 'livekit' into toger5/pseudonomous-identities
toger5 Dec 16, 2025
ab7e348
Make use of the new jwt service endpoint (with delayed event delegation)
toger5 Dec 17, 2025
50f3bf0
use correct js-sdk
toger5 Dec 17, 2025
55d18f1
temp refactored membership rtcidentity
toger5 Dec 19, 2025
3ba2d13
use the js-sdk where the hashed rtcSessionIdeintity is already part of
toger5 Dec 28, 2025
7591e2b
Merge branch 'livekit' into toger5/delayed-event-delegation
toger5 Dec 28, 2025
0f5c5d8
cleanup based on new js-sdk impl
toger5 Dec 29, 2025
df2bd22
Merge branch 'livekit' into toger5/delayed-event-delegation
toger5 Jan 5, 2026
5d5d75e
fixup merge
toger5 Jan 5, 2026
00fca6e
simplify localTransport
toger5 Jan 5, 2026
1580087
use latest js-sdk
toger5 Jan 5, 2026
009c9e0
Merge branch 'livekit' into toger5/delayed-event-delegation
toger5 Jan 5, 2026
69a4189
self review
toger5 Jan 5, 2026
556a87d
fix js-doc
toger5 Jan 5, 2026
83d04ac
fix tests and remove duplicated mocks.
toger5 Jan 7, 2026
6480df4
add tests for open id delay fallback
toger5 Jan 7, 2026
d48042f
fix lint
toger5 Jan 7, 2026
dd562bd
dont use throw for logic.
toger5 Jan 7, 2026
d814f60
review (docstrings) and remove unused artifacts.
toger5 Jan 7, 2026
75fca31
cleanup an rename compatibility mode
toger5 Jan 7, 2026
d5ad2e3
fix up tests
toger5 Jan 7, 2026
0eeed4e
fix test snapshot
toger5 Jan 7, 2026
5556d36
add retries and be more specific when we fall back to legacy endpoint
toger5 Jan 7, 2026
a5a4bb2
add retries inside the `getLiveKitJWTWithDelayDelegation` and
toger5 Jan 7, 2026
385f63e
fix tests
toger5 Jan 7, 2026
1909aef
temp
toger5 Jan 8, 2026
d4b06b0
fix connection recreation which breaks EC lk connection
toger5 Jan 8, 2026
7dbbd76
Refactor how we aquire the jwt token for the local user. (only fetch it
toger5 Jan 9, 2026
c7c6dc1
fix js-sdk dependency
toger5 Jan 9, 2026
c2e3774
prettier
toger5 Jan 9, 2026
ed3d168
fix tests
toger5 Jan 9, 2026
3943231
use actual uuid for `member.id`
toger5 Jan 9, 2026
9a88e3d
review valere (more readable force new endpoint logic)
toger5 Jan 9, 2026
32694fd
improve rtcBackendIdentity debugging
toger5 Jan 9, 2026
7387a00
lint
toger5 Jan 9, 2026
1a9e4a1
bump js-sdk
toger5 Jan 9, 2026
735c17d
fix tile flicker on video mute/unmute
toger5 Jan 9, 2026
3412779
fix tests
toger5 Jan 9, 2026
47e3896
add uuid in our own package.json
toger5 Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/dev_homeserver-othersite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ experimental_features:
# MSC4222 needed for syncv2 state_after. This allow clients to
# correctly track the state of the room.
msc4222_enabled: true
# sticky events for MatrixRTC user state
msc4354_enabled: true

# The maximum allowed duration by which sent events can be delayed, as
# per MSC4140. Must be a positive value if set. Defaults to no
Expand Down
2 changes: 1 addition & 1 deletion backend/dev_homeserver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ experimental_features:
# MSC4222 needed for syncv2 state_after. This allow clients to
# correctly track the state of the room.
msc4222_enabled: true
# sticky events for matrixRTC user state
# sticky events for MatrixRTC user state
msc4354_enabled: true

# The maximum allowed duration by which sent events can be delayed, as
Expand Down
2 changes: 2 additions & 0 deletions backend/playwright_homeserver-othersite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ experimental_features:
# MSC4222 needed for syncv2 state_after. This allow clients to
# correctly track the state of the room.
msc4222_enabled: true
# sticky events for MatrixRTC user state
msc4354_enabled: true

# The maximum allowed duration by which sent events can be delayed, as
# per MSC4140. Must be a positive value if set. Defaults to no
Expand Down
2 changes: 2 additions & 0 deletions backend/playwright_homeserver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ experimental_features:
# MSC4222 needed for syncv2 state_after. This allow clients to
# correctly track the state of the room.
msc4222_enabled: true
# sticky events for MatrixRTC user state
msc4354_enabled: true

# The maximum allowed duration by which sent events can be delayed, as
# per MSC4140. Must be a positive value if set. Defaults to no
Expand Down
8 changes: 4 additions & 4 deletions dev-backend-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ networks:

services:
auth-service:
image: ghcr.io/element-hq/lk-jwt-service:latest-ci
image: ghcr.io/element-hq/lk-jwt-service:pr_139
pull_policy: always
hostname: auth-server
environment:
Expand All @@ -25,7 +25,7 @@ services:
- ecbackend

auth-service-1:
image: ghcr.io/element-hq/lk-jwt-service:latest-ci
image: ghcr.io/element-hq/lk-jwt-service:pr_139
pull_policy: always
hostname: auth-server-1
environment:
Expand Down Expand Up @@ -88,7 +88,7 @@ services:

synapse:
hostname: homeserver
image: docker.io/matrixdotorg/synapse:latest
image: ghcr.io/element-hq/synapse:pr-18968-dcb7678281bc02d4551043a6338fe5b7e6aa47ce
pull_policy: always
environment:
- SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml
Expand All @@ -106,7 +106,7 @@ services:

synapse-1:
hostname: homeserver-1
image: docker.io/matrixdotorg/synapse:latest
image: ghcr.io/element-hq/synapse:pr-18968-dcb7678281bc02d4551043a6338fe5b7e6aa47ce
pull_policy: always
environment:
- SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml
Expand Down
1 change: 1 addition & 0 deletions locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"matrix_rtc_transport_missing": "The server is not configured to work with {{brand}}. Please contact your server admin (Domain: {{domain}}, Error Code: {{ errorCode }}).",
"membership_manager": "Membership Manager Error",
"membership_manager_description": "The Membership Manager had to shut down. This is caused by many consequtive failed network requests.",
"no_matrix_2_authorization_service": "Your authorization service for you media server (SFU) is not on the newest version",
"open_elsewhere": "Opened in another tab",
"open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page.",
"room_creation_restricted": "Failed to create call",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"livekit-client": "^2.13.0",
"lodash-es": "^4.17.21",
"loglevel": "^1.9.1",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#ff5fab7722e54be2d69a380cd6182ecb262d7859",
"matrix-widget-api": "^1.14.0",
"node-stdlib-browser": "^1.3.1",
"normalize.css": "^8.0.1",
Expand Down
7 changes: 1 addition & 6 deletions sdk/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,7 @@ export async function createMatrixRTCSdk(
const mediaDevices = new MediaDevices(scope);
const muteStates = new MuteStates(scope, mediaDevices, constant(true));
const slot = { application, id };
const rtcSession = new MatrixRTCSession(
client,
room,
MatrixRTCSession.sessionMembershipsForSlot(room, slot),
slot,
);
const rtcSession = new MatrixRTCSession(client, room, slot);
const callViewModel = createCallViewModel$(
scope,
rtcSession,
Expand Down
5 changes: 5 additions & 0 deletions src/RTCConnectionStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface Props {
audio?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
video?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
focusUrl?: string;
rtcBackendIdentity?: string;
}

const extractDomain = (url: string): string => {
Expand All @@ -37,6 +38,7 @@ export const RTCConnectionStats: FC<Props> = ({
audio,
video,
focusUrl,
rtcBackendIdentity,
...rest
}) => {
const [showModal, setShowModal] = useState(false);
Expand Down Expand Up @@ -71,6 +73,9 @@ export const RTCConnectionStats: FC<Props> = ({
</pre>
</div>
</Modal>
<Text as="span" size="xs" title="rtcBackendIdentity">
rtcBackendIdentity:{rtcBackendIdentity}
</Text>
{focusUrl && (
<div>
<Text as="span" size="xs" title="focusURL">
Expand Down
13 changes: 8 additions & 5 deletions src/e2ee/matrixKeyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ Please see LICENSE in the repository root for full details.
*/

import { BaseKeyProvider } from "livekit-client";
import { logger } from "matrix-js-sdk/lib/logger";
import {
type MatrixRTCSession,
MatrixRTCSessionEvent,
} from "matrix-js-sdk/lib/matrixrtc";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
const logger = rootLogger.getChild("[MatrixKeyProvider]");

export class MatrixKeyProvider extends BaseKeyProvider {
private rtcSession?: MatrixRTCSession;
Expand Down Expand Up @@ -42,7 +44,8 @@ export class MatrixKeyProvider extends BaseKeyProvider {
private onEncryptionKeyChanged = (
encryptionKey: Uint8Array<ArrayBuffer>,
encryptionKeyIndex: number,
participantId: string,
membershipParts: CallMembershipIdentityParts,
rtcBackendIdentity: string,
): void => {
crypto.subtle
.importKey("raw", encryptionKey, "HKDF", false, [
Expand All @@ -53,17 +56,17 @@ export class MatrixKeyProvider extends BaseKeyProvider {
(keyMaterial) => {
this.onSetEncryptionKey(
keyMaterial,
participantId,
rtcBackendIdentity,
encryptionKeyIndex,
);

logger.debug(
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`,
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${rtcBackendIdentity} (before hash: ${membershipParts.userId}:${membershipParts.deviceId}) encryptionKeyIndex=${encryptionKeyIndex}`,
);
},
(e) => {
logger.error(
`Failed to create key material from buffer for livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`,
`Failed to create key material from buffer for livekit room=${this.rtcSession?.room.roomId} participantId before hash=${membershipParts.userId}:${membershipParts.deviceId} encryptionKeyIndex=${encryptionKeyIndex}`,
e,
);
},
Expand Down
3 changes: 1 addition & 2 deletions src/livekit/MatrixAudioRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
type AudioTrackProps,
} from "@livekit/components-react";
import { logger } from "matrix-js-sdk/lib/logger";
import { type ParticipantId } from "matrix-js-sdk/lib/matrixrtc";

import { useEarpieceAudioConfig } from "../MediaDevicesContext";
import { useReactiveState } from "../useReactiveState";
Expand All @@ -32,7 +31,7 @@ export interface MatrixAudioRendererProps {
* This list needs to be composed based on the matrixRTC members so that we do not play audio from users
* that are not expected to be in the rtc session (local user is excluded).
*/
validIdentities: ParticipantId[];
validIdentities: string[];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the rtcBackendIdentity now I guess.
Would it be worth creating an alias for that in the js-sdK?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think so. There are no string semantics to that identity... The names should already imply what it is?

But If we have a PR that introduces such a thing I would not object.

/**
* If set to `true`, mutes all audio tracks rendered by the component.
* @remarks
Expand Down
106 changes: 105 additions & 1 deletion src/livekit/openIDSFU.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import fetchMock from "fetch-mock";

import { getSFUConfigWithOpenID, type OpenIDClientParts } from "./openIDSFU";
import { testJWTToken } from "../utils/test-fixtures";
import { ownMemberMock } from "../utils/test";

const sfuUrl = "https://sfu.example.org";

Expand All @@ -33,6 +34,7 @@ describe("getSFUConfigWithOpenID", () => {
vitest.clearAllMocks();
fetchMock.reset();
});

it("should handle fetching a token", async () => {
fetchMock.post("https://sfu.example.org/sfu/get", () => {
return {
Expand All @@ -42,6 +44,7 @@ describe("getSFUConfigWithOpenID", () => {
});
const config = await getSFUConfigWithOpenID(
matrixClient,
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
);
Expand All @@ -53,6 +56,7 @@ describe("getSFUConfigWithOpenID", () => {
});
void (await fetchMock.flush());
});

it("should fail if the SFU errors", async () => {
fetchMock.post("https://sfu.example.org/sfu/get", () => {
return {
Expand All @@ -63,11 +67,12 @@ describe("getSFUConfigWithOpenID", () => {
try {
await getSFUConfigWithOpenID(
matrixClient,
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
);
} catch (ex) {
expect(((ex as Error).cause as Error).message).toEqual(
expect((ex as Error).message).toEqual(
"SFU Config fetch failed with status code 500",
);
void (await fetchMock.flush());
Expand All @@ -76,6 +81,104 @@ describe("getSFUConfigWithOpenID", () => {
expect.fail("Expected test to throw;");
});

it("should try legacy and then new endpoint with delay delegation", async () => {
fetchMock.post("https://sfu.example.org/get_token", () => {
return {
status: 500,
body: { error: "Test failure" },
};
});
fetchMock.post("https://sfu.example.org/sfu/get", () => {
return {
status: 500,
body: { error: "Test failure" },
};
});
try {
await getSFUConfigWithOpenID(
matrixClient,
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
{
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
delayId: "mock_delay_id",
},
);
} catch (ex) {
expect((ex as Error).message).toEqual(
"SFU Config fetch failed with status code 500",
);
void (await fetchMock.flush());
}
const calls = fetchMock.calls();
expect(calls.length).toBe(2);

expect(calls[0][0]).toStrictEqual("https://sfu.example.org/get_token");
expect(calls[0][1]).toStrictEqual({
// check if it uses correct delayID!
body: '{"room_id":"!example_room_id","slot_id":"m.call#ROOM","member":{"id":"@alice:example.org:DEVICE","claimed_user_id":"@alice:example.org","claimed_device_id":"DEVICE"},"delay_id":"mock_delay_id","delay_timeout":1000,"delay_cs_api_url":"https://matrix.homeserverserver.org"}',
method: "POST",
headers: {
"Content-Type": "application/json",
},
});

expect(calls[1][0]).toStrictEqual("https://sfu.example.org/sfu/get");

expect(calls[1][1]).toStrictEqual({
body: '{"room":"!example_room_id","device_id":"DEVICE"}',
headers: {
"Content-Type": "application/json",
},
method: "POST",
});
});

it("dont try legacy if endpoint with delay delegation is sucessful", async () => {
fetchMock.post("https://sfu.example.org/get_token", () => {
return {
status: 200,
body: { url: sfuUrl, jwt: testJWTToken },
};
});
fetchMock.post("https://sfu.example.org/sfu/get", () => {
return {
status: 500,
body: { error: "Test failure" },
};
});
try {
await getSFUConfigWithOpenID(
matrixClient,
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
{
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
delayId: "mock_delay_id",
},
);
} catch (ex) {
expect((ex as Error).message).toEqual(
"SFU Config fetch failed with status code 500",
);
void (await fetchMock.flush());
}
const calls = fetchMock.calls();
expect(calls.length).toBe(1);

expect(calls[0][0]).toStrictEqual("https://sfu.example.org/get_token");
expect(calls[0][1]).toStrictEqual({
// check if it uses correct delayID!
body: '{"room_id":"!example_room_id","slot_id":"m.call#ROOM","member":{"id":"@alice:example.org:DEVICE","claimed_user_id":"@alice:example.org","claimed_device_id":"DEVICE"},"delay_id":"mock_delay_id","delay_timeout":1000,"delay_cs_api_url":"https://matrix.homeserverserver.org"}',
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
});

it("should retry fetching the openid token", async () => {
let count = 0;
matrixClient.getOpenIdToken.mockImplementation(async () => {
Expand All @@ -98,6 +201,7 @@ describe("getSFUConfigWithOpenID", () => {
});
const config = await getSFUConfigWithOpenID(
matrixClient,
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
);
Expand Down
Loading
Loading