1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-07 23:02:56 +03:00

Location event helper functions (#2229)

* ASSET_NODE_TYPE -> M_ASSET

Signed-off-by: Kerry Archibald <kerrya@element.io>

* export const M_TIMESTAMP = new UnstableValue("m.ts", "org.matrix.msc3488.ts");

Signed-off-by: Kerry Archibald <kerrya@element.io>

* LOCATION_EVENT_TYPE -> M_LOCATION

Signed-off-by: Kerry Archibald <kerrya@element.io>

* extensible event types for location

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add locationevent parsing helpers

Signed-off-by: Kerry Archibald <kerrya@element.io>

* rename

Signed-off-by: Kerry Archibald <kerrya@element.io>

* comment

Signed-off-by: Kerry Archibald <kerrya@element.io>

* revert makelocationcontent signature

Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
Kerry
2022-03-10 18:40:13 +01:00
committed by GitHub
parent dbcd01bb43
commit e16e7bc098
3 changed files with 188 additions and 60 deletions

View File

@@ -14,43 +14,98 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { makeLocationContent } from "../../src/content-helpers"; import { makeLocationContent, parseLocationEvent } from "../../src/content-helpers";
import { import {
ASSET_NODE_TYPE, M_ASSET,
LocationAssetType, LocationAssetType,
LOCATION_EVENT_TYPE, M_LOCATION,
TIMESTAMP_NODE_TYPE, M_TIMESTAMP,
LocationEventWireContent,
} from "../../src/@types/location"; } from "../../src/@types/location";
import { TEXT_NODE_TYPE } from "../../src/@types/extensible_events"; import { TEXT_NODE_TYPE } from "../../src/@types/extensible_events";
import { MsgType } from "../../src/@types/event";
describe("Location", function() { describe("Location", function() {
const defaultContent = {
"body": "Location geo:-36.24484561954707,175.46884959563613;u=10 at 2022-03-09T11:01:52.443Z",
"msgtype": "m.location",
"geo_uri": "geo:-36.24484561954707,175.46884959563613;u=10",
[M_LOCATION.name]: { "uri": "geo:-36.24484561954707,175.46884959563613;u=10", "description": null },
[M_ASSET.name]: { "type": "m.self" },
[TEXT_NODE_TYPE.name]: "Location geo:-36.24484561954707,175.46884959563613;u=10 at 2022-03-09T11:01:52.443Z",
[M_TIMESTAMP.name]: 1646823712443,
} as any;
const backwardsCompatibleEventContent = { ...defaultContent };
// eslint-disable-next-line camelcase
const { body, msgtype, geo_uri, ...modernProperties } = defaultContent;
const modernEventContent = { ...modernProperties };
const legacyEventContent = {
// eslint-disable-next-line camelcase
body, msgtype, geo_uri,
} as LocationEventWireContent;
it("should create a valid location with defaults", function() { it("should create a valid location with defaults", function() {
const loc = makeLocationContent("txt", "geo:foo", 134235435); const loc = makeLocationContent(undefined, "geo:foo", 134235435);
expect(loc.body).toEqual("txt"); expect(loc.body).toEqual('User Location geo:foo at 1970-01-02T13:17:15.435Z');
expect(loc.msgtype).toEqual("m.location"); expect(loc.msgtype).toEqual(MsgType.Location);
expect(loc.geo_uri).toEqual("geo:foo"); expect(loc.geo_uri).toEqual("geo:foo");
expect(LOCATION_EVENT_TYPE.findIn(loc)).toEqual({ expect(M_LOCATION.findIn(loc)).toEqual({
uri: "geo:foo", uri: "geo:foo",
description: undefined, description: undefined,
}); });
expect(ASSET_NODE_TYPE.findIn(loc)).toEqual({ type: LocationAssetType.Self }); expect(M_ASSET.findIn(loc)).toEqual({ type: LocationAssetType.Self });
expect(TEXT_NODE_TYPE.findIn(loc)).toEqual("txt"); expect(TEXT_NODE_TYPE.findIn(loc)).toEqual('User Location geo:foo at 1970-01-02T13:17:15.435Z');
expect(TIMESTAMP_NODE_TYPE.findIn(loc)).toEqual(134235435); expect(M_TIMESTAMP.findIn(loc)).toEqual(134235435);
}); });
it("should create a valid location with explicit properties", function() { it("should create a valid location with explicit properties", function() {
const loc = makeLocationContent( const loc = makeLocationContent(
"txxt", "geo:bar", 134235436, "desc", LocationAssetType.Pin); undefined, "geo:bar", 134235436, "desc", LocationAssetType.Pin);
expect(loc.body).toEqual("txxt"); expect(loc.body).toEqual('Location "desc" geo:bar at 1970-01-02T13:17:15.436Z');
expect(loc.msgtype).toEqual("m.location"); expect(loc.msgtype).toEqual(MsgType.Location);
expect(loc.geo_uri).toEqual("geo:bar"); expect(loc.geo_uri).toEqual("geo:bar");
expect(LOCATION_EVENT_TYPE.findIn(loc)).toEqual({ expect(M_LOCATION.findIn(loc)).toEqual({
uri: "geo:bar", uri: "geo:bar",
description: "desc", description: "desc",
}); });
expect(ASSET_NODE_TYPE.findIn(loc)).toEqual({ type: LocationAssetType.Pin }); expect(M_ASSET.findIn(loc)).toEqual({ type: LocationAssetType.Pin });
expect(TEXT_NODE_TYPE.findIn(loc)).toEqual("txxt"); expect(TEXT_NODE_TYPE.findIn(loc)).toEqual('Location "desc" geo:bar at 1970-01-02T13:17:15.436Z');
expect(TIMESTAMP_NODE_TYPE.findIn(loc)).toEqual(134235436); expect(M_TIMESTAMP.findIn(loc)).toEqual(134235436);
});
it('parses backwards compatible event correctly', () => {
const eventContent = parseLocationEvent(backwardsCompatibleEventContent);
expect(eventContent).toEqual(backwardsCompatibleEventContent);
});
it('parses modern correctly', () => {
const eventContent = parseLocationEvent(modernEventContent);
expect(eventContent).toEqual(backwardsCompatibleEventContent);
});
it('parses legacy event correctly', () => {
const eventContent = parseLocationEvent(legacyEventContent);
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[M_TIMESTAMP.name]: timestamp,
...expectedResult
} = defaultContent;
expect(eventContent).toEqual({
...expectedResult,
[M_LOCATION.name]: {
...expectedResult[M_LOCATION.name],
description: undefined,
},
});
// don't infer timestamp from legacy event
expect(M_TIMESTAMP.findIn(eventContent)).toBeFalsy();
}); });
}); });

