From 4870fb59478b3a6f74cdb1f97bdd8d00de627694 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 27 Jun 2022 16:46:12 +0200 Subject: [PATCH 1/4] redact beacon locations on redaction --- .../context_menus/MessageContextMenu.tsx | 4 ++ src/components/views/messages/MBeaconBody.tsx | 47 +++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 6d7eebdc0a3..98e2c2a8609 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -52,6 +52,7 @@ import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwa import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload"; import { createMapSiteLinkFromEvent } from '../../../utils/location'; import { getForwardableEvent } from '../../../events/forward/getForwardableEvent'; +import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon'; interface IProps extends IPosition { chevronFace: ChevronFace; @@ -182,6 +183,9 @@ export default class MessageContextMenu extends React.Component private onRedactClick = (): void => { const { mxEvent, onCloseDialog } = this.props; + const related = this.props.getRelationsForEvent(mxEvent.getId(), 'm.reference', M_BEACON.name); + const relatedAlt = this.props.getRelationsForEvent(mxEvent.getId(), 'm.reference', M_BEACON.altName); + console.log('hhh', { related, relatedAlt}); createRedactEventDialog({ mxEvent, onCloseDialog, diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index ddb700c07a8..e91e4263487 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -14,8 +14,15 @@ 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'; @@ -33,6 +40,8 @@ import SmartMarker from '../location/SmartMarker'; import OwnBeaconStatus from '../beacon/OwnBeaconStatus'; import BeaconViewDialog from '../beacon/BeaconViewDialog'; import { IBodyProps } from "./IBodyProps"; +import { GetRelationsForEvent } from '../rooms/EventTile'; +import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon'; const useBeaconState = (beaconInfoEvent: MatrixEvent): { beacon?: Beacon; @@ -87,7 +96,37 @@ const useUniqueId = (eventId: string): string => { return id; }; -const MBeaconBody: React.FC = React.forwardRef(({ mxEvent }, ref) => { +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; + + console.log('hhh', { relations, event }); + + 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 = React.forwardRef(({ mxEvent, getRelationsForEvent }, ref) => { const { beacon, isLive, @@ -102,6 +141,8 @@ const MBeaconBody: React.FC = 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; From c07ba0a6b5f77d6018f81ba69456693d5d1b0487 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 28 Jun 2022 17:20:14 +0200 Subject: [PATCH 2/4] redact beacon locations on beacon info redaction --- .../context_menus/MessageContextMenu.tsx | 4 - src/components/views/messages/MBeaconBody.tsx | 5 +- .../__snapshots__/BeaconMarker-test.tsx.snap | 1 + .../views/messages/MBeaconBody-test.tsx | 114 ++++++++++++++++++ test/test-utils/beacon.ts | 2 + 5 files changed, 119 insertions(+), 7 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 98e2c2a8609..6d7eebdc0a3 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -52,7 +52,6 @@ import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwa import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload"; import { createMapSiteLinkFromEvent } from '../../../utils/location'; import { getForwardableEvent } from '../../../events/forward/getForwardableEvent'; -import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon'; interface IProps extends IPosition { chevronFace: ChevronFace; @@ -183,9 +182,6 @@ export default class MessageContextMenu extends React.Component private onRedactClick = (): void => { const { mxEvent, onCloseDialog } = this.props; - const related = this.props.getRelationsForEvent(mxEvent.getId(), 'm.reference', M_BEACON.name); - const relatedAlt = this.props.getRelationsForEvent(mxEvent.getId(), 'm.reference', M_BEACON.altName); - console.log('hhh', { related, relatedAlt}); createRedactEventDialog({ mxEvent, onCloseDialog, diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index e91e4263487..8bf927a8c82 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -25,6 +25,7 @@ import { } 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'; @@ -41,7 +42,6 @@ import OwnBeaconStatus from '../beacon/OwnBeaconStatus'; import BeaconViewDialog from '../beacon/BeaconViewDialog'; import { IBodyProps } from "./IBodyProps"; import { GetRelationsForEvent } from '../rooms/EventTile'; -import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon'; const useBeaconState = (beaconInfoEvent: MatrixEvent): { beacon?: Beacon; @@ -96,6 +96,7 @@ const useUniqueId = (eventId: string): string => { return id; }; +// remove related beacon locations on beacon redaction const useHandleBeaconRedaction = ( event: MatrixEvent, getRelationsForEvent: GetRelationsForEvent, @@ -106,8 +107,6 @@ const useHandleBeaconRedaction = ( getRelationsForEvent(event.getId(), RelationType.Reference, M_BEACON.name) : undefined; - console.log('hhh', { relations, event }); - relations?.getRelations()?.forEach(locationEvent => { cli.redactEvent( locationEvent.getRoomId(), diff --git a/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap b/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap index 6a763ddc53c..291a64349f0 100644 --- a/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap @@ -38,6 +38,7 @@ exports[` renders marker when beacon has location 1`] = ` }, "org.matrix.msc3488.ts": 1647270879404, }, + "room_id": undefined, "sender": "@alice:server", "type": "org.matrix.msc3672.beacon", }, diff --git a/test/components/views/messages/MBeaconBody-test.tsx b/test/components/views/messages/MBeaconBody-test.tsx index afcb604534c..520e4967e9b 100644 --- a/test/components/views/messages/MBeaconBody-test.tsx +++ b/test/components/views/messages/MBeaconBody-test.tsx @@ -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 { @@ -53,6 +59,7 @@ describe('', () => { }), getUserId: jest.fn().mockReturnValue(aliceId), getRoom: jest.fn(), + redactEvent: jest.fn(), }); const defaultEvent = makeBeaconInfoEvent(aliceId, @@ -333,4 +340,111 @@ describe('', () => { 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); + }); + + 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' }, + ); + }); + }); }); diff --git a/test/test-utils/beacon.ts b/test/test-utils/beacon.ts index 3eee57b39f5..9501cd2cb3c 100644 --- a/test/test-utils/beacon.ts +++ b/test/test-utils/beacon.ts @@ -97,6 +97,7 @@ const DEFAULT_CONTENT_PROPS: ContentProps = { export const makeBeaconEvent = ( sender: string, contentProps: Partial = {}, + roomId?: string, ): MatrixEvent => { const { geoUri, timestamp, beaconInfoId, description } = { ...DEFAULT_CONTENT_PROPS, @@ -105,6 +106,7 @@ export const makeBeaconEvent = ( return new MatrixEvent({ type: M_BEACON.name, + room_id: roomId, sender, content: makeBeaconContent(geoUri, timestamp, beaconInfoId, description), }); From 28c74a2102b2daa7b39a8fd6251280f70a2e101e Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 28 Jun 2022 17:23:05 +0200 Subject: [PATCH 3/4] fussy import ordering --- src/components/views/messages/MBeaconBody.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index 8bf927a8c82..fda27633ec8 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -35,13 +35,13 @@ 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"; -import { GetRelationsForEvent } from '../rooms/EventTile'; const useBeaconState = (beaconInfoEvent: MatrixEvent): { beacon?: Beacon; From e5ed2a77dda71c75c5fa66407609dede58958f29 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 29 Jun 2022 10:51:59 +0200 Subject: [PATCH 4/4] use real fake redaction in beaconbody test --- .../views/messages/MBeaconBody-test.tsx | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/test/components/views/messages/MBeaconBody-test.tsx b/test/components/views/messages/MBeaconBody-test.tsx index 520e4967e9b..c38e145a9f9 100644 --- a/test/components/views/messages/MBeaconBody-test.tsx +++ b/test/components/views/messages/MBeaconBody-test.tsx @@ -22,7 +22,6 @@ import { BeaconEvent, getBeaconInfoIdentifier, RelationType, - MatrixEventEvent, MatrixEvent, EventType, } from 'matrix-js-sdk/src/matrix'; @@ -342,29 +341,36 @@ describe('', () => { }); describe('redaction', () => { - const aliceBeaconInfo = makeBeaconInfoEvent( - aliceId, - roomId, - { isLive: true }, - '$alice-room1-1', - ); + const makeEvents = (): { + beaconInfoEvent: MatrixEvent; + location1: MatrixEvent; + location2: MatrixEvent; + } => { + const beaconInfoEvent = 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 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 = (locationEvents: MatrixEvent[] = []) => { - const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient }); - const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo)); + 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[] = []) => { @@ -377,12 +383,13 @@ describe('', () => { }; it('does nothing when getRelationsForEvent is falsy', () => { - setupRoomWithBeacon([location1, location2]); + const { beaconInfoEvent, location1, location2 } = makeEvents(); + setupRoomWithBeacon(beaconInfoEvent, [location1, location2]); - getComponent({ mxEvent: aliceBeaconInfo }); + getComponent({ mxEvent: beaconInfoEvent }); act(() => { - aliceBeaconInfo.emit(MatrixEventEvent.BeforeRedaction, aliceBeaconInfo, redactionEvent); + beaconInfoEvent.makeRedacted(redactionEvent); }); // no error, no redactions @@ -390,10 +397,11 @@ describe('', () => { }); it('cleans up redaction listener on unmount', () => { - setupRoomWithBeacon([location1, location2]); - const removeListenerSpy = jest.spyOn(aliceBeaconInfo, 'removeListener'); + const { beaconInfoEvent, location1, location2 } = makeEvents(); + setupRoomWithBeacon(beaconInfoEvent, [location1, location2]); + const removeListenerSpy = jest.spyOn(beaconInfoEvent, 'removeListener'); - const component = getComponent({ mxEvent: aliceBeaconInfo }); + const component = getComponent({ mxEvent: beaconInfoEvent }); act(() => { component.unmount(); @@ -403,34 +411,37 @@ describe('', () => { }); it('does nothing when beacon has no related locations', async () => { + const { beaconInfoEvent } = makeEvents(); // no locations - setupRoomWithBeacon(); + setupRoomWithBeacon(beaconInfoEvent, []); const getRelationsForEvent = await mockGetRelationsForEvent(); - getComponent({ mxEvent: aliceBeaconInfo, getRelationsForEvent }); + getComponent({ mxEvent: beaconInfoEvent, getRelationsForEvent }); act(() => { - aliceBeaconInfo.emit(MatrixEventEvent.BeforeRedaction, aliceBeaconInfo, redactionEvent); + beaconInfoEvent.makeRedacted(redactionEvent); }); expect(getRelationsForEvent).toHaveBeenCalledWith( - aliceBeaconInfo.getId(), RelationType.Reference, M_BEACON.name, + beaconInfoEvent.getId(), RelationType.Reference, M_BEACON.name, ); expect(mockClient.redactEvent).not.toHaveBeenCalled(); }); it('redacts related locations on beacon redaction', async () => { - setupRoomWithBeacon([location1, location2]); + const { beaconInfoEvent, location1, location2 } = makeEvents(); + setupRoomWithBeacon(beaconInfoEvent, [location1, location2]); + const getRelationsForEvent = await mockGetRelationsForEvent([location1, location2]); - getComponent({ mxEvent: aliceBeaconInfo, getRelationsForEvent }); + getComponent({ mxEvent: beaconInfoEvent, getRelationsForEvent }); act(() => { - aliceBeaconInfo.emit(MatrixEventEvent.BeforeRedaction, aliceBeaconInfo, redactionEvent); + beaconInfoEvent.makeRedacted(redactionEvent); }); expect(getRelationsForEvent).toHaveBeenCalledWith( - aliceBeaconInfo.getId(), RelationType.Reference, M_BEACON.name, + beaconInfoEvent.getId(), RelationType.Reference, M_BEACON.name, ); expect(mockClient.redactEvent).toHaveBeenCalledTimes(2); expect(mockClient.redactEvent).toHaveBeenCalledWith(