diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7f0dd576..29a4a4e4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -36,7 +36,6 @@ module.exports = { rules: { "matrix-org/require-copyright-header": ["error", COPYRIGHT_HEADER], "jsx-a11y/media-has-caption": "off", - "deprecate/import": "off", // Disabled because it crashes the linter // We should use the js-sdk logger, never console directly. "no-console": ["error"], }, diff --git a/src/e2ee/sharedKeyManagement.ts b/src/e2ee/sharedKeyManagement.ts index 8190aed8..bc34f673 100644 --- a/src/e2ee/sharedKeyManagement.ts +++ b/src/e2ee/sharedKeyManagement.ts @@ -16,7 +16,7 @@ limitations under the License. import { useEffect, useMemo } from "react"; -import { useLocalStorage } from "../useLocalStorage"; +import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage"; import { useClient } from "../ClientContext"; import { useUrlParams } from "../UrlParams"; import { widget } from "../widget"; @@ -24,38 +24,52 @@ import { widget } from "../widget"; export const getRoomSharedKeyLocalStorageKey = (roomId: string): string => `room-shared-key-${roomId}`; -const useInternalRoomSharedKey = ( - roomId: string, -): [string | null, (value: string) => void] => { - const key = useMemo(() => getRoomSharedKeyLocalStorageKey(roomId), [roomId]); - const [roomSharedKey, setRoomSharedKey] = useLocalStorage(key); +const useInternalRoomSharedKey = (roomId: string): string | null => { + const key = getRoomSharedKeyLocalStorageKey(roomId); + const roomSharedKey = useLocalStorage(key)[0]; - return [roomSharedKey, setRoomSharedKey]; + return roomSharedKey; }; -const useKeyFromUrl = (roomId: string): string | null => { +/** + * Extracts the room password from the URL if one is present, saving it in localstorage + * and returning it in a tuple with the corresponding room ID from the URL. + * @returns A tuple of the roomId and password from the URL if the URL has both, + * otherwise [undefined, undefined] + */ +const useKeyFromUrl = (): [string, string] | [undefined, undefined] => { const urlParams = useUrlParams(); - const [e2eeSharedKey, setE2EESharedKey] = useInternalRoomSharedKey(roomId); useEffect(() => { - if (!urlParams.password) return; - if (urlParams.password === "") return; - if (urlParams.password === e2eeSharedKey) return; + if (!urlParams.password || !urlParams.roomId) return; + if (!urlParams.roomId) return; - setE2EESharedKey(urlParams.password); - }, [urlParams, e2eeSharedKey, setE2EESharedKey]); + setLocalStorageItem( + // We set the Item by only using data from the url. This way we + // make sure, we always have matching pairs in the LocalStorage, + // as they occur in the call links. + getRoomSharedKeyLocalStorageKey(urlParams.roomId), + urlParams.password, + ); + }, [urlParams]); - return urlParams.password ?? null; + return urlParams.roomId && urlParams.password + ? [urlParams.roomId, urlParams.password] + : [undefined, undefined]; }; -export const useRoomSharedKey = (roomId: string): string | null => { +export const useRoomSharedKey = (roomId: string): string | undefined => { // make sure we've extracted the key from the URL first // (and we still need to take the value it returns because // the effect won't run in time for it to save to localstorage in // time for us to read it out again). - const passwordFormUrl = useKeyFromUrl(roomId); + const [urlRoomId, passwordFormUrl] = useKeyFromUrl(); - return useInternalRoomSharedKey(roomId)[0] ?? passwordFormUrl; + const storedPassword = useInternalRoomSharedKey(roomId); + + if (storedPassword) return storedPassword; + if (urlRoomId === roomId) return passwordFormUrl; + return undefined; }; export const useIsRoomE2EE = (roomId: string): boolean | null => { diff --git a/src/livekit/useECConnectionState.ts b/src/livekit/useECConnectionState.ts index 0b18cd10..32fe7415 100644 --- a/src/livekit/useECConnectionState.ts +++ b/src/livekit/useECConnectionState.ts @@ -19,6 +19,7 @@ import { ConnectionState, Room, RoomEvent, + Track, } from "livekit-client"; import { useCallback, useEffect, useRef, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; @@ -60,6 +61,14 @@ async function doConnect( // doesn't publish it until you unmute. We want to publish it from the start so we're // always capturing audio: it helps keep bluetooth headsets in the right mode and // mobile browsers to know we're doing a call. + if (livekitRoom!.localParticipant.getTrack(Track.Source.Microphone)) { + logger.warn( + "Pre-creating audio track but participant already appears to have an microphone track: this shouldn't happen!", + ); + return; + } + + logger.info("Pre-creating microphone track"); const audioTracks = await livekitRoom!.localParticipant.createTracks({ audio: audioOptions, }); @@ -69,6 +78,14 @@ async function doConnect( } if (!audioEnabled) await audioTracks[0].mute(); + // check again having awaited for the track to create + if (livekitRoom!.localParticipant.getTrack(Track.Source.Microphone)) { + logger.warn( + "Publishing pre-created audio track but participant already appears to have an microphone track: this shouldn't happen!", + ); + return; + } + logger.info("Publishing pre-created mic track"); await livekitRoom?.localParticipant.publishTrack(audioTracks[0]); } diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index a4330c79..814eb096 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -127,7 +127,7 @@ export function useLiveKit( const connectionState = useECConnectionState( { - deviceId: initialDevices.current.audioOutput.selectedId, + deviceId: initialDevices.current.audioInput.selectedId, }, initialMuteStates.current.audio.enabled, room, diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 9dc124f9..bd0fd1a7 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -87,9 +87,6 @@ import { import { useOpenIDSFU } from "../livekit/openIDSFU"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); -// There is currently a bug in Safari our our code with cloning and sending MediaStreams -// or with getUsermedia and getDisplaymedia being used within the same session. -// For now we can disable screensharing in Safari. const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // How long we wait after a focus switch before showing the real participant list again @@ -369,7 +366,7 @@ export const InCallView: FC = ({ ); if (!reducedControls) { - if (canScreenshare && !hideScreensharing && !isSafari) { + if (canScreenshare && !hideScreensharing) { buttons.push(