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 3 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
114 changes: 114 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,13 @@ import maplibregl from 'maplibre-gl';
import {
BeaconEvent,
getBeaconInfoIdentifier,
RelationType,
MatrixEventEvent,
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 +59,7 @@ describe('<MBeaconBody />', () => {
}),
getUserId: jest.fn().mockReturnValue(aliceId),
getRoom: jest.fn(),
redactEvent: jest.fn(),
});

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

describe('redaction', () => {
const aliceBeaconInfo = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);

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

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

const setupRoomWithBeacon = (locationEvents: MatrixEvent[] = []) => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
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', () => {
setupRoomWithBeacon([location1, location2]);

getComponent({ mxEvent: aliceBeaconInfo });

act(() => {
aliceBeaconInfo.emit(MatrixEventEvent.BeforeRedaction, aliceBeaconInfo, redactionEvent);
});

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

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

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

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

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

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

getComponent({ mxEvent: aliceBeaconInfo, getRelationsForEvent });

act(() => {
aliceBeaconInfo.emit(MatrixEventEvent.BeforeRedaction, aliceBeaconInfo, redactionEvent);
});

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

it('redacts related locations on beacon redaction', async () => {
setupRoomWithBeacon([location1, location2]);
const getRelationsForEvent = await mockGetRelationsForEvent([location1, location2]);

getComponent({ mxEvent: aliceBeaconInfo, getRelationsForEvent });

act(() => {
aliceBeaconInfo.emit(MatrixEventEvent.BeforeRedaction, aliceBeaconInfo, redactionEvent);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way we can "really" redact an event, instead of emitting a BeforeRedaction in at least one test? If the answer is no, fair enough :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved a bit closer to 'really' redacting

});

expect(getRelationsForEvent).toHaveBeenCalledWith(
aliceBeaconInfo.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