Live location sharing - stop sharing to beacons in rooms you left (#8187)
* remove beacons on membership changes * add addMembershipToMockedRoom test util Signed-off-by: Kerry Archibald <kerrya@element.io> * test remove beacons on membership changes Signed-off-by: Kerry Archibald <kerrya@element.io> * removelistener Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
e161f0b17b
commit
2adc972eec
4 changed files with 269 additions and 60 deletions
|
@ -20,6 +20,9 @@ import {
|
||||||
BeaconEvent,
|
BeaconEvent,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
Room,
|
Room,
|
||||||
|
RoomMember,
|
||||||
|
RoomState,
|
||||||
|
RoomStateEvent,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
BeaconInfoState, makeBeaconContent, makeBeaconInfoContent,
|
BeaconInfoState, makeBeaconContent, makeBeaconInfoContent,
|
||||||
|
@ -90,6 +93,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
protected async onNotReady() {
|
protected async onNotReady() {
|
||||||
this.matrixClient.removeListener(BeaconEvent.LivenessChange, this.onBeaconLiveness);
|
this.matrixClient.removeListener(BeaconEvent.LivenessChange, this.onBeaconLiveness);
|
||||||
this.matrixClient.removeListener(BeaconEvent.New, this.onNewBeacon);
|
this.matrixClient.removeListener(BeaconEvent.New, this.onNewBeacon);
|
||||||
|
this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers);
|
||||||
|
|
||||||
this.beacons.forEach(beacon => beacon.destroy());
|
this.beacons.forEach(beacon => beacon.destroy());
|
||||||
|
|
||||||
|
@ -102,6 +106,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
protected async onReady(): Promise<void> {
|
protected async onReady(): Promise<void> {
|
||||||
this.matrixClient.on(BeaconEvent.LivenessChange, this.onBeaconLiveness);
|
this.matrixClient.on(BeaconEvent.LivenessChange, this.onBeaconLiveness);
|
||||||
this.matrixClient.on(BeaconEvent.New, this.onNewBeacon);
|
this.matrixClient.on(BeaconEvent.New, this.onNewBeacon);
|
||||||
|
this.matrixClient.on(RoomStateEvent.Members, this.onRoomStateMembers);
|
||||||
|
|
||||||
this.initialiseBeaconState();
|
this.initialiseBeaconState();
|
||||||
}
|
}
|
||||||
|
@ -136,6 +141,10 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
return await this.updateBeaconEvent(beacon, { live: false });
|
return await this.updateBeaconEvent(beacon, { live: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listeners
|
||||||
|
*/
|
||||||
|
|
||||||
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;
|
||||||
|
@ -160,6 +169,33 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
this.emit(OwnBeaconStoreEvent.LivenessChange, this.getLiveBeaconIds());
|
this.emit(OwnBeaconStoreEvent.LivenessChange, this.getLiveBeaconIds());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for changes in membership in rooms with beacons
|
||||||
|
* and stop monitoring beacons in rooms user is no longer member of
|
||||||
|
*/
|
||||||
|
private onRoomStateMembers = (_event: MatrixEvent, roomState: RoomState, member: RoomMember): void => {
|
||||||
|
// no beacons for this room, ignore
|
||||||
|
if (
|
||||||
|
!this.beaconsByRoomId.has(roomState.roomId) ||
|
||||||
|
member.userId !== this.matrixClient.getUserId()
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check powerlevels here
|
||||||
|
// in PSF-797
|
||||||
|
|
||||||
|
// stop watching beacons in rooms where user is no longer a member
|
||||||
|
if (member.membership === 'leave' || member.membership === 'ban') {
|
||||||
|
this.beaconsByRoomId.get(roomState.roomId).forEach(this.removeBeacon);
|
||||||
|
this.beaconsByRoomId.delete(roomState.roomId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State management
|
||||||
|
*/
|
||||||
|
|
||||||
private initialiseBeaconState = () => {
|
private initialiseBeaconState = () => {
|
||||||
const userId = this.matrixClient.getUserId();
|
const userId = this.matrixClient.getUserId();
|
||||||
const visibleRooms = this.matrixClient.getVisibleRooms();
|
const visibleRooms = this.matrixClient.getVisibleRooms();
|
||||||
|
@ -187,6 +223,21 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
beacon.monitorLiveness();
|
beacon.monitorLiveness();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove listeners for a given beacon
|
||||||
|
* remove from state
|
||||||
|
* and update liveness if changed
|
||||||
|
*/
|
||||||
|
private removeBeacon = (beaconId: string): void => {
|
||||||
|
if (!this.beacons.has(beaconId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.beacons.get(beaconId).destroy();
|
||||||
|
this.beacons.delete(beaconId);
|
||||||
|
|
||||||
|
this.checkLiveness();
|
||||||
|
};
|
||||||
|
|
||||||
private checkLiveness = (): void => {
|
private checkLiveness = (): void => {
|
||||||
const prevLiveBeaconIds = this.getLiveBeaconIds();
|
const prevLiveBeaconIds = this.getLiveBeaconIds();
|
||||||
this.liveBeaconIds = [...this.beacons.values()]
|
this.liveBeaconIds = [...this.beacons.values()]
|
||||||
|
@ -218,20 +269,9 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateBeaconEvent = async (beacon: Beacon, update: Partial<BeaconInfoState>): Promise<void> => {
|
/**
|
||||||
const { description, timeout, timestamp, live, assetType } = {
|
* Geolocation
|
||||||
...beacon.beaconInfo,
|
*/
|
||||||
...update,
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateContent = makeBeaconInfoContent(timeout,
|
|
||||||
live,
|
|
||||||
description,
|
|
||||||
assetType,
|
|
||||||
timestamp);
|
|
||||||
|
|
||||||
await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, beacon.beaconInfoEventType, updateContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
private togglePollingLocation = () => {
|
private togglePollingLocation = () => {
|
||||||
if (!!this.liveBeaconIds.length) {
|
if (!!this.liveBeaconIds.length) {
|
||||||
|
@ -270,17 +310,6 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
|
this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onWatchedPosition = (position: GeolocationPosition) => {
|
|
||||||
const timedGeoPosition = mapGeolocationPositionToTimedGeo(position);
|
|
||||||
|
|
||||||
// if this is our first position, publish immediateley
|
|
||||||
if (!this.lastPublishedPositionTimestamp) {
|
|
||||||
this.publishLocationToBeacons(timedGeoPosition);
|
|
||||||
} else {
|
|
||||||
this.debouncedPublishLocationToBeacons(timedGeoPosition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private stopPollingLocation = () => {
|
private stopPollingLocation = () => {
|
||||||
clearInterval(this.locationInterval);
|
clearInterval(this.locationInterval);
|
||||||
this.locationInterval = undefined;
|
this.locationInterval = undefined;
|
||||||
|
@ -295,6 +324,70 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
|
this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onWatchedPosition = (position: GeolocationPosition) => {
|
||||||
|
const timedGeoPosition = mapGeolocationPositionToTimedGeo(position);
|
||||||
|
|
||||||
|
// if this is our first position, publish immediateley
|
||||||
|
if (!this.lastPublishedPositionTimestamp) {
|
||||||
|
this.publishLocationToBeacons(timedGeoPosition);
|
||||||
|
} else {
|
||||||
|
this.debouncedPublishLocationToBeacons(timedGeoPosition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onGeolocationError = async (error: GeolocationError): Promise<void> => {
|
||||||
|
this.geolocationError = error;
|
||||||
|
logger.error('Geolocation failed', this.geolocationError);
|
||||||
|
|
||||||
|
// other errors are considered non-fatal
|
||||||
|
// and self recovering
|
||||||
|
if (![
|
||||||
|
GeolocationError.Unavailable,
|
||||||
|
GeolocationError.PermissionDenied,
|
||||||
|
].includes(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stopPollingLocation();
|
||||||
|
// kill live beacons when location permissions are revoked
|
||||||
|
// TODO may need adjustment when PSF-797 is done
|
||||||
|
await Promise.all(this.liveBeaconIds.map(this.stopBeacon));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current location
|
||||||
|
* (as opposed to using watched location)
|
||||||
|
* and publishes it to all live beacons
|
||||||
|
*/
|
||||||
|
private publishCurrentLocationToBeacons = async () => {
|
||||||
|
try {
|
||||||
|
const position = await getCurrentPosition();
|
||||||
|
// TODO error handling
|
||||||
|
this.publishLocationToBeacons(mapGeolocationPositionToTimedGeo(position));
|
||||||
|
} catch (error) {
|
||||||
|
this.onGeolocationError(error?.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MatrixClient api
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends m.location events to all live beacons
|
* Sends m.location events to all live beacons
|
||||||
* Sets last published beacon
|
* Sets last published beacon
|
||||||
|
@ -316,38 +409,4 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
const content = makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId);
|
const content = makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId);
|
||||||
await this.matrixClient.sendEvent(beacon.roomId, M_BEACON.name, content);
|
await this.matrixClient.sendEvent(beacon.roomId, M_BEACON.name, content);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current location
|
|
||||||
* (as opposed to using watched location)
|
|
||||||
* and publishes it to all live beacons
|
|
||||||
*/
|
|
||||||
private publishCurrentLocationToBeacons = async () => {
|
|
||||||
try {
|
|
||||||
const position = await getCurrentPosition();
|
|
||||||
// TODO error handling
|
|
||||||
this.publishLocationToBeacons(mapGeolocationPositionToTimedGeo(position));
|
|
||||||
} catch (error) {
|
|
||||||
this.onGeolocationError(error?.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onGeolocationError = async (error: GeolocationError): Promise<void> => {
|
|
||||||
this.geolocationError = error;
|
|
||||||
logger.error('Geolocation failed', this.geolocationError);
|
|
||||||
|
|
||||||
// other errors are considered non-fatal
|
|
||||||
// and self recovering
|
|
||||||
if (![
|
|
||||||
GeolocationError.Unavailable,
|
|
||||||
GeolocationError.PermissionDenied,
|
|
||||||
].includes(error)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stopPollingLocation();
|
|
||||||
// kill live beacons when location permissions are revoked
|
|
||||||
// TODO may need adjustment when PSF-797 is done
|
|
||||||
await Promise.all(this.liveBeaconIds.map(this.stopBeacon));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Room, Beacon, BeaconEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
import {
|
||||||
|
Room,
|
||||||
|
Beacon,
|
||||||
|
BeaconEvent,
|
||||||
|
MatrixEvent,
|
||||||
|
RoomStateEvent,
|
||||||
|
RoomMember,
|
||||||
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { makeBeaconContent } from "matrix-js-sdk/src/content-helpers";
|
import { makeBeaconContent } from "matrix-js-sdk/src/content-helpers";
|
||||||
import { M_BEACON, M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
import { M_BEACON, M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
@ -23,6 +30,7 @@ import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../src/stores/OwnBeaconS
|
||||||
import {
|
import {
|
||||||
advanceDateAndTime,
|
advanceDateAndTime,
|
||||||
flushPromisesWithFakeTimers,
|
flushPromisesWithFakeTimers,
|
||||||
|
makeMembershipEvent,
|
||||||
resetAsyncStoreWithClient,
|
resetAsyncStoreWithClient,
|
||||||
setupAsyncStoreWithClient,
|
setupAsyncStoreWithClient,
|
||||||
} from "../test-utils";
|
} from "../test-utils";
|
||||||
|
@ -243,6 +251,7 @@ describe('OwnBeaconStore', () => {
|
||||||
|
|
||||||
expect(removeSpy.mock.calls[0]).toEqual(expect.arrayContaining([BeaconEvent.LivenessChange]));
|
expect(removeSpy.mock.calls[0]).toEqual(expect.arrayContaining([BeaconEvent.LivenessChange]));
|
||||||
expect(removeSpy.mock.calls[1]).toEqual(expect.arrayContaining([BeaconEvent.New]));
|
expect(removeSpy.mock.calls[1]).toEqual(expect.arrayContaining([BeaconEvent.New]));
|
||||||
|
expect(removeSpy.mock.calls[2]).toEqual(expect.arrayContaining([RoomStateEvent.Members]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('destroys beacons', async () => {
|
it('destroys beacons', async () => {
|
||||||
|
@ -509,6 +518,112 @@ describe('OwnBeaconStore', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('on room membership changes', () => {
|
||||||
|
it('ignores events for rooms without beacons', async () => {
|
||||||
|
const membershipEvent = makeMembershipEvent(room2Id, aliceId);
|
||||||
|
// no beacons for room2
|
||||||
|
const [, room2] = makeRoomsWithStateEvents([
|
||||||
|
alicesRoom1BeaconInfo,
|
||||||
|
]);
|
||||||
|
const store = await makeOwnBeaconStore();
|
||||||
|
const emitSpy = jest.spyOn(store, 'emit');
|
||||||
|
const oldLiveBeaconIds = store.getLiveBeaconIds();
|
||||||
|
|
||||||
|
mockClient.emit(
|
||||||
|
RoomStateEvent.Members,
|
||||||
|
membershipEvent,
|
||||||
|
room2.currentState,
|
||||||
|
new RoomMember(room2Id, aliceId),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(emitSpy).not.toHaveBeenCalled();
|
||||||
|
// strictly equal
|
||||||
|
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores events for membership changes that are not current user', async () => {
|
||||||
|
// bob joins room1
|
||||||
|
const membershipEvent = makeMembershipEvent(room1Id, bobId);
|
||||||
|
const member = new RoomMember(room1Id, bobId);
|
||||||
|
member.setMembershipEvent(membershipEvent);
|
||||||
|
|
||||||
|
const [room1] = makeRoomsWithStateEvents([
|
||||||
|
alicesRoom1BeaconInfo,
|
||||||
|
]);
|
||||||
|
const store = await makeOwnBeaconStore();
|
||||||
|
const emitSpy = jest.spyOn(store, 'emit');
|
||||||
|
const oldLiveBeaconIds = store.getLiveBeaconIds();
|
||||||
|
|
||||||
|
mockClient.emit(
|
||||||
|
RoomStateEvent.Members,
|
||||||
|
membershipEvent,
|
||||||
|
room1.currentState,
|
||||||
|
member,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(emitSpy).not.toHaveBeenCalled();
|
||||||
|
// strictly equal
|
||||||
|
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores events for membership changes that are not leave/ban', async () => {
|
||||||
|
// alice joins room1
|
||||||
|
const membershipEvent = makeMembershipEvent(room1Id, aliceId);
|
||||||
|
const member = new RoomMember(room1Id, aliceId);
|
||||||
|
member.setMembershipEvent(membershipEvent);
|
||||||
|
|
||||||
|
const [room1] = makeRoomsWithStateEvents([
|
||||||
|
alicesRoom1BeaconInfo,
|
||||||
|
alicesRoom2BeaconInfo,
|
||||||
|
]);
|
||||||
|
const store = await makeOwnBeaconStore();
|
||||||
|
const emitSpy = jest.spyOn(store, 'emit');
|
||||||
|
const oldLiveBeaconIds = store.getLiveBeaconIds();
|
||||||
|
|
||||||
|
mockClient.emit(
|
||||||
|
RoomStateEvent.Members,
|
||||||
|
membershipEvent,
|
||||||
|
room1.currentState,
|
||||||
|
member,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(emitSpy).not.toHaveBeenCalled();
|
||||||
|
// strictly equal
|
||||||
|
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('destroys and removes beacons when current user leaves room', async () => {
|
||||||
|
// alice leaves room1
|
||||||
|
const membershipEvent = makeMembershipEvent(room1Id, aliceId, 'leave');
|
||||||
|
const member = new RoomMember(room1Id, aliceId);
|
||||||
|
member.setMembershipEvent(membershipEvent);
|
||||||
|
|
||||||
|
const [room1] = makeRoomsWithStateEvents([
|
||||||
|
alicesRoom1BeaconInfo,
|
||||||
|
alicesRoom2BeaconInfo,
|
||||||
|
]);
|
||||||
|
const store = await makeOwnBeaconStore();
|
||||||
|
const room1BeaconInstance = store.beacons.get(alicesRoom1BeaconInfo.getType());
|
||||||
|
const beaconDestroySpy = jest.spyOn(room1BeaconInstance, 'destroy');
|
||||||
|
const emitSpy = jest.spyOn(store, 'emit');
|
||||||
|
|
||||||
|
mockClient.emit(
|
||||||
|
RoomStateEvent.Members,
|
||||||
|
membershipEvent,
|
||||||
|
room1.currentState,
|
||||||
|
member,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(
|
||||||
|
OwnBeaconStoreEvent.LivenessChange,
|
||||||
|
// other rooms beacons still live
|
||||||
|
[alicesRoom2BeaconInfo.getType()],
|
||||||
|
);
|
||||||
|
expect(beaconDestroySpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(store.getLiveBeaconIds(room1Id)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('stopBeacon()', () => {
|
describe('stopBeacon()', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
makeRoomsWithStateEvents([
|
makeRoomsWithStateEvents([
|
||||||
|
|
|
@ -2,6 +2,7 @@ export * from './beacon';
|
||||||
export * from './client';
|
export * from './client';
|
||||||
export * from './location';
|
export * from './location';
|
||||||
export * from './platform';
|
export * from './platform';
|
||||||
|
export * from './room';
|
||||||
export * from './test-utils';
|
export * from './test-utils';
|
||||||
// TODO @@TR: Export voice.ts, which currently isn't exported here because it causes all tests to depend on skinning
|
// TODO @@TR: Export voice.ts, which currently isn't exported here because it causes all tests to depend on skinning
|
||||||
export * from './wrappers';
|
export * from './wrappers';
|
||||||
|
|
34
test/test-utils/room.ts
Normal file
34
test/test-utils/room.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
EventType,
|
||||||
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { mkEvent } from "./test-utils";
|
||||||
|
|
||||||
|
export const makeMembershipEvent = (
|
||||||
|
roomId: string, userId: string, membership = 'join',
|
||||||
|
) => mkEvent({
|
||||||
|
event: true,
|
||||||
|
type: EventType.RoomMember,
|
||||||
|
room: roomId,
|
||||||
|
user: userId,
|
||||||
|
skey: userId,
|
||||||
|
content: { membership },
|
||||||
|
ts: Date.now(),
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue