Proactively fix stuck devices in video rooms (#8587)

* Proactively fix stuck devices in video rooms

* Fix tests

* Explain why we're disabling the lint rule

* Apply code review suggestions

* Back VideoChannelStore's flags by SettingsStore instead of localStorage
This commit is contained in:
Robin 2022-05-16 16:54:08 -04:00 committed by GitHub
parent 6f851108be
commit ceda77d7dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 149 additions and 49 deletions

View file

@ -17,7 +17,8 @@ limitations under the License.
import React from "react";
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { mocked } from "jest-mock";
import { MatrixClient, IMyDevice } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixWidgetType } from "matrix-widget-api";
@ -27,9 +28,11 @@ import {
StubVideoChannelStore,
mkRoom,
wrapInMatrixClientContext,
mockStateEventImplementation,
mkVideoChannelMember,
} from "../../test-utils";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { VIDEO_CHANNEL } from "../../../src/utils/VideoChannelUtils";
import { VIDEO_CHANNEL, VIDEO_CHANNEL_MEMBER } from "../../../src/utils/VideoChannelUtils";
import WidgetStore from "../../../src/stores/WidgetStore";
import _VideoRoomView from "../../../src/components/structures/VideoRoomView";
import VideoLobby from "../../../src/components/views/voip/VideoLobby";
@ -64,6 +67,37 @@ describe("VideoRoomView", () => {
room = mkRoom(cli, "!1:example.org");
});
it("removes stuck devices on mount", async () => {
// Simulate an unclean disconnect
store.roomId = "!1:example.org";
const devices: IMyDevice[] = [
{
device_id: cli.getDeviceId(),
last_seen_ts: new Date().valueOf(),
},
{
device_id: "went offline 2 hours ago",
last_seen_ts: new Date().valueOf() - 1000 * 60 * 60 * 2,
},
];
mocked(cli).getDevices.mockResolvedValue({ devices });
// Make both devices be stuck
mocked(room.currentState).getStateEvents.mockImplementation(mockStateEventImplementation([
mkVideoChannelMember(cli.getUserId(), devices.map(d => d.device_id)),
]));
mount(<VideoRoomView room={room} resizing={false} />);
// Wait for state to settle
await act(() => Promise.resolve());
// All devices should have been removed
expect(cli.sendStateEvent).toHaveBeenLastCalledWith(
"!1:example.org", VIDEO_CHANNEL_MEMBER, { devices: [] }, cli.getUserId(),
);
});
it("shows lobby and keeps widget loaded when disconnected", async () => {
const view = mount(<VideoRoomView room={room} resizing={false} />);
// Wait for state to settle

View file

@ -17,7 +17,7 @@ limitations under the License.
import { mocked } from "jest-mock";
import { Widget, ClientWidgetApi, MatrixWidgetType, IWidgetApiRequest } from "matrix-widget-api";
import { stubClient, setupAsyncStoreWithClient } from "../test-utils";
import { stubClient, setupAsyncStoreWithClient, mkRoom } from "../test-utils";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import WidgetStore, { IApp } from "../../src/stores/WidgetStore";
import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingStore";
@ -51,6 +51,7 @@ describe("VideoChannelStore", () => {
const cli = MatrixClientPeg.get();
setupAsyncStoreWithClient(WidgetMessagingStore.instance, cli);
setupAsyncStoreWithClient(store, cli);
mocked(cli).getRoom.mockReturnValue(mkRoom(cli, "!1:example.org"));
let resolveMessageSent: () => void;
messageSent = new Promise(resolve => resolveMessageSent = resolve);

View file

@ -79,6 +79,7 @@ export function createTestClient(): MatrixClient {
getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"),
getUser: jest.fn().mockReturnValue({ on: jest.fn() }),
getDeviceId: jest.fn().mockReturnValue("ABCDEFGHI"),
getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
credentials: { userId: "@userId:matrix.rog" },
getPushActionsForEvent: jest.fn(),

View file

@ -22,24 +22,25 @@ import { VIDEO_CHANNEL_MEMBER } from "../../src/utils/VideoChannelUtils";
import VideoChannelStore, { VideoChannelEvent, IJitsiParticipant } from "../../src/stores/VideoChannelStore";
export class StubVideoChannelStore extends EventEmitter {
private _roomId: string;
public get roomId(): string { return this._roomId; }
private _roomId: string | null;
public get roomId(): string | null { return this._roomId; }
public set roomId(value: string | null) { this._roomId = value; }
private _connected: boolean;
public get connected(): boolean { return this._connected; }
public get participants(): IJitsiParticipant[] { return []; }
public startConnect = (roomId: string) => {
this._roomId = roomId;
this.roomId = roomId;
this.emit(VideoChannelEvent.StartConnect, roomId);
};
public connect = jest.fn((roomId: string) => {
this._roomId = roomId;
this.roomId = roomId;
this._connected = true;
this.emit(VideoChannelEvent.Connect, roomId);
});
public disconnect = jest.fn(() => {
const roomId = this._roomId;
this._roomId = null;
this.roomId = null;
this._connected = false;
this.emit(VideoChannelEvent.Disconnect, roomId);
});