View File

@@ -15,23 +15,44 @@ limitations under the License.
*/ */
// Types for MSC3488 - m.location: Extending events with location data // Types for MSC3488 - m.location: Extending events with location data
import { EitherAnd } from "matrix-events-sdk";
import { UnstableValue } from "../NamespacedValue"; import { UnstableValue } from "../NamespacedValue";
import { IContent } from "../models/event";
import { TEXT_NODE_TYPE } from "./extensible_events"; import { TEXT_NODE_TYPE } from "./extensible_events";
export const LOCATION_EVENT_TYPE = new UnstableValue(
"m.location", "org.matrix.msc3488.location");
export const ASSET_NODE_TYPE = new UnstableValue("m.asset", "org.matrix.msc3488.asset");
export const TIMESTAMP_NODE_TYPE = new UnstableValue("m.ts", "org.matrix.msc3488.ts");
export enum LocationAssetType { export enum LocationAssetType {
Self = "m.self", Self = "m.self",
Pin = "m.pin", Pin = "m.pin",
} }
export const M_ASSET = new UnstableValue("m.asset", "org.matrix.msc3488.asset");
export type MAssetContent = { type: LocationAssetType };
/**
* The event definition for an m.asset event (in content)
*/
export type MAssetEvent = EitherAnd<{ [M_ASSET.name]: MAssetContent }, { [M_ASSET.altName]: MAssetContent }>;
export const M_TIMESTAMP = new UnstableValue("m.ts", "org.matrix.msc3488.ts");
/**
* The event definition for an m.ts event (in content)
*/
export type MTimestampEvent = EitherAnd<{ [M_TIMESTAMP.name]: number }, { [M_TIMESTAMP.altName]: number }>;
export const M_LOCATION = new UnstableValue(
"m.location", "org.matrix.msc3488.location");
export type MLocationContent = {
uri: string;
description?: string | null;
};
export type MLocationEvent = EitherAnd<
{ [M_LOCATION.name]: MLocationContent },
{ [M_LOCATION.altName]: MLocationContent }
>;
export type MTextEvent = EitherAnd<{ [TEXT_NODE_TYPE.name]: string }, { [TEXT_NODE_TYPE.altName]: string }>;
/* From the spec at: /* From the spec at:
* https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md * https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md
{ {
@@ -52,20 +73,25 @@ export enum LocationAssetType {
} }
} }
*/ */
type OptionalTimestampEvent = MTimestampEvent | undefined;
/**
* The content for an m.location event
*/
export type MLocationEventContent = &
MLocationEvent &
MAssetEvent &
MTextEvent &
OptionalTimestampEvent;
/* eslint-disable camelcase */ export type LegacyLocationEventContent = {
export interface ILocationContent extends IContent {
body: string; body: string;
msgtype: string; msgtype: string;
geo_uri: string; geo_uri: string;
[LOCATION_EVENT_TYPE.name]: { };
uri: string;
description?: string; /**
}; * Possible content for location events as sent over the wire
[ASSET_NODE_TYPE.name]: { */
type: LocationAssetType; export type LocationEventWireContent = Partial<LegacyLocationEventContent & MLocationEventContent>;
};
[TEXT_NODE_TYPE.name]: string; export type ILocationContent = MLocationEventContent & LegacyLocationEventContent;
[TIMESTAMP_NODE_TYPE.name]: number;
}
/* eslint-enable camelcase */

