Skip to content

Commit 2a445c0

Browse files
committed
Merge branch 'develop' into voice-rooms
2 parents 145a3e5 + 75674d9 commit 2a445c0

File tree

11 files changed

+228
-28
lines changed

11 files changed

+228
-28
lines changed

spec/unit/content-helpers.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ describe('Beacon content helpers', () => {
5959
}));
6060
});
6161

62+
it('uses timestamp when provided', () => {
63+
expect(makeBeaconInfoContent(
64+
1234,
65+
true,
66+
'nice beacon_info',
67+
LocationAssetType.Pin,
68+
99999,
69+
)).toEqual(expect.objectContaining({
70+
[M_TIMESTAMP.name]: 99999,
71+
}));
72+
});
73+
6274
it('defaults asset type to self when not set', () => {
6375
expect(makeBeaconInfoContent(
6476
1234,

spec/unit/matrix-client.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { MEGOLM_ALGORITHM } from "../../src/crypto/olmlib";
1414
import { EventStatus, MatrixEvent } from "../../src/models/event";
1515
import { Preset } from "../../src/@types/partials";
1616
import * as testUtils from "../test-utils/test-utils";
17+
import { makeBeaconInfoContent } from "../../src/content-helpers";
18+
import { M_BEACON_INFO } from "../../src/@types/beacon";
1719

1820
jest.useFakeTimers();
1921

@@ -969,4 +971,43 @@ describe("MatrixClient", function() {
969971
client.supportsExperimentalThreads = supportsExperimentalThreads;
970972
});
971973
});
974+
975+
describe("beacons", () => {
976+
const roomId = '!room:server.org';
977+
const content = makeBeaconInfoContent(100, true);
978+
979+
beforeEach(() => {
980+
client.http.authedRequest.mockClear().mockResolvedValue({});
981+
});
982+
983+
it("creates new beacon info", async () => {
984+
await client.unstable_createLiveBeacon(roomId, content, '123');
985+
986+
// event type combined
987+
const expectedEventType = `${M_BEACON_INFO.name}.${userId}.123`;
988+
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
989+
expect(callback).toBeFalsy();
990+
expect(method).toBe('PUT');
991+
expect(path).toEqual(
992+
`/rooms/${encodeURIComponent(roomId)}/state/` +
993+
`${encodeURIComponent(expectedEventType)}/${encodeURIComponent(userId)}`,
994+
);
995+
expect(queryParams).toBeFalsy();
996+
expect(requestContent).toEqual(content);
997+
});
998+
999+
it("updates beacon info with specific event type", async () => {
1000+
const eventType = `${M_BEACON_INFO.name}.${userId}.456`;
1001+
1002+
await client.unstable_setLiveBeacon(roomId, eventType, content);
1003+
1004+
// event type combined
1005+
const [, , path, , requestContent] = client.http.authedRequest.mock.calls[0];
1006+
expect(path).toEqual(
1007+
`/rooms/${encodeURIComponent(roomId)}/state/` +
1008+
`${encodeURIComponent(eventType)}/${encodeURIComponent(userId)}`,
1009+
);
1010+
expect(requestContent).toEqual(content);
1011+
});
1012+
});
9721013
});

spec/unit/models/beacon.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ describe('Beacon', () => {
122122
expect(beacon.roomId).toEqual(roomId);
123123
expect(beacon.isLive).toEqual(true);
124124
expect(beacon.beaconInfoOwner).toEqual(userId);
125+
expect(beacon.beaconInfoEventType).toEqual(liveBeaconEvent.getType());
125126
});
126127

