diff --git a/package.json b/package.json index a4fd01602..e23312482 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "classnames": "^2.3.1", "color-hash": "^2.0.1", "events": "^3.3.0", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#5e766978b8cf80d943f796df1067722a6a5918a7", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#b97b862fb66bafee542e3c0baac35d6576b3a75d", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", "pako": "^2.0.4", diff --git a/src/Header.jsx b/src/Header.jsx index 85039de58..d4f83e552 100644 --- a/src/Header.jsx +++ b/src/Header.jsx @@ -1,5 +1,5 @@ import classNames from "classnames"; -import React, { useRef } from "react"; +import React, { useCallback, useRef } from "react"; import { Link } from "react-router-dom"; import styles from "./Header.module.css"; import { ReactComponent as Logo } from "./icons/Logo.svg"; @@ -8,6 +8,9 @@ import { ReactComponent as ArrowLeftIcon } from "./icons/ArrowLeft.svg"; import { useButton } from "@react-aria/button"; import { Subtitle } from "./typography/Typography"; import { Avatar } from "./Avatar"; +import { IncompatibleVersionModal } from "./IncompatibleVersionModal"; +import { useModalTriggerState } from "./Modal"; +import { Button } from "./button"; export function Header({ children, className, ...rest }) { return ( @@ -84,3 +87,25 @@ export function RoomSetupHeaderInfo({ roomName, avatarUrl, ...rest }) { ); } + +export function VersionMismatchWarning({ users, room }) { + const { modalState, modalProps } = useModalTriggerState(); + + const onDetailsClick = useCallback(() => { + modalState.open(); + }, [modalState]); + + if (users.size === 0) return null; + + return ( + + Incomaptible versions! + + {modalState.isOpen && ( + + )} + + ); +} diff --git a/src/Header.module.css b/src/Header.module.css index 04313d9d3..7f945819d 100644 --- a/src/Header.module.css +++ b/src/Header.module.css @@ -104,6 +104,24 @@ flex-shrink: 0; } +.versionMismatchWarning { + padding-left: 15px; +} + +.versionMismatchWarning::before { + content: ""; + display: inline-block; + position: relative; + top: 1px; + width: 16px; + height: 16px; + mask-image: url("./icons/AlertTriangleFilled.svg"); + mask-repeat: no-repeat; + mask-size: contain; + background-color: var(--alert); + padding-right: 5px; +} + @media (min-width: 800px) { .headerLogo, .roomAvatar, diff --git a/src/IncompatibleVersionModal.tsx b/src/IncompatibleVersionModal.tsx new file mode 100644 index 000000000..6cc5615a1 --- /dev/null +++ b/src/IncompatibleVersionModal.tsx @@ -0,0 +1,48 @@ +/* +Copyright 2022 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Room } from "matrix-js-sdk"; +import React from "react"; + +import { Modal, ModalContent } from "./Modal"; +import { Body } from "./typography/Typography"; + +interface Props { + userIds: Set; + room: Room; +} + +export const IncompatibleVersionModal: React.FC = ({ + userIds, + room, + ...rest +}) => { + const userLis = Array.from(userIds).map((u) => ( +
  • {room.getMember(u).name}
  • + )); + + return ( + + + + Other users are trying to join this call from incompatible versions. + These users should ensure that they have refreshed their browsers: +
      {userLis}
    + +
    +
    + ); +}; diff --git a/src/button/Button.jsx b/src/button/Button.jsx index 1244caf78..aabc3093f 100644 --- a/src/button/Button.jsx +++ b/src/button/Button.jsx @@ -41,6 +41,7 @@ export const variantToClassName = { iconCopy: [styles.iconCopyButton], secondaryHangup: [styles.secondaryHangup], dropdown: [styles.dropdownButton], + link: [styles.linkButton], }; export const sizeToClassName = { diff --git a/src/button/Button.module.css b/src/button/Button.module.css index ad5c7e349..53e3b2d52 100644 --- a/src/button/Button.module.css +++ b/src/button/Button.module.css @@ -207,3 +207,10 @@ limitations under the License. .lg { height: 40px; } + +.linkButton { + background-color: transparent; + border: none; + color: var(--accent); + cursor: pointer; +} diff --git a/src/icons/AlertTriangleFilled.svg b/src/icons/AlertTriangleFilled.svg new file mode 100644 index 000000000..cbdd429db --- /dev/null +++ b/src/icons/AlertTriangleFilled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/room/GroupCallView.jsx b/src/room/GroupCallView.jsx index 2809b82ac..4b84926b1 100644 --- a/src/room/GroupCallView.jsx +++ b/src/room/GroupCallView.jsx @@ -53,6 +53,7 @@ export function GroupCallView({ screenshareFeeds, hasLocalParticipant, participants, + unencryptedEventsFromUsers, } = useGroupCall(groupCall); const avatarUrl = useRoomAvatar(groupCall.room); @@ -112,6 +113,7 @@ export function GroupCallView({ localScreenshareFeed={localScreenshareFeed} screenshareFeeds={screenshareFeeds} roomId={roomId} + unencryptedEventsFromUsers={unencryptedEventsFromUsers} /> ); } diff --git a/src/room/InCallView.jsx b/src/room/InCallView.jsx index 2c6240e63..de9f5bc15 100644 --- a/src/room/InCallView.jsx +++ b/src/room/InCallView.jsx @@ -22,7 +22,13 @@ import { VideoButton, ScreenshareButton, } from "../button"; -import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header"; +import { + Header, + LeftNav, + RightNav, + RoomHeaderInfo, + VersionMismatchWarning, +} from "../Header"; import { VideoGrid, useVideoGridLayout } from "../video-grid/VideoGrid"; import { VideoTileContainer } from "../video-grid/VideoTileContainer"; import { GroupCallInspector } from "./GroupCallInspector"; @@ -59,6 +65,7 @@ export function InCallView({ isScreensharing, screenshareFeeds, roomId, + unencryptedEventsFromUsers, }) { usePreventScroll(); const [layout, setLayout] = useVideoGridLayout(screenshareFeeds.length > 0); @@ -135,6 +142,10 @@ export function InCallView({
    + diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 6816bcfc6..b3697ccfa 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -14,11 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useReducer, useState } from "react"; import { GroupCallEvent, GroupCallState, GroupCall, + GroupCallErrorCode, + GroupCallUnknownDeviceError, + GroupCallError, } from "matrix-js-sdk/src/webrtc/groupCall"; import { MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed"; @@ -48,6 +51,7 @@ export interface UseGroupCallType { localDesktopCapturerSourceId: string; participants: RoomMember[]; hasLocalParticipant: boolean; + unencryptedEventsFromUsers: Set; } interface State { @@ -106,6 +110,13 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallType { hasLocalParticipant: false, }); + const [unencryptedEventsFromUsers, addUnencryptedEventUser] = useReducer( + (state: Set, newVal: string) => { + return new Set(state).add(newVal); + }, + new Set() + ); + const updateState = (state: Partial) => setState((prevState) => ({ ...prevState, ...state })); @@ -180,6 +191,13 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallType { }); } + function onError(e: GroupCallError): void { + if (e.code === GroupCallErrorCode.UnknownDevice) { + const unknownDeviceError = e as GroupCallUnknownDeviceError; + addUnencryptedEventUser(unknownDeviceError.userId); + } + } + groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged); groupCall.on( @@ -194,6 +212,7 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallType { ); groupCall.on(GroupCallEvent.CallsChanged, onCallsChanged); groupCall.on(GroupCallEvent.ParticipantsChanged, onParticipantsChanged); + groupCall.on(GroupCallEvent.Error, onError); updateState({ error: null, @@ -242,6 +261,7 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallType { GroupCallEvent.ParticipantsChanged, onParticipantsChanged ); + groupCall.removeListener(GroupCallEvent.Error, onError); groupCall.leave(); }; }, [groupCall]); @@ -319,5 +339,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallType { localDesktopCapturerSourceId, participants, hasLocalParticipant, + unencryptedEventsFromUsers, }; } diff --git a/yarn.lock b/yarn.lock index 217ad2092..25be29f4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8597,9 +8597,9 @@ matrix-events-sdk@^0.0.1-beta.7: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934" integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#5e766978b8cf80d943f796df1067722a6a5918a7": +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#b97b862fb66bafee542e3c0baac35d6576b3a75d": version "17.2.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/5e766978b8cf80d943f796df1067722a6a5918a7" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b97b862fb66bafee542e3c0baac35d6576b3a75d" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0"