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

Commit

Permalink
Live location sharing - update beacon_info implementation to latest M…
Browse files Browse the repository at this point in the history
…SC (#8256)

* update calls to set and createLiveBeacon

Signed-off-by: Kerry Archibald <kerrya@element.io>

* fix stop beacon

Signed-off-by: Kerry Archibald <kerrya@element.io>

* remove variable event type from beacon utils

Signed-off-by: Kerry Archibald <kerrya@element.io>

* use beacon identifier

Signed-off-by: Kerry Archibald <kerrya@element.io>

* fix RoomLiveShareWarning tests

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add case for beacon update

Signed-off-by: Kerry Archibald <kerrya@element.io>

* lint

Signed-off-by: Kerry Archibald <kerrya@element.io>

* more lint

Signed-off-by: Kerry Archibald <kerrya@element.io>
  • Loading branch information
Kerry authored Apr 8, 2022
1 parent 610225a commit 03d0969
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 108 deletions.
25 changes: 18 additions & 7 deletions src/components/views/beacon/RoomLiveShareWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ limitations under the License.

import React, { useCallback, useEffect, useState } from 'react';
import classNames from 'classnames';
import { Room, Beacon } from 'matrix-js-sdk/src/matrix';
import {
Room,
Beacon,
BeaconEvent,
BeaconIdentifier,
} from 'matrix-js-sdk/src/matrix';

import { formatDuration } from '../../../DateUtils';
import { _t } from '../../../languageHandler';
Expand Down Expand Up @@ -45,16 +50,22 @@ const getUpdateInterval = (ms: number) => {
return 1000;
};
const useMsRemaining = (beacon: Beacon): number => {
const [msRemaining, setMsRemaining] = useState(() => getBeaconMsUntilExpiry(beacon));
const beaconInfo = useEventEmitterState(
beacon,
BeaconEvent.Update,
() => beacon.beaconInfo,
);

const [msRemaining, setMsRemaining] = useState(() => getBeaconMsUntilExpiry(beaconInfo));

useEffect(() => {
setMsRemaining(getBeaconMsUntilExpiry(beacon));
}, [beacon]);
setMsRemaining(getBeaconMsUntilExpiry(beaconInfo));
}, [beaconInfo]);

const updateMsRemaining = useCallback(() => {
const ms = getBeaconMsUntilExpiry(beacon);
const ms = getBeaconMsUntilExpiry(beaconInfo);
setMsRemaining(ms);
}, [beacon]);
}, [beaconInfo]);

useInterval(updateMsRemaining, getUpdateInterval(msRemaining));

Expand All @@ -74,7 +85,7 @@ type LiveBeaconsState = {
hasStopSharingError?: boolean;
hasWireError?: boolean;
};
const useLiveBeacons = (liveBeaconIds: string[], roomId: string): LiveBeaconsState => {
const useLiveBeacons = (liveBeaconIds: BeaconIdentifier[], roomId: string): LiveBeaconsState => {
const [stoppingInProgress, setStoppingInProgress] = useState(false);
const [error, setError] = useState<Error>();

Expand Down
3 changes: 1 addition & 2 deletions src/components/views/location/shareLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ export const shareLiveLocation = (
description,
LocationAssetType.Self,
),
// use timestamp as unique suffix in interim
`${Date.now()}`);
);
} catch (error) {
handleShareError(error, openMenu, LocationShareType.Live);
}
Expand Down
32 changes: 23 additions & 9 deletions src/stores/OwnBeaconStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
import { debounce } from "lodash";
import {
Beacon,
BeaconIdentifier,
BeaconEvent,
MatrixEvent,
Room,
Expand Down Expand Up @@ -58,22 +59,22 @@ const STATIC_UPDATE_INTERVAL = 30000;
const BAIL_AFTER_CONSECUTIVE_ERROR_COUNT = 2;

type OwnBeaconStoreState = {
beacons: Map<string, Beacon>;
beacons: Map<BeaconIdentifier, Beacon>;
beaconWireErrors: Map<string, Beacon>;
beaconsByRoomId: Map<Room['roomId'], Set<string>>;
liveBeaconIds: string[];
beaconsByRoomId: Map<Room['roomId'], Set<BeaconIdentifier>>;
liveBeaconIds: BeaconIdentifier[];
};
export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
private static internalInstance = new OwnBeaconStore();
// users beacons, keyed by event type
public readonly beacons = new Map<string, Beacon>();
public readonly beaconsByRoomId = new Map<Room['roomId'], Set<string>>();
public readonly beacons = new Map<BeaconIdentifier, Beacon>();
public readonly beaconsByRoomId = new Map<Room['roomId'], Set<BeaconIdentifier>>();
/**
* Track over the wire errors for published positions
* Counts consecutive wire errors per beacon
* Reset on successful publish of location
*/
public readonly beaconWireErrorCounts = new Map<string, number>();
public readonly beaconWireErrorCounts = new Map<BeaconIdentifier, number>();
/**
* ids of live beacons
* ordered by creation time descending
Expand Down Expand Up @@ -108,6 +109,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
protected async onNotReady() {
this.matrixClient.removeListener(BeaconEvent.LivenessChange, this.onBeaconLiveness);
this.matrixClient.removeListener(BeaconEvent.New, this.onNewBeacon);
this.matrixClient.removeListener(BeaconEvent.Update, this.onUpdateBeacon);
this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers);

this.beacons.forEach(beacon => beacon.destroy());
Expand All @@ -122,6 +124,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
protected async onReady(): Promise<void> {
this.matrixClient.on(BeaconEvent.LivenessChange, this.onBeaconLiveness);
this.matrixClient.on(BeaconEvent.New, this.onNewBeacon);
this.matrixClient.removeListener(BeaconEvent.Update, this.onUpdateBeacon);
this.matrixClient.on(RoomStateEvent.Members, this.onRoomStateMembers);

this.initialiseBeaconState();
Expand Down Expand Up @@ -177,8 +180,8 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
return this.beacons.get(beaconId);
};

public stopBeacon = async (beaconInfoType: string): Promise<void> => {
const beacon = this.beacons.get(beaconInfoType);
public stopBeacon = async (beaconIdentifier: string): Promise<void> => {
const beacon = this.beacons.get(beaconIdentifier);
// if no beacon, or beacon is already explicitly set isLive: false
// do nothing
if (!beacon?.beaconInfo?.live) {
Expand All @@ -200,6 +203,17 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.checkLiveness();
};

/**
* This will be called when a beacon is replaced
*/
private onUpdateBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) {
return;
}

this.checkLiveness();
};

private onBeaconLiveness = (isLive: boolean, beacon: Beacon): void => {
// check if we care about this beacon
if (!this.beacons.has(beacon.identifier)) {
Expand Down Expand Up @@ -439,7 +453,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
assetType,
timestamp);

await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, beacon.beaconInfoEventType, updateContent);
await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, updateContent);
};

/**
Expand Down
5 changes: 3 additions & 2 deletions src/utils/beacon/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { BeaconInfoState } from "matrix-js-sdk/src/content-helpers";
import { Beacon } from "matrix-js-sdk/src/matrix";

/**
Expand All @@ -26,8 +27,8 @@ import { Beacon } from "matrix-js-sdk/src/matrix";
export const msUntilExpiry = (startTimestamp: number, durationMs: number): number =>
Math.max(0, (startTimestamp + durationMs) - Date.now());

export const getBeaconMsUntilExpiry = (beacon: Beacon): number =>
msUntilExpiry(beacon.beaconInfo.timestamp, beacon.beaconInfo.timeout);
export const getBeaconMsUntilExpiry = (beaconInfo: BeaconInfoState): number =>
msUntilExpiry(beaconInfo.timestamp, beaconInfo.timeout);

export const getBeaconExpiryTimestamp = (beacon: Beacon): number =>
beacon.beaconInfo.timestamp + beacon.beaconInfo.timeout;
Expand Down
33 changes: 25 additions & 8 deletions test/components/views/beacon/RoomLiveShareWarning-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { Room, Beacon, BeaconEvent } from 'matrix-js-sdk/src/matrix';
import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';

import RoomLiveShareWarning from '../../../../src/components/views/beacon/RoomLiveShareWarning';
Expand Down Expand Up @@ -221,6 +221,25 @@ describe('<RoomLiveShareWarning />', () => {
expect(getExpiryText(component)).toEqual('35m left');
});

it('updates beacon time left when beacon updates', () => {
const component = getComponent({ roomId: room1Id });
expect(getExpiryText(component)).toEqual('1h left');

expect(getExpiryText(component)).toEqual('1h left');

act(() => {
const beacon = OwnBeaconStore.instance.getBeaconById(getBeaconInfoIdentifier(room1Beacon1));
const room1Beacon1Update = makeBeaconInfoEvent(aliceId, room1Id, {
isLive: true,
timeout: 3 * HOUR_MS,
}, '$0');
beacon.update(room1Beacon1Update);
});

// update to expiry of new beacon
expect(getExpiryText(component)).toEqual('3h left');
});

it('clears expiry time interval on unmount', () => {
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
const component = getComponent({ roomId: room1Id });
Expand All @@ -242,7 +261,7 @@ describe('<RoomLiveShareWarning />', () => {
component.setProps({});
});

expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledTimes(2);
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalled();
expect(component.find('Spinner').length).toBeTruthy();
expect(findByTestId(component, 'room-live-share-primary-button').at(0).props().disabled).toBeTruthy();
});
Expand Down Expand Up @@ -314,7 +333,7 @@ describe('<RoomLiveShareWarning />', () => {
// update mock and emit event
act(() => {
hasWireErrorsSpy.mockReturnValue(true);
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.WireError, room2Beacon1.getType());
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.WireError, getBeaconInfoIdentifier(room2Beacon1));
});
component.setProps({});

Expand All @@ -332,7 +351,7 @@ describe('<RoomLiveShareWarning />', () => {
// update mock and emit event
act(() => {
hasWireErrorsSpy.mockReturnValue(false);
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.WireError, room2Beacon1.getType());
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.WireError, getBeaconInfoIdentifier(room2Beacon1));
});
component.setProps({});

Expand All @@ -353,8 +372,7 @@ describe('<RoomLiveShareWarning />', () => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
});

expect(resetErrorSpy).toHaveBeenCalledWith(room2Beacon1.getType());
expect(resetErrorSpy).toHaveBeenCalledWith(room2Beacon2.getType());
expect(resetErrorSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));
});

it('clicking close button stops beacons', async () => {
Expand All @@ -367,8 +385,7 @@ describe('<RoomLiveShareWarning />', () => {
findByTestId(component, 'room-live-share-wire-error-close-button').at(0).simulate('click');
});

expect(stopBeaconSpy).toHaveBeenCalledWith(room2Beacon1.getType());
expect(stopBeaconSpy).toHaveBeenCalledWith(room2Beacon2.getType());
expect(stopBeaconSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is
<RoomLiveShareWarningInner
liveBeaconIds={
Array [
"org.matrix.msc3489.beacon_info.@alice:server.org.3",
"org.matrix.msc3489.beacon_info.@alice:server.org.4",
"$room2:server.org_@alice:server.org",
]
}
roomId="$room2:server.org"
Expand Down
14 changes: 5 additions & 9 deletions test/components/views/location/LocationShareMenu-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { mocked } from 'jest-mock';
import { act } from 'react-dom/test-utils';
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
import { M_ASSET, LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import { logger } from 'matrix-js-sdk/src/logger';

Expand Down Expand Up @@ -310,16 +309,13 @@ describe('<LocationShareMenu />', () => {
});

expect(onFinished).toHaveBeenCalled();
const [eventRoomId, eventContent, eventTypeSuffix] = mockClient.unstable_createLiveBeacon.mock.calls[0];
const [eventRoomId, eventContent] = mockClient.unstable_createLiveBeacon.mock.calls[0];
expect(eventRoomId).toEqual(defaultProps.roomId);
expect(eventTypeSuffix).toBeTruthy();
expect(eventContent).toEqual(expect.objectContaining({
[M_BEACON_INFO.name]: {
// default timeout
timeout: DEFAULT_DURATION_MS,
description: `Ernie's live location`,
live: true,
},
// default timeout
timeout: DEFAULT_DURATION_MS,
description: `Ernie's live location`,
live: true,
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
Expand Down
Loading

0 comments on commit 03d0969

Please sign in to comment.