kill beacons on expiry (#8075)
Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
12d26555af
commit
ab934a2a08
2 changed files with 138 additions and 5 deletions
|
@ -20,6 +20,9 @@ import {
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
Room,
|
Room,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import {
|
||||||
|
BeaconInfoState, makeBeaconInfoContent,
|
||||||
|
} from "matrix-js-sdk/src/content-helpers";
|
||||||
|
|
||||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||||
import { ActionPayload } from "../dispatcher/payloads";
|
import { ActionPayload } from "../dispatcher/payloads";
|
||||||
|
@ -83,6 +86,17 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
return this.liveBeaconIds.filter(beaconId => this.beaconsByRoomId.get(roomId)?.has(beaconId));
|
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 => {
|
private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
|
||||||
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) {
|
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) {
|
||||||
return;
|
return;
|
||||||
|
@ -106,9 +120,14 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
this.liveBeaconIds.push(beacon.beaconInfoId);
|
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());
|
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 = () => {
|
private initialiseBeaconState = () => {
|
||||||
|
@ -134,6 +153,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.beaconsByRoomId.get(beacon.roomId).add(beacon.beaconInfoId);
|
this.beaconsByRoomId.get(beacon.roomId).add(beacon.beaconInfoId);
|
||||||
|
|
||||||
beacon.monitorLiveness();
|
beacon.monitorLiveness();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -149,4 +169,19 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
this.emit(OwnBeaconStoreEvent.LivenessChange, newLiveness);
|
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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Room, Beacon, BeaconEvent } from "matrix-js-sdk/src/matrix";
|
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 { OwnBeaconStore, OwnBeaconStoreEvent } from "../../src/stores/OwnBeaconStore";
|
||||||
import { resetAsyncStoreWithClient, setupAsyncStoreWithClient } from "../test-utils";
|
import { resetAsyncStoreWithClient, setupAsyncStoreWithClient } from "../test-utils";
|
||||||
|
@ -33,6 +34,7 @@ describe('OwnBeaconStore', () => {
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
getUserId: jest.fn().mockReturnValue(aliceId),
|
getUserId: jest.fn().mockReturnValue(aliceId),
|
||||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||||
|
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
|
||||||
});
|
});
|
||||||
const room1Id = '$room1:server.org';
|
const room1Id = '$room1:server.org';
|
||||||
const room2Id = '$room2:server.org';
|
const room2Id = '$room2:server.org';
|
||||||
|
@ -78,6 +80,7 @@ describe('OwnBeaconStore', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClient.getVisibleRooms.mockReturnValue([]);
|
mockClient.getVisibleRooms.mockReturnValue([]);
|
||||||
|
mockClient.unstable_setLiveBeacon.mockClear().mockResolvedValue({ event_id: '1' });
|
||||||
jest.spyOn(global.Date, 'now').mockReturnValue(now);
|
jest.spyOn(global.Date, 'now').mockReturnValue(now);
|
||||||
jest.spyOn(OwnBeaconStore.instance, 'emit').mockRestore();
|
jest.spyOn(OwnBeaconStore.instance, 'emit').mockRestore();
|
||||||
});
|
});
|
||||||
|
@ -335,7 +338,7 @@ describe('OwnBeaconStore', () => {
|
||||||
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
|
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([
|
makeRoomsWithStateEvents([
|
||||||
alicesRoom1BeaconInfo,
|
alicesRoom1BeaconInfo,
|
||||||
]);
|
]);
|
||||||
|
@ -356,6 +359,35 @@ describe('OwnBeaconStore', () => {
|
||||||
expect(emitSpy).toHaveBeenCalledWith(OwnBeaconStoreEvent.LivenessChange, false);
|
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 () => {
|
it('updates state and when beacon liveness changes from false to true', async () => {
|
||||||
makeRoomsWithStateEvents([
|
makeRoomsWithStateEvents([
|
||||||
alicesOldRoomIdBeaconInfo,
|
alicesOldRoomIdBeaconInfo,
|
||||||
|
@ -381,9 +413,75 @@ describe('OwnBeaconStore', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('on LivenessChange event', () => {
|
describe('stopBeacon()', () => {
|
||||||
it('ignores events for irrelevant beacons', async () => {
|
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,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue