You've already forked matrix-js-sdk
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:
@@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -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;
|
|
||||||
};
|
};
|
||||||
[ASSET_NODE_TYPE.name]: {
|
|
||||||
type: LocationAssetType;
|
/**
|
||||||
};
|
* Possible content for location events as sent over the wire
|
||||||
[TEXT_NODE_TYPE.name]: string;
|
*/
|
||||||
[TIMESTAMP_NODE_TYPE.name]: number;
|
export type LocationEventWireContent = Partial<LegacyLocationEventContent & MLocationEventContent>;
|
||||||
}
|
|
||||||
/* eslint-enable camelcase */
|
export type ILocationContent = MLocationEventContent & LegacyLocationEventContent;
|
||||||
|
@@ -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);
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user