Skip to content

Commit

Permalink
Check for reaction & redaction capabilities in widget mode
Browse files Browse the repository at this point in the history
Signed-off-by: Milton Moura <[email protected]>
  • Loading branch information
mgcm committed Sep 19, 2024
1 parent 7ac5642 commit 69a50fb
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 83 deletions.
45 changes: 24 additions & 21 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -82,27 +83,29 @@ export const App: FC<AppProps> = ({ history }) => {
<TooltipProvider>
{loaded ? (
<Suspense fallback={null}>
<ClientProvider>
<MediaDevicesProvider>
<Sentry.ErrorBoundary fallback={errorPage}>
<DisconnectedBanner />
<Switch>
<SentryRoute exact path="/">
<HomePage />
</SentryRoute>
<SentryRoute exact path="/login">
<LoginPage />
</SentryRoute>
<SentryRoute exact path="/register">
<RegisterPage />
</SentryRoute>
<SentryRoute path="*">
<RoomPage />
</SentryRoute>
</Switch>
</Sentry.ErrorBoundary>
</MediaDevicesProvider>
</ClientProvider>
<ReactionsProvider>
<ClientProvider>
<MediaDevicesProvider>
<Sentry.ErrorBoundary fallback={errorPage}>
<DisconnectedBanner />
<Switch>
<SentryRoute exact path="/">
<HomePage />
</SentryRoute>
<SentryRoute exact path="/login">
<LoginPage />
</SentryRoute>
<SentryRoute exact path="/register">
<RegisterPage />
</SentryRoute>
<SentryRoute path="*">
<RoomPage />
</SentryRoute>
</Switch>
</Sentry.ErrorBoundary>
</MediaDevicesProvider>
</ClientProvider>
</ReactionsProvider>
</Suspense>
) : (
<LoadingView />
Expand Down
41 changes: 38 additions & 3 deletions src/ClientContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 {
Expand Down Expand Up @@ -144,6 +146,7 @@ interface Props {
}

export const ClientProvider: FC<Props> = ({ children }) => {
const { setSupportsReactions } = useReactions();
const history = useHistory();

// null = signed out, undefined = loading
Expand Down Expand Up @@ -188,11 +191,11 @@ export const ClientProvider: FC<Props> = ({ children }) => {
saveSession({ ...session, passwordlessUser: false });

setInitClientState({
client: initClientState.client,
...initClientState,
passwordlessUser: false,
});
},
[initClientState?.client],
[initClientState],
);

const setClient = useCallback(
Expand All @@ -206,6 +209,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
if (clientParams) {
saveSession(clientParams.session);
setInitClientState({
widgetApi: null,
client: clientParams.client,
passwordlessUser: clientParams.session.passwordlessUser,
});
Expand Down Expand Up @@ -309,12 +313,40 @@ export const ClientProvider: FC<Props> = ({ 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 <ErrorView error={alreadyOpenedErr} />;
Expand All @@ -326,6 +358,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
};

type InitResult = {
widgetApi: WidgetApi | null;
client: MatrixClient;
passwordlessUser: boolean;
};
Expand All @@ -336,6 +369,7 @@ async function loadClient(): Promise<InitResult | null> {
logger.log("Using a matryoshka client");
const client = await widget.client;
return {
widgetApi: widget.api,
client,
passwordlessUser: false,
};
Expand Down Expand Up @@ -364,6 +398,7 @@ async function loadClient(): Promise<InitResult | null> {
try {
const client = await initClient(initClientParams, true);
return {
widgetApi: null,
client,
passwordlessUser,
};
Expand Down
39 changes: 20 additions & 19 deletions src/room/InCallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? {});

Expand Down Expand Up @@ -139,14 +139,12 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {

return (
<RoomContext.Provider value={livekitRoom}>
<RaisedHandsProvider>
<InCallView
{...props}
vm={vm}
livekitRoom={livekitRoom}
connState={connState}
/>
</RaisedHandsProvider>
<InCallView
{...props}
vm={vm}
livekitRoom={livekitRoom}
connState={connState}
/>
</RoomContext.Provider>
);
};
Expand Down Expand Up @@ -179,6 +177,8 @@ export const InCallView: FC<InCallViewProps> = ({
connState,
onShareClick,
}) => {
const { supportsReactions } = useReactions();

useWakeLock();

useEffect(() => {
Expand Down Expand Up @@ -310,10 +310,9 @@ export const InCallView: FC<InCallViewProps> = ({
);

const memberships = useMatrixRTCSessionMemberships(rtcSession);
const { raisedHands, setRaisedHands } = useRaisedHands();
const { raisedHands, setRaisedHands } = useReactions();
const [reactionId, setReactionId] = useState<string | null>(null);
const [username, localpart] = localParticipant.identity.split(":");
const userId = `${username}:${localpart}`;
const userId = client.getUserId()!;
const isHandRaised = raisedHands.includes(userId);

useEffect(() => {
Expand Down Expand Up @@ -638,13 +637,15 @@ export const InCallView: FC<InCallViewProps> = ({
/>,
);
}
buttons.push(
<RaiseHandButton
key="4"
onClick={toggleRaisedHand}
raised={isHandRaised}
/>,
);
if (supportsReactions) {
buttons.push(
<RaiseHandButton
key="4"
onClick={toggleRaisedHand}
raised={isHandRaised}
/>,
);
}
buttons.push(<SettingsButton key="5" onClick={openSettings} />);
}

Expand Down
38 changes: 0 additions & 38 deletions src/room/useRaisedHands.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions src/tile/GridTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -91,7 +91,7 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
},
[vm],
);
const { raisedHands } = useRaisedHands();
const { raisedHands } = useReactions();
const raisedHand = raisedHands.includes(vm.member?.userId ?? "");

const MicIcon = audioEnabled ? MicOnSolidIcon : MicOffSolidIcon;
Expand Down
49 changes: 49 additions & 0 deletions src/useReactions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2024 Milton Moura <[email protected]>
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<React.SetStateAction<string[]>>;
supportsReactions: boolean;
setSupportsReactions: React.Dispatch<React.SetStateAction<boolean>>;
}

const ReactionsContext = createContext<ReactionsContextType | undefined>(
undefined,
);

export const useReactions = (): ReactionsContextType => {
const context = useContext(ReactionsContext);
if (!context) {
throw new Error("useReactions must be used within a ReactionsProvider");

Check failure on line 24 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/tile/GridTile.test.tsx > GridTile is accessible

Error: useReactions must be used within a ReactionsProvider ❯ Module.useReactions src/useReactions.tsx:24:11 ❯ src/tile/GridTile.tsx:94:29 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ updateForwardRef node_modules/react-dom/cjs/react-dom.development.js:19245:20 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21675:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20
}
return context;
};

export const ReactionsProvider = ({
children,
}: {
children: ReactNode;
}): JSX.Element => {
const [raisedHands, setRaisedHands] = useState<string[]>([]);
const [supportsReactions, setSupportsReactions] = useState<boolean>(true);

return (
<ReactionsContext.Provider
value={{
raisedHands,
setRaisedHands,
supportsReactions,
setSupportsReactions,
}}
>
{children}
</ReactionsContext.Provider>
);
};

0 comments on commit 69a50fb

Please sign in to comment.