Enable forwarding static locations (#8553)
* enable forwarding for location events Signed-off-by: Kerry Archibald <kerrya@element.io> * allow forwarding of static locations Signed-off-by: Kerry Archibald <kerrya@element.io> * add comment Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
3c5942aa35
commit
c67b41fbde
5 changed files with 135 additions and 11 deletions
|
@ -21,6 +21,8 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
import { ILocationContent, LocationAssetType, M_TIMESTAMP } from "matrix-js-sdk/src/@types/location";
|
||||||
|
import { makeLocationContent } from "matrix-js-sdk/src/content-helpers";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
@ -47,6 +49,8 @@ import { Action } from "../../../dispatcher/actions";
|
||||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||||
import { roomContextDetailsText } from "../../../utils/i18n-helpers";
|
import { roomContextDetailsText } from "../../../utils/i18n-helpers";
|
||||||
|
import { isLocationEvent } from "../../../utils/EventUtils";
|
||||||
|
import { isSelfLocation, locationEventGeoUri } from "../../../utils/location";
|
||||||
|
|
||||||
const AVATAR_SIZE = 30;
|
const AVATAR_SIZE = 30;
|
||||||
|
|
||||||
|
@ -156,6 +160,34 @@ const Entry: React.FC<IEntryProps> = ({ room, type, content, matrixClient: cli,
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStrippedEventContent = (event: MatrixEvent): IContent => {
|
||||||
|
const {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
"m.relates_to": _, // strip relations - in future we will attach a relation pointing at the original event
|
||||||
|
// We're taking a shallow copy here to avoid https://github.com/vector-im/element-web/issues/10924
|
||||||
|
...content
|
||||||
|
} = event.getContent();
|
||||||
|
|
||||||
|
// self location shares should have their description removed
|
||||||
|
// and become 'pin' share type
|
||||||
|
if (isLocationEvent(event) && isSelfLocation(content as ILocationContent)) {
|
||||||
|
const timestamp = M_TIMESTAMP.findIn<number>(content);
|
||||||
|
const geoUri = locationEventGeoUri(event);
|
||||||
|
return {
|
||||||
|
...content,
|
||||||
|
...makeLocationContent(
|
||||||
|
undefined, // text
|
||||||
|
geoUri,
|
||||||
|
timestamp || Date.now(),
|
||||||
|
undefined, // description
|
||||||
|
LocationAssetType.Pin,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => {
|
const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => {
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getUserId();
|
||||||
const [profileInfo, setProfileInfo] = useState<any>({});
|
const [profileInfo, setProfileInfo] = useState<any>({});
|
||||||
|
@ -163,12 +195,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
||||||
cli.getProfileInfo(userId).then(info => setProfileInfo(info));
|
cli.getProfileInfo(userId).then(info => setProfileInfo(info));
|
||||||
}, [cli, userId]);
|
}, [cli, userId]);
|
||||||
|
|
||||||
const {
|
const content = getStrippedEventContent(event);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
"m.relates_to": _, // strip relations - in future we will attach a relation pointing at the original event
|
|
||||||
// We're taking a shallow copy here to avoid https://github.com/vector-im/element-web/issues/10924
|
|
||||||
...content
|
|
||||||
} = event.getContent();
|
|
||||||
|
|
||||||
// For the message preview we fake the sender as ourselves
|
// For the message preview we fake the sender as ourselves
|
||||||
const mockEvent = new MatrixEvent({
|
const mockEvent = new MatrixEvent({
|
||||||
|
|
|
@ -277,7 +277,6 @@ export const isLocationEvent = (event: MatrixEvent): boolean => {
|
||||||
|
|
||||||
export function canForward(event: MatrixEvent): boolean {
|
export function canForward(event: MatrixEvent): boolean {
|
||||||
return !(
|
return !(
|
||||||
isLocationEvent(event) ||
|
|
||||||
M_POLL_START.matches(event.getType())
|
M_POLL_START.matches(event.getType())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ import React from "react";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import { act } from "react-dom/test-utils";
|
import { act } from "react-dom/test-utils";
|
||||||
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { ReactWrapper } from "enzyme";
|
||||||
|
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "matrix-js-sdk/src/@types/location";
|
||||||
|
import { TEXT_NODE_TYPE } from "matrix-js-sdk/src/@types/extensible_events";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
import ForwardDialog from "../../../../src/components/views/dialogs/ForwardDialog";
|
import ForwardDialog from "../../../../src/components/views/dialogs/ForwardDialog";
|
||||||
|
@ -25,10 +28,13 @@ import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||||
import {
|
import {
|
||||||
getMockClientWithEventEmitter,
|
getMockClientWithEventEmitter,
|
||||||
|
makeLegacyLocationEvent,
|
||||||
|
makeLocationEvent,
|
||||||
mkEvent,
|
mkEvent,
|
||||||
mkMessage,
|
mkMessage,
|
||||||
mkStubRoom,
|
mkStubRoom,
|
||||||
} from "../../../test-utils";
|
} from "../../../test-utils";
|
||||||
|
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
|
||||||
|
|
||||||
describe("ForwardDialog", () => {
|
describe("ForwardDialog", () => {
|
||||||
const sourceRoom = "!111111111111111111:example.org";
|
const sourceRoom = "!111111111111111111:example.org";
|
||||||
|
@ -58,6 +64,9 @@ describe("ForwardDialog", () => {
|
||||||
}),
|
}),
|
||||||
decryptEventIfNeeded: jest.fn(),
|
decryptEventIfNeeded: jest.fn(),
|
||||||
sendEvent: jest.fn(),
|
sendEvent: jest.fn(),
|
||||||
|
getClientWellKnown: jest.fn().mockReturnValue({
|
||||||
|
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const defaultRooms = ["a", "A", "b"].map(name => mkStubRoom(name, name, mockClient));
|
const defaultRooms = ["a", "A", "b"].map(name => mkStubRoom(name, name, mockClient));
|
||||||
|
|
||||||
|
@ -199,4 +208,93 @@ describe("ForwardDialog", () => {
|
||||||
const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").last();
|
const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").last();
|
||||||
expect(secondButton.prop("disabled")).toBe(false);
|
expect(secondButton.prop("disabled")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Location events', () => {
|
||||||
|
// 14.03.2022 16:15
|
||||||
|
const now = 1647270879403;
|
||||||
|
const roomId = "a";
|
||||||
|
const geoUri = "geo:51.5076,-0.1276";
|
||||||
|
const legacyLocationEvent = makeLegacyLocationEvent(geoUri);
|
||||||
|
const modernLocationEvent = makeLocationEvent(geoUri);
|
||||||
|
const pinDropLocationEvent = makeLocationEvent(geoUri, LocationAssetType.Pin);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// legacy events will default timestamp to Date.now()
|
||||||
|
// mock a stable now for easy assertion
|
||||||
|
jest.spyOn(Date, 'now').mockReturnValue(now);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.spyOn(Date, 'now').mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendToFirstRoom = (wrapper: ReactWrapper): void =>
|
||||||
|
act(() => {
|
||||||
|
const sendToFirstRoomButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first();
|
||||||
|
sendToFirstRoomButton.simulate("click");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts legacy location events to pin drop shares', async () => {
|
||||||
|
const wrapper = await mountForwardDialog(legacyLocationEvent);
|
||||||
|
|
||||||
|
expect(wrapper.find('MLocationBody').length).toBeTruthy();
|
||||||
|
sendToFirstRoom(wrapper);
|
||||||
|
|
||||||
|
// text and description from original event are removed
|
||||||
|
// text gets new default message from event values
|
||||||
|
// timestamp is defaulted to now
|
||||||
|
const text = `Location ${geoUri} at ${new Date(now).toISOString()}`;
|
||||||
|
const expectedStrippedContent = {
|
||||||
|
...modernLocationEvent.getContent(),
|
||||||
|
body: text,
|
||||||
|
[TEXT_NODE_TYPE.name]: text,
|
||||||
|
[M_TIMESTAMP.name]: now,
|
||||||
|
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||||
|
[M_LOCATION.name]: {
|
||||||
|
uri: geoUri,
|
||||||
|
description: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||||
|
roomId, legacyLocationEvent.getType(), expectedStrippedContent,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes personal information from static self location shares', async () => {
|
||||||
|
const wrapper = await mountForwardDialog(modernLocationEvent);
|
||||||
|
|
||||||
|
expect(wrapper.find('MLocationBody').length).toBeTruthy();
|
||||||
|
sendToFirstRoom(wrapper);
|
||||||
|
|
||||||
|
const timestamp = M_TIMESTAMP.findIn<number>(modernLocationEvent.getContent());
|
||||||
|
// text and description from original event are removed
|
||||||
|
// text gets new default message from event values
|
||||||
|
const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`;
|
||||||
|
const expectedStrippedContent = {
|
||||||
|
...modernLocationEvent.getContent(),
|
||||||
|
body: text,
|
||||||
|
[TEXT_NODE_TYPE.name]: text,
|
||||||
|
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||||
|
[M_LOCATION.name]: {
|
||||||
|
uri: geoUri,
|
||||||
|
description: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||||
|
roomId, modernLocationEvent.getType(), expectedStrippedContent,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards pin drop event', async () => {
|
||||||
|
const wrapper = await mountForwardDialog(pinDropLocationEvent);
|
||||||
|
|
||||||
|
expect(wrapper.find('MLocationBody').length).toBeTruthy();
|
||||||
|
|
||||||
|
sendToFirstRoom(wrapper);
|
||||||
|
|
||||||
|
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||||
|
roomId, pinDropLocationEvent.getType(), pinDropLocationEvent.getContent(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,14 +16,14 @@ limitations under the License.
|
||||||
|
|
||||||
import { LocationAssetType, M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
import { LocationAssetType, M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
||||||
import { makeLocationContent } from "matrix-js-sdk/src/content-helpers";
|
import { makeLocationContent } from "matrix-js-sdk/src/content-helpers";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
let id = 1;
|
let id = 1;
|
||||||
export const makeLegacyLocationEvent = (geoUri: string): MatrixEvent => {
|
export const makeLegacyLocationEvent = (geoUri: string): MatrixEvent => {
|
||||||
return new MatrixEvent(
|
return new MatrixEvent(
|
||||||
{
|
{
|
||||||
"event_id": `$${++id}`,
|
"event_id": `$${++id}`,
|
||||||
"type": M_LOCATION.name,
|
"type": EventType.RoomMessage,
|
||||||
"content": {
|
"content": {
|
||||||
"body": "Something about where I am",
|
"body": "Something about where I am",
|
||||||
"msgtype": "m.location",
|
"msgtype": "m.location",
|
||||||
|
|
|
@ -315,11 +315,11 @@ describe('EventUtils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('canForward()', () => {
|
describe('canForward()', () => {
|
||||||
it('returns false for a location event', () => {
|
it('returns true for a location event', () => {
|
||||||
const event = new MatrixEvent({
|
const event = new MatrixEvent({
|
||||||
type: M_LOCATION.name,
|
type: M_LOCATION.name,
|
||||||
});
|
});
|
||||||
expect(canForward(event)).toBe(false);
|
expect(canForward(event)).toBe(true);
|
||||||
});
|
});
|
||||||
it('returns false for a poll event', () => {
|
it('returns false for a poll event', () => {
|
||||||
const event = makePollStartEvent('Who?', userId);
|
const event = makePollStartEvent('Who?', userId);
|
||||||
|
|
Loading…
Reference in a new issue