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

Live location sharing - handle geolocation errors #8179

Merged
merged 3 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/components/views/beacon/LeftPanelLiveShareWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ interface Props {
}

const LeftPanelLiveShareWarning: React.FC<Props> = ({ isMinimized }) => {
const hasLiveBeacons = useEventEmitterState(
const isMonitoringLiveLocation = useEventEmitterState(
OwnBeaconStore.instance,
OwnBeaconStoreEvent.LivenessChange,
() => OwnBeaconStore.instance.hasLiveBeacons(),
OwnBeaconStoreEvent.MonitoringLivePosition,
() => OwnBeaconStore.instance.isMonitoringLiveLocation,
);

if (!hasLiveBeacons) {
if (!isMonitoringLiveLocation) {
return null;
}

Expand Down
9 changes: 8 additions & 1 deletion src/components/views/beacon/RoomLiveShareWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ type LiveBeaconsState = {
const useLiveBeacons = (roomId: Room['roomId']): LiveBeaconsState => {
const [stoppingInProgress, setStoppingInProgress] = useState(false);

// do we have an active geolocation.watchPosition
const isMonitoringLiveLocation = useEventEmitterState(
OwnBeaconStore.instance,
OwnBeaconStoreEvent.MonitoringLivePosition,
() => OwnBeaconStore.instance.isMonitoringLiveLocation,
);

const liveBeaconIds = useEventEmitterState(
OwnBeaconStore.instance,
OwnBeaconStoreEvent.LivenessChange,
Expand All @@ -88,7 +95,7 @@ const useLiveBeacons = (roomId: Room['roomId']): LiveBeaconsState => {
setStoppingInProgress(false);
}, [liveBeaconIds]);

if (!liveBeaconIds?.length) {
if (!isMonitoringLiveLocation || !liveBeaconIds?.length) {
return {};
}

Expand Down
57 changes: 45 additions & 12 deletions src/stores/OwnBeaconStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const isOwnBeacon = (beacon: Beacon, userId: string): boolean => beacon.beaconIn

export enum OwnBeaconStoreEvent {
LivenessChange = 'OwnBeaconStore.LivenessChange',
MonitoringLivePosition = 'OwnBeaconStore.MonitoringLivePosition',
}

const MOVING_UPDATE_INTERVAL = 2000;
Expand Down Expand Up @@ -232,18 +233,28 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, beacon.beaconInfoEventType, updateContent);
};

private togglePollingLocation = async (): Promise<void> => {
private togglePollingLocation = () => {
if (!!this.liveBeaconIds.length) {
return this.startPollingLocation();
this.startPollingLocation();
} else {
this.stopPollingLocation();
}
return this.stopPollingLocation();
};

private startPollingLocation = async () => {
// clear any existing interval
this.stopPollingLocation();

this.clearPositionWatch = await watchPosition(this.onWatchedPosition, this.onWatchedPositionError);
try {
this.clearPositionWatch = await watchPosition(
this.onWatchedPosition,
this.onGeolocationError,
);
} catch (error) {
this.onGeolocationError(error?.message);
// don't set locationInterval if geolocation failed to setup
return;
}

this.locationInterval = setInterval(() => {
if (!this.lastPublishedPositionTimestamp) {
Expand All @@ -255,6 +266,8 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.publishCurrentLocationToBeacons();
}
}, STATIC_UPDATE_INTERVAL);

this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
};

private onWatchedPosition = (position: GeolocationPosition) => {
Expand All @@ -268,11 +281,6 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
}
};

private onWatchedPositionError = (error: GeolocationError) => {
this.geolocationError = error;
logger.error(this.geolocationError);
};

private stopPollingLocation = () => {
clearInterval(this.locationInterval);
this.locationInterval = undefined;
Expand All @@ -283,6 +291,8 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.clearPositionWatch();
this.clearPositionWatch = undefined;
}

this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
};

/**
Expand Down Expand Up @@ -313,8 +323,31 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* and publishes it to all live beacons
*/
private publishCurrentLocationToBeacons = async () => {
const position = await getCurrentPosition();
// TODO error handling
this.publishLocationToBeacons(mapGeolocationPositionToTimedGeo(position));
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));
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ describe('<LeftPanelLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});

describe('when user has live beacons', () => {
describe('when user has live location monitor', () => {
beforeEach(() => {
mocked(OwnBeaconStore.instance).hasLiveBeacons.mockReturnValue(true);
mocked(OwnBeaconStore.instance).isMonitoringLiveLocation = true;
});
it('renders correctly when not minimized', () => {
const component = getComponent();
Expand All @@ -68,8 +68,8 @@ describe('<LeftPanelLiveShareWarning />', () => {
// started out rendered
expect(component.html()).toBeTruthy();

mocked(OwnBeaconStore.instance).hasLiveBeacons.mockReturnValue(false);
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.LivenessChange, false);
mocked(OwnBeaconStore.instance).isMonitoringLiveLocation = false;
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.MonitoringLivePosition);

await flushPromises();
component.setProps({});
Expand Down
38 changes: 35 additions & 3 deletions test/components/views/beacon/RoomLiveShareWarning-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ 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 { logger } from 'matrix-js-sdk/src/logger';

import '../../../skinned-sdk';
import RoomLiveShareWarning from '../../../../src/components/views/beacon/RoomLiveShareWarning';
import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore';
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnBeaconStore';
import {
advanceDateAndTime,
findByTestId,
Expand All @@ -33,7 +34,6 @@ import {
} from '../../../test-utils';

jest.useFakeTimers();
mockGeolocation();
describe('<RoomLiveShareWarning />', () => {
const aliceId = '@alice:server.org';
const room1Id = '$room1:server.org';
Expand Down Expand Up @@ -94,6 +94,7 @@ describe('<RoomLiveShareWarning />', () => {
};

beforeEach(() => {
mockGeolocation();
jest.spyOn(global.Date, 'now').mockReturnValue(now);
mockClient.unstable_setLiveBeacon.mockClear();
});
Expand Down Expand Up @@ -123,7 +124,22 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});

describe('when user has live beacons', () => {
it('does not render when geolocation is not working', async () => {
jest.spyOn(logger, 'error').mockImplementation(() => { });
// @ts-ignore
navigator.geolocation = undefined;
await act(async () => {
await makeRoomsWithStateEvents([room1Beacon1, room2Beacon1, room2Beacon2]);
await makeOwnBeaconStore();
});
const component = getComponent({ roomId: room1Id });

// beacons have generated ids that break snapshots
// assert on html
expect(component.html()).toBeNull();
});

describe('when user has live beacons and geolocation is available', () => {
beforeEach(async () => {
await act(async () => {
await makeRoomsWithStateEvents([room1Beacon1, room2Beacon1, room2Beacon2]);
Expand Down Expand Up @@ -164,6 +180,22 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});

it('removes itself when user stops monitoring live position', async () => {
const component = getComponent({ roomId: room1Id });
// started out rendered
expect(component.html()).toBeTruthy();

act(() => {
// cheat to clear this
// @ts-ignore
OwnBeaconStore.instance.clearPositionWatch = undefined;
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
component.setProps({});
});

expect(component.html()).toBe(null);
});

it('renders when user adds a live beacon', async () => {
const component = getComponent({ roomId: room3Id });
// started out not rendered
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<LeftPanelLiveShareWarning /> when user has live beacons renders correctly when minimized 1`] = `
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders correctly when minimized 1`] = `
<LeftPanelLiveShareWarning
isMinimized={true}
>
Expand All @@ -15,7 +15,7 @@ exports[`<LeftPanelLiveShareWarning /> when user has live beacons renders correc
</LeftPanelLiveShareWarning>
`;

exports[`<LeftPanelLiveShareWarning /> when user has live beacons renders correctly when not minimized 1`] = `
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders correctly when not minimized 1`] = `
<LeftPanelLiveShareWarning>
<div
className="mx_LeftPanelLiveShareWarning"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<RoomLiveShareWarning /> when user has live beacons renders correctly with one live beacon in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">1h left</span><button data-test-id=\\"room-live-share-stop-sharing\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available renders correctly with one live beacon in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">1h left</span><button data-test-id=\\"room-live-share-stop-sharing\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;

exports[`<RoomLiveShareWarning /> when user has live beacons renders correctly with two live beacons in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">12h left</span><button data-test-id=\\"room-live-share-stop-sharing\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available renders correctly with two live beacons in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">12h left</span><button data-test-id=\\"room-live-share-stop-sharing\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;
Loading