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

Live location share - redact related locations on beacon redaction (PSF-1151) #8926

Merged
merged 4 commits into from
Jun 29, 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
48 changes: 44 additions & 4 deletions src/components/views/messages/MBeaconBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { useContext, useEffect, useState } from 'react';
import { Beacon, BeaconEvent, MatrixEvent } from 'matrix-js-sdk/src/matrix';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import {
Beacon,
BeaconEvent,
MatrixEvent,
MatrixEventEvent,
MatrixClient,
RelationType,
} from 'matrix-js-sdk/src/matrix';
import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers';
import { randomString } from 'matrix-js-sdk/src/randomstring';
import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon';

import MatrixClientContext from '../../../contexts/MatrixClientContext';
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
Expand All @@ -27,10 +35,11 @@ import { isBeaconWaitingToStart, useBeacon } from '../../../utils/beacon';
import { isSelfLocation } from '../../../utils/location';
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
import BeaconStatus from '../beacon/BeaconStatus';
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
import Map from '../location/Map';
import MapFallback from '../location/MapFallback';
import SmartMarker from '../location/SmartMarker';
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
import { GetRelationsForEvent } from '../rooms/EventTile';
import BeaconViewDialog from '../beacon/BeaconViewDialog';
import { IBodyProps } from "./IBodyProps";

Expand Down Expand Up @@ -87,7 +96,36 @@ const useUniqueId = (eventId: string): string => {
return id;
};

const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) => {
// remove related beacon locations on beacon redaction
const useHandleBeaconRedaction = (
event: MatrixEvent,
getRelationsForEvent: GetRelationsForEvent,
cli: MatrixClient,
): void => {
const onBeforeBeaconInfoRedaction = useCallback((_event: MatrixEvent, redactionEvent: MatrixEvent) => {
const relations = getRelationsForEvent ?
getRelationsForEvent(event.getId(), RelationType.Reference, M_BEACON.name) :
undefined;

relations?.getRelations()?.forEach(locationEvent => {
cli.redactEvent(
locationEvent.getRoomId(),
locationEvent.getId(),
undefined,
redactionEvent.getContent(),
);
});
}, [event, getRelationsForEvent, cli]);

useEffect(() => {
event.addListener(MatrixEventEvent.BeforeRedaction, onBeforeBeaconInfoRedaction);
return () => {
event.removeListener(MatrixEventEvent.BeforeRedaction, onBeforeBeaconInfoRedaction);
};
}, [event, onBeforeBeaconInfoRedaction]);
};

const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelationsForEvent }, ref) => {
const {
beacon,
isLive,
Expand All @@ -102,6 +140,8 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();

useHandleBeaconRedaction(mxEvent, getRelationsForEvent, matrixClient);

const onClick = () => {
if (displayStatus !== BeaconDisplayStatus.Active) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
},
"org.matrix.msc3488.ts": 1647270879404,
},
"room_id": undefined,
"sender": "@alice:server",
"type": "org.matrix.msc3672.beacon",
},
Expand Down
125 changes: 125 additions & 0 deletions test/components/views/messages/MBeaconBody-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import maplibregl from 'maplibre-gl';
import {
BeaconEvent,
getBeaconInfoIdentifier,
RelationType,
MatrixEvent,
EventType,
} from 'matrix-js-sdk/src/matrix';
import { Relations } from 'matrix-js-sdk/src/models/relations';
import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon';

import MBeaconBody from '../../../../src/components/views/messages/MBeaconBody';
import {
Expand Down Expand Up @@ -53,6 +58,7 @@ describe('<MBeaconBody />', () => {
}),
getUserId: jest.fn().mockReturnValue(aliceId),
getRoom: jest.fn(),
redactEvent: jest.fn(),
});

const defaultEvent = makeBeaconInfoEvent(aliceId,
Expand Down Expand Up @@ -333,4 +339,123 @@ describe('<MBeaconBody />', () => {
expect(mockMarker.setLngLat).toHaveBeenCalledWith({ lat: 52, lon: 42 });
});
});

describe('redaction', () => {
const makeEvents = (): {
beaconInfoEvent: MatrixEvent;
location1: MatrixEvent;
location2: MatrixEvent;
} => {
const beaconInfoEvent = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);

const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: beaconInfoEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
roomId,
);
location1.event.event_id = '1';
const location2 = makeBeaconEvent(
aliceId, { beaconInfoId: beaconInfoEvent.getId(), geoUri: 'geo:52,42', timestamp: now + 10000 },
roomId,
);
location2.event.event_id = '2';
return { beaconInfoEvent, location1, location2 };
};

const redactionEvent = new MatrixEvent({ type: EventType.RoomRedaction, content: { reason: 'test reason' } });

const setupRoomWithBeacon = (beaconInfoEvent, locationEvents: MatrixEvent[] = []) => {
const room = makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(beaconInfoEvent));
beaconInstance.addLocations(locationEvents);
};
const mockGetRelationsForEvent = (locationEvents: MatrixEvent[] = []) => {
const relations = new Relations(RelationType.Reference, M_BEACON.name, mockClient);
jest.spyOn(relations, 'getRelations').mockReturnValue(locationEvents);

const getRelationsForEvent = jest.fn().mockReturnValue(relations);

return getRelationsForEvent;
};

it('does nothing when getRelationsForEvent is falsy', () => {
const { beaconInfoEvent, location1, location2 } = makeEvents();
setupRoomWithBeacon(beaconInfoEvent, [location1, location2]);

getComponent({ mxEvent: beaconInfoEvent });

act(() => {
beaconInfoEvent.makeRedacted(redactionEvent);
});

// no error, no redactions
expect(mockClient.redactEvent).not.toHaveBeenCalled();
});

it('cleans up redaction listener on unmount', () => {
const { beaconInfoEvent, location1, location2 } = makeEvents();
setupRoomWithBeacon(beaconInfoEvent, [location1, location2]);
const removeListenerSpy = jest.spyOn(beaconInfoEvent, 'removeListener');

const component = getComponent({ mxEvent: beaconInfoEvent });

act(() => {
component.unmount();
});

expect(removeListenerSpy).toHaveBeenCalled();
});

it('does nothing when beacon has no related locations', async () => {
const { beaconInfoEvent } = makeEvents();
// no locations
setupRoomWithBeacon(beaconInfoEvent, []);
const getRelationsForEvent = await mockGetRelationsForEvent();

getComponent({ mxEvent: beaconInfoEvent, getRelationsForEvent });

act(() => {
beaconInfoEvent.makeRedacted(redactionEvent);
});

expect(getRelationsForEvent).toHaveBeenCalledWith(
beaconInfoEvent.getId(), RelationType.Reference, M_BEACON.name,
);
expect(mockClient.redactEvent).not.toHaveBeenCalled();
});

it('redacts related locations on beacon redaction', async () => {
const { beaconInfoEvent, location1, location2 } = makeEvents();
setupRoomWithBeacon(beaconInfoEvent, [location1, location2]);

const getRelationsForEvent = await mockGetRelationsForEvent([location1, location2]);

getComponent({ mxEvent: beaconInfoEvent, getRelationsForEvent });

act(() => {
beaconInfoEvent.makeRedacted(redactionEvent);
});

expect(getRelationsForEvent).toHaveBeenCalledWith(
beaconInfoEvent.getId(), RelationType.Reference, M_BEACON.name,
);
expect(mockClient.redactEvent).toHaveBeenCalledTimes(2);
expect(mockClient.redactEvent).toHaveBeenCalledWith(
roomId,
location1.getId(),
undefined,
{ reason: 'test reason' },
);
expect(mockClient.redactEvent).toHaveBeenCalledWith(
roomId,
location2.getId(),
undefined,
{ reason: 'test reason' },
);
});
});
});
2 changes: 2 additions & 0 deletions test/test-utils/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const DEFAULT_CONTENT_PROPS: ContentProps = {
export const makeBeaconEvent = (
sender: string,
contentProps: Partial<ContentProps> = {},
roomId?: string,
): MatrixEvent => {
const { geoUri, timestamp, beaconInfoId, description } = {
...DEFAULT_CONTENT_PROPS,
Expand All @@ -105,6 +106,7 @@ export const makeBeaconEvent = (

return new MatrixEvent({
type: M_BEACON.name,
room_id: roomId,
sender,
content: makeBeaconContent(geoUri, timestamp, beaconInfoId, description),
});
Expand Down