Live location share - redact related locations on beacon redaction (PSF-1151) (#8926)
* redact beacon locations on redaction * redact beacon locations on beacon info redaction * fussy import ordering * use real fake redaction in beaconbody test
This commit is contained in:
parent
1304ff18a5
commit
4486509d88
4 changed files with 172 additions and 4 deletions
|
@ -14,10 +14,18 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { Beacon, BeaconEvent, MatrixEvent } from 'matrix-js-sdk/src/matrix';
|
import {
|
||||||
|
Beacon,
|
||||||
|
BeaconEvent,
|
||||||
|
MatrixEvent,
|
||||||
|
MatrixEventEvent,
|
||||||
|
MatrixClient,
|
||||||
|
RelationType,
|
||||||
|
} from 'matrix-js-sdk/src/matrix';
|
||||||
import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers';
|
import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers';
|
||||||
import { randomString } from 'matrix-js-sdk/src/randomstring';
|
import { randomString } from 'matrix-js-sdk/src/randomstring';
|
||||||
|
import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon';
|
||||||
|
|
||||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||||
|
@ -27,10 +35,11 @@ import { isBeaconWaitingToStart, useBeacon } from '../../../utils/beacon';
|
||||||
import { isSelfLocation } from '../../../utils/location';
|
import { isSelfLocation } from '../../../utils/location';
|
||||||
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
|
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
|
||||||
import BeaconStatus from '../beacon/BeaconStatus';
|
import BeaconStatus from '../beacon/BeaconStatus';
|
||||||
|
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
||||||
import Map from '../location/Map';
|
import Map from '../location/Map';
|
||||||
import MapFallback from '../location/MapFallback';
|
import MapFallback from '../location/MapFallback';
|
||||||
import SmartMarker from '../location/SmartMarker';
|
import SmartMarker from '../location/SmartMarker';
|
||||||
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
import { GetRelationsForEvent } from '../rooms/EventTile';
|
||||||
import BeaconViewDialog from '../beacon/BeaconViewDialog';
|
import BeaconViewDialog from '../beacon/BeaconViewDialog';
|
||||||
import { IBodyProps } from "./IBodyProps";
|
import { IBodyProps } from "./IBodyProps";
|
||||||
|
|
||||||
|
@ -87,7 +96,36 @@ const useUniqueId = (eventId: string): string => {
|
||||||
return id;
|
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 {
|
const {
|
||||||
beacon,
|
beacon,
|
||||||
isLive,
|
isLive,
|
||||||
|
@ -102,6 +140,8 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
|
||||||
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
||||||
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();
|
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();
|
||||||
|
|
||||||
|
useHandleBeaconRedaction(mxEvent, getRelationsForEvent, matrixClient);
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (displayStatus !== BeaconDisplayStatus.Active) {
|
if (displayStatus !== BeaconDisplayStatus.Active) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -38,6 +38,7 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
||||||
},
|
},
|
||||||
"org.matrix.msc3488.ts": 1647270879404,
|
"org.matrix.msc3488.ts": 1647270879404,
|
||||||
},
|
},
|
||||||
|
"room_id": undefined,
|
||||||
"sender": "@alice:server",
|
"sender": "@alice:server",
|
||||||
"type": "org.matrix.msc3672.beacon",
|
"type": "org.matrix.msc3672.beacon",
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,7 +21,12 @@ import maplibregl from 'maplibre-gl';
|
||||||
import {
|
import {
|
||||||
BeaconEvent,
|
BeaconEvent,
|
||||||
getBeaconInfoIdentifier,
|
getBeaconInfoIdentifier,
|
||||||
|
RelationType,
|
||||||
|
MatrixEvent,
|
||||||
|
EventType,
|
||||||
} from 'matrix-js-sdk/src/matrix';
|
} 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 MBeaconBody from '../../../../src/components/views/messages/MBeaconBody';
|
||||||
import {
|
import {
|
||||||
|
@ -53,6 +58,7 @@ describe('<MBeaconBody />', () => {
|
||||||
}),
|
}),
|
||||||
getUserId: jest.fn().mockReturnValue(aliceId),
|
getUserId: jest.fn().mockReturnValue(aliceId),
|
||||||
getRoom: jest.fn(),
|
getRoom: jest.fn(),
|
||||||
|
redactEvent: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultEvent = makeBeaconInfoEvent(aliceId,
|
const defaultEvent = makeBeaconInfoEvent(aliceId,
|
||||||
|
@ -333,4 +339,123 @@ describe('<MBeaconBody />', () => {
|
||||||
expect(mockMarker.setLngLat).toHaveBeenCalledWith({ lat: 52, lon: 42 });
|
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' },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -97,6 +97,7 @@ const DEFAULT_CONTENT_PROPS: ContentProps = {
|
||||||
export const makeBeaconEvent = (
|
export const makeBeaconEvent = (
|
||||||
sender: string,
|
sender: string,
|
||||||
contentProps: Partial<ContentProps> = {},
|
contentProps: Partial<ContentProps> = {},
|
||||||
|
roomId?: string,
|
||||||
): MatrixEvent => {
|
): MatrixEvent => {
|
||||||
const { geoUri, timestamp, beaconInfoId, description } = {
|
const { geoUri, timestamp, beaconInfoId, description } = {
|
||||||
...DEFAULT_CONTENT_PROPS,
|
...DEFAULT_CONTENT_PROPS,
|
||||||
|
@ -105,6 +106,7 @@ export const makeBeaconEvent = (
|
||||||
|
|
||||||
return new MatrixEvent({
|
return new MatrixEvent({
|
||||||
type: M_BEACON.name,
|
type: M_BEACON.name,
|
||||||
|
room_id: roomId,
|
||||||
sender,
|
sender,
|
||||||
content: makeBeaconContent(geoUri, timestamp, beaconInfoId, description),
|
content: makeBeaconContent(geoUri, timestamp, beaconInfoId, description),
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue