Update parser to msc2644 and fix tests
This commit is contained in:
+28
-32
@@ -1,42 +1,36 @@
|
||||
import {
|
||||
parseLink,
|
||||
parseHash,
|
||||
parsePermalink,
|
||||
parseArgs,
|
||||
verifiers,
|
||||
discriminate,
|
||||
toURI,
|
||||
identifyTypeFromRegex,
|
||||
toURL,
|
||||
} from "./parser";
|
||||
|
||||
import { LinkDiscriminator } from "./types";
|
||||
|
||||
const curriedDiscriminate = (id: string) =>
|
||||
discriminate(id, verifiers, LinkDiscriminator.ParseFailed);
|
||||
function identifierType(id: string) {
|
||||
return identifyTypeFromRegex(id, verifiers, LinkDiscriminator.ParseFailed);
|
||||
}
|
||||
|
||||
it("types identifiers correctly", () => {
|
||||
expect(curriedDiscriminate("@user:matrix.org")).toEqual(
|
||||
LinkDiscriminator.UserId
|
||||
expect(identifierType("@user:matrix.org")).toEqual(LinkDiscriminator.UserId);
|
||||
expect(identifierType("!room:matrix.org")).toEqual(LinkDiscriminator.RoomId);
|
||||
expect(identifierType("!somewhere:example.org/$event:example.org")).toEqual(
|
||||
LinkDiscriminator.Permalink
|
||||
);
|
||||
expect(curriedDiscriminate("!room:matrix.org")).toEqual(
|
||||
LinkDiscriminator.RoomId
|
||||
);
|
||||
expect(
|
||||
curriedDiscriminate("!somewhere:example.org/$event:example.org")
|
||||
).toEqual(LinkDiscriminator.Permalink);
|
||||
expect(curriedDiscriminate("+group:matrix.org")).toEqual(
|
||||
expect(identifierType("+group:matrix.org")).toEqual(
|
||||
LinkDiscriminator.GroupId
|
||||
);
|
||||
expect(curriedDiscriminate("#alias:matrix.org")).toEqual(
|
||||
LinkDiscriminator.Alias
|
||||
);
|
||||
expect(identifierType("#alias:matrix.org")).toEqual(LinkDiscriminator.Alias);
|
||||
});
|
||||
|
||||
it("types garbadge as such", () => {
|
||||
expect(curriedDiscriminate("sdfa;fdlkja")).toEqual(
|
||||
expect(identifierType("sdfa;fdlkja")).toEqual(LinkDiscriminator.ParseFailed);
|
||||
expect(identifierType("$event$matrix.org")).toEqual(
|
||||
LinkDiscriminator.ParseFailed
|
||||
);
|
||||
expect(curriedDiscriminate("$event$matrix.org")).toEqual(
|
||||
LinkDiscriminator.ParseFailed
|
||||
);
|
||||
expect(curriedDiscriminate("/user:matrix.org")).toEqual(
|
||||
expect(identifierType("/user:matrix.org")).toEqual(
|
||||
LinkDiscriminator.ParseFailed
|
||||
);
|
||||
});
|
||||
@@ -51,11 +45,13 @@ it("parses sharer", () => {
|
||||
expect(parseArgs("sharer=blah")).toHaveProperty("sharer", "blah");
|
||||
});
|
||||
|
||||
it("parses random args", () => {
|
||||
expect(parseArgs("via=qreqrqwer&banter=2342")).toHaveProperty(
|
||||
"extras.banter",
|
||||
["2342"]
|
||||
);
|
||||
it("parses client", () => {
|
||||
expect(parseArgs("client=blah.com")).toHaveProperty("client", "blah.com");
|
||||
});
|
||||
|
||||
it("parses federated", () => {
|
||||
expect(parseArgs("federated=true")).toHaveProperty("federated", true);
|
||||
expect(parseArgs("federated=false")).toHaveProperty("federated", false);
|
||||
});
|
||||
|
||||
it("parses permalinks", () => {
|
||||
@@ -68,15 +64,15 @@ it("parses permalinks", () => {
|
||||
|
||||
it("formats links correctly", () => {
|
||||
const bigLink =
|
||||
"!somewhere:example.org/$event:example.org?via=dfasdf&via=jfjafjaf&uselesstag=useless";
|
||||
const host = "matrix.org";
|
||||
const prefix = host + "/#/";
|
||||
const parse = parseLink(bigLink);
|
||||
"!somewhere:example.org/$event:example.org?via=dfasdf&via=jfjafjaf";
|
||||
const origin = "https://matrix.org";
|
||||
const prefix = origin + "/#/";
|
||||
const parse = parseHash(bigLink);
|
||||
|
||||
switch (parse.kind) {
|
||||
case LinkDiscriminator.ParseFailed:
|
||||
fail("Parse failed");
|
||||
default:
|
||||
expect(toURI(host, parse)).toEqual(prefix + bigLink);
|
||||
expect(toURL(origin, parse).toString()).toEqual(prefix + bigLink);
|
||||
}
|
||||
});
|
||||
|
||||
+84
-93
@@ -1,4 +1,4 @@
|
||||
import _ from "lodash";
|
||||
import forEach from "lodash/forEach";
|
||||
|
||||
import {
|
||||
LinkDiscriminator,
|
||||
@@ -9,47 +9,23 @@ import {
|
||||
} from "./types";
|
||||
|
||||
/*
|
||||
* Verifiers are regexes which will match valid
|
||||
* identifiers to their type. (This is a lie, they
|
||||
* can return anything)
|
||||
*/
|
||||
type Verifier<A> = [RegExp, A];
|
||||
export const roomVerifiers: Verifier<
|
||||
LinkDiscriminator.Alias | LinkDiscriminator.RoomId
|
||||
>[] = [
|
||||
[/^#([^\/:]+?):(.+)$/, LinkDiscriminator.Alias],
|
||||
[/^!([^\/:]+?):(.+)$/, LinkDiscriminator.RoomId],
|
||||
];
|
||||
export const verifiers: Verifier<LinkDiscriminator>[] = [
|
||||
[/^[\!#]([^\/:]+?):(.+?)\/\$([^\/:]+?):(.+?)$/, LinkDiscriminator.Permalink],
|
||||
[/^@([^\/:]+?):(.+)$/, LinkDiscriminator.UserId],
|
||||
[/^\+([^\/:]+?):(.+)$/, LinkDiscriminator.GroupId],
|
||||
...roomVerifiers,
|
||||
];
|
||||
|
||||
/*
|
||||
* parseLink takes a striped hash link (without the '#/' prefix)
|
||||
* parseLink takes a striped matrix.to hash link (without the '#/' prefix)
|
||||
* and parses into a Link. If the parse failed the result will
|
||||
* be ParseFailed
|
||||
*/
|
||||
export function parseLink(link: string): Link {
|
||||
const [identifier, args] = link.split("?");
|
||||
export function parseHash(hash: string): Link {
|
||||
const [identifier, args] = hash.split("?");
|
||||
|
||||
const kind = discriminate(
|
||||
const kind = identifyTypeFromRegex(
|
||||
identifier,
|
||||
verifiers,
|
||||
LinkDiscriminator.ParseFailed
|
||||
);
|
||||
const { vias, sharer, extras } = parseArgs(args);
|
||||
|
||||
let parsedLink: LinkContent = {
|
||||
identifier,
|
||||
arguments: {
|
||||
vias,
|
||||
sharer,
|
||||
extras,
|
||||
},
|
||||
originalLink: link,
|
||||
arguments: parseArgs(args),
|
||||
originalLink: hash,
|
||||
};
|
||||
|
||||
if (kind === LinkDiscriminator.Permalink) {
|
||||
@@ -76,7 +52,7 @@ export function parseLink(link: string): Link {
|
||||
*/
|
||||
export function parsePermalink(identifier: string) {
|
||||
const [roomLink, eventId] = identifier.split("/");
|
||||
const roomKind = discriminate(
|
||||
const roomKind = identifyTypeFromRegex(
|
||||
roomLink,
|
||||
roomVerifiers,
|
||||
// This is hacky but we're assuming identifier is a valid permalink
|
||||
@@ -91,10 +67,84 @@ export function parsePermalink(identifier: string) {
|
||||
}
|
||||
|
||||
/*
|
||||
* descriminate applies the verifiers to the identifier and
|
||||
* parseArgs parses the <extra args> part of matrix.to links
|
||||
*/
|
||||
export function parseArgs(args: string): Arguments {
|
||||
const params = new URLSearchParams(args);
|
||||
const _federated = params.get("federated");
|
||||
const federated = _federated !== null ? _federated === "true" : null;
|
||||
|
||||
return {
|
||||
vias: params.getAll("via"),
|
||||
federated: bottomExchange(federated),
|
||||
client: bottomExchange(params.get("client")),
|
||||
sharer: bottomExchange(params.get("sharer")),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Repalces null with undefined
|
||||
*/
|
||||
function bottomExchange<T>(nullable: T | null): T | undefined {
|
||||
if (nullable === null) return undefined;
|
||||
return nullable;
|
||||
}
|
||||
|
||||
/*
|
||||
* toURI converts a Link to a url. It's recommended
|
||||
* to use the original link instead of toURI if it existed.
|
||||
* This is handy function in case the Link was constructed.
|
||||
*/
|
||||
export function toURL(origin: string, link: SafeLink): URL {
|
||||
switch (link.kind) {
|
||||
case LinkDiscriminator.GroupId:
|
||||
case LinkDiscriminator.UserId:
|
||||
case LinkDiscriminator.RoomId:
|
||||
case LinkDiscriminator.Alias:
|
||||
case LinkDiscriminator.Permalink:
|
||||
const params = new URLSearchParams();
|
||||
forEach(link.arguments, (value, key) => {
|
||||
if (value === undefined) {
|
||||
// do nothing
|
||||
} else if (key === "vias") {
|
||||
(<string[]>(<unknown>value)).forEach((via) =>
|
||||
params.append("via", via)
|
||||
);
|
||||
} else {
|
||||
params.append(key, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const url = new URL(origin);
|
||||
url.hash = `/${link.identifier}?${params.toString()}`;
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifiers are regexes which will match valid
|
||||
* identifiers to their type. (This is a lie, they
|
||||
* can return anything)
|
||||
*/
|
||||
type Verifier<A> = [RegExp, A];
|
||||
export const roomVerifiers: Verifier<
|
||||
LinkDiscriminator.Alias | LinkDiscriminator.RoomId
|
||||
>[] = [
|
||||
[/^#([^\/:]+?):(.+)$/, LinkDiscriminator.Alias],
|
||||
[/^!([^\/:]+?):(.+)$/, LinkDiscriminator.RoomId],
|
||||
];
|
||||
export const verifiers: Verifier<LinkDiscriminator>[] = [
|
||||
[/^[\!#]([^\/:]+?):(.+?)\/\$([^\/:]+?):(.+?)$/, LinkDiscriminator.Permalink],
|
||||
[/^@([^\/:]+?):(.+)$/, LinkDiscriminator.UserId],
|
||||
[/^\+([^\/:]+?):(.+)$/, LinkDiscriminator.GroupId],
|
||||
...roomVerifiers,
|
||||
];
|
||||
|
||||
/*
|
||||
* identifyTypeFromRegex applies the verifiers to the identifier and
|
||||
* returns the identifier's type
|
||||
*/
|
||||
export function discriminate<T, F>(
|
||||
export function identifyTypeFromRegex<T, F>(
|
||||
identifier: string,
|
||||
verifiers: Verifier<T>[],
|
||||
fail: F
|
||||
@@ -115,62 +165,3 @@ export function discriminate<T, F>(
|
||||
return discriminator;
|
||||
}, fail);
|
||||
}
|
||||
|
||||
/*
|
||||
* parseArgs parses the <extra args> part of matrix.to links
|
||||
*/
|
||||
export function parseArgs(args: string): Arguments {
|
||||
const parsedArgTuples = _.groupBy(
|
||||
args
|
||||
.split("&")
|
||||
.map((x) => x.split("="))
|
||||
.filter((x) => x.length == 2),
|
||||
(arg) => {
|
||||
return arg[0];
|
||||
}
|
||||
);
|
||||
|
||||
const parsedArgs = _.mapValues(parsedArgTuples, (arg) =>
|
||||
arg.map((x) => x[1])
|
||||
);
|
||||
|
||||
const { via, sharer, ...extras } = parsedArgs;
|
||||
|
||||
return {
|
||||
vias: via,
|
||||
sharer: (parsedArgs.sharer || [undefined])[0],
|
||||
extras,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* toURI converts a Link to uri. It's recommended
|
||||
* to use the original link instead of toURI if it existed.
|
||||
* This is handy function in case the Link was constructed.
|
||||
*/
|
||||
export function toURI(hostname: string, link: SafeLink): string {
|
||||
const cleanHostname = hostname.trim().replace(/\/+$/, "");
|
||||
switch (link.kind) {
|
||||
case LinkDiscriminator.GroupId:
|
||||
case LinkDiscriminator.UserId:
|
||||
case LinkDiscriminator.RoomId:
|
||||
case LinkDiscriminator.Alias:
|
||||
case LinkDiscriminator.Permalink:
|
||||
const uri = encodeURI(cleanHostname + "/#/" + link.identifier);
|
||||
const vias = link.arguments.vias.map((s) => "via=" + s).join("&");
|
||||
const sharer = link.arguments.sharer
|
||||
? "sharer=" + link.arguments.sharer
|
||||
: "";
|
||||
const extras = _.map(link.arguments.extras, (vals, key) =>
|
||||
vals.map((v) => key + "=" + v).join("&")
|
||||
).join("&");
|
||||
|
||||
const args = [vias, sharer, extras].filter(Boolean).join("&");
|
||||
|
||||
if (args) {
|
||||
return uri + "?" + args;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-5
@@ -1,10 +1,13 @@
|
||||
import _ from "lodash";
|
||||
|
||||
export interface Arguments {
|
||||
vias: string[];
|
||||
// Either one of the enums or a custom link
|
||||
sharer: string;
|
||||
extras: { [key: string]: string[] };
|
||||
// Schemeless http identifier
|
||||
client?: string;
|
||||
// Indicates whether a room exists on a federating server (assumed to be the
|
||||
// default), or if the client must connect via the server identified by the
|
||||
// room ID or event ID
|
||||
federated?: boolean;
|
||||
// MXID
|
||||
sharer?: string;
|
||||
}
|
||||
|
||||
export interface LinkContent {
|
||||
|
||||
Reference in New Issue
Block a user