Prevent multiple Jitsi calls started at the same time (#10183)
This commit is contained in:
parent
b9ff6558e9
commit
d7b4740a7e
2 changed files with 150 additions and 1 deletions
|
@ -114,8 +114,11 @@ import { RoomSearchView } from "./RoomSearchView";
|
||||||
import eventSearch from "../../Searching";
|
import eventSearch from "../../Searching";
|
||||||
import VoipUserMapper from "../../VoipUserMapper";
|
import VoipUserMapper from "../../VoipUserMapper";
|
||||||
import { isCallEvent } from "./LegacyCallEventGrouper";
|
import { isCallEvent } from "./LegacyCallEventGrouper";
|
||||||
|
import { WidgetType } from "../../widgets/WidgetType";
|
||||||
|
import WidgetUtils from "../../utils/WidgetUtils";
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
|
const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
|
||||||
let debuglog = function (msg: string): void {};
|
let debuglog = function (msg: string): void {};
|
||||||
|
|
||||||
const BROWSER_SUPPORTS_SANDBOX = "sandbox" in document.createElement("iframe");
|
const BROWSER_SUPPORTS_SANDBOX = "sandbox" in document.createElement("iframe");
|
||||||
|
@ -483,6 +486,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
private onWidgetStoreUpdate = (): void => {
|
private onWidgetStoreUpdate = (): void => {
|
||||||
if (!this.state.room) return;
|
if (!this.state.room) return;
|
||||||
this.checkWidgets(this.state.room);
|
this.checkWidgets(this.state.room);
|
||||||
|
this.doMaybeRemoveOwnJitsiWidget();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onWidgetEchoStoreUpdate = (): void => {
|
private onWidgetEchoStoreUpdate = (): void => {
|
||||||
|
@ -503,6 +507,56 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
this.checkWidgets(this.state.room);
|
this.checkWidgets(this.state.room);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the Jitsi widget from the current user if
|
||||||
|
* - Multiple Jitsi widgets have been added within {@link PREVENT_MULTIPLE_JITSI_WITHIN}
|
||||||
|
* - The last (server timestamp) of these widgets is from the currrent user
|
||||||
|
* This solves the issue if some people decide to start a conference and click the call button at the same time.
|
||||||
|
*/
|
||||||
|
private doMaybeRemoveOwnJitsiWidget(): void {
|
||||||
|
if (!this.state.roomId || !this.state.room || !this.context.client) return;
|
||||||
|
|
||||||
|
const apps = this.context.widgetStore.getApps(this.state.roomId);
|
||||||
|
const jitsiApps = apps.filter((app) => app.eventId && WidgetType.JITSI.matches(app.type));
|
||||||
|
|
||||||
|
// less than two Jitsi widgets → nothing to do
|
||||||
|
if (jitsiApps.length < 2) return;
|
||||||
|
|
||||||
|
const currentUserId = this.context.client.getSafeUserId();
|
||||||
|
const createdByCurrentUser = jitsiApps.find((apps) => apps.creatorUserId === currentUserId);
|
||||||
|
|
||||||
|
// no Jitsi widget from current user → nothing to do
|
||||||
|
if (!createdByCurrentUser) return;
|
||||||
|
|
||||||
|
const createdByCurrentUserEvent = this.state.room.findEventById(createdByCurrentUser.eventId!);
|
||||||
|
|
||||||
|
// widget event not found → nothing can be done
|
||||||
|
if (!createdByCurrentUserEvent) return;
|
||||||
|
|
||||||
|
const createdByCurrentUserTs = createdByCurrentUserEvent.getTs();
|
||||||
|
|
||||||
|
// widget timestamp is empty → nothing can be done
|
||||||
|
if (!createdByCurrentUserTs) return;
|
||||||
|
|
||||||
|
const lastCreatedByOtherTs = jitsiApps.reduce((maxByNow: number, app) => {
|
||||||
|
if (app.eventId === createdByCurrentUser.eventId) return maxByNow;
|
||||||
|
|
||||||
|
const appCreateTs = this.state.room!.findEventById(app.eventId!)?.getTs() || 0;
|
||||||
|
return Math.max(maxByNow, appCreateTs);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// last widget timestamp from other is empty → nothing can be done
|
||||||
|
if (!lastCreatedByOtherTs) return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
createdByCurrentUserTs > lastCreatedByOtherTs &&
|
||||||
|
createdByCurrentUserTs - lastCreatedByOtherTs < PREVENT_MULTIPLE_JITSI_WITHIN
|
||||||
|
) {
|
||||||
|
// more than one Jitsi widget with the last one from the current user → remove it
|
||||||
|
WidgetUtils.setRoomWidget(this.state.roomId, createdByCurrentUser.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private checkWidgets = (room: Room): void => {
|
private checkWidgets = (room: Room): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
hasPinnedWidgets: this.context.widgetLayoutStore.hasPinnedWidgets(room),
|
hasPinnedWidgets: this.context.widgetLayoutStore.hasPinnedWidgets(room),
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { mocked, MockedObject } from "jest-mock";
|
||||||
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { EventType } from "matrix-js-sdk/src/matrix";
|
import { EventType, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||||
import { MEGOLM_ALGORITHM } from "matrix-js-sdk/src/crypto/olmlib";
|
import { MEGOLM_ALGORITHM } from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
import { fireEvent, render } from "@testing-library/react";
|
import { fireEvent, render } from "@testing-library/react";
|
||||||
|
|
||||||
|
@ -31,6 +31,9 @@ import {
|
||||||
unmockPlatformPeg,
|
unmockPlatformPeg,
|
||||||
wrapInMatrixClientContext,
|
wrapInMatrixClientContext,
|
||||||
flushPromises,
|
flushPromises,
|
||||||
|
mkEvent,
|
||||||
|
setupAsyncStoreWithClient,
|
||||||
|
filterConsole,
|
||||||
} from "../../test-utils";
|
} from "../../test-utils";
|
||||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||||
import { Action } from "../../../src/dispatcher/actions";
|
import { Action } from "../../../src/dispatcher/actions";
|
||||||
|
@ -49,6 +52,9 @@ import { createDmLocalRoom } from "../../../src/utils/dm/createDmLocalRoom";
|
||||||
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
|
||||||
import { SdkContextClass, SDKContext } from "../../../src/contexts/SDKContext";
|
import { SdkContextClass, SDKContext } from "../../../src/contexts/SDKContext";
|
||||||
import VoipUserMapper from "../../../src/VoipUserMapper";
|
import VoipUserMapper from "../../../src/VoipUserMapper";
|
||||||
|
import WidgetUtils from "../../../src/utils/WidgetUtils";
|
||||||
|
import { WidgetType } from "../../../src/widgets/WidgetType";
|
||||||
|
import WidgetStore from "../../../src/stores/WidgetStore";
|
||||||
|
|
||||||
const RoomView = wrapInMatrixClientContext(_RoomView);
|
const RoomView = wrapInMatrixClientContext(_RoomView);
|
||||||
|
|
||||||
|
@ -59,6 +65,9 @@ describe("RoomView", () => {
|
||||||
let roomCount = 0;
|
let roomCount = 0;
|
||||||
let stores: SdkContextClass;
|
let stores: SdkContextClass;
|
||||||
|
|
||||||
|
// mute some noise
|
||||||
|
filterConsole("RVS update", "does not have an m.room.create event", "Current version: 1", "Version capability");
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockPlatformPeg({ reload: () => {} });
|
mockPlatformPeg({ reload: () => {} });
|
||||||
stubClient();
|
stubClient();
|
||||||
|
@ -359,4 +368,90 @@ describe("RoomView", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when there is a RoomView", () => {
|
||||||
|
const widget1Id = "widget1";
|
||||||
|
const widget2Id = "widget2";
|
||||||
|
const otherUserId = "@other:example.com";
|
||||||
|
|
||||||
|
const addJitsiWidget = async (id: string, user: string, ts?: number): Promise<void> => {
|
||||||
|
const widgetEvent = mkEvent({
|
||||||
|
event: true,
|
||||||
|
room: room.roomId,
|
||||||
|
user,
|
||||||
|
type: "im.vector.modular.widgets",
|
||||||
|
content: {
|
||||||
|
id,
|
||||||
|
name: "Jitsi",
|
||||||
|
type: WidgetType.JITSI.preferred,
|
||||||
|
url: "https://example.com",
|
||||||
|
},
|
||||||
|
skey: id,
|
||||||
|
ts,
|
||||||
|
});
|
||||||
|
room.addLiveEvents([widgetEvent]);
|
||||||
|
room.currentState.setStateEvents([widgetEvent]);
|
||||||
|
cli.emit(RoomStateEvent.Events, widgetEvent, room.currentState, null);
|
||||||
|
await flushPromises();
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.spyOn(WidgetUtils, "setRoomWidget");
|
||||||
|
const widgetStore = WidgetStore.instance;
|
||||||
|
await setupAsyncStoreWithClient(widgetStore, cli);
|
||||||
|
getRoomViewInstance();
|
||||||
|
});
|
||||||
|
|
||||||
|
const itShouldNotRemoveTheLastWidget = (): void => {
|
||||||
|
it("should not remove the last widget", (): void => {
|
||||||
|
expect(WidgetUtils.setRoomWidget).not.toHaveBeenCalledWith(room.roomId, widget2Id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("and there is a Jitsi widget from another user", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await addJitsiWidget(widget1Id, otherUserId, 10_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and the current user adds a Jitsi widget after 10s", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await addJitsiWidget(widget2Id, cli.getSafeUserId(), 20_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("the last Jitsi widget should be removed", () => {
|
||||||
|
expect(WidgetUtils.setRoomWidget).toHaveBeenCalledWith(room.roomId, widget2Id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and the current user adds a Jitsi widget after two minutes", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await addJitsiWidget(widget2Id, cli.getSafeUserId(), 130_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
itShouldNotRemoveTheLastWidget();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and the current user adds a Jitsi widget without timestamp", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await addJitsiWidget(widget2Id, cli.getSafeUserId());
|
||||||
|
});
|
||||||
|
|
||||||
|
itShouldNotRemoveTheLastWidget();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and there is a Jitsi widget from another user without timestamp", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await addJitsiWidget(widget1Id, otherUserId);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and the current user adds a Jitsi widget", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await addJitsiWidget(widget2Id, cli.getSafeUserId(), 10_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
itShouldNotRemoveTheLastWidget();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue