Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Knocking support #2281

Merged
merged 22 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"i18next-http-backend": "^2.0.0",
"livekit-client": "^2.0.2",
"lodash": "^4.17.21",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#d55c6a36df539f6adacc335efe5b9be27c9cee4a",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#e874468ba3e84819cf4b342d2e66af67ab4cf804",
"matrix-widget-api": "^1.3.1",
"normalize.css": "^8.0.1",
"pako": "^2.0.4",
Expand Down
17 changes: 14 additions & 3 deletions public/locales/en-GB/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,17 @@
"disconnected_banner": "Connectivity to the server has been lost.",
"full_screen_view_description": "<0>Submitting debug logs will help us track down the problem.</0>",
"full_screen_view_h1": "<0>Oops, something's gone wrong.</0>",
"group_call_loader_failed_heading": "Call not found",
"group_call_loader_failed_text": "Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.",
"group_call_loader": {
"banned_body": "You have been banned from the room.",
"banned_heading": "Banned",
"call_ended_body": "You have been removed from the call.",
"call_ended_heading": "Call ended",
"failed_heading": "Call not found",
"failed_text": "Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.",
"knock_reject_body": "The room members declined your request to join.",
"knock_reject_heading": "Not allowed to join",
"reason": "Reason"
},
"hangup_button_label": "End call",
"header_label": "Element Call Home",
"header_participants_label": "Participants",
Expand All @@ -77,8 +86,10 @@
"layout_grid_label": "Grid",
"layout_spotlight_label": "Spotlight",
"lobby": {
"ask_to_join": "Ask to join call",
"join_button": "Join call",
"leave_button": "Back to recents"
"leave_button": "Back to recents",
"waiting_for_invite": "Request sent"
},
"log_in": "Log In",
"logging_in": "Logging in…",
Expand Down
40 changes: 21 additions & 19 deletions src/FullScreenView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ interface ErrorViewProps {

export const ErrorView: FC<ErrorViewProps> = ({ error }) => {
const location = useLocation();
const { confineToRoom } = useUrlParams();
const { t } = useTranslation();

useEffect(() => {
Expand All @@ -78,25 +79,26 @@ export const ErrorView: FC<ErrorViewProps> = ({ error }) => {
: error.message}
</p>
<RageshakeButton description={`***Error View***: ${error.message}`} />
{location.pathname === "/" ? (
<Button
size="lg"
variant="default"
className={styles.homeLink}
onPress={onReload}
>
{t("return_home_button")}
</Button>
) : (
<LinkButton
size="lg"
variant="default"
className={styles.homeLink}
to="/"
>
{t("return_home_button")}
</LinkButton>
)}
{!confineToRoom &&
(location.pathname === "/" ? (
<Button
size="lg"
variant="default"
className={styles.homeLink}
onPress={onReload}
>
{t("return_home_button")}
</Button>
) : (
<LinkButton
size="lg"
variant="default"
className={styles.homeLink}
to="/"
>
{t("return_home_button")}
</LinkButton>
))}
</FullScreenView>
);
};
Expand Down
6 changes: 3 additions & 3 deletions src/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ interface RoomHeaderInfoProps {
name: string;
avatarUrl: string | null;
encrypted: boolean;
participantCount: number;
participantCount: number | null;
}

export const RoomHeaderInfo: FC<RoomHeaderInfoProps> = ({
Expand Down Expand Up @@ -150,15 +150,15 @@ export const RoomHeaderInfo: FC<RoomHeaderInfoProps> = ({
</Heading>
<EncryptionLock encrypted={encrypted} />
</div>
{participantCount > 0 && (
{(participantCount ?? 0) > 0 && (
<div className={styles.participantsLine}>
<UserProfileIcon
width={20}
height={20}
aria-label={t("header_participants_label")}
/>
<Text as="span" size="sm" weight="medium">
{t("participant_count", { count: participantCount })}
{t("participant_count", { count: participantCount ?? 0 })}
</Text>
</div>
)}
Expand Down
34 changes: 32 additions & 2 deletions src/UrlParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ limitations under the License.

import { useMemo } from "react";
import { useLocation } from "react-router-dom";
import { logger } from "matrix-js-sdk/src/logger";

import { Config } from "./config/Config";

export const PASSWORD_STRING = "password=";
import { EncryptionSystem } from "./e2ee/sharedKeyManagement";
import { E2eeType } from "./e2ee/e2eeType";

interface RoomIdentifier {
roomAlias: string | null;
Expand Down Expand Up @@ -328,3 +329,32 @@ export const useRoomIdentifier = (): RoomIdentifier => {
[pathname, search, hash],
);
};

export function generateUrlSearchParams(
roomId: string,
encryptionSystem: EncryptionSystem,
viaServers?: string[],
): URLSearchParams {
const params = new URLSearchParams();
// The password shouldn't need URL encoding here (we generate URL-safe ones) but encode
// it in case it came from another client that generated a non url-safe one
switch (encryptionSystem?.kind) {
case E2eeType.SHARED_KEY: {
const encodedPassword = encodeURIComponent(encryptionSystem.secret);
if (encodedPassword !== encryptionSystem.secret) {
logger.info(
"Encoded call password used non URL-safe chars: buggy client?",
);
}
params.set("password", encodedPassword);
break;
}
case E2eeType.PER_PARTICIPANT:
params.set("perParticipantE2EE", "true");
break;
}
params.set("roomId", roomId);
viaServers?.forEach((s) => params.set("viaServers", s));

return params;
}
54 changes: 30 additions & 24 deletions src/e2ee/sharedKeyManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ limitations under the License.
*/

import { useEffect, useMemo } from "react";
import { Room } from "matrix-js-sdk";

import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage";
import { useClient } from "../ClientContext";
import { UrlParams, getUrlParams, useUrlParams } from "../UrlParams";
import { widget } from "../widget";
import { E2eeType } from "./e2eeType";
import { useClient } from "../ClientContext";

export function saveKeyForRoom(roomId: string, password: string): void {
setLocalStorageItem(getRoomSharedKeyLocalStorageKey(roomId), password);
Expand Down Expand Up @@ -68,30 +67,37 @@ const useKeyFromUrl = (): [string, string] | [undefined, undefined] => {
: [undefined, undefined];
};

export const useRoomSharedKey = (roomId: string): string | undefined => {
export type Unencrypted = { kind: E2eeType.NONE };
export type SharedSecret = { kind: E2eeType.SHARED_KEY; secret: string };
export type PerParticipantE2EE = { kind: E2eeType.PER_PARTICIPANT };
export type EncryptionSystem = Unencrypted | SharedSecret | PerParticipantE2EE;

export function useRoomEncryptionSystem(roomId: string): EncryptionSystem {
const { client } = useClient();

// 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 [urlRoomId, passwordFormUrl] = useKeyFromUrl();

const [urlRoomId, passwordFromUrl] = useKeyFromUrl();
const storedPassword = useInternalRoomSharedKey(roomId);

if (storedPassword) return storedPassword;
if (urlRoomId === roomId) return passwordFormUrl;
return undefined;
};

export const useIsRoomE2EE = (roomId: string): boolean | null => {
const { client } = useClient();
const room = useMemo(() => client?.getRoom(roomId), [roomId, client]);

return useMemo(() => !room || isRoomE2EE(room), [room]);
};

export function isRoomE2EE(room: Room): boolean {
// For now, rooms in widget mode are never considered encrypted.
// In the future, when widget mode gains encryption support, then perhaps we
// should inspect the e2eEnabled URL parameter here?
return widget === null && !room.getCanonicalAlias();
const room = client?.getRoom(roomId);
const e2eeSystem = <EncryptionSystem>useMemo(() => {
if (!room) return { kind: E2eeType.NONE };
if (storedPassword)
return {
kind: E2eeType.SHARED_KEY,
secret: storedPassword,
};
if (urlRoomId === roomId)
return {
kind: E2eeType.SHARED_KEY,
secret: passwordFromUrl,
};
if (room.hasEncryptionStateEvent()) {
return { kind: E2eeType.PER_PARTICIPANT };
}
return { kind: E2eeType.NONE };
}, [passwordFromUrl, room, roomId, storedPassword, urlRoomId]);
return e2eeSystem;
}
18 changes: 5 additions & 13 deletions src/home/CallList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import styles from "./CallList.module.css";
import { getAbsoluteRoomUrl, getRelativeRoomUrl } from "../matrix-utils";
import { Body } from "../typography/Typography";
import { GroupCallRoom } from "./useGroupCallRooms";
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";

interface CallListProps {
rooms: GroupCallRoom[];
Expand Down Expand Up @@ -66,16 +66,11 @@ interface CallTileProps {
}

const CallTile: FC<CallTileProps> = ({ name, avatarUrl, room }) => {
const roomSharedKey = useRoomSharedKey(room.roomId);

const roomEncryptionSystem = useRoomEncryptionSystem(room.roomId);
return (
<div className={styles.callTile}>
<Link
to={getRelativeRoomUrl(
room.roomId,
room.name,
roomSharedKey ?? undefined,
)}
to={getRelativeRoomUrl(room.roomId, roomEncryptionSystem, room.name)}
className={styles.callTileLink}
>
<Avatar id={room.roomId} name={name} size={Size.LG} src={avatarUrl} />
Expand All @@ -89,11 +84,8 @@ const CallTile: FC<CallTileProps> = ({ name, avatarUrl, room }) => {
<CopyButton
className={styles.copyButton}
variant="icon"
value={getAbsoluteRoomUrl(
room.roomId,
room.name,
roomSharedKey ?? undefined,
)}
// Todo add the viaServers to the created link
value={getAbsoluteRoomUrl(room.roomId, roomEncryptionSystem, room.name)}
/>
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion src/home/RegisteredView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ export const RegisteredView: FC<Props> = ({ client }) => {
roomName,
E2eeType.SHARED_KEY,
);
if (!createRoomResult.password)
throw new Error("Failed to create room with shared secret");

history.push(
getRelativeRoomUrl(
createRoomResult.roomId,
{ kind: E2eeType.SHARED_KEY, secret: createRoomResult.password },
roomName,
createRoomResult.password,
),
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/home/UnauthenticatedView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,15 @@ export const UnauthenticatedView: FC = () => {
if (!setClient) {
throw new Error("setClient is undefined");
}
if (!createRoomResult.password)
throw new Error("Failed to create room with shared secret");

setClient({ client, session });
history.push(
getRelativeRoomUrl(
createRoomResult.roomId,
{ kind: E2eeType.SHARED_KEY, secret: createRoomResult.password },
roomName,
createRoomResult.password,
),
);
}
Expand Down
Loading
Loading