Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
kill beacons on expiry
Browse files Browse the repository at this point in the history
Signed-off-by: Kerry Archibald <kerrya@element.io>
  • Loading branch information
Kerry Archibald committed Mar 18, 2022
1 parent cdcf6d0 commit 8cd6470
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 5 deletions.
39 changes: 37 additions & 2 deletions src/stores/OwnBeaconStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {
MatrixEvent,
Room,
} from "matrix-js-sdk/src/matrix";
import {
BeaconInfoState, makeBeaconInfoContent,
} from "matrix-js-sdk/src/content-helpers";

import defaultDispatcher from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads";
Expand Down Expand Up @@ -83,6 +86,17 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
return this.liveBeaconIds.filter(beaconId => this.beaconsByRoomId.get(roomId)?.has(beaconId));
}

public stopBeacon = async (beaconInfoId: string): Promise<void> => {
const beacon = this.beacons.get(beaconInfoId);
// if no beacon, or beacon is already explicitly set isLive: false
// do nothing
if (!beacon?.beaconInfo?.live) {
return;
}

return await this.updateBeaconEvent(beacon, { live: false });
};

private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) {
return;
Expand All @@ -106,9 +120,14 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.liveBeaconIds.push(beacon.beaconInfoId);
}

// beacon expired, update beacon to un-alive state
if (!isLive) {
this.stopBeacon(beacon.beaconInfoId);
}

// TODO start location polling here

this.emit(OwnBeaconStoreEvent.LivenessChange, this.hasLiveBeacons());
// TODO stop or start polling here
// if not content is live but beacon is not, update state event with live: false
};

private initialiseBeaconState = () => {
Expand All @@ -134,6 +153,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
}

this.beaconsByRoomId.get(beacon.roomId).add(beacon.beaconInfoId);

beacon.monitorLiveness();
};

Expand All @@ -149,4 +169,19 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.emit(OwnBeaconStoreEvent.LivenessChange, newLiveness);
}
};

private updateBeaconEvent = async (beacon: Beacon, update: Partial<BeaconInfoState>): Promise<void> => {
const { description, timeout, timestamp, live, assetType } = {
...beacon.beaconInfo,
...update,
};

const updateContent = makeBeaconInfoContent(timeout,
live,
description,
assetType,
timestamp);

await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, beacon.beaconInfoEventType, updateContent);
};
}
104 changes: 101 additions & 3 deletions test/stores/OwnBeaconStore-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import { Room, Beacon, BeaconEvent } from "matrix-js-sdk/src/matrix";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";

import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../src/stores/OwnBeaconStore";
import { resetAsyncStoreWithClient, setupAsyncStoreWithClient } from "../test-utils";
Expand All @@ -33,6 +34,7 @@ describe('OwnBeaconStore', () => {
const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(aliceId),
getVisibleRooms: jest.fn().mockReturnValue([]),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
});
const room1Id = '$room1:server.org';
const room2Id = '$room2:server.org';
Expand Down Expand Up @@ -78,6 +80,7 @@ describe('OwnBeaconStore', () => {

beforeEach(() => {
mockClient.getVisibleRooms.mockReturnValue([]);
mockClient.unstable_setLiveBeacon.mockClear().mockResolvedValue({ event_id: '1' });
jest.spyOn(global.Date, 'now').mockReturnValue(now);
jest.spyOn(OwnBeaconStore.instance, 'emit').mockRestore();
});
Expand Down Expand Up @@ -335,7 +338,7 @@ describe('OwnBeaconStore', () => {
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
});

it('updates state and when beacon liveness changes from true to false', async () => {
it('updates state and emits beacon liveness changes from true to false', async () => {
makeRoomsWithStateEvents([
alicesRoom1BeaconInfo,
]);
Expand All @@ -356,6 +359,35 @@ describe('OwnBeaconStore', () => {
expect(emitSpy).toHaveBeenCalledWith(OwnBeaconStoreEvent.LivenessChange, false);
});

it('stops beacon when liveness changes from true to false and beacon is expired', async () => {
makeRoomsWithStateEvents([
alicesRoom1BeaconInfo,
]);
await makeOwnBeaconStore();
const alicesBeacon = new Beacon(alicesRoom1BeaconInfo);
const prevEventContent = alicesRoom1BeaconInfo.getContent();

// time travel until beacon is expired
advanceDateAndTime(HOUR_MS * 3);

mockClient.emit(BeaconEvent.LivenessChange, false, alicesBeacon);

// matches original state of event content
// except for live property
const expectedUpdateContent = {
...prevEventContent,
[M_BEACON_INFO.name]: {
...prevEventContent[M_BEACON_INFO.name],
live: false,
},
};
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledWith(
room1Id,
alicesRoom1BeaconInfo.getType(),
expectedUpdateContent,
);
});

it('updates state and when beacon liveness changes from false to true', async () => {
makeRoomsWithStateEvents([
alicesOldRoomIdBeaconInfo,
Expand All @@ -381,9 +413,75 @@ describe('OwnBeaconStore', () => {
});
});

describe('on LivenessChange event', () => {
it('ignores events for irrelevant beacons', async () => {
describe('stopBeacon()', () => {
beforeEach(() => {
makeRoomsWithStateEvents([
alicesRoom1BeaconInfo,
alicesOldRoomIdBeaconInfo,
]);
});

it('does nothing for an unknown beacon id', async () => {
const store = await makeOwnBeaconStore();
await store.stopBeacon('randomBeaconId');
expect(mockClient.unstable_setLiveBeacon).not.toHaveBeenCalled();
});

it('does nothing for a beacon that is already not live', async () => {
const store = await makeOwnBeaconStore();
await store.stopBeacon(alicesOldRoomIdBeaconInfo.getId());
expect(mockClient.unstable_setLiveBeacon).not.toHaveBeenCalled();
});

it('updates beacon to live:false when it is unexpired', async () => {
const store = await makeOwnBeaconStore();

await store.stopBeacon(alicesOldRoomIdBeaconInfo.getId());
const prevEventContent = alicesRoom1BeaconInfo.getContent();

await store.stopBeacon(alicesRoom1BeaconInfo.getId());

// matches original state of event content
// except for live property
const expectedUpdateContent = {
...prevEventContent,
[M_BEACON_INFO.name]: {
...prevEventContent[M_BEACON_INFO.name],
live: false,
},
};
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledWith(
room1Id,
alicesRoom1BeaconInfo.getType(),
expectedUpdateContent,
);
});

it('updates beacon to live:false when it is expired but live property is true', async () => {
const store = await makeOwnBeaconStore();

await store.stopBeacon(alicesOldRoomIdBeaconInfo.getId());
const prevEventContent = alicesRoom1BeaconInfo.getContent();

// time travel until beacon is expired
advanceDateAndTime(HOUR_MS * 3);

await store.stopBeacon(alicesRoom1BeaconInfo.getId());

// matches original state of event content
// except for live property
const expectedUpdateContent = {
...prevEventContent,
[M_BEACON_INFO.name]: {
...prevEventContent[M_BEACON_INFO.name],
live: false,
},
};
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledWith(
room1Id,
alicesRoom1BeaconInfo.getType(),
expectedUpdateContent,
);
});
});
});

0 comments on commit 8cd6470

Please sign in to comment.