From 69a50fb2a8cd0a8794b8df689bc93fd4068be33c Mon Sep 17 00:00:00 2001 From: Milton Moura Date: Thu, 19 Sep 2024 11:33:48 +0100 Subject: [PATCH] Check for reaction & redaction capabilities in widget mode Signed-off-by: Milton Moura --- src/App.tsx | 45 ++++++++++++++++++---------------- src/ClientContext.tsx | 41 ++++++++++++++++++++++++++++--- src/room/InCallView.tsx | 39 +++++++++++++++-------------- src/room/useRaisedHands.tsx | 38 ---------------------------- src/tile/GridTile.tsx | 4 +-- src/useReactions.tsx | 49 +++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 83 deletions(-) delete mode 100644 src/room/useRaisedHands.tsx create mode 100644 src/useReactions.tsx diff --git a/src/App.tsx b/src/App.tsx index 8d841dba..9f0f5f14 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -28,6 +28,7 @@ import { Initializer } from "./initializer"; import { MediaDevicesProvider } from "./livekit/MediaDevicesContext"; import { widget } from "./widget"; import { useTheme } from "./useTheme"; +import { ReactionsProvider } from "./useReactions"; const SentryRoute = Sentry.withSentryRouting(Route); @@ -82,27 +83,29 @@ export const App: FC = ({ history }) => { {loaded ? ( - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + ) : ( diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 805b2313..1b40f308 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -25,6 +25,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { useTranslation } from "react-i18next"; import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; import { MatrixError } from "matrix-js-sdk/src/matrix"; +import { WidgetApi } from "matrix-widget-api"; import { ErrorView } from "./FullScreenView"; import { fallbackICEServerAllowed, initClient } from "./utils/matrix"; @@ -36,6 +37,7 @@ import { import { translatedError } from "./TranslatedError"; import { useEventTarget } from "./useEvents"; import { Config } from "./config/Config"; +import { useReactions } from "./useReactions"; declare global { interface Window { @@ -144,6 +146,7 @@ interface Props { } export const ClientProvider: FC = ({ children }) => { + const { setSupportsReactions } = useReactions(); const history = useHistory(); // null = signed out, undefined = loading @@ -188,11 +191,11 @@ export const ClientProvider: FC = ({ children }) => { saveSession({ ...session, passwordlessUser: false }); setInitClientState({ - client: initClientState.client, + ...initClientState, passwordlessUser: false, }); }, - [initClientState?.client], + [initClientState], ); const setClient = useCallback( @@ -206,6 +209,7 @@ export const ClientProvider: FC = ({ children }) => { if (clientParams) { saveSession(clientParams.session); setInitClientState({ + widgetApi: null, client: clientParams.client, passwordlessUser: clientParams.session.passwordlessUser, }); @@ -309,12 +313,40 @@ export const ClientProvider: FC = ({ children }) => { initClientState.client.on(ClientEvent.Sync, onSync); } + if (initClientState.widgetApi) { + let supportsReactions = true; + + const reactSend = initClientState.widgetApi.hasCapability( + "org.matrix.msc2762.send.event:m.reaction", + ); + const redactSend = initClientState.widgetApi.hasCapability( + "org.matrix.msc2762.send.event:m.room.redaction", + ); + const reactRcv = initClientState.widgetApi.hasCapability( + "org.matrix.msc2762.receive.event:m.reaction", + ); + const redactRcv = initClientState.widgetApi.hasCapability( + "org.matrix.msc2762.receive.event:m.room.redaction", + ); + + if (!reactSend || !reactRcv || !redactSend || !redactRcv) { + supportsReactions = false; + } + + setSupportsReactions(supportsReactions); + if (!supportsReactions) { + logger.warn("Widget does not support reactions"); + } else { + logger.warn("Widget does support reactions"); + } + } + return (): void => { if (initClientState.client) { initClientState.client.removeListener(ClientEvent.Sync, onSync); } }; - }, [initClientState, onSync]); + }, [initClientState, onSync, setSupportsReactions]); if (alreadyOpenedErr) { return ; @@ -326,6 +358,7 @@ export const ClientProvider: FC = ({ children }) => { }; type InitResult = { + widgetApi: WidgetApi | null; client: MatrixClient; passwordlessUser: boolean; }; @@ -336,6 +369,7 @@ async function loadClient(): Promise { logger.log("Using a matryoshka client"); const client = await widget.client; return { + widgetApi: widget.api, client, passwordlessUser: false, }; @@ -364,6 +398,7 @@ async function loadClient(): Promise { try { const client = await initClient(initClientParams, true); return { + widgetApi: null, client, passwordlessUser, }; diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 931ddaad..b4e7a4a7 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -85,8 +85,8 @@ import { makeOneOnOneLayout } from "../grid/OneOnOneLayout"; import { makeSpotlightExpandedLayout } from "../grid/SpotlightExpandedLayout"; import { makeSpotlightLandscapeLayout } from "../grid/SpotlightLandscapeLayout"; import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout"; -import { RaisedHandsProvider, useRaisedHands } from "./useRaisedHands"; import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships"; +import { useReactions } from "../useReactions"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); @@ -139,14 +139,12 @@ export const ActiveCall: FC = (props) => { return ( - - - + ); }; @@ -179,6 +177,8 @@ export const InCallView: FC = ({ connState, onShareClick, }) => { + const { supportsReactions } = useReactions(); + useWakeLock(); useEffect(() => { @@ -310,10 +310,9 @@ export const InCallView: FC = ({ ); const memberships = useMatrixRTCSessionMemberships(rtcSession); - const { raisedHands, setRaisedHands } = useRaisedHands(); + const { raisedHands, setRaisedHands } = useReactions(); const [reactionId, setReactionId] = useState(null); - const [username, localpart] = localParticipant.identity.split(":"); - const userId = `${username}:${localpart}`; + const userId = client.getUserId()!; const isHandRaised = raisedHands.includes(userId); useEffect(() => { @@ -638,13 +637,15 @@ export const InCallView: FC = ({ />, ); } - buttons.push( - , - ); + if (supportsReactions) { + buttons.push( + , + ); + } buttons.push(); } diff --git a/src/room/useRaisedHands.tsx b/src/room/useRaisedHands.tsx deleted file mode 100644 index 458ad59b..00000000 --- a/src/room/useRaisedHands.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2024 Milton Moura - -SPDX-License-Identifier: AGPL-3.0-only -Please see LICENSE in the repository root for full details. -*/ - -import React, { createContext, useContext, useState, ReactNode } from "react"; - -interface RaisedHandsContextType { - raisedHands: string[]; - setRaisedHands: React.Dispatch>; -} - -const RaisedHandsContext = createContext( - undefined, -); - -export const useRaisedHands = (): RaisedHandsContextType => { - const context = useContext(RaisedHandsContext); - if (!context) { - throw new Error("useRaisedHands must be used within a RaisedHandsProvider"); - } - return context; -}; - -export const RaisedHandsProvider = ({ - children, -}: { - children: ReactNode; -}): JSX.Element => { - const [raisedHands, setRaisedHands] = useState([]); - return ( - - {children} - - ); -}; diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx index 0dc434b6..959ae089 100644 --- a/src/tile/GridTile.tsx +++ b/src/tile/GridTile.tsx @@ -44,7 +44,7 @@ import { import { Slider } from "../Slider"; import { MediaView } from "./MediaView"; import { useLatest } from "../useLatest"; -import { useRaisedHands } from "../room/useRaisedHands"; +import { useReactions } from "../useReactions"; interface TileProps { className?: string; @@ -91,7 +91,7 @@ const UserMediaTile = forwardRef( }, [vm], ); - const { raisedHands } = useRaisedHands(); + const { raisedHands } = useReactions(); const raisedHand = raisedHands.includes(vm.member?.userId ?? ""); const MicIcon = audioEnabled ? MicOnSolidIcon : MicOffSolidIcon; diff --git a/src/useReactions.tsx b/src/useReactions.tsx new file mode 100644 index 00000000..9cd920c6 --- /dev/null +++ b/src/useReactions.tsx @@ -0,0 +1,49 @@ +/* +Copyright 2024 Milton Moura + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import React, { createContext, useContext, useState, ReactNode } from "react"; + +interface ReactionsContextType { + raisedHands: string[]; + setRaisedHands: React.Dispatch>; + supportsReactions: boolean; + setSupportsReactions: React.Dispatch>; +} + +const ReactionsContext = createContext( + undefined, +); + +export const useReactions = (): ReactionsContextType => { + const context = useContext(ReactionsContext); + if (!context) { + throw new Error("useReactions must be used within a ReactionsProvider"); + } + return context; +}; + +export const ReactionsProvider = ({ + children, +}: { + children: ReactNode; +}): JSX.Element => { + const [raisedHands, setRaisedHands] = useState([]); + const [supportsReactions, setSupportsReactions] = useState(true); + + return ( + + {children} + + ); +};