mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
Compare commits
12 Commits
robin/clos
...
robin/in-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
564349c96f | ||
|
|
8bf81defd4 | ||
|
|
cd05df3e33 | ||
|
|
9b59544db2 | ||
|
|
91671d9ff1 | ||
|
|
e4d7baf7bb | ||
|
|
832a5aaf57 | ||
|
|
5e8395262c | ||
|
|
ad461a7643 | ||
|
|
028b3104e4 | ||
|
|
4015efa6c6 | ||
|
|
f111c64264 |
@@ -31,6 +31,7 @@
|
||||
"@formatjs/intl-segmenter": "^11.7.3",
|
||||
"@livekit/components-core": "^0.12.0",
|
||||
"@livekit/components-react": "^2.0.0",
|
||||
"@livekit/protocol": "^1.33.0",
|
||||
"@opentelemetry/api": "^1.4.0",
|
||||
"@opentelemetry/core": "^1.25.1",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
|
||||
|
||||
@@ -30,10 +30,14 @@ export const FullScreenView: FC<FullScreenViewProps> = ({
|
||||
const { hideHeader } = useUrlParams();
|
||||
return (
|
||||
<div className={classNames(styles.page, className)}>
|
||||
<Header>
|
||||
<LeftNav>{!hideHeader && <HeaderLogo />}</LeftNav>
|
||||
<RightNav />
|
||||
</Header>
|
||||
{!hideHeader && (
|
||||
<Header>
|
||||
<LeftNav>
|
||||
<HeaderLogo />
|
||||
</LeftNav>
|
||||
<RightNav />
|
||||
</Header>
|
||||
)}
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -5,17 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import {
|
||||
type FC,
|
||||
type FormEventHandler,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useState,
|
||||
} from "react";
|
||||
import { type FC, type FormEventHandler, useCallback, useState } from "react";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Button, Heading, Text } from "@vector-im/compound-web";
|
||||
import { OfflineIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
@@ -28,15 +21,12 @@ import { FieldRow, InputField } from "../input/Input";
|
||||
import { StarRatingInput } from "../input/StarRatingInput";
|
||||
import { Link } from "../button/Link";
|
||||
import { LinkButton } from "../button";
|
||||
import { ErrorView } from "../ErrorView";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
isPasswordlessUser: boolean;
|
||||
confineToRoom: boolean;
|
||||
endedCallId: string;
|
||||
leaveError?: Error;
|
||||
reconnect: () => void;
|
||||
}
|
||||
|
||||
export const CallEndedView: FC<Props> = ({
|
||||
@@ -44,8 +34,6 @@ export const CallEndedView: FC<Props> = ({
|
||||
isPasswordlessUser,
|
||||
confineToRoom,
|
||||
endedCallId,
|
||||
leaveError,
|
||||
reconnect,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
@@ -143,61 +131,32 @@ export const CallEndedView: FC<Props> = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderBody = (): ReactNode => {
|
||||
if (leaveError) {
|
||||
return (
|
||||
<>
|
||||
<main className={styles.main}>
|
||||
<ErrorView
|
||||
Icon={OfflineIcon}
|
||||
title={t("error.connection_lost")}
|
||||
rageshake
|
||||
>
|
||||
<p>{t("error.connection_lost_description")}</p>
|
||||
<Button onClick={reconnect}>
|
||||
{t("call_ended_view.reconnect_button")}
|
||||
</Button>
|
||||
</ErrorView>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<main className={styles.main}>
|
||||
<Heading size="xl" weight="semibold" className={styles.headline}>
|
||||
{surveySubmitted
|
||||
? t("call_ended_view.headline", {
|
||||
displayName,
|
||||
})
|
||||
: t("call_ended_view.headline", {
|
||||
displayName,
|
||||
}) +
|
||||
"\n" +
|
||||
t("call_ended_view.survey_prompt")}
|
||||
</Heading>
|
||||
{(!surveySubmitted || confineToRoom) &&
|
||||
PosthogAnalytics.instance.isEnabled()
|
||||
? qualitySurveyDialog
|
||||
: createAccountDialog}
|
||||
</main>
|
||||
{!confineToRoom && (
|
||||
<Text className={styles.footer}>
|
||||
<Link to="/"> {t("call_ended_view.not_now_button")} </Link>
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<LeftNav>{!confineToRoom && <HeaderLogo />}</LeftNav>
|
||||
<RightNav />
|
||||
</Header>
|
||||
<div className={styles.container}>{renderBody()}</div>
|
||||
<div className={styles.container}>
|
||||
<main className={styles.main}>
|
||||
<Heading size="xl" weight="semibold" className={styles.headline}>
|
||||
{surveySubmitted
|
||||
? t("call_ended_view.headline", { displayName })
|
||||
: t("call_ended_view.headline", { displayName }) +
|
||||
"\n" +
|
||||
t("call_ended_view.survey_prompt")}
|
||||
</Heading>
|
||||
{(!surveySubmitted || confineToRoom) &&
|
||||
PosthogAnalytics.instance.isEnabled()
|
||||
? qualitySurveyDialog
|
||||
: createAccountDialog}
|
||||
</main>
|
||||
{!confineToRoom && (
|
||||
<Text className={styles.footer}>
|
||||
<Link to="/"> {t("call_ended_view.not_now_button")} </Link>
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { beforeEach, expect, type MockedFunction, test, vitest } from "vitest";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { render, waitFor, screen } from "@testing-library/react";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
|
||||
import { of } from "rxjs";
|
||||
@@ -14,6 +14,7 @@ import { JoinRule, type RoomState } from "matrix-js-sdk/src/matrix";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { type RelationsContainer } from "matrix-js-sdk/src/models/relations-container";
|
||||
import { useState } from "react";
|
||||
|
||||
import { type MuteStates } from "./MuteStates";
|
||||
import { prefetchSounds } from "../soundUtils";
|
||||
@@ -184,3 +185,28 @@ test("will play a leave sound synchronously in widget mode", async () => {
|
||||
);
|
||||
expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test("GroupCallView leaves the session when an error occurs", async () => {
|
||||
(ActiveCall as MockedFunction<typeof ActiveCall>).mockImplementation(() => {
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
if (error !== null) throw error;
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setError(new Error())}>Panic!</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
const { rtcSession } = createGroupCallView(null);
|
||||
await user.click(screen.getByRole("button", { name: "Panic!" }));
|
||||
screen.getByText("error.generic");
|
||||
expect(leaveRTCSession).toHaveBeenCalledWith(
|
||||
rtcSession,
|
||||
"error",
|
||||
expect.any(Promise),
|
||||
);
|
||||
expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce();
|
||||
// Ensure that the playSound promise resolves within this test to avoid
|
||||
// impacting the results of other tests
|
||||
await waitFor(() => expect(leaveRTCSession).toHaveResolved());
|
||||
});
|
||||
|
||||
@@ -7,10 +7,11 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import {
|
||||
type FC,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||
@@ -21,9 +22,14 @@ import {
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
||||
import { WebBrowserIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import {
|
||||
OfflineIcon,
|
||||
WebBrowserIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ErrorBoundary } from "@sentry/react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
|
||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||
import {
|
||||
@@ -31,19 +37,16 @@ import {
|
||||
type JoinCallData,
|
||||
type WidgetHelpers,
|
||||
} from "../widget";
|
||||
import { FullScreenView } from "../FullScreenView";
|
||||
import { ErrorPage, FullScreenView } from "../FullScreenView";
|
||||
import { LobbyView } from "./LobbyView";
|
||||
import { type MatrixInfo } from "./VideoPreview";
|
||||
import { CallEndedView } from "./CallEndedView";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { findDeviceByName } from "../utils/media";
|
||||
import { ActiveCall } from "./InCallView";
|
||||
import { ActiveCall, ConnectionLostError } from "./InCallView";
|
||||
import { MUTE_PARTICIPANT_COUNT, type MuteStates } from "./MuteStates";
|
||||
import {
|
||||
useMediaDevices,
|
||||
type MediaDevices,
|
||||
} from "../livekit/MediaDevicesContext";
|
||||
import { useMediaDevices } from "../livekit/MediaDevicesContext";
|
||||
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
|
||||
import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers";
|
||||
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||
@@ -65,6 +68,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupCallErrorPageProps {
|
||||
error: Error | unknown;
|
||||
resetError: () => void;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
isPasswordlessUser: boolean;
|
||||
@@ -154,12 +162,8 @@ export const GroupCallView: FC<Props> = ({
|
||||
);
|
||||
|
||||
const deviceContext = useMediaDevices();
|
||||
const latestDevices = useRef<MediaDevices | undefined>(undefined);
|
||||
latestDevices.current = deviceContext;
|
||||
|
||||
// TODO: why do we use a ref here instead of using muteStates directly?
|
||||
const latestMuteStates = useRef<MuteStates | undefined>(undefined);
|
||||
latestMuteStates.current = muteStates;
|
||||
const latestDevices = useLatest(deviceContext);
|
||||
const latestMuteStates = useLatest(muteStates);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultDeviceSetup = async ({
|
||||
@@ -232,19 +236,25 @@ export const GroupCallView: FC<Props> = ({
|
||||
void enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
}
|
||||
}
|
||||
}, [widget, rtcSession, preload, skipLobby, perParticipantE2EE]);
|
||||
}, [
|
||||
widget,
|
||||
rtcSession,
|
||||
preload,
|
||||
skipLobby,
|
||||
perParticipantE2EE,
|
||||
latestDevices,
|
||||
latestMuteStates,
|
||||
]);
|
||||
|
||||
const [left, setLeft] = useState(false);
|
||||
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onLeave = useCallback(
|
||||
(leaveError?: Error): void => {
|
||||
(cause: "user" | "error" = "user"): void => {
|
||||
const audioPromise = leaveSoundContext.current?.playSound("left");
|
||||
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
||||
// therefore we want the event to be sent instantly without getting queued/batched.
|
||||
const sendInstantly = !!widget;
|
||||
setLeaveError(leaveError);
|
||||
setLeft(true);
|
||||
// we need to wait until the callEnded event is tracked on posthog.
|
||||
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||
@@ -260,7 +270,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
|
||||
leaveRTCSession(
|
||||
rtcSession,
|
||||
leaveError === undefined ? "user" : "error",
|
||||
cause,
|
||||
// Wait for the sound in widget mode (it's not long)
|
||||
Promise.all([audioPromise, posthogRequest]),
|
||||
)
|
||||
@@ -309,14 +319,6 @@ export const GroupCallView: FC<Props> = ({
|
||||
}
|
||||
}, [widget, isJoined, rtcSession]);
|
||||
|
||||
const onReconnect = useCallback(() => {
|
||||
setLeft(false);
|
||||
setLeaveError(undefined);
|
||||
enterRTCSession(rtcSession, perParticipantE2EE).catch((e) => {
|
||||
logger.error("Error re-entering RTC session on reconnect", e);
|
||||
});
|
||||
}, [rtcSession, perParticipantE2EE]);
|
||||
|
||||
const joinRule = useJoinRule(rtcSession.room);
|
||||
|
||||
const [shareModalOpen, setInviteModalOpen] = useState(false);
|
||||
@@ -333,6 +335,43 @@ export const GroupCallView: FC<Props> = ({
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const errorPage = useMemo(() => {
|
||||
function GroupCallErrorPage({
|
||||
error,
|
||||
resetError,
|
||||
}: GroupCallErrorPageProps): ReactElement {
|
||||
useEffect(() => {
|
||||
if (rtcSession.isJoined()) onLeave("error");
|
||||
}, [error]);
|
||||
|
||||
const onReconnect = useCallback(() => {
|
||||
setLeft(false);
|
||||
resetError();
|
||||
enterRTCSession(rtcSession, perParticipantE2EE).catch((e) => {
|
||||
logger.error("Error re-entering RTC session on reconnect", e);
|
||||
});
|
||||
}, [resetError]);
|
||||
|
||||
return error instanceof ConnectionLostError ? (
|
||||
<FullScreenView>
|
||||
<ErrorView
|
||||
Icon={OfflineIcon}
|
||||
title={t("error.connection_lost")}
|
||||
rageshake
|
||||
>
|
||||
<p>{t("error.connection_lost_description")}</p>
|
||||
<Button onClick={onReconnect}>
|
||||
{t("call_ended_view.reconnect_button")}
|
||||
</Button>
|
||||
</ErrorView>
|
||||
</FullScreenView>
|
||||
) : (
|
||||
<ErrorPage error={error} />
|
||||
);
|
||||
}
|
||||
return GroupCallErrorPage;
|
||||
}, [onLeave, rtcSession, perParticipantE2EE, t]);
|
||||
|
||||
if (!isE2EESupportedBrowser() && e2eeSystem.kind !== E2eeType.NONE) {
|
||||
// If we have a encryption system but the browser does not support it.
|
||||
return (
|
||||
@@ -367,8 +406,9 @@ export const GroupCallView: FC<Props> = ({
|
||||
</>
|
||||
);
|
||||
|
||||
let body: ReactNode;
|
||||
if (isJoined) {
|
||||
return (
|
||||
body = (
|
||||
<>
|
||||
{shareModal}
|
||||
<ActiveCall
|
||||
@@ -396,36 +436,32 @@ export const GroupCallView: FC<Props> = ({
|
||||
// submitting anything.
|
||||
if (
|
||||
isPasswordlessUser ||
|
||||
(PosthogAnalytics.instance.isEnabled() && widget === null) ||
|
||||
leaveError
|
||||
(PosthogAnalytics.instance.isEnabled() && widget === null)
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<CallEndedView
|
||||
endedCallId={rtcSession.room.roomId}
|
||||
client={client}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
confineToRoom={confineToRoom}
|
||||
leaveError={leaveError}
|
||||
reconnect={onReconnect}
|
||||
/>
|
||||
;
|
||||
</>
|
||||
body = (
|
||||
<CallEndedView
|
||||
endedCallId={rtcSession.room.roomId}
|
||||
client={client}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
confineToRoom={confineToRoom}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// If the user is a regular user, we'll have sent them back to the homepage,
|
||||
// so just sit here & do nothing: otherwise we would (briefly) mount the
|
||||
// LobbyView again which would open capture devices again.
|
||||
return null;
|
||||
body = null;
|
||||
}
|
||||
} else if (left && widget !== null) {
|
||||
// Left in widget mode:
|
||||
if (!returnToLobby) {
|
||||
return null;
|
||||
body = null;
|
||||
}
|
||||
} else if (preload || skipLobby) {
|
||||
return null;
|
||||
body = null;
|
||||
} else {
|
||||
body = lobbyView;
|
||||
}
|
||||
|
||||
return lobbyView;
|
||||
return <ErrorBoundary fallback={errorPage}>{body}</ErrorBoundary>;
|
||||
};
|
||||
|
||||
@@ -102,6 +102,8 @@ const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||
|
||||
const maxTapDurationMs = 400;
|
||||
|
||||
export class ConnectionLostError extends Error {}
|
||||
|
||||
export interface ActiveCallProps
|
||||
extends Omit<InCallViewProps, "vm" | "livekitRoom" | "connState"> {
|
||||
e2eeSystem: EncryptionSystem;
|
||||
@@ -173,7 +175,8 @@ export interface InCallViewProps {
|
||||
livekitRoom: Room;
|
||||
muteStates: MuteStates;
|
||||
participantCount: number;
|
||||
onLeave: (error?: Error) => void;
|
||||
/** Function to call when the user explicitly ends the call */
|
||||
onLeave: () => void;
|
||||
hideHeader: boolean;
|
||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||
connState: ECConnectionState;
|
||||
@@ -198,13 +201,10 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
|
||||
useWakeLock();
|
||||
|
||||
useEffect(() => {
|
||||
if (connState === ConnectionState.Disconnected) {
|
||||
// annoyingly we don't get the disconnection reason this way,
|
||||
// only by listening for the emitted event
|
||||
onLeave(new Error("Disconnected from call server"));
|
||||
}
|
||||
}, [connState, onLeave]);
|
||||
// annoyingly we don't get the disconnection reason this way,
|
||||
// only by listening for the emitted event
|
||||
if (connState === ConnectionState.Disconnected)
|
||||
throw new ConnectionLostError();
|
||||
|
||||
const containerRef1 = useRef<HTMLDivElement | null>(null);
|
||||
const [containerRef2, bounds] = useMeasure();
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
type TrackReference,
|
||||
type TrackReferencePlaceholder,
|
||||
} from "@livekit/components-core";
|
||||
import { Track, TrackPublication } from "livekit-client";
|
||||
import { LocalTrackPublication, Track } from "livekit-client";
|
||||
import { TrackInfo } from "@livekit/protocol";
|
||||
import { type ComponentProps } from "react";
|
||||
|
||||
import { MediaView } from "./MediaView";
|
||||
@@ -28,7 +29,10 @@ describe("MediaView", () => {
|
||||
};
|
||||
const trackReference: TrackReference = {
|
||||
...trackReferencePlaceholder,
|
||||
publication: new TrackPublication(Track.Kind.Video, "id", "name"),
|
||||
publication: new LocalTrackPublication(
|
||||
Track.Kind.Video,
|
||||
new TrackInfo({ sid: "id", name: "name" }),
|
||||
),
|
||||
};
|
||||
|
||||
const baseProps: ComponentProps<typeof MediaView> = {
|
||||
|
||||
@@ -8,12 +8,13 @@ Please see LICENSE in the repository root for full details.
|
||||
import { type RefObject, useRef } from "react";
|
||||
|
||||
export interface LatestRef<T> extends RefObject<T> {
|
||||
current: T;
|
||||
current: T; // Always defined, unlike RefObject["current"]
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook that returns a ref containing the value given on the latest
|
||||
* render.
|
||||
* render. Useful for accessing the latest value of something in an effect or
|
||||
* callback when you don't want reactivity.
|
||||
*/
|
||||
export function useLatest<T>(value: T): LatestRef<T> {
|
||||
const ref = useRef<T>(value);
|
||||
|
||||
37
yarn.lock
37
yarn.lock
@@ -1756,10 +1756,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@livekit/mutex/-/mutex-1.1.1.tgz#72492b611d55be8130ba2271b7a436d94b1bc6d4"
|
||||
integrity sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==
|
||||
|
||||
"@livekit/protocol@1.30.0":
|
||||
version "1.30.0"
|
||||
resolved "https://registry.yarnpkg.com/@livekit/protocol/-/protocol-1.30.0.tgz#c79e98aca2a822f9bd217d016c6efd0f472f206f"
|
||||
integrity sha512-SDI9ShVKj8N3oOSinr8inaxD3FXgmgoJlqN35uU/Yx1sdoDeQbzAuBFox7bYjM+VhnZ1V22ivIDjAsKr00H+XQ==
|
||||
"@livekit/protocol@1.33.0", "@livekit/protocol@^1.33.0":
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/@livekit/protocol/-/protocol-1.33.0.tgz#b8dfcf82ca501e747533d519e51b5c80176bf03e"
|
||||
integrity sha512-361mBlFgI3nvn8oSQIL38gDUBGbOSwsEOqPgX0c1Jwz75/sD/TTvPeAM4zAz6OrV5Q4vI4Ruswecnyv5SG4oig==
|
||||
dependencies:
|
||||
"@bufbuild/protobuf" "^1.10.0"
|
||||
|
||||
@@ -6165,19 +6165,19 @@ lines-and-columns@^1.1.6:
|
||||
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||
|
||||
livekit-client@^2.5.7:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.8.1.tgz#2492587cde93ffb7bafcc3ce91f67e108fc63c4a"
|
||||
integrity sha512-HPv9iHNrnBANI9ucK7CKZspx0sBZK3hjR2EbwaV08+J3RM9+tNGL2ob2n76nxJLEZG7LzdWlLZdbr4fQBP6Hkg==
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.9.3.tgz#1f0e494561ba8790d214047d413e37db3aebdf16"
|
||||
integrity sha512-mDLgenjmGI3ga1qXsuonw8cTpQMUTm6T8tmjB/O+uzjsjI9UP0ebzjgrcOBn+IamjbulIdWWvtrt5A/6ftuH5A==
|
||||
dependencies:
|
||||
"@livekit/mutex" "1.1.1"
|
||||
"@livekit/protocol" "1.30.0"
|
||||
"@livekit/protocol" "1.33.0"
|
||||
events "^3.3.0"
|
||||
loglevel "^1.8.0"
|
||||
sdp-transform "^2.14.1"
|
||||
loglevel "^1.9.2"
|
||||
sdp-transform "^2.15.0"
|
||||
ts-debounce "^4.0.0"
|
||||
tslib "2.8.1"
|
||||
typed-emitter "^2.1.0"
|
||||
webrtc-adapter "^9.0.0"
|
||||
webrtc-adapter "^9.0.1"
|
||||
|
||||
locate-path@^5.0.0:
|
||||
version "5.0.0"
|
||||
@@ -6218,7 +6218,7 @@ loglevel@1.9.1:
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7"
|
||||
integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==
|
||||
|
||||
loglevel@^1.7.1, loglevel@^1.8.0, loglevel@^1.9.1:
|
||||
loglevel@^1.7.1, loglevel@^1.9.1, loglevel@^1.9.2:
|
||||
version "1.9.2"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08"
|
||||
integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==
|
||||
@@ -7626,13 +7626,20 @@ rxjs-report-usage@^1.0.4:
|
||||
glob "~7.2.0"
|
||||
prompts "~2.4.2"
|
||||
|
||||
rxjs@7.8.1, rxjs@^7.5.2, rxjs@^7.8.1:
|
||||
rxjs@7.8.1, rxjs@^7.8.1:
|
||||
version "7.8.1"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
|
||||
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
rxjs@^7.5.2:
|
||||
version "7.8.2"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b"
|
||||
integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-array-concat@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3"
|
||||
@@ -7701,7 +7708,7 @@ scheduler@^0.23.2:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
sdp-transform@^2.14.1:
|
||||
sdp-transform@^2.14.1, sdp-transform@^2.15.0:
|
||||
version "2.15.0"
|
||||
resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.15.0.tgz#79d37a2481916f36a0534e07b32ceaa87f71df42"
|
||||
integrity sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==
|
||||
@@ -8745,7 +8752,7 @@ webpack-virtual-modules@^0.6.2:
|
||||
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8"
|
||||
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
|
||||
|
||||
webrtc-adapter@^9.0.0:
|
||||
webrtc-adapter@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz#d4efa22ca9604cb2c8cdb9e492815ba37acfa0b2"
|
||||
integrity sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==
|
||||
|
||||
Reference in New Issue
Block a user