mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
@@ -61,14 +61,15 @@
|
|||||||
"full_screen_view_description": "<0>Submitting debug logs will help us track down the problem.</0>",
|
"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>",
|
"full_screen_view_h1": "<0>Oops, something's gone wrong.</0>",
|
||||||
"group_call_loader": {
|
"group_call_loader": {
|
||||||
"banned_body": "You got banned from the room.",
|
"banned_body": "You have been banned from the room.",
|
||||||
"banned_heading": "Banned",
|
"banned_heading": "Banned",
|
||||||
"call_ended_body": "You got removed from the call.",
|
"call_ended_body": "You have been removed from the call.",
|
||||||
"call_ended_heading": "Call Ended",
|
"call_ended_heading": "Call ended",
|
||||||
"failed_heading": "Call not found",
|
"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.",
|
"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_body": "The room members declined your request to join.",
|
||||||
"knock_reject_heading": "Not allowed to join"
|
"knock_reject_heading": "Not allowed to join",
|
||||||
|
"reason": "Reason"
|
||||||
},
|
},
|
||||||
"hangup_button_label": "End call",
|
"hangup_button_label": "End call",
|
||||||
"header_label": "Element Call Home",
|
"header_label": "Element Call Home",
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
"mute_microphone_button_label": "Mute microphone",
|
"mute_microphone_button_label": "Mute microphone",
|
||||||
"participant_count_one": "{{count, number}}",
|
"participant_count_one": "{{count, number}}",
|
||||||
"participant_count_other": "{{count, number}}",
|
"participant_count_other": "{{count, number}}",
|
||||||
|
"participant_count_unkonwn": "unknown",
|
||||||
"rageshake_button_error_caption": "Retry sending logs",
|
"rageshake_button_error_caption": "Retry sending logs",
|
||||||
"rageshake_request_modal": {
|
"rageshake_request_modal": {
|
||||||
"body": "Another user on this call is having an issue. In order to better diagnose these issues we'd like to collect a debug log.",
|
"body": "Another user on this call is having an issue. In order to better diagnose these issues we'd like to collect a debug log.",
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ interface RoomHeaderInfoProps {
|
|||||||
name: string;
|
name: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
encrypted: boolean;
|
encrypted: boolean;
|
||||||
participantCount: number;
|
participantCount: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RoomHeaderInfo: FC<RoomHeaderInfoProps> = ({
|
export const RoomHeaderInfo: FC<RoomHeaderInfoProps> = ({
|
||||||
@@ -150,7 +150,7 @@ export const RoomHeaderInfo: FC<RoomHeaderInfoProps> = ({
|
|||||||
</Heading>
|
</Heading>
|
||||||
<EncryptionLock encrypted={encrypted} />
|
<EncryptionLock encrypted={encrypted} />
|
||||||
</div>
|
</div>
|
||||||
{participantCount > 0 && (
|
{(participantCount ?? 0) > 0 && (
|
||||||
<div className={styles.participantsLine}>
|
<div className={styles.participantsLine}>
|
||||||
<UserProfileIcon
|
<UserProfileIcon
|
||||||
width={20}
|
width={20}
|
||||||
@@ -158,7 +158,9 @@ export const RoomHeaderInfo: FC<RoomHeaderInfoProps> = ({
|
|||||||
aria-label={t("header_participants_label")}
|
aria-label={t("header_participants_label")}
|
||||||
/>
|
/>
|
||||||
<Text as="span" size="sm" weight="medium">
|
<Text as="span" size="sm" weight="medium">
|
||||||
{t("participant_count", { count: participantCount })}
|
{participantCount
|
||||||
|
? t("participant_count", { count: participantCount })
|
||||||
|
: t("participant_count_unkonwn")}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ limitations under the License.
|
|||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
|
import { EncryptionSystem } from "./e2ee/sharedKeyManagement";
|
||||||
|
import { E2eeType } from "./e2ee/e2eeType";
|
||||||
|
|
||||||
export const PASSWORD_STRING = "password=";
|
|
||||||
export const PER_PARTICIPANT_STRING = "perParticipantE2EE=";
|
|
||||||
export const VIA_SERVERS_STRING = "viaServers=";
|
|
||||||
interface RoomIdentifier {
|
interface RoomIdentifier {
|
||||||
roomAlias: string | null;
|
roomAlias: string | null;
|
||||||
roomId: string | null;
|
roomId: string | null;
|
||||||
@@ -329,3 +329,32 @@ export const useRoomIdentifier = (): RoomIdentifier => {
|
|||||||
[pathname, search, hash],
|
[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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,25 +79,25 @@ export function useRoomEncryptionSystem(roomId: string): EncryptionSystem {
|
|||||||
// (and we still need to take the value it returns because
|
// (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
|
// the effect won't run in time for it to save to localstorage in
|
||||||
// time for us to read it out again).
|
// time for us to read it out again).
|
||||||
const [urlRoomId, passwordFormUrl] = useKeyFromUrl();
|
const [urlRoomId, passwordFromUrl] = useKeyFromUrl();
|
||||||
const storedPassword = useInternalRoomSharedKey(roomId);
|
const storedPassword = useInternalRoomSharedKey(roomId);
|
||||||
const room = client?.getRoom(roomId);
|
const room = client?.getRoom(roomId);
|
||||||
const e2eeSystem = useMemo(() => {
|
const e2eeSystem = <EncryptionSystem>useMemo(() => {
|
||||||
if (!room) return { kind: E2eeType.NONE } as Unencrypted;
|
if (!room) return { kind: E2eeType.NONE };
|
||||||
if (storedPassword)
|
if (storedPassword)
|
||||||
return {
|
return {
|
||||||
kind: E2eeType.SHARED_KEY,
|
kind: E2eeType.SHARED_KEY,
|
||||||
secret: storedPassword,
|
secret: storedPassword,
|
||||||
} as SharedSecret;
|
};
|
||||||
if (urlRoomId === roomId)
|
if (urlRoomId === roomId)
|
||||||
return {
|
return {
|
||||||
kind: E2eeType.SHARED_KEY,
|
kind: E2eeType.SHARED_KEY,
|
||||||
secret: passwordFormUrl,
|
secret: passwordFromUrl,
|
||||||
} as SharedSecret;
|
};
|
||||||
if (room.hasEncryptionStateEvent()) {
|
if (room.hasEncryptionStateEvent()) {
|
||||||
return { kind: E2eeType.PER_PARTICIPANT } as PerParticipantE2EE;
|
return { kind: E2eeType.PER_PARTICIPANT };
|
||||||
}
|
}
|
||||||
return { kind: E2eeType.NONE } as EncryptionSystem;
|
return { kind: E2eeType.NONE };
|
||||||
}, [passwordFormUrl, room, roomId, storedPassword, urlRoomId]);
|
}, [passwordFromUrl, room, roomId, storedPassword, urlRoomId]);
|
||||||
return e2eeSystem;
|
return e2eeSystem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const roomIsJoinable = (room: Room): boolean => {
|
|||||||
return joinRule === JoinRule.Knock || joinRule === JoinRule.Public;
|
return joinRule === JoinRule.Knock || joinRule === JoinRule.Public;
|
||||||
};
|
};
|
||||||
|
|
||||||
const roomIsJoinedWithCall = (room: Room): boolean => {
|
const roomHasCallMembershipEvents = (room: Room): boolean => {
|
||||||
const roomStateEvents = room
|
const roomStateEvents = room
|
||||||
.getLiveTimeline()
|
.getLiveTimeline()
|
||||||
.getState(EventTimeline.FORWARDS)?.events;
|
.getState(EventTimeline.FORWARDS)?.events;
|
||||||
@@ -108,13 +108,13 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function updateRooms(): void {
|
function updateRooms(): void {
|
||||||
|
// We want to show all rooms that historically had a call and which we are (can become) part of.
|
||||||
const rooms = client
|
const rooms = client
|
||||||
.getRooms()
|
.getRooms()
|
||||||
.filter(roomIsJoinedWithCall)
|
.filter(roomHasCallMembershipEvents)
|
||||||
.filter(roomIsJoinable);
|
.filter(roomIsJoinable);
|
||||||
const sortedRooms = sortRooms(client, rooms);
|
const sortedRooms = sortRooms(client, rooms);
|
||||||
const items = sortedRooms.map((room) => {
|
const items = sortedRooms.map((room) => {
|
||||||
// const groupCall = client.getGroupCallForRoom(room.roomId)!;
|
|
||||||
const session = client.matrixRTC.getRoomSession(room);
|
const session = client.matrixRTC.getRoomSession(room);
|
||||||
session.memberships;
|
session.memberships;
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -29,12 +29,7 @@ import { secureRandomBase64Url } from "matrix-js-sdk/src/randomstring";
|
|||||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import IndexedDBWorker from "./IndexedDBWorker?worker";
|
import IndexedDBWorker from "./IndexedDBWorker?worker";
|
||||||
import {
|
import { generateUrlSearchParams, getUrlParams } from "./UrlParams";
|
||||||
getUrlParams,
|
|
||||||
PASSWORD_STRING,
|
|
||||||
PER_PARTICIPANT_STRING,
|
|
||||||
VIA_SERVERS_STRING,
|
|
||||||
} from "./UrlParams";
|
|
||||||
import { loadOlm } from "./olm";
|
import { loadOlm } from "./olm";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
import { E2eeType } from "./e2ee/e2eeType";
|
import { E2eeType } from "./e2ee/e2eeType";
|
||||||
@@ -339,8 +334,6 @@ export async function createRoom(
|
|||||||
|
|
||||||
const result = await createPromise;
|
const result = await createPromise;
|
||||||
|
|
||||||
logger.log(`Creating group call in ${result.room_id}`);
|
|
||||||
|
|
||||||
let password;
|
let password;
|
||||||
if (e2ee == E2eeType.SHARED_KEY) {
|
if (e2ee == E2eeType.SHARED_KEY) {
|
||||||
password = secureRandomBase64Url(16);
|
password = secureRandomBase64Url(16);
|
||||||
@@ -383,33 +376,10 @@ export function getRelativeRoomUrl(
|
|||||||
roomName?: string,
|
roomName?: string,
|
||||||
viaServers?: string[],
|
viaServers?: string[],
|
||||||
): string {
|
): string {
|
||||||
// The password shouldn't need URL encoding here (we generate URL-safe ones) but encode
|
const roomPart = roomName
|
||||||
// it in case it came from another client that generated a non url-safe one
|
? "/" + roomAliasLocalpartFromRoomName(roomName)
|
||||||
const encryptionPart = ((): string => {
|
|
||||||
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?",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return "&" + PASSWORD_STRING + encodedPassword;
|
|
||||||
}
|
|
||||||
case E2eeType.PER_PARTICIPANT:
|
|
||||||
return "&" + PER_PARTICIPANT_STRING + "true";
|
|
||||||
case E2eeType.NONE:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
const roomIdPart = `roomId=${roomId}`;
|
|
||||||
const viaServersPart = viaServers
|
|
||||||
? viaServers.map((s) => "&" + VIA_SERVERS_STRING + s).join("")
|
|
||||||
: "";
|
: "";
|
||||||
return `/room/#${
|
return `/room/#${roomPart}?${generateUrlSearchParams(roomId, encryptionSystem, viaServers).toString()}`;
|
||||||
roomName ? "/" + roomAliasLocalpartFromRoomName(roomName) : ""
|
|
||||||
}?${roomIdPart}${encryptionPart}${viaServersPart}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAvatarUrl(
|
export function getAvatarUrl(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { MatrixError } from "matrix-js-sdk";
|
import { MatrixError } from "matrix-js-sdk";
|
||||||
@@ -24,7 +24,7 @@ import { Heading, Link, Text } from "@vector-im/compound-web";
|
|||||||
import {
|
import {
|
||||||
useLoadGroupCall,
|
useLoadGroupCall,
|
||||||
GroupCallStatus,
|
GroupCallStatus,
|
||||||
CustomMessage,
|
CallTerminatedMessage,
|
||||||
} from "./useLoadGroupCall";
|
} from "./useLoadGroupCall";
|
||||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ interface Props {
|
|||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
roomIdOrAlias: string;
|
roomIdOrAlias: string;
|
||||||
viaServers: string[];
|
viaServers: string[];
|
||||||
children: (groupCallState: GroupCallStatus) => ReactNode;
|
children: (groupCallState: GroupCallStatus) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupCallLoader({
|
export function GroupCallLoader({
|
||||||
@@ -57,7 +57,7 @@ export function GroupCallLoader({
|
|||||||
case "loaded":
|
case "loaded":
|
||||||
case "waitForInvite":
|
case "waitForInvite":
|
||||||
case "canKnock":
|
case "canKnock":
|
||||||
return <>{children(groupCallState)}</>;
|
return children(groupCallState);
|
||||||
case "loading":
|
case "loading":
|
||||||
return (
|
return (
|
||||||
<FullScreenView>
|
<FullScreenView>
|
||||||
@@ -77,14 +77,14 @@ export function GroupCallLoader({
|
|||||||
</Link>
|
</Link>
|
||||||
</FullScreenView>
|
</FullScreenView>
|
||||||
);
|
);
|
||||||
} else if (groupCallState.error instanceof CustomMessage) {
|
} else if (groupCallState.error instanceof CallTerminatedMessage) {
|
||||||
return (
|
return (
|
||||||
<FullScreenView>
|
<FullScreenView>
|
||||||
<Heading>{groupCallState.error.message}</Heading>
|
<Heading>{groupCallState.error.message}</Heading>
|
||||||
<Text>{groupCallState.error.messageBody}</Text>
|
<Text>{groupCallState.error.messageBody}</Text>
|
||||||
{groupCallState.error.reason && (
|
{groupCallState.error.reason && (
|
||||||
<>
|
<>
|
||||||
Reason:
|
{t("group_call_loader.reason")}:
|
||||||
<Text size="sm">"{groupCallState.error.reason}"</Text>
|
<Text size="sm">"{groupCallState.error.reason}"</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -83,12 +83,12 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
// The mute state reactively gets updated once the participant count reaches the threshold.
|
// The mute state reactively gets updated once the participant count reaches the threshold.
|
||||||
// The user then still is able to unmute again.
|
// The user then still is able to unmute again.
|
||||||
// The more common case is that the user is muted from the start (participant count is already over the threshold).
|
// The more common case is that the user is muted from the start (participant count is already over the threshold).
|
||||||
const [autoMuteHappened, setAutoMuteHappened] = useState(false);
|
const autoMuteHappened = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (autoMuteHappened) return;
|
if (autoMuteHappened.current) return;
|
||||||
if (memberships.length >= MUTE_PARTICIPANT_COUNT) {
|
if (memberships.length >= MUTE_PARTICIPANT_COUNT) {
|
||||||
muteStates.audio.setEnabled?.(false);
|
muteStates.audio.setEnabled?.(false);
|
||||||
setAutoMuteHappened(true);
|
autoMuteHappened.current = true;
|
||||||
}
|
}
|
||||||
}, [autoMuteHappened, memberships, muteStates.audio]);
|
}, [autoMuteHappened, memberships, muteStates.audio]);
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
|
|
||||||
if (e2eeSystem.kind === E2eeType.NONE && !widget) {
|
if (e2eeSystem.kind === E2eeType.NONE && !widget) {
|
||||||
// the url wants encryption, but we don't have a encryption system. (e.g. when joining a call without password)
|
// the url wants encryption, but we don't have a encryption system. (e.g. when joining a call without password)
|
||||||
// TODO: we need to figure out what we do with encryption goning forward.
|
// TODO: we need to figure out what we do with encryption going forward.
|
||||||
// In the SPA a room is unencrypted but uses a shared secret for the call (since we distribute via url that works)
|
// In the SPA a room is unencrypted but uses a shared secret for the call (since we distribute via url that works)
|
||||||
// In embedded mode unencrypted rooms would make most sense to also call unencrypted.
|
// In embedded mode unencrypted rooms would make most sense to also call unencrypted.
|
||||||
// A call where one person joins with the spa and another person with the widget would be a problem.
|
// A call where one person joins with the spa and another person with the widget would be a problem.
|
||||||
@@ -301,7 +301,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
<ErrorView
|
<ErrorView
|
||||||
error={
|
error={
|
||||||
new Error(
|
new Error(
|
||||||
"No E2EE key or other encryption system provided: Element call calls are always encrypted, please make sure the URL you're using to join this call has been retrieved using the in-app button.",
|
`No E2EE key or other encryption system provided: ${import.meta.env.VITE_PRODUCT_NAME || "Element Call"} calls are always encrypted. Please make sure the URL you're using to join this call has been retrieved using the in-app button.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -24,9 +24,17 @@ limitations under the License.
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wait {
|
.wait {
|
||||||
color: black !important;
|
color: var(--cpd-color-text-primary) !important;
|
||||||
background-color: rgba(255, 255, 255, 0.67) !important;
|
background-color: var(--cpd-color-bg-canvas-default) !important;
|
||||||
|
/* relative colors are only supported on chromium based browsers */
|
||||||
|
background-color: rgb(
|
||||||
|
from var(--cpd-color-bg-canvas-default) r g b / 0.5
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
.wait > svg {
|
||||||
|
color: var(--cpd-color-theme-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ interface Props {
|
|||||||
matrixInfo: MatrixInfo;
|
matrixInfo: MatrixInfo;
|
||||||
muteStates: MuteStates;
|
muteStates: MuteStates;
|
||||||
onEnter: () => void;
|
onEnter: () => void;
|
||||||
enterLabel?: JSX.Element;
|
enterLabel?: JSX.Element | string;
|
||||||
confineToRoom: boolean;
|
confineToRoom: boolean;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
participantCount: number;
|
participantCount: number | null;
|
||||||
onShareClick: (() => void) | null;
|
onShareClick: (() => void) | null;
|
||||||
waitingForInvite?: boolean;
|
waitingForInvite?: boolean;
|
||||||
}
|
}
|
||||||
@@ -121,11 +121,9 @@ export const LobbyView: FC<Props> = ({
|
|||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<VideoPreview matrixInfo={matrixInfo} muteStates={muteStates}>
|
<VideoPreview matrixInfo={matrixInfo} muteStates={muteStates}>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(
|
className={classNames(styles.join, {
|
||||||
"cpd-theme-dark",
|
[styles.wait]: waitingForInvite,
|
||||||
styles.join,
|
})}
|
||||||
waitingForInvite ? styles.wait : "",
|
|
||||||
)}
|
|
||||||
size={waitingForInvite ? "sm" : "lg"}
|
size={waitingForInvite ? "sm" : "lg"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!waitingForInvite) onEnter();
|
if (!waitingForInvite) onEnter();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { useReactiveState } from "../useReactiveState";
|
|||||||
* If there already are this many participants in the call, we automatically mute
|
* If there already are this many participants in the call, we automatically mute
|
||||||
* the user.
|
* the user.
|
||||||
*/
|
*/
|
||||||
// TODO set back to 8. for demo purposes we use 1 here.
|
|
||||||
export const MUTE_PARTICIPANT_COUNT = 8;
|
export const MUTE_PARTICIPANT_COUNT = 8;
|
||||||
|
|
||||||
interface DeviceAvailable {
|
interface DeviceAvailable {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const RoomPage: FC = () => {
|
|||||||
}, [optInAnalytics, setOptInAnalytics]);
|
}, [optInAnalytics, setOptInAnalytics]);
|
||||||
|
|
||||||
const groupCallView = useCallback(
|
const groupCallView = useCallback(
|
||||||
(groupCallState: GroupCallStatus) => {
|
(groupCallState: GroupCallStatus): JSX.Element => {
|
||||||
switch (groupCallState.kind) {
|
switch (groupCallState.kind) {
|
||||||
case "loaded":
|
case "loaded":
|
||||||
return (
|
return (
|
||||||
@@ -106,9 +106,9 @@ export const RoomPage: FC = () => {
|
|||||||
case "canKnock": {
|
case "canKnock": {
|
||||||
const knock =
|
const knock =
|
||||||
groupCallState.kind === "canKnock" ? groupCallState.knock : null;
|
groupCallState.kind === "canKnock" ? groupCallState.knock : null;
|
||||||
const label =
|
const label: string | JSX.Element =
|
||||||
groupCallState.kind === "canKnock" ? (
|
groupCallState.kind === "canKnock" ? (
|
||||||
<>{t("lobby.ask_to_join")}</>
|
t("lobby.ask_to_join")
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{t("lobby.waiting_for_invite")}
|
{t("lobby.waiting_for_invite")}
|
||||||
@@ -139,12 +139,14 @@ export const RoomPage: FC = () => {
|
|||||||
waitingForInvite={groupCallState.kind === "waitForInvite"}
|
waitingForInvite={groupCallState.kind === "waitForInvite"}
|
||||||
confineToRoom={confineToRoom}
|
confineToRoom={confineToRoom}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
participantCount={0}
|
participantCount={null}
|
||||||
muteStates={muteStates}
|
muteStates={muteStates}
|
||||||
onShareClick={null}
|
onShareClick={null}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return <> </>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -63,9 +63,21 @@ export type GroupCallStatus =
|
|||||||
| GroupCallWaitForInvite
|
| GroupCallWaitForInvite
|
||||||
| GroupCallCanKnock;
|
| GroupCallCanKnock;
|
||||||
|
|
||||||
export class CustomMessage extends Error {
|
export class CallTerminatedMessage extends Error {
|
||||||
|
/**
|
||||||
|
* @param messageBody The message explaining the kind of termination (kick, ban, knock reject, etc.) (translated)
|
||||||
|
*/
|
||||||
public messageBody: string;
|
public messageBody: string;
|
||||||
|
/**
|
||||||
|
* @param reason The user provided reason for the termination (kick/ban)
|
||||||
|
*/
|
||||||
public reason?: string;
|
public reason?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param messageTitle The title of the call ended screen message (translated)
|
||||||
|
* @param messageBody The message explaining the kind of termination (kick, ban, knock reject, etc.) (translated)
|
||||||
|
* @param reason The user provided reason for the termination (kick/ban)
|
||||||
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
messageTitle: string,
|
messageTitle: string,
|
||||||
messageBody: string,
|
messageBody: string,
|
||||||
@@ -87,8 +99,8 @@ export const useLoadGroupCall = (
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const bannedError = useCallback(
|
const bannedError = useCallback(
|
||||||
(): CustomMessage =>
|
(): CallTerminatedMessage =>
|
||||||
new CustomMessage(
|
new CallTerminatedMessage(
|
||||||
t("group_call_loader.banned_heading"),
|
t("group_call_loader.banned_heading"),
|
||||||
t("group_call_loader.banned_body"),
|
t("group_call_loader.banned_body"),
|
||||||
leaveReason(),
|
leaveReason(),
|
||||||
@@ -96,8 +108,8 @@ export const useLoadGroupCall = (
|
|||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
const knockRejectError = useCallback(
|
const knockRejectError = useCallback(
|
||||||
(): CustomMessage =>
|
(): CallTerminatedMessage =>
|
||||||
new CustomMessage(
|
new CallTerminatedMessage(
|
||||||
t("group_call_loader.knock_reject_heading"),
|
t("group_call_loader.knock_reject_heading"),
|
||||||
t("group_call_loader.knock_reject_body"),
|
t("group_call_loader.knock_reject_body"),
|
||||||
leaveReason(),
|
leaveReason(),
|
||||||
@@ -105,8 +117,8 @@ export const useLoadGroupCall = (
|
|||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
const removeNoticeError = useCallback(
|
const removeNoticeError = useCallback(
|
||||||
(): CustomMessage =>
|
(): CallTerminatedMessage =>
|
||||||
new CustomMessage(
|
new CallTerminatedMessage(
|
||||||
t("group_call_loader.call_ended_heading"),
|
t("group_call_loader.call_ended_heading"),
|
||||||
t("group_call_loader.call_ended_body"),
|
t("group_call_loader.call_ended_body"),
|
||||||
leaveReason(),
|
leaveReason(),
|
||||||
@@ -148,7 +160,7 @@ export const useLoadGroupCall = (
|
|||||||
onKnockSent: () => void,
|
onKnockSent: () => void,
|
||||||
): Promise<Room> => {
|
): Promise<Room> => {
|
||||||
let joinedRoom: Room | null = null;
|
let joinedRoom: Room | null = null;
|
||||||
await client.knockRoom(roomId);
|
await client.knockRoom(roomId, { viaServers });
|
||||||
onKnockSent();
|
onKnockSent();
|
||||||
const invitePromise = new Promise<void>((resolve, reject) => {
|
const invitePromise = new Promise<void>((resolve, reject) => {
|
||||||
client.on(
|
client.on(
|
||||||
|
|||||||
Reference in New Issue
Block a user