You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-23 17:02:25 +03:00
emit aggregate room beacon liveness (#2241)
* emit aggregate room beacon liveness Signed-off-by: Kerry Archibald <kerrya@element.io> * tidy and comment Signed-off-by: Kerry Archibald <kerrya@element.io> * add export for models/beacon Signed-off-by: Kerry Archibald <kerrya@element.io> * add owner and roomId Signed-off-by: Kerry Archibald <kerrya@element.io> * copyright Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
28
spec/test-utils/emitter.ts
Normal file
28
spec/test-utils/emitter.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter emitter.emit mock calls to find relevant events
|
||||||
|
* eg:
|
||||||
|
* ```
|
||||||
|
* const emitSpy = jest.spyOn(state, 'emit');
|
||||||
|
* << actions >>
|
||||||
|
* const beaconLivenessEmits = emitCallsByEventType(BeaconEvent.New, emitSpy);
|
||||||
|
* expect(beaconLivenessEmits.length).toBe(1);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance<any, unknown[]>) =>
|
||||||
|
spy.mock.calls.filter((args) => args[0] === eventType);
|
||||||
@@ -24,6 +24,8 @@ import {
|
|||||||
} from "../../../src/models/beacon";
|
} from "../../../src/models/beacon";
|
||||||
import { makeBeaconInfoEvent } from "../../test-utils/beacon";
|
import { makeBeaconInfoEvent } from "../../test-utils/beacon";
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
describe('Beacon', () => {
|
describe('Beacon', () => {
|
||||||
describe('isTimestampInDuration()', () => {
|
describe('isTimestampInDuration()', () => {
|
||||||
const startTs = new Date('2022-03-11T12:07:47.592Z').getTime();
|
const startTs = new Date('2022-03-11T12:07:47.592Z').getTime();
|
||||||
@@ -86,6 +88,14 @@ describe('Beacon', () => {
|
|||||||
// without timeout of 3 hours
|
// without timeout of 3 hours
|
||||||
let liveBeaconEvent;
|
let liveBeaconEvent;
|
||||||
let notLiveBeaconEvent;
|
let notLiveBeaconEvent;
|
||||||
|
|
||||||
|
const advanceDateAndTime = (ms: number) => {
|
||||||
|
// bc liveness check uses Date.now we have to advance this mock
|
||||||
|
jest.spyOn(global.Date, 'now').mockReturnValue(now + ms);
|
||||||
|
// then advance time for the interval by the same amount
|
||||||
|
jest.advanceTimersByTime(ms);
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// go back in time to create the beacon
|
// go back in time to create the beacon
|
||||||
jest.spyOn(global.Date, 'now').mockReturnValue(now - HOUR_MS);
|
jest.spyOn(global.Date, 'now').mockReturnValue(now - HOUR_MS);
|
||||||
@@ -109,7 +119,9 @@ describe('Beacon', () => {
|
|||||||
const beacon = new Beacon(liveBeaconEvent);
|
const beacon = new Beacon(liveBeaconEvent);
|
||||||
|
|
||||||
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
|
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
|
||||||
|
expect(beacon.roomId).toEqual(roomId);
|
||||||
expect(beacon.isLive).toEqual(true);
|
expect(beacon.isLive).toEqual(true);
|
||||||
|
expect(beacon.beaconInfoOwner).toEqual(userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isLive()', () => {
|
describe('isLive()', () => {
|
||||||
@@ -163,6 +175,69 @@ describe('Beacon', () => {
|
|||||||
expect(beacon.isLive).toEqual(false);
|
expect(beacon.isLive).toEqual(false);
|
||||||
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.Update, updatedBeaconEvent, beacon);
|
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.Update, updatedBeaconEvent, beacon);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('emits livenesschange event when beacon liveness changes', () => {
|
||||||
|
const beacon = new Beacon(liveBeaconEvent);
|
||||||
|
const emitSpy = jest.spyOn(beacon, 'emit');
|
||||||
|
|
||||||
|
expect(beacon.isLive).toEqual(true);
|
||||||
|
|
||||||
|
const updatedBeaconEvent = makeBeaconInfoEvent(
|
||||||
|
userId, roomId, { timeout: HOUR_MS * 3, isLive: false }, beacon.beaconInfoId);
|
||||||
|
|
||||||
|
beacon.update(updatedBeaconEvent);
|
||||||
|
expect(beacon.isLive).toEqual(false);
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LivenessChange, false, beacon);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('monitorLiveness()', () => {
|
||||||
|
it('does not set a monitor interval when beacon is not live', () => {
|
||||||
|
// beacon was created an hour ago
|
||||||
|
// and has a 3hr duration
|
||||||
|
const beacon = new Beacon(notLiveBeaconEvent);
|
||||||
|
const emitSpy = jest.spyOn(beacon, 'emit');
|
||||||
|
|
||||||
|
beacon.monitorLiveness();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
expect(beacon.livenessWatchInterval).toBeFalsy();
|
||||||
|
advanceDateAndTime(HOUR_MS * 2 + 1);
|
||||||
|
|
||||||
|
// no emit
|
||||||
|
expect(emitSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks liveness of beacon at expected expiry time', () => {
|
||||||
|
// live beacon was created an hour ago
|
||||||
|
// and has a 3hr duration
|
||||||
|
const beacon = new Beacon(liveBeaconEvent);
|
||||||
|
expect(beacon.isLive).toBeTruthy();
|
||||||
|
const emitSpy = jest.spyOn(beacon, 'emit');
|
||||||
|
|
||||||
|
beacon.monitorLiveness();
|
||||||
|
advanceDateAndTime(HOUR_MS * 2 + 1);
|
||||||
|
|
||||||
|
expect(emitSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LivenessChange, false, beacon);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('destroy kills liveness monitor', () => {
|
||||||
|
// live beacon was created an hour ago
|
||||||
|
// and has a 3hr duration
|
||||||
|
const beacon = new Beacon(liveBeaconEvent);
|
||||||
|
expect(beacon.isLive).toBeTruthy();
|
||||||
|
const emitSpy = jest.spyOn(beacon, 'emit');
|
||||||
|
|
||||||
|
beacon.monitorLiveness();
|
||||||
|
|
||||||
|
// destroy the beacon
|
||||||
|
beacon.destroy();
|
||||||
|
|
||||||
|
advanceDateAndTime(HOUR_MS * 2 + 1);
|
||||||
|
|
||||||
|
expect(emitSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
import { makeBeaconInfoEvent } from "../test-utils/beacon";
|
import { makeBeaconInfoEvent } from "../test-utils/beacon";
|
||||||
import { RoomState } from "../../src/models/room-state";
|
import { filterEmitCallsByEventType } from "../test-utils/emitter";
|
||||||
|
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
|
||||||
|
|
||||||
describe("RoomState", function() {
|
describe("RoomState", function() {
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
@@ -275,6 +276,29 @@ describe("RoomState", function() {
|
|||||||
// updated liveness
|
// updated liveness
|
||||||
expect(state.beacons.get(beaconId).isLive).toEqual(false);
|
expect(state.beacons.get(beaconId).isLive).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('updates live beacon ids once after setting state events', () => {
|
||||||
|
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
|
||||||
|
const deadBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, '$beacon2');
|
||||||
|
|
||||||
|
const emitSpy = jest.spyOn(state, 'emit');
|
||||||
|
|
||||||
|
state.setStateEvents([liveBeaconEvent, deadBeaconEvent]);
|
||||||
|
|
||||||
|
// called once
|
||||||
|
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);
|
||||||
|
|
||||||
|
// live beacon is now not live
|
||||||
|
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
|
||||||
|
userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
|
||||||
|
);
|
||||||
|
|
||||||
|
state.setStateEvents([updatedLiveBeaconEvent]);
|
||||||
|
|
||||||
|
expect(state.hasLiveBeacons).toBe(false);
|
||||||
|
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(2);
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setOutOfBandMembers", function() {
|
describe("setOutOfBandMembers", function() {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export * from "./http-api";
|
|||||||
export * from "./autodiscovery";
|
export * from "./autodiscovery";
|
||||||
export * from "./sync-accumulator";
|
export * from "./sync-accumulator";
|
||||||
export * from "./errors";
|
export * from "./errors";
|
||||||
|
export * from "./models/beacon";
|
||||||
export * from "./models/event";
|
export * from "./models/event";
|
||||||
export * from "./models/room";
|
export * from "./models/room";
|
||||||
export * from "./models/group";
|
export * from "./models/group";
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ import { TypedEventEmitter } from "./typed-event-emitter";
|
|||||||
export enum BeaconEvent {
|
export enum BeaconEvent {
|
||||||
New = "Beacon.new",
|
New = "Beacon.new",
|
||||||
Update = "Beacon.update",
|
Update = "Beacon.update",
|
||||||
|
LivenessChange = "Beacon.LivenessChange",
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmittedEvents = BeaconEvent.New | BeaconEvent.Update;
|
type EmittedEvents = BeaconEvent;
|
||||||
type EventHandlerMap = {
|
type EventHandlerMap = {
|
||||||
[BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;
|
[BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;
|
||||||
[BeaconEvent.Update]: (event: MatrixEvent, beacon: Beacon) => void;
|
[BeaconEvent.Update]: (event: MatrixEvent, beacon: Beacon) => void;
|
||||||
|
[BeaconEvent.LivenessChange]: (isLive: boolean, beacon: Beacon) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isTimestampInDuration = (
|
export const isTimestampInDuration = (
|
||||||
@@ -42,32 +44,77 @@ export const isBeaconInfoEventType = (type: string) =>
|
|||||||
|
|
||||||
// https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
// https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
||||||
export class Beacon extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
export class Beacon extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||||
|
public readonly roomId: string;
|
||||||
private beaconInfo: BeaconInfoState;
|
private beaconInfo: BeaconInfoState;
|
||||||
|
private _isLive: boolean;
|
||||||
|
private livenessWatchInterval: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private rootEvent: MatrixEvent,
|
private rootEvent: MatrixEvent,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.beaconInfo = parseBeaconInfoContent(this.rootEvent.getContent());
|
this.setBeaconInfo(this.rootEvent);
|
||||||
|
this.roomId = this.rootEvent.getRoomId();
|
||||||
this.emit(BeaconEvent.New, this.rootEvent, this);
|
this.emit(BeaconEvent.New, this.rootEvent, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isLive(): boolean {
|
public get isLive(): boolean {
|
||||||
return this.beaconInfo?.live &&
|
return this._isLive;
|
||||||
isTimestampInDuration(this.beaconInfo?.timestamp, this.beaconInfo?.timeout, Date.now());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get beaconInfoId(): string {
|
public get beaconInfoId(): string {
|
||||||
return this.rootEvent.getId();
|
return this.rootEvent.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get beaconInfoOwner(): string {
|
||||||
|
return this.rootEvent.getStateKey();
|
||||||
|
}
|
||||||
|
|
||||||
public update(beaconInfoEvent: MatrixEvent): void {
|
public update(beaconInfoEvent: MatrixEvent): void {
|
||||||
if (beaconInfoEvent.getId() !== this.beaconInfoId) {
|
if (beaconInfoEvent.getId() !== this.beaconInfoId) {
|
||||||
throw new Error('Invalid updating event');
|
throw new Error('Invalid updating event');
|
||||||
}
|
}
|
||||||
this.rootEvent = beaconInfoEvent;
|
this.rootEvent = beaconInfoEvent;
|
||||||
this.beaconInfo = parseBeaconInfoContent(this.rootEvent.getContent());
|
this.setBeaconInfo(this.rootEvent);
|
||||||
|
|
||||||
this.emit(BeaconEvent.Update, beaconInfoEvent, this);
|
this.emit(BeaconEvent.Update, beaconInfoEvent, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
if (this.livenessWatchInterval) {
|
||||||
|
clearInterval(this.livenessWatchInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitor liveness of a beacon
|
||||||
|
* Emits BeaconEvent.LivenessChange when beacon expires
|
||||||
|
*/
|
||||||
|
public monitorLiveness(): void {
|
||||||
|
if (this.livenessWatchInterval) {
|
||||||
|
clearInterval(this.livenessWatchInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isLive) {
|
||||||
|
const expiryInMs = (this.beaconInfo?.timestamp + this.beaconInfo?.timeout + 1) - Date.now();
|
||||||
|
if (expiryInMs > 1) {
|
||||||
|
this.livenessWatchInterval = setInterval(this.checkLiveness.bind(this), expiryInMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setBeaconInfo(event: MatrixEvent): void {
|
||||||
|
this.beaconInfo = parseBeaconInfoContent(event.getContent());
|
||||||
|
this.checkLiveness();
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkLiveness(): void {
|
||||||
|
const prevLiveness = this.isLive;
|
||||||
|
this._isLive = this.beaconInfo?.live &&
|
||||||
|
isTimestampInDuration(this.beaconInfo?.timestamp, this.beaconInfo?.timeout, Date.now());
|
||||||
|
|
||||||
|
if (prevLiveness !== this.isLive) {
|
||||||
|
this.emit(BeaconEvent.LivenessChange, this.isLive, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { MatrixEvent } from "./event";
|
|||||||
import { MatrixClient } from "../client";
|
import { MatrixClient } from "../client";
|
||||||
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
||||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||||
import { Beacon, isBeaconInfoEventType } from "./beacon";
|
import { Beacon, BeaconEvent, isBeaconInfoEventType } from "./beacon";
|
||||||
|
|
||||||
// possible statuses for out-of-band member loading
|
// possible statuses for out-of-band member loading
|
||||||
enum OobStatus {
|
enum OobStatus {
|
||||||
@@ -40,6 +40,7 @@ export enum RoomStateEvent {
|
|||||||
Members = "RoomState.members",
|
Members = "RoomState.members",
|
||||||
NewMember = "RoomState.newMember",
|
NewMember = "RoomState.newMember",
|
||||||
Update = "RoomState.update", // signals batches of updates without specificity
|
Update = "RoomState.update", // signals batches of updates without specificity
|
||||||
|
BeaconLiveness = "RoomState.BeaconLiveness",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoomStateEventHandlerMap = {
|
export type RoomStateEventHandlerMap = {
|
||||||
@@ -47,6 +48,7 @@ export type RoomStateEventHandlerMap = {
|
|||||||
[RoomStateEvent.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
|
[RoomStateEvent.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
|
||||||
[RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
|
[RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
|
||||||
[RoomStateEvent.Update]: (state: RoomState) => void;
|
[RoomStateEvent.Update]: (state: RoomState) => void;
|
||||||
|
[RoomStateEvent.BeaconLiveness]: (state: RoomState, hasLiveBeacons: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventHandlerMap> {
|
export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventHandlerMap> {
|
||||||
@@ -73,6 +75,7 @@ export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventH
|
|||||||
public paginationToken: string = null;
|
public paginationToken: string = null;
|
||||||
|
|
||||||
public readonly beacons = new Map<string, Beacon>();
|
public readonly beacons = new Map<string, Beacon>();
|
||||||
|
private liveBeaconIds: string[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct room state.
|
* Construct room state.
|
||||||
@@ -235,6 +238,10 @@ export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventH
|
|||||||
return event ? event : null;
|
return event ? event : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get hasLiveBeacons(): boolean {
|
||||||
|
return !!this.liveBeaconIds?.length;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a copy of this room state so that mutations to either won't affect the other.
|
* Creates a copy of this room state so that mutations to either won't affect the other.
|
||||||
* @return {RoomState} the copy of the room state
|
* @return {RoomState} the copy of the room state
|
||||||
@@ -330,6 +337,8 @@ export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventH
|
|||||||
this.emit(RoomStateEvent.Events, event, this, lastStateEvent);
|
this.emit(RoomStateEvent.Events, event, this, lastStateEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.onBeaconLivenessChange();
|
||||||
|
|
||||||
// update higher level data structures. This needs to be done AFTER the
|
// update higher level data structures. This needs to be done AFTER the
|
||||||
// core event dict as these structures may depend on other state events in
|
// core event dict as these structures may depend on other state events in
|
||||||
// the given array (e.g. disambiguating display names in one go to do both
|
// the given array (e.g. disambiguating display names in one go to do both
|
||||||
@@ -418,15 +427,37 @@ export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventH
|
|||||||
this.events.get(event.getType()).set(event.getStateKey(), event);
|
this.events.get(event.getType()).set(event.getStateKey(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
private setBeacon(event: MatrixEvent): void {
|
private setBeacon(event: MatrixEvent): void {
|
||||||
if (this.beacons.has(event.getId())) {
|
if (this.beacons.has(event.getId())) {
|
||||||
return this.beacons.get(event.getId()).update(event);
|
return this.beacons.get(event.getId()).update(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const beacon = new Beacon(event);
|
const beacon = new Beacon(event);
|
||||||
|
beacon.on(BeaconEvent.LivenessChange, this.onBeaconLivenessChange.bind(this));
|
||||||
this.beacons.set(beacon.beaconInfoId, beacon);
|
this.beacons.set(beacon.beaconInfoId, beacon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
* Check liveness of room beacons
|
||||||
|
* emit RoomStateEvent.BeaconLiveness when
|
||||||
|
* roomstate.hasLiveBeacons has changed
|
||||||
|
*/
|
||||||
|
private onBeaconLivenessChange(): void {
|
||||||
|
const prevHasLiveBeacons = !!this.liveBeaconIds?.length;
|
||||||
|
this.liveBeaconIds = Array.from(this.beacons.values())
|
||||||
|
.filter(beacon => beacon.isLive)
|
||||||
|
.map(beacon => beacon.beaconInfoId);
|
||||||
|
|
||||||
|
const hasLiveBeacons = !!this.liveBeaconIds.length;
|
||||||
|
if (prevHasLiveBeacons !== hasLiveBeacons) {
|
||||||
|
this.emit(RoomStateEvent.BeaconLiveness, this, hasLiveBeacons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {
|
private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {
|
||||||
return this.events.get(event.getType())?.get(event.getStateKey()) ?? null;
|
return this.events.get(event.getType())?.get(event.getStateKey()) ?? null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user