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

kill beacons on expiry #8075

Merged
merged 1 commit into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
);
});
});
});