127128
describe('isLive()', () => {

spec/unit/room-state.spec.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as utils from "../test-utils/test-utils";
22
import { makeBeaconInfoEvent } from "../test-utils/beacon";
33
import { filterEmitCallsByEventType } from "../test-utils/emitter";
44
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
5+
import { BeaconEvent } from "../../src/models/beacon";
56

67
describe("RoomState", function() {
78
const roomId = "!foo:bar";
@@ -251,13 +252,16 @@ describe("RoomState", function() {
251252
);
252253
});
253254

254-
it('adds new beacon info events to state', () => {
255+
it('adds new beacon info events to state and emits', () => {
255256
const beaconEvent = makeBeaconInfoEvent(userA, roomId);
257+
const emitSpy = jest.spyOn(state, 'emit');
256258

257259
state.setStateEvents([beaconEvent]);
258260

259261
expect(state.beacons.size).toEqual(1);
260-
expect(state.beacons.get(beaconEvent.getId())).toBeTruthy();
262+
const beaconInstance = state.beacons.get(beaconEvent.getId());
263+
expect(beaconInstance).toBeTruthy();
264+
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
261265
});
262266

263267
it('updates existing beacon info events in state', () => {

src/client.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3686,14 +3686,32 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
36863686
* @returns {ISendEventResponse}
36873687
*/
36883688
// eslint-disable-next-line @typescript-eslint/naming-convention
3689-
unstable_createLiveBeacon(
3689+
public async unstable_createLiveBeacon(
36903690
roomId: Room["roomId"],
36913691
beaconInfoContent: MBeaconInfoEventContent,
36923692
eventTypeSuffix: string,
36933693
) {
36943694
const userId = this.getUserId();
36953695
const eventType = M_BEACON_INFO_VARIABLE.name.replace('*', `${userId}.${eventTypeSuffix}`);
3696-
return this.sendStateEvent(roomId, eventType, beaconInfoContent, userId);
3696+
return this.unstable_setLiveBeacon(roomId, eventType, beaconInfoContent);
3697+
}
3698+
3699+
/**
3700+
* Upsert a live beacon event
3701+
* using a specific m.beacon_info.* event variable type
3702+
* @param {string} roomId string
3703+
* @param {string} beaconInfoEventType event type including variable suffix
3704+
* @param {MBeaconInfoEventContent} beaconInfoContent
3705+
* @returns {ISendEventResponse}
3706+
*/
3707+
// eslint-disable-next-line @typescript-eslint/naming-convention
3708+
public async unstable_setLiveBeacon(
3709+
roomId: string,
3710+
beaconInfoEventType: string,
3711+
beaconInfoContent: MBeaconInfoEventContent,
3712+
) {
3713+
const userId = this.getUserId();
3714+
return this.sendStateEvent(roomId, beaconInfoEventType, beaconInfoContent, userId);
36973715
}
36983716

36993717
/**

src/content-helpers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,20 +198,22 @@ export type MakeBeaconInfoContent = (
198198
isLive?: boolean,
199199
description?: string,
200200
assetType?: LocationAssetType,
201+
timestamp?: number
201202
) => MBeaconInfoEventContent;
202203

203204
export const makeBeaconInfoContent: MakeBeaconInfoContent = (
204205
timeout,
205206
isLive,
206207
description,
207208
assetType,
209+
timestamp,
208210
) => ({
209211
[M_BEACON_INFO.name]: {
210212
description,
211213
timeout,
212214
live: isLive,
213215
},
214-
[M_TIMESTAMP.name]: Date.now(),
216+
[M_TIMESTAMP.name]: timestamp || Date.now(),
215217
[M_ASSET.name]: {
216218
type: assetType ?? LocationAssetType.Self,
217219
},

src/models/beacon.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export enum BeaconEvent {
2626
}
2727

2828
export type BeaconEventHandlerMap = {
29-
[BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;
3029
[BeaconEvent.Update]: (event: MatrixEvent, beacon: Beacon) => void;
3130
[BeaconEvent.LivenessChange]: (isLive: boolean, beacon: Beacon) => void;
3231
};
@@ -42,9 +41,9 @@ export const isBeaconInfoEventType = (type: string) =>
4241
type.startsWith(M_BEACON_INFO.altName);
4342

4443
// https://github.com/matrix-org/matrix-spec-proposals/pull/3489
45-
export class Beacon extends TypedEventEmitter<BeaconEvent, BeaconEventHandlerMap> {
44+
export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.New>, BeaconEventHandlerMap> {
4645
public readonly roomId: string;
47-
private beaconInfo: BeaconInfoState;
46+
private _beaconInfo: BeaconInfoState;
4847
private _isLive: boolean;
4948
private livenessWatchInterval: number;
5049

@@ -54,7 +53,6 @@ export class Beacon extends TypedEventEmitter<BeaconEvent, BeaconEventHandlerMap
5453
super();
5554
this.setBeaconInfo(this.rootEvent);
5655
this.roomId = this.rootEvent.getRoomId();
57-
this.emit(BeaconEvent.New, this.rootEvent, this);
5856
}
5957

6058
public get isLive(): boolean {
@@ -69,6 +67,14 @@ export class Beacon extends TypedEventEmitter<BeaconEvent, BeaconEventHandlerMap
6967
return this.rootEvent.getStateKey();
7068
}
7169

70+
public get beaconInfoEventType(): string {
71+
return this.rootEvent.getType();
72+
}
73+
74+
public get beaconInfo(): BeaconInfoState {
75+
return this._beaconInfo;
76+
}
77+
7278
public update(beaconInfoEvent: MatrixEvent): void {
7379
if (beaconInfoEvent.getId() !== this.beaconInfoId) {
7480
throw new Error('Invalid updating event');
@@ -95,22 +101,22 @@ export class Beacon extends TypedEventEmitter<BeaconEvent, BeaconEventHandlerMap
95101
}
96102

97103
if (this.isLive) {
98-
const expiryInMs = (this.beaconInfo?.timestamp + this.beaconInfo?.timeout + 1) - Date.now();
104+
const expiryInMs = (this._beaconInfo?.timestamp + this._beaconInfo?.timeout + 1) - Date.now();
99105
if (expiryInMs > 1) {
100106
this.livenessWatchInterval = setInterval(this.checkLiveness.bind(this), expiryInMs);
101107
}
102108
}
103109
}
104110

105111
private setBeaconInfo(event: MatrixEvent): void {
106-
this.beaconInfo = parseBeaconInfoContent(event.getContent());
112+
this._beaconInfo = parseBeaconInfoContent(event.getContent());
107113
this.checkLiveness();
108114
}
109115

110116
private checkLiveness(): void {
111117
const prevLiveness = this.isLive;
112-
this._isLive = this.beaconInfo?.live &&
113-
isTimestampInDuration(this.beaconInfo?.timestamp, this.beaconInfo?.timeout, Date.now());
118+
this._isLive = this._beaconInfo?.live &&
119+
isTimestampInDuration(this._beaconInfo?.timestamp, this._beaconInfo?.timeout, Date.now());
114120

115121
if (prevLiveness !== this.isLive) {
116122
this.emit(BeaconEvent.LivenessChange, this.isLive, this);

src/models/room-state.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import { MatrixEvent } from "./event";
2626
import { MatrixClient } from "../client";
2727
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
2828
import { TypedEventEmitter } from "./typed-event-emitter";
29-
import { Beacon, BeaconEvent, isBeaconInfoEventType } from "./beacon";
29+
import { Beacon, BeaconEvent, isBeaconInfoEventType, BeaconEventHandlerMap } from "./beacon";
30+
import { TypedReEmitter } from "../ReEmitter";
3031

3132
// possible statuses for out-of-band member loading
3233
enum OobStatus {
@@ -49,9 +50,14 @@ export type RoomStateEventHandlerMap = {
4950
[RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
5051
[RoomStateEvent.Update]: (state: RoomState) => void;
5152
[RoomStateEvent.BeaconLiveness]: (state: RoomState, hasLiveBeacons: boolean) => void;
53+
[BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;
5254
};
5355

54-
export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventHandlerMap> {
56+
type EmittedEvents = RoomStateEvent | BeaconEvent;
57+
type EventHandlerMap = RoomStateEventHandlerMap & BeaconEventHandlerMap;
58+
59+
export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
60+
public readonly reEmitter = new TypedReEmitter<EmittedEvents, EventHandlerMap>(this);
5561
private sentinels: Record<string, RoomMember> = {}; // userId: RoomMember
5662
// stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys)
5763
private displayNameToUserIds: Record<string, string[]> = {};
@@ -436,6 +442,14 @@ export class RoomState extends TypedEventEmitter<RoomStateEvent, RoomStateEventH
436442
}
437443

438444
const beacon = new Beacon(event);
445+
446+
this.reEmitter.reEmit<BeaconEvent, BeaconEvent>(beacon, [
447+
BeaconEvent.New,
448+
BeaconEvent.Update,
449+
BeaconEvent.LivenessChange,
450+
]);
451+
452+
this.emit(BeaconEvent.New, event, beacon);
439453
beacon.on(BeaconEvent.LivenessChange, this.onBeaconLivenessChange.bind(this));
440454
this.beacons.set(beacon.beaconInfoId, beacon);
441455
}

0 commit comments

Comments
 (0)