Remove even more dead code
This commit is contained in:
@@ -81,6 +81,7 @@ vi.mock("../rtcSessionHelpers", async (importOriginal) => {
|
|||||||
// TODO: perhaps there is a more elegant way to manage the type import here?
|
// TODO: perhaps there is a more elegant way to manage the type import here?
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||||
const orig = await importOriginal<typeof import("../rtcSessionHelpers")>();
|
const orig = await importOriginal<typeof import("../rtcSessionHelpers")>();
|
||||||
|
// TODO: leaveRTCSession no longer exists! Tests need adapting.
|
||||||
return { ...orig, enterRTCSession, leaveRTCSession };
|
return { ...orig, enterRTCSession, leaveRTCSession };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { vi, type Mocked, test, expect } from "vitest";
|
|
||||||
import { type RoomState } from "matrix-js-sdk";
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { EventType, type RoomState } from "matrix-js-sdk";
|
|
||||||
|
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
|
||||||
|
|
||||||
function isObject(x: unknown): x is Record<string, unknown> {
|
|
||||||
return typeof x === "object" && x !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the state of a room for multiple calls happening in parallel, sending
|
|
||||||
* the details to PostHog if that is indeed what's happening. (This is unwanted
|
|
||||||
* as it indicates a split-brain scenario.)
|
|
||||||
*/
|
|
||||||
export function checkForParallelCalls(state: RoomState): void {
|
|
||||||
const now = Date.now();
|
|
||||||
const participantsPerCall = new Map<string, number>();
|
|
||||||
|
|
||||||
// For each participant in each call, increment the participant count
|
|
||||||
for (const e of state.getStateEvents(EventType.GroupCallMemberPrefix)) {
|
|
||||||
const content = e.getContent<Record<string, unknown>>();
|
|
||||||
const calls: unknown[] = Array.isArray(content["m.calls"])
|
|
||||||
? content["m.calls"]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
for (const call of calls) {
|
|
||||||
if (isObject(call) && typeof call["m.call_id"] === "string") {
|
|
||||||
const devices: unknown[] = Array.isArray(call["m.devices"])
|
|
||||||
? call["m.devices"]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
for (const device of devices) {
|
|
||||||
if (isObject(device) && (device["expires_ts"] as number) > now) {
|
|
||||||
const participantCount =
|
|
||||||
participantsPerCall.get(call["m.call_id"]) ?? 0;
|
|
||||||
participantsPerCall.set(call["m.call_id"], participantCount + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (participantsPerCall.size > 1) {
|
|
||||||
PosthogAnalytics.instance.trackEvent({
|
|
||||||
eventName: "ParallelCalls",
|
|
||||||
participantsPerCall: Object.fromEntries(participantsPerCall),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,12 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { expect, onTestFinished, test, vi } from "vitest";
|
import { expect, test, vi } from "vitest";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
|
|
||||||
import { enterRTCSession, leaveRTCSession } from "../src/rtcSessionHelpers";
|
import { enterRTCSession } from "../src/rtcSessionHelpers";
|
||||||
import { mockConfig } from "./utils/test";
|
import { mockConfig } from "./utils/test";
|
||||||
import { ElementWidgetActions, widget } from "./widget";
|
|
||||||
|
|
||||||
const USE_MUTI_SFU = false;
|
const USE_MUTI_SFU = false;
|
||||||
const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
|
const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
|
||||||
@@ -116,47 +115,6 @@ test("It joins the correct Session", async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function testLeaveRTCSession(
|
|
||||||
cause: "user" | "error",
|
|
||||||
expectClose: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession;
|
|
||||||
await leaveRTCSession(session, cause);
|
|
||||||
expect(session.leaveRoomSession).toHaveBeenCalled();
|
|
||||||
expect(widget!.api.transport.send).toHaveBeenCalledWith(
|
|
||||||
ElementWidgetActions.HangupCall,
|
|
||||||
expect.anything(),
|
|
||||||
);
|
|
||||||
if (expectClose) {
|
|
||||||
expect(widget!.api.transport.send).toHaveBeenCalledWith(
|
|
||||||
ElementWidgetActions.Close,
|
|
||||||
expect.anything(),
|
|
||||||
);
|
|
||||||
expect(widget!.api.transport.stop).toHaveBeenCalled();
|
|
||||||
} else {
|
|
||||||
expect(widget!.api.transport.send).not.toHaveBeenCalledWith(
|
|
||||||
ElementWidgetActions.Close,
|
|
||||||
expect.anything(),
|
|
||||||
);
|
|
||||||
expect(widget!.api.transport.stop).not.toHaveBeenCalled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("leaveRTCSession closes the widget on a normal hangup", async () => {
|
|
||||||
await testLeaveRTCSession("user", true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("leaveRTCSession doesn't close the widget on a fatal error", async () => {
|
|
||||||
await testLeaveRTCSession("error", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("leaveRTCSession doesn't close the widget when returning to lobby", async () => {
|
|
||||||
getUrlParams.mockReturnValue({ returnToLobby: true });
|
|
||||||
onTestFinished(() => void getUrlParams.mockReset());
|
|
||||||
await testLeaveRTCSession("user", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("It should not fail with configuration error if homeserver config has livekit url but not fallback", async () => {
|
test("It should not fail with configuration error if homeserver config has livekit url but not fallback", async () => {
|
||||||
mockConfig({});
|
mockConfig({});
|
||||||
vi.spyOn(AutoDiscovery, "getRawClientConfig").mockResolvedValue({
|
vi.spyOn(AutoDiscovery, "getRawClientConfig").mockResolvedValue({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
|||||||
|
|
||||||
import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget";
|
import { ElementWidgetActions, widget } from "./widget";
|
||||||
import { MatrixRTCTransportMissingError } from "./utils/errors";
|
import { MatrixRTCTransportMissingError } from "./utils/errors";
|
||||||
import { getUrlParams } from "./UrlParams";
|
import { getUrlParams } from "./UrlParams";
|
||||||
import { getSFUConfigWithOpenID } from "./livekit/openIDSFU.ts";
|
import { getSFUConfigWithOpenID } from "./livekit/openIDSFU.ts";
|
||||||
@@ -159,49 +159,3 @@ export async function enterRTCSession(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const widgetPostHangupProcedure = async (
|
|
||||||
widget: WidgetHelpers,
|
|
||||||
cause: "user" | "error",
|
|
||||||
promiseBeforeHangup?: Promise<unknown>,
|
|
||||||
): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await widget.api.setAlwaysOnScreen(false);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Failed to set call widget `alwaysOnScreen` to false", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for any last bits before hanging up.
|
|
||||||
await promiseBeforeHangup;
|
|
||||||
// We send the hangup event after the memberships have been updated
|
|
||||||
// calling leaveRTCSession.
|
|
||||||
// We need to wait because this makes the client hosting this widget killing the IFrame.
|
|
||||||
try {
|
|
||||||
await widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Failed to send hangup action", e);
|
|
||||||
}
|
|
||||||
// On a normal user hangup we can shut down and close the widget. But if an
|
|
||||||
// error occurs we should keep the widget open until the user reads it.
|
|
||||||
if (cause === "user" && !getUrlParams().returnToLobby) {
|
|
||||||
try {
|
|
||||||
await widget.api.transport.send(ElementWidgetActions.Close, {});
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Failed to send close action", e);
|
|
||||||
}
|
|
||||||
widget.api.transport.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function leaveRTCSession(
|
|
||||||
rtcSession: MatrixRTCSession,
|
|
||||||
cause: "user" | "error",
|
|
||||||
promiseBeforeHangup?: Promise<unknown>,
|
|
||||||
): Promise<void> {
|
|
||||||
await rtcSession.leaveRoomSession();
|
|
||||||
if (widget) {
|
|
||||||
await widgetPostHangupProcedure(widget, cause, promiseBeforeHangup);
|
|
||||||
} else {
|
|
||||||
await promiseBeforeHangup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2025 New Vector Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { it, vi } from "vitest";
|
|
||||||
import { render, screen } from "@testing-library/react";
|
|
||||||
import { type ReactElement, useCallback } from "react";
|
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
import { BrowserRouter } from "react-router-dom";
|
|
||||||
|
|
||||||
import { GroupCallErrorBoundary } from "./room/GroupCallErrorBoundary";
|
|
||||||
import { useErrorBoundary } from "./useErrorBoundary";
|
|
||||||
import { ConnectionLostError } from "./utils/errors";
|
|
||||||
|
|
||||||
it("should show async error", async () => {
|
|
||||||
const user = userEvent.setup();
|
|
||||||
|
|
||||||
const TestComponent = (): ReactElement => {
|
|
||||||
const { showErrorBoundary } = useErrorBoundary();
|
|
||||||
|
|
||||||
const onClick = useCallback((): void => {
|
|
||||||
showErrorBoundary(new ConnectionLostError());
|
|
||||||
}, [showErrorBoundary]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>HELLO</h1>
|
|
||||||
<button onClick={onClick}>Click me</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render(
|
|
||||||
<BrowserRouter>
|
|
||||||
<GroupCallErrorBoundary widget={null} recoveryActionHandler={vi.fn()}>
|
|
||||||
<TestComponent />
|
|
||||||
</GroupCallErrorBoundary>
|
|
||||||
</BrowserRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.click(screen.getByRole("button", { name: "Click me" }));
|
|
||||||
|
|
||||||
await screen.findByText("Connection lost");
|
|
||||||
|
|
||||||
await user.click(screen.getByRole("button", { name: "Reconnect" }));
|
|
||||||
|
|
||||||
await screen.findByText("HELLO");
|
|
||||||
});
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
|
|
||||||
export type UseErrorBoundaryApi = {
|
|
||||||
showErrorBoundary: (error: Error) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useErrorBoundary(): UseErrorBoundaryApi {
|
|
||||||
const [error, setError] = useState<Error | null>(null);
|
|
||||||
|
|
||||||
const memoized: UseErrorBoundaryApi = useMemo(
|
|
||||||
() => ({
|
|
||||||
showErrorBoundary: (error: Error) => setError(error),
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return memoized;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user