View File

@@ -19,11 +19,15 @@ limitations under the License.
import { MsgType } from "./@types/event"; import { MsgType } from "./@types/event";
import { TEXT_NODE_TYPE } from "./@types/extensible_events"; import { TEXT_NODE_TYPE } from "./@types/extensible_events";
import { import {
ASSET_NODE_TYPE, M_ASSET,
ILocationContent,
LocationAssetType, LocationAssetType,
LOCATION_EVENT_TYPE, M_LOCATION,
TIMESTAMP_NODE_TYPE, M_TIMESTAMP,
LocationEventWireContent,
MLocationEventContent,
MLocationContent,
MAssetContent,
LegacyLocationEventContent,
} from "./@types/location"; } from "./@types/location";
/** /**
@@ -107,35 +111,78 @@ export function makeEmoteMessage(body: string) {
}; };
} }
/** Location content helpers */
export const getTextForLocationEvent = (
uri: string,
assetType: LocationAssetType,
timestamp: number,
description?: string,
): string => {
const date = `at ${new Date(timestamp).toISOString()}`;
const assetName = assetType === LocationAssetType.Self ? 'User' : undefined;
const quotedDescription = description ? `"${description}"` : undefined;
return [
assetName,
'Location',
quotedDescription,
uri,
date,
].filter(Boolean).join(' ');
};
/** /**
* Generates the content for a Location event * Generates the content for a Location event
* @param text a text for of our location
* @param uri a geo:// uri for the location * @param uri a geo:// uri for the location
* @param ts the timestamp when the location was correct (milliseconds since * @param ts the timestamp when the location was correct (milliseconds since
* the UNIX epoch) * the UNIX epoch)
* @param description the (optional) label for this location on the map * @param description the (optional) label for this location on the map
* @param asset_type the (optional) asset type of this location e.g. "m.self" * @param asset_type the (optional) asset type of this location e.g. "m.self"
* @param text optional. A text for the location
*/ */
export function makeLocationContent( export const makeLocationContent = (
text: string, // this is first but optional
// to avoid a breaking change
text: string | undefined,
uri: string, uri: string,
ts: number, timestamp?: number,
description?: string, description?: string,
assetType?: LocationAssetType, assetType?: LocationAssetType,
): ILocationContent { ): LegacyLocationEventContent & MLocationEventContent => {
const defaultedText = text ??
getTextForLocationEvent(uri, assetType || LocationAssetType.Self, timestamp, description);
const timestampEvent = timestamp ? { [M_TIMESTAMP.name]: timestamp } : {};
return { return {
"body": text, msgtype: MsgType.Location,
"msgtype": MsgType.Location, body: defaultedText,
"geo_uri": uri, geo_uri: uri,
[LOCATION_EVENT_TYPE.name]: { [M_LOCATION.name]: {
uri,
description, description,
uri,
}, },
[ASSET_NODE_TYPE.name]: { [M_ASSET.name]: {
type: assetType ?? LocationAssetType.Self, type: assetType || LocationAssetType.Self,
}, },
[TEXT_NODE_TYPE.name]: text, [TEXT_NODE_TYPE.name]: defaultedText,
[TIMESTAMP_NODE_TYPE.name]: ts, ...timestampEvent,
// TODO: MSC1767 fallbacks m.image thumbnail } as LegacyLocationEventContent & MLocationEventContent;
}; };
}
/**
* Parse location event content and transform to
* a backwards compatible modern m.location event format
*/
export const parseLocationEvent = (wireEventContent: LocationEventWireContent): MLocationEventContent => {
const location = M_LOCATION.findIn<MLocationContent>(wireEventContent);
const asset = M_ASSET.findIn<MAssetContent>(wireEventContent);
const timestamp = M_TIMESTAMP.findIn<number>(wireEventContent);
const text = TEXT_NODE_TYPE.findIn<string>(wireEventContent);
const geoUri = location?.uri ?? wireEventContent?.geo_uri;
const description = location?.description;
const assetType = asset?.type ?? LocationAssetType.Self;
const fallbackText = text ?? wireEventContent.body;
return makeLocationContent(fallbackText, geoUri, timestamp, description, assetType);
};