You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-30 04:23:07 +03:00
Live location sharing - update beacon_info implementation to latest MSC (#2281)
* remove M_BEACON_INFO_VARIABLE Signed-off-by: Kerry Archibald <kerrya@element.io> * create beacon_info events with non-variable event type Signed-off-by: Kerry Archibald <kerrya@element.io> * remove isBeaconInfoEventType Signed-off-by: Kerry Archibald <kerrya@element.io> * refer to msc3673 instead of msc3489 Signed-off-by: Kerry Archibald <kerrya@element.io> * remove event type suffix Signed-off-by: Kerry Archibald <kerrya@element.io> * update beacon identifier to use state key Signed-off-by: Kerry Archibald <kerrya@element.io> * fix beacon spec Signed-off-by: Kerry Archibald <kerrya@element.io> * fix room-state tests Signed-off-by: Kerry Archibald <kerrya@element.io> * add beacon identifier Signed-off-by: Kerry Archibald <kerrya@element.io> * dont allow update to older beacon event Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io> * unnest beacon_info content Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io> * check redaction event id Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
@ -42,7 +42,6 @@ export const makeBeaconInfoEvent = (
|
|||||||
roomId: string,
|
roomId: string,
|
||||||
contentProps: Partial<InfoContentProps> = {},
|
contentProps: Partial<InfoContentProps> = {},
|
||||||
eventId?: string,
|
eventId?: string,
|
||||||
eventTypeSuffix?: string,
|
|
||||||
): MatrixEvent => {
|
): MatrixEvent => {
|
||||||
const {
|
const {
|
||||||
timeout, isLive, description, assetType,
|
timeout, isLive, description, assetType,
|
||||||
@ -51,12 +50,14 @@ export const makeBeaconInfoEvent = (
|
|||||||
...contentProps,
|
...contentProps,
|
||||||
};
|
};
|
||||||
const event = new MatrixEvent({
|
const event = new MatrixEvent({
|
||||||
type: `${M_BEACON_INFO.name}.${sender}.${eventTypeSuffix || Date.now()}`,
|
type: M_BEACON_INFO.name,
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
state_key: sender,
|
state_key: sender,
|
||||||
content: makeBeaconInfoContent(timeout, isLive, description, assetType),
|
content: makeBeaconInfoContent(timeout, isLive, description, assetType),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
event.event.origin_server_ts = Date.now();
|
||||||
|
|
||||||
// live beacons use the beacon_info event id
|
// live beacons use the beacon_info event id
|
||||||
// set or default this
|
// set or default this
|
||||||
event.replaceLocalEventId(eventId || `$${Math.random()}-${Math.random()}`);
|
event.replaceLocalEventId(eventId || `$${Math.random()}-${Math.random()}`);
|
||||||
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||||||
|
|
||||||
import { REFERENCE_RELATION } from "matrix-events-sdk";
|
import { REFERENCE_RELATION } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { M_BEACON_INFO } from "../../src/@types/beacon";
|
|
||||||
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "../../src/@types/location";
|
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "../../src/@types/location";
|
||||||
import { makeBeaconContent, makeBeaconInfoContent } from "../../src/content-helpers";
|
import { makeBeaconContent, makeBeaconInfoContent } from "../../src/content-helpers";
|
||||||
|
|
||||||
@ -36,11 +35,9 @@ describe('Beacon content helpers', () => {
|
|||||||
'nice beacon_info',
|
'nice beacon_info',
|
||||||
LocationAssetType.Pin,
|
LocationAssetType.Pin,
|
||||||
)).toEqual({
|
)).toEqual({
|
||||||
[M_BEACON_INFO.name]: {
|
description: 'nice beacon_info',
|
||||||
description: 'nice beacon_info',
|
timeout: 1234,
|
||||||
timeout: 1234,
|
live: true,
|
||||||
live: true,
|
|
||||||
},
|
|
||||||
[M_TIMESTAMP.name]: mockDateNow,
|
[M_TIMESTAMP.name]: mockDateNow,
|
||||||
[M_ASSET.name]: {
|
[M_ASSET.name]: {
|
||||||
type: LocationAssetType.Pin,
|
type: LocationAssetType.Pin,
|
||||||
|
@ -999,10 +999,10 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("creates new beacon info", async () => {
|
it("creates new beacon info", async () => {
|
||||||
await client.unstable_createLiveBeacon(roomId, content, '123');
|
await client.unstable_createLiveBeacon(roomId, content);
|
||||||
|
|
||||||
// event type combined
|
// event type combined
|
||||||
const expectedEventType = `${M_BEACON_INFO.name}.${userId}.123`;
|
const expectedEventType = M_BEACON_INFO.name;
|
||||||
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
|
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
|
||||||
expect(callback).toBeFalsy();
|
expect(callback).toBeFalsy();
|
||||||
expect(method).toBe('PUT');
|
expect(method).toBe('PUT');
|
||||||
@ -1015,15 +1015,13 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates beacon info with specific event type", async () => {
|
it("updates beacon info with specific event type", async () => {
|
||||||
const eventType = `${M_BEACON_INFO.name}.${userId}.456`;
|
await client.unstable_setLiveBeacon(roomId, content);
|
||||||
|
|
||||||
await client.unstable_setLiveBeacon(roomId, eventType, content);
|
|
||||||
|
|
||||||
// event type combined
|
// event type combined
|
||||||
const [, , path, , requestContent] = client.http.authedRequest.mock.calls[0];
|
const [, , path, , requestContent] = client.http.authedRequest.mock.calls[0];
|
||||||
expect(path).toEqual(
|
expect(path).toEqual(
|
||||||
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
||||||
`${encodeURIComponent(eventType)}/${encodeURIComponent(userId)}`,
|
`${encodeURIComponent(M_BEACON_INFO.name)}/${encodeURIComponent(userId)}`,
|
||||||
);
|
);
|
||||||
expect(requestContent).toEqual(content);
|
expect(requestContent).toEqual(content);
|
||||||
});
|
});
|
||||||
|
@ -14,11 +14,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventType } from "../../../src";
|
|
||||||
import { M_BEACON_INFO } from "../../../src/@types/beacon";
|
|
||||||
import {
|
import {
|
||||||
isTimestampInDuration,
|
isTimestampInDuration,
|
||||||
isBeaconInfoEventType,
|
|
||||||
Beacon,
|
Beacon,
|
||||||
BeaconEvent,
|
BeaconEvent,
|
||||||
} from "../../../src/models/beacon";
|
} from "../../../src/models/beacon";
|
||||||
@ -57,27 +54,9 @@ describe('Beacon', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isBeaconInfoEventType', () => {
|
|
||||||
it.each([
|
|
||||||
EventType.CallAnswer,
|
|
||||||
`prefix.${M_BEACON_INFO.name}`,
|
|
||||||
`prefix.${M_BEACON_INFO.altName}`,
|
|
||||||
])('returns false for %s', (type) => {
|
|
||||||
expect(isBeaconInfoEventType(type)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
M_BEACON_INFO.name,
|
|
||||||
M_BEACON_INFO.altName,
|
|
||||||
`${M_BEACON_INFO.name}.@test:server.org.12345`,
|
|
||||||
`${M_BEACON_INFO.altName}.@test:server.org.12345`,
|
|
||||||
])('returns true for %s', (type) => {
|
|
||||||
expect(isBeaconInfoEventType(type)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Beacon', () => {
|
describe('Beacon', () => {
|
||||||
const userId = '@user:server.org';
|
const userId = '@user:server.org';
|
||||||
|
const userId2 = '@user2:server.org';
|
||||||
const roomId = '$room:server.org';
|
const roomId = '$room:server.org';
|
||||||
// 14.03.2022 16:15
|
// 14.03.2022 16:15
|
||||||
const now = 1647270879403;
|
const now = 1647270879403;
|
||||||
@ -88,6 +67,7 @@ describe('Beacon', () => {
|
|||||||
// without timeout of 3 hours
|
// without timeout of 3 hours
|
||||||
let liveBeaconEvent;
|
let liveBeaconEvent;
|
||||||
let notLiveBeaconEvent;
|
let notLiveBeaconEvent;
|
||||||
|
let user2BeaconEvent;
|
||||||
|
|
||||||
const advanceDateAndTime = (ms: number) => {
|
const advanceDateAndTime = (ms: number) => {
|
||||||
// bc liveness check uses Date.now we have to advance this mock
|
// bc liveness check uses Date.now we have to advance this mock
|
||||||
@ -107,14 +87,21 @@ describe('Beacon', () => {
|
|||||||
isLive: true,
|
isLive: true,
|
||||||
},
|
},
|
||||||
'$live123',
|
'$live123',
|
||||||
'$live123',
|
|
||||||
);
|
);
|
||||||
notLiveBeaconEvent = makeBeaconInfoEvent(
|
notLiveBeaconEvent = makeBeaconInfoEvent(
|
||||||
userId,
|
userId,
|
||||||
roomId,
|
roomId,
|
||||||
{ timeout: HOUR_MS * 3, isLive: false },
|
{ timeout: HOUR_MS * 3, isLive: false },
|
||||||
'$dead123',
|
'$dead123',
|
||||||
'$dead123',
|
);
|
||||||
|
user2BeaconEvent = makeBeaconInfoEvent(
|
||||||
|
userId2,
|
||||||
|
roomId,
|
||||||
|
{
|
||||||
|
timeout: HOUR_MS * 3,
|
||||||
|
isLive: true,
|
||||||
|
},
|
||||||
|
'$user2live123',
|
||||||
);
|
);
|
||||||
|
|
||||||
// back to now
|
// back to now
|
||||||
@ -133,7 +120,7 @@ describe('Beacon', () => {
|
|||||||
expect(beacon.isLive).toEqual(true);
|
expect(beacon.isLive).toEqual(true);
|
||||||
expect(beacon.beaconInfoOwner).toEqual(userId);
|
expect(beacon.beaconInfoOwner).toEqual(userId);
|
||||||
expect(beacon.beaconInfoEventType).toEqual(liveBeaconEvent.getType());
|
expect(beacon.beaconInfoEventType).toEqual(liveBeaconEvent.getType());
|
||||||
expect(beacon.identifier).toEqual(liveBeaconEvent.getType());
|
expect(beacon.identifier).toEqual(`${roomId}_${userId}`);
|
||||||
expect(beacon.beaconInfo).toBeTruthy();
|
expect(beacon.beaconInfo).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -171,8 +158,27 @@ describe('Beacon', () => {
|
|||||||
|
|
||||||
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
|
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
|
||||||
|
|
||||||
expect(() => beacon.update(notLiveBeaconEvent)).toThrow();
|
expect(() => beacon.update(user2BeaconEvent)).toThrow();
|
||||||
expect(beacon.isLive).toEqual(true);
|
// didnt update
|
||||||
|
expect(beacon.identifier).toEqual(`${roomId}_${userId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not update with an older event', () => {
|
||||||
|
const beacon = new Beacon(liveBeaconEvent);
|
||||||
|
const emitSpy = jest.spyOn(beacon, 'emit').mockClear();
|
||||||
|
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
|
||||||
|
|
||||||
|
const oldUpdateEvent = makeBeaconInfoEvent(
|
||||||
|
userId,
|
||||||
|
roomId,
|
||||||
|
);
|
||||||
|
// less than the original event
|
||||||
|
oldUpdateEvent.event.origin_server_ts = liveBeaconEvent.event.origin_server_ts - 1000;
|
||||||
|
|
||||||
|
beacon.update(oldUpdateEvent);
|
||||||
|
// didnt update
|
||||||
|
expect(emitSpy).not.toHaveBeenCalled();
|
||||||
|
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates event', () => {
|
it('updates event', () => {
|
||||||
@ -182,7 +188,7 @@ describe('Beacon', () => {
|
|||||||
expect(beacon.isLive).toEqual(true);
|
expect(beacon.isLive).toEqual(true);
|
||||||
|
|
||||||
const updatedBeaconEvent = makeBeaconInfoEvent(
|
const updatedBeaconEvent = makeBeaconInfoEvent(
|
||||||
userId, roomId, { timeout: HOUR_MS * 3, isLive: false }, '$live123', '$live123');
|
userId, roomId, { timeout: HOUR_MS * 3, isLive: false }, '$live123');
|
||||||
|
|
||||||
beacon.update(updatedBeaconEvent);
|
beacon.update(updatedBeaconEvent);
|
||||||
expect(beacon.isLive).toEqual(false);
|
expect(beacon.isLive).toEqual(false);
|
||||||
@ -200,7 +206,6 @@ describe('Beacon', () => {
|
|||||||
roomId,
|
roomId,
|
||||||
{ timeout: HOUR_MS * 3, isLive: false },
|
{ timeout: HOUR_MS * 3, isLive: false },
|
||||||
beacon.beaconInfoId,
|
beacon.beaconInfoId,
|
||||||
'$live123',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
beacon.update(updatedBeaconEvent);
|
beacon.update(updatedBeaconEvent);
|
||||||
|
@ -2,7 +2,7 @@ import * as utils from "../test-utils/test-utils";
|
|||||||
import { makeBeaconInfoEvent } from "../test-utils/beacon";
|
import { makeBeaconInfoEvent } from "../test-utils/beacon";
|
||||||
import { filterEmitCallsByEventType } from "../test-utils/emitter";
|
import { filterEmitCallsByEventType } from "../test-utils/emitter";
|
||||||
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
|
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
|
||||||
import { BeaconEvent } from "../../src/models/beacon";
|
import { BeaconEvent, getBeaconInfoIdentifier } from "../../src/models/beacon";
|
||||||
|
|
||||||
describe("RoomState", function() {
|
describe("RoomState", function() {
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
@ -260,7 +260,7 @@ describe("RoomState", function() {
|
|||||||
state.setStateEvents([beaconEvent]);
|
state.setStateEvents([beaconEvent]);
|
||||||
|
|
||||||
expect(state.beacons.size).toEqual(1);
|
expect(state.beacons.size).toEqual(1);
|
||||||
const beaconInstance = state.beacons.get(beaconEvent.getType());
|
const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
|
||||||
expect(beaconInstance).toBeTruthy();
|
expect(beaconInstance).toBeTruthy();
|
||||||
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
|
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
|
||||||
});
|
});
|
||||||
@ -275,49 +275,49 @@ describe("RoomState", function() {
|
|||||||
|
|
||||||
// no beacon added
|
// no beacon added
|
||||||
expect(state.beacons.size).toEqual(0);
|
expect(state.beacons.size).toEqual(0);
|
||||||
expect(state.beacons.get(redactedBeaconEvent.getType)).toBeFalsy();
|
expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
|
||||||
// no new beacon emit
|
// no new beacon emit
|
||||||
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
|
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates existing beacon info events in state', () => {
|
it('updates existing beacon info events in state', () => {
|
||||||
const beaconId = '$beacon1';
|
const beaconId = '$beacon1';
|
||||||
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
|
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId, beaconId);
|
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);
|
||||||
|
|
||||||
state.setStateEvents([beaconEvent]);
|
state.setStateEvents([beaconEvent]);
|
||||||
const beaconInstance = state.beacons.get(beaconEvent.getType());
|
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
||||||
expect(beaconInstance.isLive).toEqual(true);
|
expect(beaconInstance.isLive).toEqual(true);
|
||||||
|
|
||||||
state.setStateEvents([updatedBeaconEvent]);
|
state.setStateEvents([updatedBeaconEvent]);
|
||||||
|
|
||||||
// same Beacon
|
// same Beacon
|
||||||
expect(state.beacons.get(beaconEvent.getType())).toBe(beaconInstance);
|
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
|
||||||
// updated liveness
|
// updated liveness
|
||||||
expect(state.beacons.get(beaconEvent.getType()).isLive).toEqual(false);
|
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent)).isLive).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('destroys and removes redacted beacon events', () => {
|
it('destroys and removes redacted beacon events', () => {
|
||||||
const beaconId = '$beacon1';
|
const beaconId = '$beacon1';
|
||||||
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
|
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
|
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const redactionEvent = { event: { type: 'm.room.redaction' } };
|
const redactionEvent = { event: { type: 'm.room.redaction', redacts: beaconEvent.getId() } };
|
||||||
redactedBeaconEvent.makeRedacted(redactionEvent);
|
redactedBeaconEvent.makeRedacted(redactionEvent);
|
||||||
|
|
||||||
state.setStateEvents([beaconEvent]);
|
state.setStateEvents([beaconEvent]);
|
||||||
const beaconInstance = state.beacons.get(beaconEvent.getType());
|
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
||||||
const destroySpy = jest.spyOn(beaconInstance, 'destroy');
|
const destroySpy = jest.spyOn(beaconInstance, 'destroy');
|
||||||
expect(beaconInstance.isLive).toEqual(true);
|
expect(beaconInstance.isLive).toEqual(true);
|
||||||
|
|
||||||
state.setStateEvents([redactedBeaconEvent]);
|
state.setStateEvents([redactedBeaconEvent]);
|
||||||
|
|
||||||
expect(destroySpy).toHaveBeenCalled();
|
expect(destroySpy).toHaveBeenCalled();
|
||||||
expect(state.beacons.get(beaconEvent.getType())).toBe(undefined);
|
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates live beacon ids once after setting state events', () => {
|
it('updates live beacon ids once after setting state events', () => {
|
||||||
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1', '$beacon1');
|
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
|
||||||
const deadBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, '$beacon2', '$beacon2');
|
const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2');
|
||||||
|
|
||||||
const emitSpy = jest.spyOn(state, 'emit');
|
const emitSpy = jest.spyOn(state, 'emit');
|
||||||
|
|
||||||
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EitherAnd, RELATES_TO_RELATIONSHIP, REFERENCE_RELATION } from "matrix-events-sdk";
|
import { RELATES_TO_RELATIONSHIP, REFERENCE_RELATION } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { UnstableValue } from "../NamespacedValue";
|
import { UnstableValue } from "../NamespacedValue";
|
||||||
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
|
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beacon info and beacon event types as described in MSC3489
|
* Beacon info and beacon event types as described in MSC3672
|
||||||
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
* https://github.com/matrix-org/matrix-spec-proposals/pull/3672
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,16 +60,11 @@ import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Variable event type for m.beacon_info
|
|
||||||
*/
|
|
||||||
export const M_BEACON_INFO_VARIABLE = new UnstableValue("m.beacon_info.*", "org.matrix.msc3489.beacon_info.*");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-variable type for m.beacon_info event content
|
* Non-variable type for m.beacon_info event content
|
||||||
*/
|
*/
|
||||||
export const M_BEACON_INFO = new UnstableValue("m.beacon_info", "org.matrix.msc3489.beacon_info");
|
export const M_BEACON_INFO = new UnstableValue("m.beacon_info", "org.matrix.msc3672.beacon_info");
|
||||||
export const M_BEACON = new UnstableValue("m.beacon", "org.matrix.msc3489.beacon");
|
export const M_BEACON = new UnstableValue("m.beacon", "org.matrix.msc3672.beacon");
|
||||||
|
|
||||||
export type MBeaconInfoContent = {
|
export type MBeaconInfoContent = {
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -80,16 +75,11 @@ export type MBeaconInfoContent = {
|
|||||||
live?: boolean;
|
live?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MBeaconInfoEvent = EitherAnd<
|
|
||||||
{ [M_BEACON_INFO.name]: MBeaconInfoContent },
|
|
||||||
{ [M_BEACON_INFO.altName]: MBeaconInfoContent }
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* m.beacon_info Event example from the spec
|
* m.beacon_info Event example from the spec
|
||||||
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
* https://github.com/matrix-org/matrix-spec-proposals/pull/3672
|
||||||
* {
|
* {
|
||||||
"type": "m.beacon_info.@matthew:matrix.org.1",
|
"type": "m.beacon_info",
|
||||||
"state_key": "@matthew:matrix.org",
|
"state_key": "@matthew:matrix.org",
|
||||||
"content": {
|
"content": {
|
||||||
"m.beacon_info": {
|
"m.beacon_info": {
|
||||||
@ -108,7 +98,7 @@ export type MBeaconInfoEvent = EitherAnd<
|
|||||||
* m.beacon_info.* event content
|
* m.beacon_info.* event content
|
||||||
*/
|
*/
|
||||||
export type MBeaconInfoEventContent = &
|
export type MBeaconInfoEventContent = &
|
||||||
MBeaconInfoEvent &
|
MBeaconInfoContent &
|
||||||
// creation timestamp of the beacon on the client
|
// creation timestamp of the beacon on the client
|
||||||
MTimestampEvent &
|
MTimestampEvent &
|
||||||
// the type of asset being tracked as per MSC3488
|
// the type of asset being tracked as per MSC3488
|
||||||
@ -116,7 +106,7 @@ export type MBeaconInfoEventContent = &
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* m.beacon event example
|
* m.beacon event example
|
||||||
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
* https://github.com/matrix-org/matrix-spec-proposals/pull/3672
|
||||||
*
|
*
|
||||||
* {
|
* {
|
||||||
"type": "m.beacon",
|
"type": "m.beacon",
|
||||||
|
@ -180,7 +180,7 @@ import { MediaHandler } from "./webrtc/mediaHandler";
|
|||||||
import { IRefreshTokenResponse } from "./@types/auth";
|
import { IRefreshTokenResponse } from "./@types/auth";
|
||||||
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
||||||
import { Thread, THREAD_RELATION_TYPE } from "./models/thread";
|
import { Thread, THREAD_RELATION_TYPE } from "./models/thread";
|
||||||
import { MBeaconInfoEventContent, M_BEACON_INFO_VARIABLE } from "./@types/beacon";
|
import { MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon";
|
||||||
|
|
||||||
export type Store = IStore;
|
export type Store = IStore;
|
||||||
export type SessionStore = WebStorageSessionStore;
|
export type SessionStore = WebStorageSessionStore;
|
||||||
@ -3649,39 +3649,30 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* Create an m.beacon_info event
|
* Create an m.beacon_info event
|
||||||
* @param {string} roomId
|
* @param {string} roomId
|
||||||
* @param {MBeaconInfoEventContent} beaconInfoContent
|
* @param {MBeaconInfoEventContent} beaconInfoContent
|
||||||
* @param {string} eventTypeSuffix - string to suffix event type
|
|
||||||
* to make event type unique.
|
|
||||||
* See MSC3489 for more context
|
|
||||||
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
|
||||||
* @returns {ISendEventResponse}
|
* @returns {ISendEventResponse}
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
public async unstable_createLiveBeacon(
|
public async unstable_createLiveBeacon(
|
||||||
roomId: Room["roomId"],
|
roomId: Room["roomId"],
|
||||||
beaconInfoContent: MBeaconInfoEventContent,
|
beaconInfoContent: MBeaconInfoEventContent,
|
||||||
eventTypeSuffix: string,
|
|
||||||
) {
|
) {
|
||||||
const userId = this.getUserId();
|
return this.unstable_setLiveBeacon(roomId, beaconInfoContent);
|
||||||
const eventType = M_BEACON_INFO_VARIABLE.name.replace('*', `${userId}.${eventTypeSuffix}`);
|
|
||||||
return this.unstable_setLiveBeacon(roomId, eventType, beaconInfoContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upsert a live beacon event
|
* Upsert a live beacon event
|
||||||
* using a specific m.beacon_info.* event variable type
|
* using a specific m.beacon_info.* event variable type
|
||||||
* @param {string} roomId string
|
* @param {string} roomId string
|
||||||
* @param {string} beaconInfoEventType event type including variable suffix
|
|
||||||
* @param {MBeaconInfoEventContent} beaconInfoContent
|
* @param {MBeaconInfoEventContent} beaconInfoContent
|
||||||
* @returns {ISendEventResponse}
|
* @returns {ISendEventResponse}
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
public async unstable_setLiveBeacon(
|
public async unstable_setLiveBeacon(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
beaconInfoEventType: string,
|
|
||||||
beaconInfoContent: MBeaconInfoEventContent,
|
beaconInfoContent: MBeaconInfoEventContent,
|
||||||
) {
|
) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
return this.sendStateEvent(roomId, beaconInfoEventType, beaconInfoContent, userId);
|
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { REFERENCE_RELATION } from "matrix-events-sdk";
|
import { REFERENCE_RELATION } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon";
|
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent } from "./@types/beacon";
|
||||||
import { MsgType } from "./@types/event";
|
import { MsgType } from "./@types/event";
|
||||||
import { TEXT_NODE_TYPE } from "./@types/extensible_events";
|
import { TEXT_NODE_TYPE } from "./@types/extensible_events";
|
||||||
import {
|
import {
|
||||||
@ -208,11 +208,9 @@ export const makeBeaconInfoContent: MakeBeaconInfoContent = (
|
|||||||
assetType,
|
assetType,
|
||||||
timestamp,
|
timestamp,
|
||||||
) => ({
|
) => ({
|
||||||
[M_BEACON_INFO.name]: {
|
description,
|
||||||
description,
|
timeout,
|
||||||
timeout,
|
live: isLive,
|
||||||
live: isLive,
|
|
||||||
},
|
|
||||||
[M_TIMESTAMP.name]: timestamp || Date.now(),
|
[M_TIMESTAMP.name]: timestamp || Date.now(),
|
||||||
[M_ASSET.name]: {
|
[M_ASSET.name]: {
|
||||||
type: assetType ?? LocationAssetType.Self,
|
type: assetType ?? LocationAssetType.Self,
|
||||||
@ -227,7 +225,7 @@ export type BeaconInfoState = MBeaconInfoContent & {
|
|||||||
* Flatten beacon info event content
|
* Flatten beacon info event content
|
||||||
*/
|
*/
|
||||||
export const parseBeaconInfoContent = (content: MBeaconInfoEventContent): BeaconInfoState => {
|
export const parseBeaconInfoContent = (content: MBeaconInfoEventContent): BeaconInfoState => {
|
||||||
const { description, timeout, live } = M_BEACON_INFO.findIn<MBeaconInfoContent>(content);
|
const { description, timeout, live } = content;
|
||||||
const { type: assetType } = M_ASSET.findIn<MAssetContent>(content);
|
const { type: assetType } = M_ASSET.findIn<MAssetContent>(content);
|
||||||
const timestamp = M_TIMESTAMP.findIn<number>(content);
|
const timestamp = M_TIMESTAMP.findIn<number>(content);
|
||||||
|
|
||||||
@ -243,14 +241,14 @@ export const parseBeaconInfoContent = (content: MBeaconInfoEventContent): Beacon
|
|||||||
export type MakeBeaconContent = (
|
export type MakeBeaconContent = (
|
||||||
uri: string,
|
uri: string,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
beaconInfoId: string,
|
beaconInfoEventId: string,
|
||||||
description?: string,
|
description?: string,
|
||||||
) => MBeaconEventContent;
|
) => MBeaconEventContent;
|
||||||
|
|
||||||
export const makeBeaconContent: MakeBeaconContent = (
|
export const makeBeaconContent: MakeBeaconContent = (
|
||||||
uri,
|
uri,
|
||||||
timestamp,
|
timestamp,
|
||||||
beaconInfoId,
|
beaconInfoEventId,
|
||||||
description,
|
description,
|
||||||
) => ({
|
) => ({
|
||||||
[M_LOCATION.name]: {
|
[M_LOCATION.name]: {
|
||||||
@ -260,6 +258,6 @@ export const makeBeaconContent: MakeBeaconContent = (
|
|||||||
[M_TIMESTAMP.name]: timestamp,
|
[M_TIMESTAMP.name]: timestamp,
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
rel_type: REFERENCE_RELATION.name,
|
rel_type: REFERENCE_RELATION.name,
|
||||||
event_id: beaconInfoId,
|
event_id: beaconInfoEventId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { M_BEACON_INFO } from "../@types/beacon";
|
|
||||||
import { BeaconInfoState, parseBeaconInfoContent } from "../content-helpers";
|
import { BeaconInfoState, parseBeaconInfoContent } from "../content-helpers";
|
||||||
import { MatrixEvent } from "../matrix";
|
import { MatrixEvent } from "../matrix";
|
||||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||||
@ -38,11 +37,13 @@ export const isTimestampInDuration = (
|
|||||||
timestamp: number,
|
timestamp: number,
|
||||||
): boolean => timestamp >= startTimestamp && startTimestamp + durationMs >= timestamp;
|
): boolean => timestamp >= startTimestamp && startTimestamp + durationMs >= timestamp;
|
||||||
|
|
||||||
export const isBeaconInfoEventType = (type: string) =>
|
// beacon info events are uniquely identified by
|
||||||
type.startsWith(M_BEACON_INFO.name) ||
|
// `<roomId>_<state_key>`
|
||||||
type.startsWith(M_BEACON_INFO.altName);
|
export type BeaconIdentifier = string;
|
||||||
|
export const getBeaconInfoIdentifier = (event: MatrixEvent): BeaconIdentifier =>
|
||||||
|
`${event.getRoomId()}_${event.getStateKey()}`;
|
||||||
|
|
||||||
// https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
// https://github.com/matrix-org/matrix-spec-proposals/pull/3672
|
||||||
export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.New>, BeaconEventHandlerMap> {
|
export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.New>, BeaconEventHandlerMap> {
|
||||||
public readonly roomId: string;
|
public readonly roomId: string;
|
||||||
private _beaconInfo: BeaconInfoState;
|
private _beaconInfo: BeaconInfoState;
|
||||||
@ -61,8 +62,8 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
|||||||
return this._isLive;
|
return this._isLive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get identifier(): string {
|
public get identifier(): BeaconIdentifier {
|
||||||
return this.beaconInfoEventType;
|
return getBeaconInfoIdentifier(this.rootEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get beaconInfoId(): string {
|
public get beaconInfoId(): string {
|
||||||
@ -82,9 +83,13 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
|||||||
}
|
}
|
||||||
|
|
||||||
public update(beaconInfoEvent: MatrixEvent): void {
|
public update(beaconInfoEvent: MatrixEvent): void {
|
||||||
if (beaconInfoEvent.getType() !== this.beaconInfoEventType) {
|
if (getBeaconInfoIdentifier(beaconInfoEvent) !== this.identifier) {
|
||||||
throw new Error('Invalid updating event');
|
throw new Error('Invalid updating event');
|
||||||
}
|
}
|
||||||
|
// don't update beacon with an older event
|
||||||
|
if (beaconInfoEvent.event.origin_server_ts < this.rootEvent.event.origin_server_ts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.rootEvent = beaconInfoEvent;
|
this.rootEvent = beaconInfoEvent;
|
||||||
this.setBeaconInfo(this.rootEvent);
|
this.setBeaconInfo(this.rootEvent);
|
||||||
|
|
||||||
|
@ -26,8 +26,11 @@ import { MatrixEvent } from "./event";
|
|||||||
import { MatrixClient } from "../client";
|
import { MatrixClient } from "../client";
|
||||||
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
||||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||||
import { Beacon, BeaconEvent, isBeaconInfoEventType, BeaconEventHandlerMap } from "./beacon";
|
import { Beacon, BeaconEvent, BeaconEventHandlerMap } from "./beacon";
|
||||||
import { TypedReEmitter } from "../ReEmitter";
|
import { TypedReEmitter } from "../ReEmitter";
|
||||||
|
import { M_BEACON_INFO } from "../@types/beacon";
|
||||||
|
import { getBeaconInfoIdentifier } from "./beacon";
|
||||||
|
import { BeaconIdentifier } from "..";
|
||||||
|
|
||||||
// possible statuses for out-of-band member loading
|
// possible statuses for out-of-band member loading
|
||||||
enum OobStatus {
|
enum OobStatus {
|
||||||
@ -80,8 +83,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
public events = new Map<string, Map<string, MatrixEvent>>(); // Map<eventType, Map<stateKey, MatrixEvent>>
|
public events = new Map<string, Map<string, MatrixEvent>>(); // Map<eventType, Map<stateKey, MatrixEvent>>
|
||||||
public paginationToken: string = null;
|
public paginationToken: string = null;
|
||||||
|
|
||||||
public readonly beacons = new Map<string, Beacon>();
|
public readonly beacons = new Map<BeaconIdentifier, Beacon>();
|
||||||
private liveBeaconIds: string[] = [];
|
private liveBeaconIds: BeaconIdentifier[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct room state.
|
* Construct room state.
|
||||||
@ -330,7 +333,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBeaconInfoEventType(event.getType())) {
|
if (M_BEACON_INFO.matches(event.getType())) {
|
||||||
this.setBeacon(event);
|
this.setBeacon(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,12 +440,15 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
private setBeacon(event: MatrixEvent): void {
|
private setBeacon(event: MatrixEvent): void {
|
||||||
if (this.beacons.has(event.getType())) {
|
const beaconIdentifier = getBeaconInfoIdentifier(event);
|
||||||
const beacon = this.beacons.get(event.getType());
|
if (this.beacons.has(beaconIdentifier)) {
|
||||||
|
const beacon = this.beacons.get(beaconIdentifier);
|
||||||
|
|
||||||
if (event.isRedacted()) {
|
if (event.isRedacted()) {
|
||||||
beacon.destroy();
|
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
|
||||||
this.beacons.delete(event.getType());
|
beacon.destroy();
|
||||||
|
this.beacons.delete(beaconIdentifier);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,7 +470,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
|
|
||||||
this.emit(BeaconEvent.New, event, beacon);
|
this.emit(BeaconEvent.New, event, beacon);
|
||||||
beacon.on(BeaconEvent.LivenessChange, this.onBeaconLivenessChange.bind(this));
|
beacon.on(BeaconEvent.LivenessChange, this.onBeaconLivenessChange.bind(this));
|
||||||
this.beacons.set(beacon.beaconInfoEventType, beacon);
|
this.beacons.set(beacon.identifier, beacon);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -477,7 +483,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
const prevHasLiveBeacons = !!this.liveBeaconIds?.length;
|
const prevHasLiveBeacons = !!this.liveBeaconIds?.length;
|
||||||
this.liveBeaconIds = Array.from(this.beacons.values())
|
this.liveBeaconIds = Array.from(this.beacons.values())
|
||||||
.filter(beacon => beacon.isLive)
|
.filter(beacon => beacon.isLive)
|
||||||
.map(beacon => beacon.beaconInfoId);
|
.map(beacon => beacon.identifier);
|
||||||
|
|
||||||
const hasLiveBeacons = !!this.liveBeaconIds.length;
|
const hasLiveBeacons = !!this.liveBeaconIds.length;
|
||||||
if (prevHasLiveBeacons !== hasLiveBeacons) {
|
if (prevHasLiveBeacons !== hasLiveBeacons) {
|
||||||
|
Reference in New Issue
Block a user