Fix to-device encryption info label (#3208)
* Fix to-device encryption info label The label was shown also without checking that we use PerParticipantE2EE. Which is a prerequisite for toDevice transport. As a result the label was shown when not desired. * rename: useLiveKit -> useLivekit * make the settings naming consistent
This commit is contained in:
@@ -48,7 +48,7 @@ interface UseLivekitResult {
|
|||||||
connState: ECConnectionState;
|
connState: ECConnectionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLiveKit(
|
export function useLivekit(
|
||||||
rtcSession: MatrixRTCSession,
|
rtcSession: MatrixRTCSession,
|
||||||
muteStates: MuteStates,
|
muteStates: MuteStates,
|
||||||
sfuConfig: SFUConfig | undefined,
|
sfuConfig: SFUConfig | undefined,
|
||||||
249
src/room/InCallView.test.tsx
Normal file
249
src/room/InCallView.test.tsx
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
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 {
|
||||||
|
beforeEach,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
it,
|
||||||
|
type MockedFunction,
|
||||||
|
vi,
|
||||||
|
} from "vitest";
|
||||||
|
import { act, render, type RenderResult } from "@testing-library/react";
|
||||||
|
import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk";
|
||||||
|
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container";
|
||||||
|
import { ConnectionState, type LocalParticipant } from "livekit-client";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
|
import {
|
||||||
|
RoomAudioRenderer,
|
||||||
|
RoomContext,
|
||||||
|
useLocalParticipant,
|
||||||
|
} from "@livekit/components-react";
|
||||||
|
import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
|
||||||
|
|
||||||
|
import { type MuteStates } from "./MuteStates";
|
||||||
|
import { InCallView } from "./InCallView";
|
||||||
|
import {
|
||||||
|
mockLivekitRoom,
|
||||||
|
mockLocalParticipant,
|
||||||
|
mockMatrixRoom,
|
||||||
|
mockMatrixRoomMember,
|
||||||
|
mockRemoteParticipant,
|
||||||
|
mockRtcMembership,
|
||||||
|
type MockRTCSession,
|
||||||
|
} from "../utils/test";
|
||||||
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
|
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||||
|
import { alice, local } from "../utils/test-fixtures";
|
||||||
|
import { useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting } from "../settings/settings";
|
||||||
|
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
||||||
|
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||||
|
|
||||||
|
// vi.hoisted(() => {
|
||||||
|
// localStorage = {} as unknown as Storage;
|
||||||
|
// });
|
||||||
|
vi.hoisted(
|
||||||
|
() =>
|
||||||
|
(global.ImageData = class MockImageData {
|
||||||
|
public data: number[] = [];
|
||||||
|
} as unknown as typeof ImageData),
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.mock("../soundUtils");
|
||||||
|
vi.mock("../useAudioContext");
|
||||||
|
vi.mock("../tile/GridTile");
|
||||||
|
vi.mock("../tile/SpotlightTile");
|
||||||
|
vi.mock("@livekit/components-react");
|
||||||
|
vi.mock("../e2ee/sharedKeyManagement");
|
||||||
|
vi.mock("react-use-measure", () => ({
|
||||||
|
default: (): [() => void, object] => [(): void => {}, {}],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC");
|
||||||
|
const localParticipant = mockLocalParticipant({
|
||||||
|
identity: "@local:example.org:AAAAAA",
|
||||||
|
});
|
||||||
|
const remoteParticipant = mockRemoteParticipant({
|
||||||
|
identity: "@alice:example.org:AAAAAA",
|
||||||
|
});
|
||||||
|
const carol = mockMatrixRoomMember(localRtcMember);
|
||||||
|
const roomMembers = new Map([carol].map((p) => [p.userId, p]));
|
||||||
|
|
||||||
|
const roomId = "!foo:bar";
|
||||||
|
let useRoomEncryptionSystemMock: MockedFunction<typeof useRoomEncryptionSystem>;
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
// RoomAudioRenderer is tested separately.
|
||||||
|
(
|
||||||
|
RoomAudioRenderer as MockedFunction<typeof RoomAudioRenderer>
|
||||||
|
).mockImplementation((_props) => {
|
||||||
|
return <div>mocked: RoomAudioRenderer</div>;
|
||||||
|
});
|
||||||
|
(
|
||||||
|
useLocalParticipant as MockedFunction<typeof useLocalParticipant>
|
||||||
|
).mockImplementation(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
isScreenShareEnabled: false,
|
||||||
|
localParticipant: localRtcMember as unknown as LocalParticipant,
|
||||||
|
}) as unknown as ReturnType<typeof useLocalParticipant>,
|
||||||
|
);
|
||||||
|
|
||||||
|
useRoomEncryptionSystemMock =
|
||||||
|
useRoomEncryptionSystem as typeof useRoomEncryptionSystemMock;
|
||||||
|
useRoomEncryptionSystemMock.mockReturnValue({ kind: E2eeType.NONE });
|
||||||
|
});
|
||||||
|
|
||||||
|
function createInCallView(): RenderResult & {
|
||||||
|
rtcSession: MockRTCSession;
|
||||||
|
} {
|
||||||
|
const client = {
|
||||||
|
getUser: () => null,
|
||||||
|
getUserId: () => localRtcMember.sender,
|
||||||
|
getDeviceId: () => localRtcMember.deviceId,
|
||||||
|
getRoom: (rId) => (rId === roomId ? room : null),
|
||||||
|
} as Partial<MatrixClient> as MatrixClient;
|
||||||
|
const room = mockMatrixRoom({
|
||||||
|
relations: {
|
||||||
|
getChildEventsForEvent: () =>
|
||||||
|
vi.mocked({
|
||||||
|
getRelations: () => [],
|
||||||
|
}),
|
||||||
|
} as unknown as RelationsContainer,
|
||||||
|
client,
|
||||||
|
roomId,
|
||||||
|
getMember: (userId) => roomMembers.get(userId) ?? null,
|
||||||
|
getMxcAvatarUrl: () => null,
|
||||||
|
hasEncryptionStateEvent: vi.fn().mockReturnValue(true),
|
||||||
|
getCanonicalAlias: () => null,
|
||||||
|
currentState: {
|
||||||
|
getJoinRule: () => JoinRule.Invite,
|
||||||
|
} as Partial<RoomState> as RoomState,
|
||||||
|
});
|
||||||
|
|
||||||
|
const muteState = {
|
||||||
|
audio: { enabled: false },
|
||||||
|
video: { enabled: false },
|
||||||
|
} as MuteStates;
|
||||||
|
const livekitRoom = mockLivekitRoom(
|
||||||
|
{
|
||||||
|
localParticipant,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
remoteParticipants$: of([remoteParticipant]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
|
|
||||||
|
rtcSession.joined = true;
|
||||||
|
const renderResult = render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<ReactionsSenderProvider
|
||||||
|
vm={vm}
|
||||||
|
rtcSession={rtcSession as unknown as MatrixRTCSession}
|
||||||
|
>
|
||||||
|
<TooltipProvider>
|
||||||
|
<RoomContext.Provider value={livekitRoom}>
|
||||||
|
<InCallView
|
||||||
|
client={client}
|
||||||
|
hideHeader={true}
|
||||||
|
rtcSession={rtcSession as unknown as MatrixRTCSession}
|
||||||
|
muteStates={muteState}
|
||||||
|
vm={vm}
|
||||||
|
matrixInfo={{
|
||||||
|
userId: "",
|
||||||
|
displayName: "",
|
||||||
|
avatarUrl: "",
|
||||||
|
roomId: "",
|
||||||
|
roomName: "",
|
||||||
|
roomAlias: null,
|
||||||
|
roomAvatar: null,
|
||||||
|
e2eeSystem: {
|
||||||
|
kind: E2eeType.NONE,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
livekitRoom={livekitRoom}
|
||||||
|
participantCount={0}
|
||||||
|
onLeave={function (): void {
|
||||||
|
throw new Error("Function not implemented.");
|
||||||
|
}}
|
||||||
|
connState={ConnectionState.Connected}
|
||||||
|
onShareClick={null}
|
||||||
|
/>
|
||||||
|
</RoomContext.Provider>
|
||||||
|
</TooltipProvider>
|
||||||
|
</ReactionsSenderProvider>
|
||||||
|
</BrowserRouter>,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...renderResult,
|
||||||
|
rtcSession,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("InCallView", () => {
|
||||||
|
describe("rendering", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const { container } = createInCallView();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("toDevice label", () => {
|
||||||
|
it("is shown if setting activated and room encrypted", () => {
|
||||||
|
useRoomEncryptionSystemMock.mockReturnValue({
|
||||||
|
kind: E2eeType.PER_PARTICIPANT,
|
||||||
|
});
|
||||||
|
useExperimentalToDeviceTransportSetting.setValue(true);
|
||||||
|
const { getByText } = createInCallView();
|
||||||
|
expect(getByText("using to Device key transport")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is not shown in unenecrypted room", () => {
|
||||||
|
useRoomEncryptionSystemMock.mockReturnValue({
|
||||||
|
kind: E2eeType.NONE,
|
||||||
|
});
|
||||||
|
useExperimentalToDeviceTransportSetting.setValue(true);
|
||||||
|
const { queryByText } = createInCallView();
|
||||||
|
expect(
|
||||||
|
queryByText("using to Device key transport"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is hidden once fallback was triggered", async () => {
|
||||||
|
useRoomEncryptionSystemMock.mockReturnValue({
|
||||||
|
kind: E2eeType.PER_PARTICIPANT,
|
||||||
|
});
|
||||||
|
useExperimentalToDeviceTransportSetting.setValue(true);
|
||||||
|
const { rtcSession, queryByText } = createInCallView();
|
||||||
|
expect(queryByText("using to Device key transport")).toBeInTheDocument();
|
||||||
|
expect(rtcSession).toBeDefined();
|
||||||
|
await act(() =>
|
||||||
|
rtcSession.emit(RoomAndToDeviceEvents.EnabledTransportsChanged, {
|
||||||
|
toDevice: true,
|
||||||
|
room: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
queryByText("using to Device key transport"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("is not shown if setting is disabled", () => {
|
||||||
|
useExperimentalToDeviceTransportSetting.setValue(false);
|
||||||
|
|
||||||
|
useRoomEncryptionSystemMock.mockReturnValue({
|
||||||
|
kind: E2eeType.PER_PARTICIPANT,
|
||||||
|
});
|
||||||
|
const { queryByText } = createInCallView();
|
||||||
|
expect(
|
||||||
|
queryByText("using to Device key transport"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -56,7 +56,7 @@ import { type OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
|||||||
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
|
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
|
||||||
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
||||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||||
import { useLiveKit } from "../livekit/useLiveKit";
|
import { useLivekit } from "../livekit/useLivekit.ts";
|
||||||
import { useWakeLock } from "../useWakeLock";
|
import { useWakeLock } from "../useWakeLock";
|
||||||
import { useMergedRefs } from "../useMergedRefs";
|
import { useMergedRefs } from "../useMergedRefs";
|
||||||
import { type MuteStates } from "./MuteStates";
|
import { type MuteStates } from "./MuteStates";
|
||||||
@@ -73,7 +73,10 @@ import {
|
|||||||
import { Grid, type TileProps } from "../grid/Grid";
|
import { Grid, type TileProps } from "../grid/Grid";
|
||||||
import { useInitial } from "../useInitial";
|
import { useInitial } from "../useInitial";
|
||||||
import { SpotlightTile } from "../tile/SpotlightTile";
|
import { SpotlightTile } from "../tile/SpotlightTile";
|
||||||
import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
|
import {
|
||||||
|
useRoomEncryptionSystem,
|
||||||
|
type EncryptionSystem,
|
||||||
|
} from "../e2ee/sharedKeyManagement";
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { makeGridLayout } from "../grid/GridLayout";
|
import { makeGridLayout } from "../grid/GridLayout";
|
||||||
import {
|
import {
|
||||||
@@ -115,7 +118,7 @@ export interface ActiveCallProps
|
|||||||
|
|
||||||
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||||
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
|
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
|
||||||
const { livekitRoom, connState } = useLiveKit(
|
const { livekitRoom, connState } = useLivekit(
|
||||||
props.rtcSession,
|
props.rtcSession,
|
||||||
props.muteStates,
|
props.muteStates,
|
||||||
sfuConfig,
|
sfuConfig,
|
||||||
@@ -223,19 +226,28 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
|
|
||||||
const [muteAllAudio] = useSetting(muteAllAudioSetting);
|
const [muteAllAudio] = useSetting(muteAllAudioSetting);
|
||||||
|
|
||||||
const [toDeviceEncryptionSetting] = useSetting(
|
// This seems like it might be enough logic to use move it into the call view model?
|
||||||
useExperimentalToDeviceTransportSetting,
|
const [didFallbackToRoomKey, setDidFallbackToRoomKey] = useState(false);
|
||||||
);
|
|
||||||
const [showToDeviceEncryption, setShowToDeviceEncryption] = useState(
|
|
||||||
() => toDeviceEncryptionSetting,
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
setShowToDeviceEncryption(toDeviceEncryptionSetting);
|
|
||||||
}, [toDeviceEncryptionSetting]);
|
|
||||||
useTypedEventEmitter(
|
useTypedEventEmitter(
|
||||||
rtcSession,
|
rtcSession,
|
||||||
RoomAndToDeviceEvents.EnabledTransportsChanged,
|
RoomAndToDeviceEvents.EnabledTransportsChanged,
|
||||||
(enabled) => setShowToDeviceEncryption(enabled.to_device),
|
(enabled) => setDidFallbackToRoomKey(enabled.room),
|
||||||
|
);
|
||||||
|
const [useExperimentalToDeviceTransport] = useSetting(
|
||||||
|
useExperimentalToDeviceTransportSetting,
|
||||||
|
);
|
||||||
|
const encryptionSystem = useRoomEncryptionSystem(rtcSession.room.roomId);
|
||||||
|
|
||||||
|
const showToDeviceEncryption = useMemo(
|
||||||
|
() =>
|
||||||
|
useExperimentalToDeviceTransport &&
|
||||||
|
encryptionSystem.kind === E2eeType.PER_PARTICIPANT &&
|
||||||
|
!didFallbackToRoomKey,
|
||||||
|
[
|
||||||
|
encryptionSystem.kind,
|
||||||
|
didFallbackToRoomKey,
|
||||||
|
useExperimentalToDeviceTransport,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleMicrophone = useCallback(
|
const toggleMicrophone = useCallback(
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import { act, type ReactNode } from "react";
|
|||||||
|
|
||||||
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
||||||
import {
|
import {
|
||||||
playReactionsSound,
|
playReactionsSound as playReactionsSoundSetting,
|
||||||
soundEffectVolumeSetting,
|
soundEffectVolume as soundEffectVolumeSetting,
|
||||||
} from "../settings/settings";
|
} from "../settings/settings";
|
||||||
import { useAudioContext } from "../useAudioContext";
|
import { useAudioContext } from "../useAudioContext";
|
||||||
import { GenericReaction, ReactionSet } from "../reactions";
|
import { GenericReaction, ReactionSet } from "../reactions";
|
||||||
@@ -50,7 +50,7 @@ vitest.mock("../soundUtils");
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vitest.resetAllMocks();
|
vitest.resetAllMocks();
|
||||||
playReactionsSound.setValue(playReactionsSound.defaultValue);
|
playReactionsSoundSetting.setValue(playReactionsSoundSetting.defaultValue);
|
||||||
soundEffectVolumeSetting.setValue(soundEffectVolumeSetting.defaultValue);
|
soundEffectVolumeSetting.setValue(soundEffectVolumeSetting.defaultValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ beforeEach(() => {
|
|||||||
|
|
||||||
test("preloads all audio elements", () => {
|
test("preloads all audio elements", () => {
|
||||||
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
playReactionsSound.setValue(true);
|
playReactionsSoundSetting.setValue(true);
|
||||||
render(<TestComponent vm={vm} />);
|
render(<TestComponent vm={vm} />);
|
||||||
expect(prefetchSounds).toHaveBeenCalledOnce();
|
expect(prefetchSounds).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
@@ -84,7 +84,7 @@ test("will play an audio sound when there is a reaction", () => {
|
|||||||
local,
|
local,
|
||||||
alice,
|
alice,
|
||||||
]);
|
]);
|
||||||
playReactionsSound.setValue(true);
|
playReactionsSoundSetting.setValue(true);
|
||||||
render(<TestComponent vm={vm} />);
|
render(<TestComponent vm={vm} />);
|
||||||
|
|
||||||
// Find the first reaction with a sound effect
|
// Find the first reaction with a sound effect
|
||||||
@@ -110,7 +110,7 @@ test("will play the generic audio sound when there is soundless reaction", () =>
|
|||||||
local,
|
local,
|
||||||
alice,
|
alice,
|
||||||
]);
|
]);
|
||||||
playReactionsSound.setValue(true);
|
playReactionsSoundSetting.setValue(true);
|
||||||
render(<TestComponent vm={vm} />);
|
render(<TestComponent vm={vm} />);
|
||||||
|
|
||||||
// Find the first reaction with a sound effect
|
// Find the first reaction with a sound effect
|
||||||
@@ -136,7 +136,7 @@ test("will play multiple audio sounds when there are multiple different reaction
|
|||||||
local,
|
local,
|
||||||
alice,
|
alice,
|
||||||
]);
|
]);
|
||||||
playReactionsSound.setValue(true);
|
playReactionsSoundSetting.setValue(true);
|
||||||
render(<TestComponent vm={vm} />);
|
render(<TestComponent vm={vm} />);
|
||||||
|
|
||||||
// Find the first reaction with a sound effect
|
// Find the first reaction with a sound effect
|
||||||
|
|||||||
181
src/room/__snapshots__/InCallView.test.tsx.snap
Normal file
181
src/room/__snapshots__/InCallView.test.tsx.snap
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`InCallView > rendering > renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="inRoom"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="header filler"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
mocked: RoomAudioRenderer
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="scrollingGrid grid"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="layer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="container slot"
|
||||||
|
data-id="1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="slot local slot"
|
||||||
|
data-block-alignment="start"
|
||||||
|
data-id="0"
|
||||||
|
data-inline-alignment="end"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="fixedGrid grid"
|
||||||
|
>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="footer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-labelledby=":r0:"
|
||||||
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="lg"
|
||||||
|
data-testid="incall_mute"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8 8v-.006l6.831 6.832-.002.002 1.414 1.415.003-.003 1.414 1.414-.003.003L20.5 20.5a1 1 0 0 1-1.414 1.414l-3.022-3.022A7.949 7.949 0 0 1 13 19.938V21a1 1 0 0 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 1 1 2 0 6 6 0 0 0 8.587 5.415l-1.55-1.55A4.005 4.005 0 0 1 8 12v-1.172L2.086 4.914A1 1 0 0 1 3.5 3.5L8 8Zm9.417 6.583 1.478 1.477A7.963 7.963 0 0 0 20 12a1 1 0 0 0-2 0c0 .925-.21 1.8-.583 2.583ZM8.073 5.238l7.793 7.793c.087-.329.134-.674.134-1.031V6a4 4 0 0 0-7.927-.762Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-labelledby=":r5:"
|
||||||
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="lg"
|
||||||
|
data-testid="incall_videomute"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2.747 2.753 4.35 4.355l.007-.003L18 17.994v.012l3.247 3.247a1 1 0 0 1-1.414 1.414l-2.898-2.898A1.992 1.992 0 0 1 16 20H6a4 4 0 0 1-4-4V8c0-.892.292-1.715.785-2.38L1.333 4.166a1 1 0 0 1 1.414-1.414ZM18 15.166 6.834 4H16a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715v1.45Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-labelledby=":ra:"
|
||||||
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="lg"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12.731 2C13.432 2 14 2.568 14 3.269c0 .578.396 1.074.935 1.286.085.034.17.07.253.106.531.23 1.162.16 1.572-.25a1.269 1.269 0 0 1 1.794 0l1.034 1.035a1.269 1.269 0 0 1 0 1.794c-.41.41-.48 1.04-.248 1.572.036.084.07.168.105.253.212.539.708.935 1.286.935.701 0 1.269.568 1.269 1.269v1.462c0 .701-.568 1.269-1.269 1.269-.578 0-1.074.396-1.287.935-.033.085-.068.17-.104.253-.232.531-.161 1.162.248 1.572a1.269 1.269 0 0 1 0 1.794l-1.034 1.034a1.269 1.269 0 0 1-1.794 0c-.41-.41-1.04-.48-1.572-.248a7.935 7.935 0 0 1-.253.105c-.539.212-.935.708-.935 1.286 0 .701-.568 1.269-1.269 1.269H11.27c-.702 0-1.27-.568-1.27-1.269 0-.578-.396-1.074-.935-1.287a7.975 7.975 0 0 1-.253-.104c-.531-.232-1.162-.161-1.572.248a1.269 1.269 0 0 1-1.794 0l-1.034-1.034a1.269 1.269 0 0 1 0-1.794c.41-.41.48-1.04.249-1.572a7.89 7.89 0 0 1-.106-.253C4.343 14.396 3.847 14 3.27 14 2.568 14 2 13.432 2 12.731V11.27c0-.702.568-1.27 1.269-1.27.578 0 1.074-.396 1.286-.935.034-.085.07-.17.106-.253.23-.531.16-1.162-.25-1.572a1.269 1.269 0 0 1 0-1.794l1.035-1.034a1.269 1.269 0 0 1 1.794 0c.41.41 1.04.48 1.572.249a7.93 7.93 0 0 1 .253-.106c.539-.212.935-.708.935-1.286C10 2.568 10.568 2 11.269 2h1.462ZM12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-labelledby=":rf:"
|
||||||
|
class="_button_i91xf_17 endCall _has-icon_i91xf_66 _icon-only_i91xf_59 _destructive_i91xf_116"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="lg"
|
||||||
|
data-testid="incall_leave"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m2.765 16.02-2.47-2.416A1.018 1.018 0 0 1 0 12.852c0-.304.098-.555.295-.751a15.64 15.64 0 0 1 5.316-3.786A15.89 15.89 0 0 1 12 7c2.237 0 4.367.443 6.39 1.329a15.977 15.977 0 0 1 5.315 3.772c.197.196.295.447.295.751 0 .305-.098.555-.295.752l-2.47 2.416a1.047 1.047 0 0 1-1.396.108l-3.114-2.363a1.067 1.067 0 0 1-.322-.376 1.066 1.066 0 0 1-.108-.483v-2.27a13.593 13.593 0 0 0-2.12-.524C13.459 9.996 12 9.937 12 9.937s-1.459.059-2.175.175c-.715.116-1.422.29-2.12.523v2.271c0 .179-.036.34-.108.483a1.066 1.066 0 0 1-.322.376l-3.114 2.363a1.047 1.047 0 0 1-1.396-.107Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="toggle layout"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-labelledby=":rk:"
|
||||||
|
name="layout"
|
||||||
|
type="radio"
|
||||||
|
value="spotlight"
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 5h14v8h-5a1 1 0 0 0-1 1v5H5V5Zm10 14v-4h4v4h-4ZM5 21h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
aria-labelledby=":rp:"
|
||||||
|
checked=""
|
||||||
|
name="layout"
|
||||||
|
type="radio"
|
||||||
|
value="grid"
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4 11a.967.967 0 0 1-.712-.287A.968.968 0 0 1 3 10V4c0-.283.096-.52.288-.712A.968.968 0 0 1 4 3h6a.97.97 0 0 1 .713.288A.968.968 0 0 1 11 4v6c0 .283-.096.52-.287.713A.968.968 0 0 1 10 11H4Zm5-2V5H5v4h4Zm5 12a.968.968 0 0 1-.713-.288A.968.968 0 0 1 13 20v-6c0-.283.096-.52.287-.713A.968.968 0 0 1 14 13h6a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v6c0 .283-.096.52-.288.712A.968.968 0 0 1 20 21h-6Zm5-2v-4h-4v4h4ZM4 21a.967.967 0 0 1-.712-.288A.968.968 0 0 1 3 20v-6a.97.97 0 0 1 .288-.713A.967.967 0 0 1 4 13h6c.283 0 .52.096.713.287.191.192.287.43.287.713v6a.97.97 0 0 1-.287.712A.968.968 0 0 1 10 21H4Zm5-2v-4H5v4h4Zm5-8a.968.968 0 0 1-.713-.287A.968.968 0 0 1 13 10V4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 14 3h6c.283 0 .52.096.712.288A.965.965 0 0 1 21 4v6a.97.97 0 0 1-.288.713A.968.968 0 0 1 20 11h-6Zm5-2V5h-4v4h4Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
import {
|
import {
|
||||||
useSetting,
|
useSetting,
|
||||||
soundEffectVolumeSetting,
|
soundEffectVolume as soundEffectVolumeSetting,
|
||||||
backgroundBlur as backgroundBlurSetting,
|
backgroundBlur as backgroundBlurSetting,
|
||||||
developerMode,
|
developerMode,
|
||||||
} from "./settings";
|
} from "./settings";
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export const playReactionsSound = new Setting<boolean>(
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const soundEffectVolumeSetting = new Setting<number>(
|
export const soundEffectVolume = new Setting<number>(
|
||||||
"sound-effect-volume",
|
"sound-effect-volume",
|
||||||
0.5,
|
0.5,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import userEvent from "@testing-library/user-event";
|
|||||||
|
|
||||||
import { deviceStub, MediaDevicesContext } from "./livekit/MediaDevicesContext";
|
import { deviceStub, MediaDevicesContext } from "./livekit/MediaDevicesContext";
|
||||||
import { useAudioContext } from "./useAudioContext";
|
import { useAudioContext } from "./useAudioContext";
|
||||||
import { soundEffectVolumeSetting } from "./settings/settings";
|
import { soundEffectVolume as soundEffectVolumeSetting } from "./settings/settings";
|
||||||
|
|
||||||
const staticSounds = Promise.resolve({
|
const staticSounds = Promise.resolve({
|
||||||
aSound: new ArrayBuffer(0),
|
aSound: new ArrayBuffer(0),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { logger } from "matrix-js-sdk/lib/logger";
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
soundEffectVolumeSetting as effectSoundVolumeSetting,
|
soundEffectVolume as soundEffectVolumeSetting,
|
||||||
useSetting,
|
useSetting,
|
||||||
} from "./settings/settings";
|
} from "./settings/settings";
|
||||||
import { useMediaDevices } from "./livekit/MediaDevicesContext";
|
import { useMediaDevices } from "./livekit/MediaDevicesContext";
|
||||||
@@ -63,7 +63,7 @@ interface UseAudioContext<S> {
|
|||||||
export function useAudioContext<S extends string>(
|
export function useAudioContext<S extends string>(
|
||||||
props: Props<S>,
|
props: Props<S>,
|
||||||
): UseAudioContext<S> | null {
|
): UseAudioContext<S> | null {
|
||||||
const [effectSoundVolume] = useSetting(effectSoundVolumeSetting);
|
const [effectSoundVolume] = useSetting(soundEffectVolumeSetting);
|
||||||
const devices = useMediaDevices();
|
const devices = useMediaDevices();
|
||||||
const [audioContext, setAudioContext] = useState<AudioContext>();
|
const [audioContext, setAudioContext] = useState<AudioContext>();
|
||||||
const [audioBuffers, setAudioBuffers] = useState<Record<S, AudioBuffer>>();
|
const [audioBuffers, setAudioBuffers] = useState<Record<S, AudioBuffer>>();
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ import {
|
|||||||
type Room as LivekitRoom,
|
type Room as LivekitRoom,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
|
import {
|
||||||
|
type RoomAndToDeviceEvents,
|
||||||
|
type RoomAndToDeviceEventsHandlerMap,
|
||||||
|
} from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LocalUserMediaViewModel,
|
LocalUserMediaViewModel,
|
||||||
@@ -269,8 +273,8 @@ export function mockConfig(config: Partial<ResolvedConfigOptions> = {}): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MockRTCSession extends TypedEventEmitter<
|
export class MockRTCSession extends TypedEventEmitter<
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent | RoomAndToDeviceEvents,
|
||||||
MatrixRTCSessionEventHandlerMap
|
MatrixRTCSessionEventHandlerMap & RoomAndToDeviceEventsHandlerMap
|
||||||
> {
|
> {
|
||||||
public readonly statistics = {
|
public readonly statistics = {
|
||||||
counters: {},
|
counters: {},
|
||||||
|
|||||||
Reference in New Issue
Block a user