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

@ -16,6 +16,8 @@ limitations under the License.
import { useState } from "react";
import { throttle } from "lodash";
import { Optional } from "matrix-events-sdk";
import { IMyDevice } from "matrix-js-sdk/src/client";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
@ -26,14 +28,16 @@ import WidgetStore, { IApp } from "../stores/WidgetStore";
import { WidgetType } from "../widgets/WidgetType";
import WidgetUtils from "./WidgetUtils";
export const VIDEO_CHANNEL = "io.element.video";
export const VIDEO_CHANNEL_MEMBER = "io.element.video.member";
const STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60;
export interface IVideoChannelMemberContent {
interface IVideoChannelMemberContent {
// Connected device IDs
devices: string[];
}
export const VIDEO_CHANNEL = "io.element.video";
export const VIDEO_CHANNEL_MEMBER = "io.element.video.member";
export const getVideoChannel = (roomId: string): IApp => {
const apps = WidgetStore.instance.getApps(roomId);
return apps.find(app => WidgetType.JITSI.matches(app.type) && app.id === VIDEO_CHANNEL);
@ -72,3 +76,54 @@ export const useConnectedMembers = (
}, throttleMs, { leading: true, trailing: true }));
return members;
};
const updateDevices = async (room: Optional<Room>, fn: (devices: string[] | null) => string[]) => {
if (room?.getMyMembership() !== "join") return;
const devicesState = room.currentState.getStateEvents(VIDEO_CHANNEL_MEMBER, room.client.getUserId());
const devices = devicesState?.getContent<IVideoChannelMemberContent>()?.devices ?? [];
const newDevices = fn(devices);
if (newDevices) {
await room.client.sendStateEvent(
room.roomId, VIDEO_CHANNEL_MEMBER, { devices: newDevices }, room.client.getUserId(),
);
}
};
export const addOurDevice = async (room: Room) => {
await updateDevices(room, devices => Array.from(new Set(devices).add(room.client.getDeviceId())));
};
export const removeOurDevice = async (room: Room) => {
await updateDevices(room, devices => {
const devicesSet = new Set(devices);
devicesSet.delete(room.client.getDeviceId());
return Array.from(devicesSet);
});
};
/**
* Fixes devices that may have gotten stuck in video channel member state after
* an unclean disconnection, by filtering out logged out devices, inactive
* devices, and our own device (if we're disconnected).
* @param {Room} room The room to fix
* @param {boolean} connectedLocalEcho Local echo of whether this device is connected
*/
export const fixStuckDevices = async (room: Room, connectedLocalEcho: boolean) => {
const now = new Date().valueOf();
const { devices: myDevices } = await room.client.getDevices();
const deviceMap = new Map<string, IMyDevice>(myDevices.map(d => [d.device_id, d]));
await updateDevices(room, devices => {
const newDevices = devices.filter(d => {
const device = deviceMap.get(d);
return device?.last_seen_ts
&& !(d === room.client.getDeviceId() && !connectedLocalEcho)
&& (now - device.last_seen_ts) < STUCK_DEVICE_TIMEOUT_MS;
});
// Skip the update if the devices are unchanged
return newDevices.length === devices.length ? null : newDevices;
});
};