mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-07 05:47:03 +00:00
small refactor to make it testable.
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
||||
|
||||
import {
|
||||
exampleTransport,
|
||||
mockComputeLivekitParticipantIdentity$,
|
||||
mockMatrixRoomMember,
|
||||
mockRtcMembership,
|
||||
} from "../utils/test";
|
||||
@@ -47,6 +48,13 @@ vitest.mock("../rtcSessionHelpers", async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
makeTransport: (): [LivekitTransport] => [exampleTransport],
|
||||
}));
|
||||
vitest.mock(
|
||||
import("../state/CallViewModel/remoteMembers/LivekitParticipantIdentity.ts"),
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
computeLivekitParticipantIdentity$: mockComputeLivekitParticipantIdentity$,
|
||||
}),
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
vitest.clearAllMocks();
|
||||
|
||||
@@ -22,8 +22,8 @@ import { BrowserRouter } from "react-router-dom";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { RoomContext, useLocalParticipant } from "@livekit/components-react";
|
||||
|
||||
import { InCallView } from "./InCallView";
|
||||
import {
|
||||
mockComputeLivekitParticipantIdentity$,
|
||||
mockLivekitRoom,
|
||||
mockLocalParticipant,
|
||||
mockMatrixRoom,
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
mockRtcMembership,
|
||||
type MockRTCSession,
|
||||
} from "../utils/test";
|
||||
import { InCallView } from "./InCallView";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||
import { alice, local } from "../utils/test-fixtures";
|
||||
@@ -61,6 +62,13 @@ vi.mock("../livekit/MatrixAudioRenderer");
|
||||
vi.mock("react-use-measure", () => ({
|
||||
default: (): [() => void, object] => [(): void => {}, {}],
|
||||
}));
|
||||
vi.mock(
|
||||
import("../state/CallViewModel/remoteMembers/LivekitParticipantIdentity.ts"),
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
computeLivekitParticipantIdentity$: mockComputeLivekitParticipantIdentity$,
|
||||
}),
|
||||
);
|
||||
|
||||
const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC");
|
||||
const localParticipant = mockLocalParticipant({
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
mockRtcMembership,
|
||||
testScope,
|
||||
exampleTransport,
|
||||
mockComputeLivekitParticipantIdentity$,
|
||||
} from "../../utils/test.ts";
|
||||
import { E2eeType } from "../../e2ee/e2eeType.ts";
|
||||
import {
|
||||
@@ -77,11 +78,22 @@ vi.mock("../e2ee/matrixKeyProvider");
|
||||
const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
|
||||
vi.mock("../UrlParams", () => ({ getUrlParams }));
|
||||
|
||||
vi.mock("../rtcSessionHelpers", async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
makeTransport: async (): Promise<LivekitTransport> =>
|
||||
Promise.resolve(exampleTransport),
|
||||
}));
|
||||
vi.mock(
|
||||
"../state/CallViewModel/localMember/localTransport",
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
makeTransport: async (): Promise<LivekitTransport> =>
|
||||
Promise.resolve(exampleTransport),
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock(
|
||||
import("./remoteMembers/LivekitParticipantIdentity.ts"),
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
computeLivekitParticipantIdentity$: mockComputeLivekitParticipantIdentity$,
|
||||
}),
|
||||
);
|
||||
|
||||
const yesNo = {
|
||||
y: true,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { encodeUnpaddedBase64Url } from "matrix-js-sdk";
|
||||
import { sha256 } from "matrix-js-sdk/lib/digest";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
import { from, type Observable } from "rxjs";
|
||||
|
||||
const livekitParticipantIdentityCache = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* The string that is computed based on the membership and used for the computing the hash.
|
||||
* `${userId}:${deviceId}:${membershipID}`
|
||||
* as the direct imput for: await sha256(input)
|
||||
*/
|
||||
export const livekitIdentityInput = ({
|
||||
userId,
|
||||
deviceId,
|
||||
memberId,
|
||||
}: CallMembershipIdentityParts): string => `${userId}|${deviceId}|${memberId}`;
|
||||
|
||||
export function computeLivekitParticipantIdentity$(
|
||||
membership: CallMembershipIdentityParts,
|
||||
kind: "rtc" | "session",
|
||||
): Observable<string> {
|
||||
const compute = async (): Promise<string> => {
|
||||
switch (kind) {
|
||||
case "rtc": {
|
||||
const input = livekitIdentityInput(membership);
|
||||
if (livekitParticipantIdentityCache.size > 400)
|
||||
// prevent memory leaks in a stupid/simple way
|
||||
livekitParticipantIdentityCache.clear();
|
||||
// TODO use non deprecated memberId
|
||||
if (livekitParticipantIdentityCache.has(input))
|
||||
return livekitParticipantIdentityCache.get(input)!;
|
||||
else {
|
||||
const hashBuffer = await sha256(input);
|
||||
const hashedString = encodeUnpaddedBase64Url(hashBuffer);
|
||||
livekitParticipantIdentityCache.set(input, hashedString);
|
||||
return hashedString;
|
||||
}
|
||||
}
|
||||
case "session":
|
||||
default:
|
||||
return `${membership.userId}:${membership.deviceId}`;
|
||||
}
|
||||
};
|
||||
return from(compute());
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
flushPromises,
|
||||
mockCallMembership,
|
||||
mockRemoteParticipant,
|
||||
withTestScheduler,
|
||||
} from "../../../utils/test.ts";
|
||||
import { type Connection } from "./Connection.ts";
|
||||
import { constant } from "../../Behavior.ts";
|
||||
|
||||
@@ -15,15 +15,13 @@ import {
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { combineLatest, filter, map, switchMap } from "rxjs";
|
||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||
import { sha256 } from "matrix-js-sdk/lib/digest";
|
||||
import { encodeUnpaddedBase64Url } from "matrix-js-sdk";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
|
||||
import { type Behavior } from "../../Behavior";
|
||||
import { type IConnectionManager } from "./ConnectionManager";
|
||||
import { Epoch, type ObservableScope } from "../../ObservableScope";
|
||||
import { type Connection } from "./Connection";
|
||||
import { generateItemsWithEpoch } from "../../../utils/observable";
|
||||
import { computeLivekitParticipantIdentity$ } from "./LivekitParticipantIdentity";
|
||||
|
||||
const logger = rootLogger.getChild("[MatrixLivekitMembers]");
|
||||
|
||||
@@ -73,25 +71,28 @@ export function createMatrixLivekitMembers$({
|
||||
*/
|
||||
const membershipsWithTransportAndLivekitIdentity$ =
|
||||
membershipsWithTransport$.pipe(
|
||||
switchMap(async (membershipsWithTransport) => {
|
||||
switchMap((membershipsWithTransport) => {
|
||||
const { value, epoch } = membershipsWithTransport;
|
||||
const membershipsWithTransportAndLkIdentityPromises = value.map(
|
||||
async (obj) => {
|
||||
return computeLivekitParticipantIdentity(
|
||||
(obj) => {
|
||||
return computeLivekitParticipantIdentity$(
|
||||
obj.membership,
|
||||
obj.membership.kind,
|
||||
);
|
||||
},
|
||||
);
|
||||
const identities = await Promise.all(
|
||||
return combineLatest(
|
||||
membershipsWithTransportAndLkIdentityPromises,
|
||||
).pipe(
|
||||
map((identities) => {
|
||||
const membershipsWithTransportAndLkIdentity = value.map(
|
||||
({ transport, membership }, index) => {
|
||||
return { transport, membership, identity: identities[index] };
|
||||
},
|
||||
);
|
||||
return new Epoch(membershipsWithTransportAndLkIdentity, epoch);
|
||||
}),
|
||||
);
|
||||
const membershipsWithTransportAndLkIdentity = value.map(
|
||||
({ transport, membership }, index) => {
|
||||
return { transport, membership, identity: identities[index] };
|
||||
},
|
||||
);
|
||||
return new Epoch(membershipsWithTransportAndLkIdentity, epoch);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -164,42 +165,3 @@ export function areLivekitTransportsEqual(
|
||||
if (!t1 && !t2) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const livekitParticipantIdentityCache = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* The string that is computed based on the membership and used for the computing the hash.
|
||||
* `${userId}:${deviceId}:${membershipID}`
|
||||
* as the direct imput for: await sha256(input)
|
||||
*/
|
||||
export const livekitIdentityInput = ({
|
||||
userId,
|
||||
deviceId,
|
||||
memberId,
|
||||
}: CallMembershipIdentityParts): string => `${userId}|${deviceId}|${memberId}`;
|
||||
|
||||
export async function computeLivekitParticipantIdentity(
|
||||
membership: CallMembershipIdentityParts,
|
||||
kind: "rtc" | "session",
|
||||
): Promise<string> {
|
||||
switch (kind) {
|
||||
case "rtc": {
|
||||
const input = livekitIdentityInput(membership);
|
||||
if (livekitParticipantIdentityCache.size > 400)
|
||||
// prevent memory leaks in a stupid/simple way
|
||||
livekitParticipantIdentityCache.clear();
|
||||
// TODO use non deprecated memberId
|
||||
if (livekitParticipantIdentityCache.has(input))
|
||||
return livekitParticipantIdentityCache.get(input)!;
|
||||
else {
|
||||
const hashBuffer = await sha256(input);
|
||||
const hashedString = encodeUnpaddedBase64Url(hashBuffer);
|
||||
livekitParticipantIdentityCache.set(input, hashedString);
|
||||
return hashedString;
|
||||
}
|
||||
}
|
||||
case "session":
|
||||
default:
|
||||
return `${membership.userId}:${membership.deviceId}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { ECConnectionFactory } from "./ConnectionFactory.ts";
|
||||
import { type OpenIDClientParts } from "../../../livekit/openIDSFU.ts";
|
||||
import {
|
||||
mockCallMembership,
|
||||
mockComputeLivekitParticipantIdentity$,
|
||||
mockMediaDevices,
|
||||
withTestScheduler,
|
||||
} from "../../../utils/test.ts";
|
||||
@@ -43,6 +44,11 @@ let lkRoomFactory: () => LivekitRoom;
|
||||
|
||||
const createdMockLivekitRooms: Map<string, LivekitRoom> = new Map();
|
||||
|
||||
vi.mock(import("./LivekitParticipantIdentity.ts"), async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
computeLivekitParticipantIdentity$: mockComputeLivekitParticipantIdentity$,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
testScope = new ObservableScope();
|
||||
mockClient = {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
vitest,
|
||||
} from "vitest";
|
||||
import {
|
||||
encodeUnpaddedBase64,
|
||||
MatrixEvent,
|
||||
type Room as MatrixRoom,
|
||||
type Room,
|
||||
@@ -43,13 +44,14 @@ import {
|
||||
type Room as LivekitRoom,
|
||||
Track,
|
||||
} from "livekit-client";
|
||||
import { randomUUID } from "crypto";
|
||||
import { createHash, randomUUID } from "crypto";
|
||||
import { type TrackReference } from "@livekit/components-core";
|
||||
import EventEmitter from "events";
|
||||
import {
|
||||
type KeyTransportEvents,
|
||||
type KeyTransportEventsHandlerMap,
|
||||
} from "matrix-js-sdk/lib/matrixrtc/IKeyTransport";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
|
||||
import {
|
||||
LocalUserMediaViewModel,
|
||||
@@ -522,3 +524,27 @@ export function mockMuteStates(
|
||||
const observableScope = new ObservableScope();
|
||||
return new MuteStates(observableScope, mockMediaDevices({}), joined$);
|
||||
}
|
||||
|
||||
export const mockComputeLivekitParticipantIdentity$ = (
|
||||
membership: CallMembershipIdentityParts,
|
||||
kind: "rtc" | "session",
|
||||
): Observable<string> => {
|
||||
function sha256(commitmentStr: string): string {
|
||||
return encodeUnpaddedBase64(
|
||||
createHash("sha256").update(commitmentStr, "utf8").digest(),
|
||||
);
|
||||
}
|
||||
let hash;
|
||||
switch (kind) {
|
||||
case "rtc": {
|
||||
hash = sha256(
|
||||
`${membership.userId}|${membership.deviceId}|${membership.memberId}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "session":
|
||||
default:
|
||||
hash = `${membership.userId}:${membership.deviceId}`;
|
||||
}
|
||||
return of(hash);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user