Merge branch 'livekit' into resize-observer
This commit is contained in:
@@ -22,7 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||
import { getAbsoluteRoomUrl } from "../matrix-utils";
|
||||
import { getAbsoluteRoomUrl } from "../utils/matrix";
|
||||
import styles from "./AppSelectionModal.module.css";
|
||||
import { editFragmentQuery } from "../UrlParams";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
|
||||
@@ -42,9 +42,8 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.callEndedButton {
|
||||
margin: auto;
|
||||
margin-top: 54px;
|
||||
margin-left: 30px;
|
||||
margin-right: 30px !important;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
|
||||
@@ -18,17 +18,19 @@ import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
|
||||
import styles from "./CallEndedView.module.css";
|
||||
import feedbackStyle from "../input/FeedbackInput.module.css";
|
||||
import { Button, LinkButton } from "../button";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { Body, Link, Headline } from "../typography/Typography";
|
||||
import { Body, Headline } from "../typography/Typography";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { StarRatingInput } from "../input/StarRatingInput";
|
||||
import { RageshakeButton } from "../settings/RageshakeButton";
|
||||
import { Link } from "../button/Link";
|
||||
import { LinkButton } from "../button";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
@@ -95,12 +97,7 @@ export const CallEndedView: FC<Props> = ({
|
||||
calls
|
||||
</p>
|
||||
</Trans>
|
||||
<LinkButton
|
||||
className={styles.callEndedButton}
|
||||
size="lg"
|
||||
variant="default"
|
||||
to="/register"
|
||||
>
|
||||
<LinkButton className={styles.callEndedButton} to="/register">
|
||||
{t("call_ended_view.create_account_button")}
|
||||
</LinkButton>
|
||||
</div>
|
||||
@@ -136,8 +133,6 @@ export const CallEndedView: FC<Props> = ({
|
||||
<Button
|
||||
type="submit"
|
||||
className={styles.submitButton}
|
||||
size="lg"
|
||||
variant="default"
|
||||
data-testid="home_go"
|
||||
>
|
||||
{submitting ? t("submitting") : t("action.submit")}
|
||||
@@ -159,7 +154,7 @@ export const CallEndedView: FC<Props> = ({
|
||||
</Trans>
|
||||
</Headline>
|
||||
<div className={styles.disconnectedButtons}>
|
||||
<Button size="lg" variant="default" onClick={reconnect}>
|
||||
<Button onClick={reconnect}>
|
||||
{t("call_ended_view.reconnect_button")}
|
||||
</Button>
|
||||
<div className={styles.rageshakeButton}>
|
||||
@@ -169,9 +164,7 @@ export const CallEndedView: FC<Props> = ({
|
||||
</main>
|
||||
{!confineToRoom && (
|
||||
<Body className={styles.footer}>
|
||||
<Link color="primary" to="/">
|
||||
{t("return_home_button")}
|
||||
</Link>
|
||||
<Link to="/"> {t("return_home_button")} </Link>
|
||||
</Body>
|
||||
)}
|
||||
</>
|
||||
@@ -198,9 +191,7 @@ export const CallEndedView: FC<Props> = ({
|
||||
</main>
|
||||
{!confineToRoom && (
|
||||
<Body className={styles.footer}>
|
||||
<Link color="primary" to="/">
|
||||
{t("call_ended_view.not_now_button")}
|
||||
</Link>
|
||||
<Link to="/"> {t("call_ended_view.not_now_button")} </Link>
|
||||
</Body>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
import { useCallback } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MatrixError } from "matrix-js-sdk";
|
||||
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Heading, Link, Text } from "@vector-im/compound-web";
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import { MatrixInfo } from "./VideoPreview";
|
||||
import { CallEndedView } from "./CallEndedView";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { findDeviceByName } from "../media-utils";
|
||||
import { findDeviceByName } from "../utils/media";
|
||||
import { ActiveCall } from "./InCallView";
|
||||
import { MUTE_PARTICIPANT_COUNT, MuteStates } from "./MuteStates";
|
||||
import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext";
|
||||
|
||||
@@ -58,6 +58,10 @@ limitations under the License.
|
||||
);
|
||||
}
|
||||
|
||||
.footer.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer.overlay {
|
||||
position: absolute;
|
||||
inset-block-end: 0;
|
||||
@@ -67,6 +71,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.footer.overlay.hidden {
|
||||
display: grid;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
RoomContext,
|
||||
useLocalParticipant,
|
||||
} from "@livekit/components-react";
|
||||
import { usePreventScroll } from "@react-aria/overlays";
|
||||
import { ConnectionState, Room } from "livekit-client";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import {
|
||||
@@ -44,10 +43,10 @@ import LogoMark from "../icons/LogoMark.svg?react";
|
||||
import LogoType from "../icons/LogoType.svg?react";
|
||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||
import {
|
||||
HangupButton,
|
||||
EndCallButton,
|
||||
MicButton,
|
||||
VideoButton,
|
||||
ScreenshareButton,
|
||||
ShareScreenButton,
|
||||
SettingsButton,
|
||||
} from "../button";
|
||||
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
|
||||
@@ -69,7 +68,7 @@ import { InviteButton } from "../button/InviteButton";
|
||||
import { LayoutToggle } from "./LayoutToggle";
|
||||
import { ECConnectionState } from "../livekit/useECConnectionState";
|
||||
import { useOpenIDSFU } from "../livekit/openIDSFU";
|
||||
import { GridMode, Layout, useCallViewModel } from "../state/CallViewModel";
|
||||
import { CallViewModel, GridMode, Layout } from "../state/CallViewModel";
|
||||
import { Grid, TileProps } from "../grid/Grid";
|
||||
import { useObservable } from "../state/useObservable";
|
||||
import { useInitial } from "../useInitial";
|
||||
@@ -93,7 +92,7 @@ const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||
const maxTapDurationMs = 400;
|
||||
|
||||
export interface ActiveCallProps
|
||||
extends Omit<InCallViewProps, "livekitRoom" | "connState"> {
|
||||
extends Omit<InCallViewProps, "vm" | "livekitRoom" | "connState"> {
|
||||
e2eeSystem: EncryptionSystem;
|
||||
}
|
||||
|
||||
@@ -105,6 +104,8 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
sfuConfig,
|
||||
props.e2eeSystem,
|
||||
);
|
||||
const connStateObservable = useObservable(connState);
|
||||
const [vm, setVm] = useState<CallViewModel | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
@@ -113,17 +114,41 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!livekitRoom) return null;
|
||||
useEffect(() => {
|
||||
if (livekitRoom !== undefined) {
|
||||
const vm = new CallViewModel(
|
||||
props.rtcSession.room,
|
||||
livekitRoom,
|
||||
props.e2eeSystem.kind !== E2eeType.NONE,
|
||||
connStateObservable,
|
||||
);
|
||||
setVm(vm);
|
||||
return (): void => vm.destroy();
|
||||
}
|
||||
}, [
|
||||
props.rtcSession.room,
|
||||
livekitRoom,
|
||||
props.e2eeSystem.kind,
|
||||
connStateObservable,
|
||||
]);
|
||||
|
||||
if (livekitRoom === undefined || vm === null) return null;
|
||||
|
||||
return (
|
||||
<RoomContext.Provider value={livekitRoom}>
|
||||
<InCallView {...props} livekitRoom={livekitRoom} connState={connState} />
|
||||
<InCallView
|
||||
{...props}
|
||||
vm={vm}
|
||||
livekitRoom={livekitRoom}
|
||||
connState={connState}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export interface InCallViewProps {
|
||||
client: MatrixClient;
|
||||
vm: CallViewModel;
|
||||
matrixInfo: MatrixInfo;
|
||||
rtcSession: MatrixRTCSession;
|
||||
livekitRoom: Room;
|
||||
@@ -138,6 +163,7 @@ export interface InCallViewProps {
|
||||
|
||||
export const InCallView: FC<InCallViewProps> = ({
|
||||
client,
|
||||
vm,
|
||||
matrixInfo,
|
||||
rtcSession,
|
||||
livekitRoom,
|
||||
@@ -148,7 +174,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
connState,
|
||||
onShareClick,
|
||||
}) => {
|
||||
usePreventScroll();
|
||||
useWakeLock();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -193,12 +218,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
const reducedControls = boundsValid && bounds.width <= 340;
|
||||
const noControls = reducedControls && bounds.height <= 400;
|
||||
|
||||
const vm = useCallViewModel(
|
||||
rtcSession.room,
|
||||
livekitRoom,
|
||||
matrixInfo.e2eeSystem.kind !== E2eeType.NONE,
|
||||
connState,
|
||||
);
|
||||
const windowMode = useObservableEagerState(vm.windowMode);
|
||||
const layout = useObservableEagerState(vm.layout);
|
||||
const gridMode = useObservableEagerState(vm.gridMode);
|
||||
@@ -471,14 +490,14 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
<MicButton
|
||||
key="1"
|
||||
muted={!muteStates.audio.enabled}
|
||||
onPress={toggleMicrophone}
|
||||
onClick={toggleMicrophone}
|
||||
disabled={muteStates.audio.setEnabled === null}
|
||||
data-testid="incall_mute"
|
||||
/>,
|
||||
<VideoButton
|
||||
key="2"
|
||||
muted={!muteStates.video.enabled}
|
||||
onPress={toggleCamera}
|
||||
onClick={toggleCamera}
|
||||
disabled={muteStates.video.setEnabled === null}
|
||||
data-testid="incall_videomute"
|
||||
/>,
|
||||
@@ -486,21 +505,21 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
if (!reducedControls) {
|
||||
if (canScreenshare && !hideScreensharing) {
|
||||
buttons.push(
|
||||
<ScreenshareButton
|
||||
<ShareScreenButton
|
||||
key="3"
|
||||
enabled={isScreenShareEnabled}
|
||||
onPress={toggleScreensharing}
|
||||
onClick={toggleScreensharing}
|
||||
data-testid="incall_screenshare"
|
||||
/>,
|
||||
);
|
||||
}
|
||||
buttons.push(<SettingsButton key="4" onPress={openSettings} />);
|
||||
buttons.push(<SettingsButton key="4" onClick={openSettings} />);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<HangupButton
|
||||
<EndCallButton
|
||||
key="6"
|
||||
onPress={function (): void {
|
||||
onClick={function (): void {
|
||||
onLeave();
|
||||
}}
|
||||
data-testid="incall_leave"
|
||||
|
||||
@@ -24,3 +24,12 @@ limitations under the License.
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.qrCode {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qrCode img {
|
||||
margin-block-end: var(--cpd-space-8x);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
|
||||
import { FC, MouseEvent, useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Room } from "matrix-js-sdk";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { Button, Text } from "@vector-im/compound-web";
|
||||
import {
|
||||
LinkIcon,
|
||||
@@ -25,10 +25,11 @@ import {
|
||||
import useClipboard from "react-use-clipboard";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import { getAbsoluteRoomUrl } from "../matrix-utils";
|
||||
import { getAbsoluteRoomUrl } from "../utils/matrix";
|
||||
import styles from "./InviteModal.module.css";
|
||||
import { Toast } from "../Toast";
|
||||
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||
import { QrCode } from "../QrCode";
|
||||
|
||||
interface Props {
|
||||
room: Room;
|
||||
@@ -61,6 +62,7 @@ export const InviteModal: FC<Props> = ({ room, open, onDismiss }) => {
|
||||
return (
|
||||
<>
|
||||
<Modal title={t("invite_modal.title")} open={open} onDismiss={onDismiss}>
|
||||
<QrCode className={styles.qrCode} data={url} />
|
||||
<Text className={styles.url} size="sm" weight="semibold">
|
||||
{url}
|
||||
</Text>
|
||||
|
||||
@@ -19,7 +19,6 @@ limitations under the License.
|
||||
border: 1px solid var(--cpd-color-border-interactive-secondary);
|
||||
border-radius: var(--cpd-radius-pill-effect);
|
||||
background: var(--cpd-color-bg-canvas-default);
|
||||
box-shadow: 0px 0px 40px 0px rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
@@ -32,9 +31,9 @@ limitations under the License.
|
||||
inline-size: var(--cpd-space-11x);
|
||||
cursor: pointer;
|
||||
border-radius: var(--cpd-radius-pill-effect);
|
||||
color: var(--cpd-color-icon-primary);
|
||||
background: var(--cpd-color-bg-action-secondary-rest);
|
||||
box-shadow: var(--small-drop-shadow);
|
||||
transition: background-color 0.1s;
|
||||
}
|
||||
|
||||
.toggle svg {
|
||||
@@ -43,6 +42,7 @@ limitations under the License.
|
||||
padding: calc(2.5 * var(--cpd-space-1x));
|
||||
pointer-events: none;
|
||||
color: var(--cpd-color-icon-primary);
|
||||
transition: color 0.1s;
|
||||
}
|
||||
|
||||
.toggle svg:nth-child(2) {
|
||||
@@ -61,7 +61,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.toggle input:active {
|
||||
background: var(--cpd-color-bg-action-secondary-hovered);
|
||||
background: var(--cpd-color-bg-action-secondary-pressed);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.toggle input:checked:active {
|
||||
background: var(--cpd-color-bg-action-primary-hovered);
|
||||
background: var(--cpd-color-bg-action-primary-pressed);
|
||||
}
|
||||
|
||||
.toggle input:first-child {
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
import { FC, useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { Button, Link } from "@vector-im/compound-web";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import classNames from "classnames";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
@@ -29,7 +29,7 @@ import { MatrixInfo, VideoPreview } from "./VideoPreview";
|
||||
import { MuteStates } from "./MuteStates";
|
||||
import { InviteButton } from "../button/InviteButton";
|
||||
import {
|
||||
HangupButton,
|
||||
EndCallButton,
|
||||
MicButton,
|
||||
SettingsButton,
|
||||
VideoButton,
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
|
||||
import { useMediaQuery } from "../useMediaQuery";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { Link } from "../button/Link";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
@@ -92,7 +93,7 @@ export const LobbyView: FC<Props> = ({
|
||||
|
||||
const recentsButtonInFooter = useMediaQuery("(max-height: 500px)");
|
||||
const recentsButton = !confineToRoom && (
|
||||
<Link className={styles.recents} href="#" onClick={onLeaveClick}>
|
||||
<Link className={styles.recents} to="/">
|
||||
{t("lobby.leave_button")}
|
||||
</Link>
|
||||
);
|
||||
@@ -140,16 +141,16 @@ export const LobbyView: FC<Props> = ({
|
||||
<div className={inCallStyles.buttons}>
|
||||
<MicButton
|
||||
muted={!muteStates.audio.enabled}
|
||||
onPress={onAudioPress}
|
||||
onClick={onAudioPress}
|
||||
disabled={muteStates.audio.setEnabled === null}
|
||||
/>
|
||||
<VideoButton
|
||||
muted={!muteStates.video.enabled}
|
||||
onPress={onVideoPress}
|
||||
onClick={onVideoPress}
|
||||
disabled={muteStates.video.setEnabled === null}
|
||||
/>
|
||||
<SettingsButton onPress={openSettings} />
|
||||
{!confineToRoom && <HangupButton onPress={onLeaveClick} />}
|
||||
<SettingsButton onClick={openSettings} />
|
||||
{!confineToRoom && <EndCallButton onClick={onLeaveClick} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,9 +16,9 @@ limitations under the License.
|
||||
|
||||
import { FC, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
|
||||
import { Modal, Props as ModalProps } from "../Modal";
|
||||
import { Button } from "../button";
|
||||
import { FieldRow, ErrorMessage } from "../input/Input";
|
||||
import { useSubmitRageshake } from "../settings/submit-rageshake";
|
||||
import { Body } from "../typography/Typography";
|
||||
@@ -52,7 +52,7 @@ export const RageshakeRequestModal: FC<Props> = ({
|
||||
<Body>{t("rageshake_request_modal.body")}</Body>
|
||||
<FieldRow>
|
||||
<Button
|
||||
onPress={(): void =>
|
||||
onClick={(): void =>
|
||||
void submitRageshake({
|
||||
sendLogs: true,
|
||||
rageshakeRequestId,
|
||||
|
||||
@@ -18,9 +18,9 @@ import { FC, useCallback, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
|
||||
import styles from "./RoomAuthView.module.css";
|
||||
import { Button } from "../button";
|
||||
import { Body, Caption, Link, Headline } from "../typography/Typography";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
@@ -117,7 +117,7 @@ export const RoomAuthView: FC = () => {
|
||||
Not registered yet?{" "}
|
||||
<Link
|
||||
color="primary"
|
||||
to={{ pathname: "/login", state: { from: location } }}
|
||||
to={{ pathname: "/register", state: { from: location } }}
|
||||
>
|
||||
Create an account
|
||||
</Link>
|
||||
|
||||
@@ -35,10 +35,7 @@ import { LobbyView } from "./LobbyView";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { useMuteStates } from "./MuteStates";
|
||||
import {
|
||||
useSetting,
|
||||
optInAnalytics as optInAnalyticsSetting,
|
||||
} from "../settings/settings";
|
||||
import { useOptInAnalytics } from "../settings/settings";
|
||||
|
||||
export const RoomPage: FC = () => {
|
||||
const {
|
||||
@@ -83,7 +80,7 @@ export const RoomPage: FC = () => {
|
||||
registerPasswordlessUser,
|
||||
]);
|
||||
|
||||
const [optInAnalytics, setOptInAnalytics] = useSetting(optInAnalyticsSetting);
|
||||
const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics();
|
||||
useEffect(() => {
|
||||
// During the beta, opt into analytics by default
|
||||
if (optInAnalytics === null && setOptInAnalytics) setOptInAnalytics(true);
|
||||
|
||||
164
src/room/checkForParallelCalls.test.ts
Normal file
164
src/room/checkForParallelCalls.test.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright 2023 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 { vi, Mocked, test, expect } from "vitest";
|
||||
import { RoomState } from "matrix-js-sdk/src/models/room-state";
|
||||
|
||||
import { PosthogAnalytics } from "../../src/analytics/PosthogAnalytics";
|
||||
import { checkForParallelCalls } from "../../src/room/checkForParallelCalls";
|
||||
import { withFakeTimers } from "../utils/test";
|
||||
|
||||
const withMockedPosthog = (
|
||||
continuation: (posthog: Mocked<PosthogAnalytics>) => void,
|
||||
): void => {
|
||||
const posthog = vi.mocked({
|
||||
trackEvent: vi.fn(),
|
||||
} as unknown as PosthogAnalytics);
|
||||
const instanceSpy = vi
|
||||
.spyOn(PosthogAnalytics, "instance", "get")
|
||||
.mockReturnValue(posthog);
|
||||
try {
|
||||
continuation(posthog);
|
||||
} finally {
|
||||
instanceSpy.mockRestore();
|
||||
}
|
||||
};
|
||||
|
||||
const mockRoomState = (
|
||||
groupCallMemberContents: Record<string, unknown>[],
|
||||
): RoomState => {
|
||||
const stateEvents = groupCallMemberContents.map((content) => ({
|
||||
getContent: (): Record<string, unknown> => content,
|
||||
}));
|
||||
return { getStateEvents: () => stateEvents } as unknown as RoomState;
|
||||
};
|
||||
|
||||
test("checkForParallelCalls does nothing if all participants are in the same call", () => {
|
||||
withFakeTimers(() => {
|
||||
withMockedPosthog((posthog) => {
|
||||
const roomState = mockRoomState([
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": "1",
|
||||
"m.devices": [
|
||||
{
|
||||
device_id: "Element Call",
|
||||
session_id: "a",
|
||||
expires_ts: Date.now() + 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"m.call_id": null, // invalid
|
||||
"m.devices": [
|
||||
{
|
||||
device_id: "Element Android",
|
||||
session_id: "a",
|
||||
expires_ts: Date.now() + 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
null, // invalid
|
||||
],
|
||||
},
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": "1",
|
||||
"m.devices": [
|
||||
{
|
||||
device_id: "Element Desktop",
|
||||
session_id: "a",
|
||||
expires_ts: Date.now() + 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
checkForParallelCalls(roomState);
|
||||
expect(posthog.trackEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("checkForParallelCalls sends diagnostics to PostHog if there is a split-brain", () => {
|
||||
withFakeTimers(() => {
|
||||
withMockedPosthog((posthog) => {
|
||||
const roomState = mockRoomState([
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": "1",
|
||||
"m.devices": [
|
||||
{
|
||||
device_id: "Element Call",
|
||||
session_id: "a",
|
||||
expires_ts: Date.now() + 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"m.call_id": "2",
|
||||
"m.devices": [
|
||||
{
|
||||
device_id: "Element Android",
|
||||
session_id: "a",
|
||||
expires_ts: Date.now() + 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": "1",
|
||||
"m.devices": [
|
||||
{
|
||||
device_id: "Element Desktop",
|
||||
session_id: "a",
|
||||
expires_ts: Date.now() + 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"m.call_id": "2",
|
||||
"m.devices": [
|
||||
{
|
||||
device_id: "Element Call",
|
||||
session_id: "a",
|
||||
expires_ts: Date.now() - 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
checkForParallelCalls(roomState);
|
||||
expect(posthog.trackEvent).toHaveBeenCalledWith({
|
||||
eventName: "ParallelCalls",
|
||||
participantsPerCall: {
|
||||
"1": 2,
|
||||
"2": 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,7 @@ import { SyncState } from "matrix-js-sdk/src/sync";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { RoomEvent, Room } from "matrix-js-sdk/src/models/room";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { JoinRule } from "matrix-js-sdk";
|
||||
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { widget } from "../widget";
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
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 { useEffect } from "react";
|
||||
|
||||
import { platform } from "../Platform";
|
||||
|
||||
export function usePageUnload(callback: () => void): void {
|
||||
useEffect(() => {
|
||||
let pageVisibilityTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
function onBeforeUnload(event: PageTransitionEvent): void {
|
||||
if (event.type === "visibilitychange") {
|
||||
if (document.visibilityState === "visible") {
|
||||
clearTimeout(pageVisibilityTimeout);
|
||||
} else {
|
||||
// Wait 5 seconds before closing the page to avoid accidentally leaving
|
||||
// TODO: Make this configurable?
|
||||
pageVisibilityTimeout = setTimeout(() => {
|
||||
callback();
|
||||
}, 5000);
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
// iOS doesn't fire beforeunload event, so leave the call when you hide the page.
|
||||
if (platform === "ios") {
|
||||
window.addEventListener("pagehide", onBeforeUnload);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
document.addEventListener("visibilitychange", onBeforeUnload);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
|
||||
return (): void => {
|
||||
window.removeEventListener("pagehide", onBeforeUnload);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
document.removeEventListener("visibilitychange", onBeforeUnload);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
clearTimeout(pageVisibilityTimeout);
|
||||
};
|
||||
}, [callback]);
|
||||
}
|
||||
Reference in New Issue
Block a user