diff --git a/spec/unit/models/beacon.spec.ts b/spec/unit/models/beacon.spec.ts index 5f63f1bce8a..0e39e7c6961 100644 --- a/spec/unit/models/beacon.spec.ts +++ b/spec/unit/models/beacon.spec.ts @@ -256,7 +256,7 @@ describe('Beacon', () => { expect(beacon.livenessWatchInterval).not.toEqual(oldMonitor); }); - it('destroy kills liveness monitor', () => { + it('destroy kills liveness monitor and emits', () => { // live beacon was created an hour ago // and has a 3hr duration const beacon = new Beacon(liveBeaconEvent); @@ -267,10 +267,14 @@ describe('Beacon', () => { // destroy the beacon beacon.destroy(); + expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.Destroy, beacon.identifier); + // live forced to false + expect(beacon.isLive).toBe(false); advanceDateAndTime(HOUR_MS * 2 + 1); - expect(emitSpy).not.toHaveBeenCalled(); + // no additional calls + expect(emitSpy).toHaveBeenCalledTimes(1); }); }); }); diff --git a/spec/unit/room-state.spec.js b/spec/unit/room-state.spec.js index e17f0bbba2c..fa00d21fc9a 100644 --- a/spec/unit/room-state.spec.js +++ b/spec/unit/room-state.spec.js @@ -252,56 +252,91 @@ describe("RoomState", function() { ); }); - it('adds new beacon info events to state and emits', () => { - const beaconEvent = makeBeaconInfoEvent(userA, roomId); - const emitSpy = jest.spyOn(state, 'emit'); + describe('beacon events', () => { + it('adds new beacon info events to state and emits', () => { + const beaconEvent = makeBeaconInfoEvent(userA, roomId); + const emitSpy = jest.spyOn(state, 'emit'); - state.setStateEvents([beaconEvent]); + state.setStateEvents([beaconEvent]); - expect(state.beacons.size).toEqual(1); - const beaconInstance = state.beacons.get(beaconEvent.getType()); - expect(beaconInstance).toBeTruthy(); - expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance); - }); + expect(state.beacons.size).toEqual(1); + const beaconInstance = state.beacons.get(beaconEvent.getType()); + expect(beaconInstance).toBeTruthy(); + expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance); + }); - it('updates existing beacon info events in state', () => { - const beaconId = '$beacon1'; - const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId); - const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId, beaconId); + it('does not add redacted beacon info events to state', () => { + const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId); + const redactionEvent = { event: { type: 'm.room.redaction' } }; + redactedBeaconEvent.makeRedacted(redactionEvent); + const emitSpy = jest.spyOn(state, 'emit'); - state.setStateEvents([beaconEvent]); - const beaconInstance = state.beacons.get(beaconEvent.getType()); - expect(beaconInstance.isLive).toEqual(true); + state.setStateEvents([redactedBeaconEvent]); - state.setStateEvents([updatedBeaconEvent]); + // no beacon added + expect(state.beacons.size).toEqual(0); + expect(state.beacons.get(redactedBeaconEvent.getType)).toBeFalsy(); + // no new beacon emit + expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy(); + }); - // same Beacon - expect(state.beacons.get(beaconEvent.getType())).toBe(beaconInstance); - // updated liveness - expect(state.beacons.get(beaconEvent.getType()).isLive).toEqual(false); - }); + it('updates existing beacon info events in state', () => { + const beaconId = '$beacon1'; + const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId); + const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId, beaconId); - it('updates live beacon ids once after setting state events', () => { - const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1', '$beacon1'); - const deadBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, '$beacon2', '$beacon2'); + state.setStateEvents([beaconEvent]); + const beaconInstance = state.beacons.get(beaconEvent.getType()); + expect(beaconInstance.isLive).toEqual(true); - const emitSpy = jest.spyOn(state, 'emit'); + state.setStateEvents([updatedBeaconEvent]); - state.setStateEvents([liveBeaconEvent, deadBeaconEvent]); + // same Beacon + expect(state.beacons.get(beaconEvent.getType())).toBe(beaconInstance); + // updated liveness + expect(state.beacons.get(beaconEvent.getType()).isLive).toEqual(false); + }); - // called once - expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1); + it('destroys and removes redacted beacon events', () => { + const beaconId = '$beacon1'; + const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId); + const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId); + const redactionEvent = { event: { type: 'm.room.redaction' } }; + redactedBeaconEvent.makeRedacted(redactionEvent); - // live beacon is now not live - const updatedLiveBeaconEvent = makeBeaconInfoEvent( - userA, roomId, { isLive: false }, liveBeaconEvent.getId(), '$beacon1', - ); + state.setStateEvents([beaconEvent]); + const beaconInstance = state.beacons.get(beaconEvent.getType()); + const destroySpy = jest.spyOn(beaconInstance, 'destroy'); + expect(beaconInstance.isLive).toEqual(true); - state.setStateEvents([updatedLiveBeaconEvent]); + state.setStateEvents([redactedBeaconEvent]); + + expect(destroySpy).toHaveBeenCalled(); + expect(state.beacons.get(beaconEvent.getType())).toBe(undefined); + }); - expect(state.hasLiveBeacons).toBe(false); - expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(2); - expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false); + it('updates live beacon ids once after setting state events', () => { + const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1', '$beacon1'); + const deadBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, '$beacon2', '$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(), '$beacon1', + ); + + state.setStateEvents([updatedLiveBeaconEvent]); + + expect(state.hasLiveBeacons).toBe(false); + expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(2); + expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false); + }); }); }); diff --git a/src/models/beacon.ts b/src/models/beacon.ts index d05647b81e0..329fc04e6bd 100644 --- a/src/models/beacon.ts +++ b/src/models/beacon.ts @@ -23,11 +23,13 @@ export enum BeaconEvent { New = "Beacon.new", Update = "Beacon.update", LivenessChange = "Beacon.LivenessChange", + Destroy = "Destroy", } export type BeaconEventHandlerMap = { [BeaconEvent.Update]: (event: MatrixEvent, beacon: Beacon) => void; [BeaconEvent.LivenessChange]: (isLive: boolean, beacon: Beacon) => void; + [BeaconEvent.Destroy]: (beaconIdentifier: string) => void; }; export const isTimestampInDuration = ( @@ -93,6 +95,9 @@ export class Beacon extends TypedEventEmitter */ private setBeacon(event: MatrixEvent): void { if (this.beacons.has(event.getType())) { - return this.beacons.get(event.getType()).update(event); + const beacon = this.beacons.get(event.getType()); + + if (event.isRedacted()) { + beacon.destroy(); + this.beacons.delete(event.getType()); + return; + } + + return beacon.update(event); + } + + if (event.isRedacted()) { + return; } const beacon = new Beacon(event); @@ -446,6 +458,7 @@ export class RoomState extends TypedEventEmitter this.reEmitter.reEmit(beacon, [ BeaconEvent.New, BeaconEvent.Update, + BeaconEvent.Destroy, BeaconEvent.LivenessChange, ]); diff --git a/src/sync.ts b/src/sync.ts index bab1da97040..8ac3949ba59 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -227,6 +227,7 @@ export class SyncApi { RoomStateEvent.Update, BeaconEvent.New, BeaconEvent.Update, + BeaconEvent.Destroy, BeaconEvent.LivenessChange, ]);