You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-08-07 21:23:00 +03:00
Expire video member events after 1 hour (#8776)
* Expire video member events after 1 hour * Iterate based on feedback * Validate the types of video member events better * Even more parentheses
This commit is contained in:
@@ -95,7 +95,10 @@ describe("VideoRoomView", () => {
|
||||
|
||||
// All devices should have been removed
|
||||
expect(cli.sendStateEvent).toHaveBeenLastCalledWith(
|
||||
"!1:example.org", VIDEO_CHANNEL_MEMBER, { devices: [] }, cli.getUserId(),
|
||||
"!1:example.org",
|
||||
VIDEO_CHANNEL_MEMBER,
|
||||
{ devices: [], expires_ts: expect.any(Number) },
|
||||
cli.getUserId(),
|
||||
);
|
||||
});
|
||||
|
||||
|
@@ -30,6 +30,7 @@ import {
|
||||
stubVideoChannelStore,
|
||||
StubVideoChannelStore,
|
||||
} from "../../../test-utils";
|
||||
import { STUCK_DEVICE_TIMEOUT_MS } from "../../../../src/utils/VideoChannelUtils";
|
||||
import RoomTile from "../../../../src/components/views/rooms/RoomTile";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { DefaultTagID } from "../../../../src/stores/room-list/models";
|
||||
@@ -38,6 +39,18 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import PlatformPeg from "../../../../src/PlatformPeg";
|
||||
import BasePlatform from "../../../../src/BasePlatform";
|
||||
|
||||
const mockGetMember = (room: Room, getMembership: (userId: string) => string = () => "join") => {
|
||||
mocked(room).getMember.mockImplementation(userId => ({
|
||||
userId,
|
||||
membership: getMembership(userId),
|
||||
name: userId,
|
||||
rawDisplayName: userId,
|
||||
roomId: "!1:example.org",
|
||||
getAvatarUrl: () => {},
|
||||
getMxcAvatarUrl: () => {},
|
||||
}) as unknown as RoomMember);
|
||||
};
|
||||
|
||||
describe("RoomTile", () => {
|
||||
jest.spyOn(PlatformPeg, 'get')
|
||||
.mockReturnValue({ overrideBrowserShortcuts: () => false } as unknown as BasePlatform);
|
||||
@@ -59,7 +72,10 @@ describe("RoomTile", () => {
|
||||
DMRoomMap.makeShared();
|
||||
});
|
||||
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe("video rooms", () => {
|
||||
let room: Room;
|
||||
@@ -68,31 +84,34 @@ describe("RoomTile", () => {
|
||||
mocked(room.isElementVideoRoom).mockReturnValue(true);
|
||||
});
|
||||
|
||||
const mountTile = () => mount(
|
||||
<RoomTile
|
||||
room={room}
|
||||
showMessagePreview={false}
|
||||
isMinimized={false}
|
||||
tag={DefaultTagID.Untagged}
|
||||
/>,
|
||||
);
|
||||
|
||||
it("tracks connection state", () => {
|
||||
const tile = mount(
|
||||
<RoomTile
|
||||
room={room}
|
||||
showMessagePreview={false}
|
||||
isMinimized={false}
|
||||
tag={DefaultTagID.Untagged}
|
||||
/>,
|
||||
);
|
||||
expect(tile.find(".mx_RoomTile_videoIndicator").text()).toEqual("Video");
|
||||
const tile = mountTile();
|
||||
expect(tile.find(".mx_VideoRoomSummary_indicator").text()).toEqual("Video");
|
||||
|
||||
act(() => { store.startConnect("!1:example.org"); });
|
||||
tile.update();
|
||||
expect(tile.find(".mx_RoomTile_videoIndicator").text()).toEqual("Joining…");
|
||||
expect(tile.find(".mx_VideoRoomSummary_indicator").text()).toEqual("Joining…");
|
||||
|
||||
act(() => { store.connect("!1:example.org"); });
|
||||
tile.update();
|
||||
expect(tile.find(".mx_RoomTile_videoIndicator").text()).toEqual("Joined");
|
||||
expect(tile.find(".mx_VideoRoomSummary_indicator").text()).toEqual("Joined");
|
||||
|
||||
act(() => { store.disconnect(); });
|
||||
tile.update();
|
||||
expect(tile.find(".mx_RoomTile_videoIndicator").text()).toEqual("Video");
|
||||
expect(tile.find(".mx_VideoRoomSummary_indicator").text()).toEqual("Video");
|
||||
});
|
||||
|
||||
it("displays connected members", () => {
|
||||
mockGetMember(room, userId => userId === "@chris:example.org" ? "leave" : "join");
|
||||
mocked(room.currentState).getStateEvents.mockImplementation(mockStateEventImplementation([
|
||||
// A user connected from 2 devices
|
||||
mkVideoChannelMember("@alice:example.org", ["device 1", "device 2"]),
|
||||
@@ -102,56 +121,41 @@ describe("RoomTile", () => {
|
||||
mkVideoChannelMember("@chris:example.org", ["device 1"]),
|
||||
]));
|
||||
|
||||
mocked(room).getMember.mockImplementation(userId => ({
|
||||
userId,
|
||||
membership: userId === "@chris:example.org" ? "leave" : "join",
|
||||
name: userId,
|
||||
rawDisplayName: userId,
|
||||
roomId: "!1:example.org",
|
||||
getAvatarUrl: () => {},
|
||||
getMxcAvatarUrl: () => {},
|
||||
}) as unknown as RoomMember);
|
||||
|
||||
const tile = mount(
|
||||
<RoomTile
|
||||
room={room}
|
||||
showMessagePreview={false}
|
||||
isMinimized={false}
|
||||
tag={DefaultTagID.Untagged}
|
||||
/>,
|
||||
);
|
||||
const tile = mountTile();
|
||||
|
||||
// Only Alice should display as connected
|
||||
expect(tile.find(".mx_RoomTile_videoParticipants").text()).toEqual("1");
|
||||
expect(tile.find(".mx_VideoRoomSummary_participants").text()).toEqual("1");
|
||||
});
|
||||
|
||||
it("reflects local echo in connected members", () => {
|
||||
mockGetMember(room);
|
||||
mocked(room.currentState).getStateEvents.mockImplementation(mockStateEventImplementation([
|
||||
// Make the remote echo claim that we're connected, while leaving the store disconnected
|
||||
mkVideoChannelMember(cli.getUserId(), [cli.getDeviceId()]),
|
||||
]));
|
||||
|
||||
mocked(room).getMember.mockImplementation(userId => ({
|
||||
userId,
|
||||
membership: "join",
|
||||
name: userId,
|
||||
rawDisplayName: userId,
|
||||
roomId: "!1:example.org",
|
||||
getAvatarUrl: () => {},
|
||||
getMxcAvatarUrl: () => {},
|
||||
}) as unknown as RoomMember);
|
||||
|
||||
const tile = mount(
|
||||
<RoomTile
|
||||
room={room}
|
||||
showMessagePreview={false}
|
||||
isMinimized={false}
|
||||
tag={DefaultTagID.Untagged}
|
||||
/>,
|
||||
);
|
||||
const tile = mountTile();
|
||||
|
||||
// Because of our local echo, we should still appear as disconnected
|
||||
expect(tile.find(".mx_RoomTile_videoParticipants").exists()).toEqual(false);
|
||||
expect(tile.find(".mx_VideoRoomSummary_participants").exists()).toEqual(false);
|
||||
});
|
||||
|
||||
it("doesn't count members whose device data has expired", () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
|
||||
mockGetMember(room);
|
||||
mocked(room.currentState).getStateEvents.mockImplementation(mockStateEventImplementation([
|
||||
mkVideoChannelMember("@alice:example.org", ["device 1"], STUCK_DEVICE_TIMEOUT_MS),
|
||||
]));
|
||||
|
||||
const tile = mountTile();
|
||||
|
||||
expect(tile.find(".mx_VideoRoomSummary_participants").text()).toEqual("1");
|
||||
// Expire Alice's device data
|
||||
act(() => { jest.advanceTimersByTime(STUCK_DEVICE_TIMEOUT_MS); });
|
||||
tile.update();
|
||||
expect(tile.find(".mx_VideoRoomSummary_participants").exists()).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -16,12 +16,14 @@ limitations under the License.
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { Widget, ClientWidgetApi, MatrixWidgetType, IWidgetApiRequest } from "matrix-widget-api";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
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";
|
||||
import { ElementWidgetActions } from "../../src/stores/widgets/ElementWidgetActions";
|
||||
import { VIDEO_CHANNEL_MEMBER, STUCK_DEVICE_TIMEOUT_MS } from "../../src/utils/VideoChannelUtils";
|
||||
import VideoChannelStore, { VideoChannelEvent } from "../../src/stores/VideoChannelStore";
|
||||
|
||||
describe("VideoChannelStore", () => {
|
||||
@@ -46,9 +48,10 @@ describe("VideoChannelStore", () => {
|
||||
let onMock: (action: string, listener: (ev: CustomEvent<IWidgetApiRequest>) => void) => void;
|
||||
let onceMock: (action: string, listener: (ev: CustomEvent<IWidgetApiRequest>) => void) => void;
|
||||
let messaging: ClientWidgetApi;
|
||||
let cli: MatrixClient;
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli = MatrixClientPeg.get();
|
||||
setupAsyncStoreWithClient(WidgetMessagingStore.instance, cli);
|
||||
setupAsyncStoreWithClient(store, cli);
|
||||
mocked(cli).getRoom.mockReturnValue(mkRoom(cli, "!1:example.org"));
|
||||
@@ -72,6 +75,8 @@ describe("VideoChannelStore", () => {
|
||||
} as unknown as ClientWidgetApi;
|
||||
});
|
||||
|
||||
afterEach(() => jest.useRealTimers());
|
||||
|
||||
const widgetReady = () => {
|
||||
// Tell the WidgetStore that the widget is ready
|
||||
const [, ready] = mocked(onceMock).mock.calls.find(([action]) =>
|
||||
@@ -109,6 +114,9 @@ describe("VideoChannelStore", () => {
|
||||
};
|
||||
|
||||
it("connects and disconnects", async () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, "!1:example.org", messaging);
|
||||
widgetReady();
|
||||
expect(store.roomId).toBeFalsy();
|
||||
@@ -120,12 +128,40 @@ describe("VideoChannelStore", () => {
|
||||
expect(store.roomId).toEqual("!1:example.org");
|
||||
expect(store.connected).toEqual(true);
|
||||
|
||||
// Our device should now appear as connected
|
||||
expect(cli.sendStateEvent).toHaveBeenLastCalledWith(
|
||||
"!1:example.org",
|
||||
VIDEO_CHANNEL_MEMBER,
|
||||
{ devices: [cli.getDeviceId()], expires_ts: expect.any(Number) },
|
||||
cli.getUserId(),
|
||||
);
|
||||
mocked(cli).sendStateEvent.mockClear();
|
||||
|
||||
// Our devices should be resent within the timeout period to prevent
|
||||
// the data from becoming stale
|
||||
jest.advanceTimersByTime(STUCK_DEVICE_TIMEOUT_MS);
|
||||
expect(cli.sendStateEvent).toHaveBeenLastCalledWith(
|
||||
"!1:example.org",
|
||||
VIDEO_CHANNEL_MEMBER,
|
||||
{ devices: [cli.getDeviceId()], expires_ts: expect.any(Number) },
|
||||
cli.getUserId(),
|
||||
);
|
||||
mocked(cli).sendStateEvent.mockClear();
|
||||
|
||||
const disconnectPromise = store.disconnect();
|
||||
await confirmDisconnect();
|
||||
await expect(disconnectPromise).resolves.toBeUndefined();
|
||||
expect(store.roomId).toBeFalsy();
|
||||
expect(store.connected).toEqual(false);
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, "!1:example.org");
|
||||
|
||||
// Our device should now be marked as disconnected
|
||||
expect(cli.sendStateEvent).toHaveBeenLastCalledWith(
|
||||
"!1:example.org",
|
||||
VIDEO_CHANNEL_MEMBER,
|
||||
{ devices: [], expires_ts: expect.any(Number) },
|
||||
cli.getUserId(),
|
||||
);
|
||||
});
|
||||
|
||||
it("waits for messaging when connecting", async () => {
|
||||
|
@@ -18,7 +18,7 @@ import { EventEmitter } from "events";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import { mkEvent } from "./test-utils";
|
||||
import { VIDEO_CHANNEL_MEMBER } from "../../src/utils/VideoChannelUtils";
|
||||
import { VIDEO_CHANNEL_MEMBER, STUCK_DEVICE_TIMEOUT_MS } from "../../src/utils/VideoChannelUtils";
|
||||
import VideoChannelStore, { VideoChannelEvent, IJitsiParticipant } from "../../src/stores/VideoChannelStore";
|
||||
|
||||
export class StubVideoChannelStore extends EventEmitter {
|
||||
@@ -52,11 +52,14 @@ export const stubVideoChannelStore = (): StubVideoChannelStore => {
|
||||
return store;
|
||||
};
|
||||
|
||||
export const mkVideoChannelMember = (userId: string, devices: string[]): MatrixEvent => mkEvent({
|
||||
export const mkVideoChannelMember = (userId: string, devices: string[], expiresAt?: number): MatrixEvent => mkEvent({
|
||||
event: true,
|
||||
type: VIDEO_CHANNEL_MEMBER,
|
||||
room: "!1:example.org",
|
||||
user: userId,
|
||||
skey: userId,
|
||||
content: { devices },
|
||||
content: {
|
||||
devices,
|
||||
expires_ts: expiresAt == null ? Date.now() + STUCK_DEVICE_TIMEOUT_MS : expiresAt,
|
||||
},
|
||||
});
|
||||
|
Reference in New Issue
Block a user