matrix.to/src/parser/parser.ts

166 lines
4.0 KiB
TypeScript
Raw Normal View History

2020-08-06 09:49:33 -04:00
import forEach from "lodash/forEach";
2020-06-10 20:53:49 -04:00
import {
2020-08-10 07:35:15 -04:00
LinkKind,
SafeLink,
Link,
LinkContent,
Arguments,
Permalink,
2020-06-10 20:53:49 -04:00
} from "./types";
/*
2020-08-10 07:35:15 -04:00
* Verifiers are regexes which will match valid
* identifiers to their type. (This is a lie, they
* can return anything)
2020-06-10 20:53:49 -04:00
*/
2020-08-10 07:35:15 -04:00
type Verifier<A> = [RegExp, A];
export const roomVerifiers: Verifier<LinkKind.Alias | LinkKind.RoomId>[] = [
[/^#([^/:]+?):(.+)$/, LinkKind.Alias],
[/^!([^/:]+?):(.+)$/, LinkKind.RoomId],
];
export const verifiers: Verifier<LinkKind>[] = [
[/^[!#]([^/:]+?):(.+?)\/\$([^/:]+?)$/, LinkKind.Permalink],
2020-08-10 07:35:15 -04:00
[/^@([^/:]+?):(.+)$/, LinkKind.UserId],
[/^\+([^/:]+?):(.+)$/, LinkKind.GroupId],
...roomVerifiers,
];
2020-06-10 20:53:49 -04:00
2020-08-10 07:35:15 -04:00
/*
* identifyTypeFromRegex applies the verifiers to the identifier and
* returns the identifier's type
*/
export function identifyTypeFromRegex<T, F>(
identifier: string,
verifiers: Verifier<T>[],
fail: F
): T | F {
if (identifier !== encodeURI(identifier)) {
return fail;
}
2020-06-10 20:53:49 -04:00
2020-08-10 07:35:15 -04:00
return verifiers.reduce<T | F>((kind, verifier) => {
if (kind !== fail) {
return kind;
}
2020-06-10 20:53:49 -04:00
2020-08-10 07:35:15 -04:00
if (identifier.match(verifier[0])) {
return verifier[1];
}
2020-06-10 20:53:49 -04:00
2020-08-10 07:35:15 -04:00
return kind;
}, fail);
2020-06-10 20:53:49 -04:00
}
/*
* Parses a permalink.
* Assumes the permalink is correct.
*/
export function parsePermalink(
identifier: string
): Pick<Permalink, "roomKind" | "roomLink" | "eventId"> {
2020-08-10 07:35:15 -04:00
const [roomLink, eventId] = identifier.split("/");
const roomKind = identifyTypeFromRegex(
roomLink,
roomVerifiers,
// This is hacky but we're assuming identifier is a valid permalink
LinkKind.Alias
);
2020-06-10 20:53:49 -04:00
2020-08-10 07:35:15 -04:00
return {
roomKind,
roomLink,
eventId,
};
2020-08-06 09:49:33 -04:00
}
/*
* Repalces null with undefined
*/
function bottomExchange<T>(nullable: T | null): T | undefined {
2020-08-10 07:35:15 -04:00
if (nullable === null) return undefined;
return nullable;
2020-08-06 09:49:33 -04:00
}
/*
2020-08-10 07:35:15 -04:00
* parseArgs parses the <extra args> part of matrix.to links
2020-08-06 09:49:33 -04:00
*/
2020-08-10 07:35:15 -04:00
export function parseArgs(args: string): Arguments {
const params = new URLSearchParams(args);
2020-08-06 09:49:33 -04:00
2020-08-10 07:35:15 -04:00
return {
vias: params.getAll("via"),
client: bottomExchange(params.get("client")),
sharer: bottomExchange(params.get("sharer")),
};
2020-08-06 09:49:33 -04:00
}
/*
2020-08-10 07:35:15 -04:00
* 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
2020-08-06 09:49:33 -04:00
*/
2020-08-10 07:35:15 -04:00
export function parseHash(hash: string): Link {
const [identifier, args] = hash.split("?");
const kind = identifyTypeFromRegex(
identifier,
verifiers,
LinkKind.ParseFailed
);
const parsedLink: LinkContent = {
identifier,
arguments: parseArgs(args),
originalLink: hash,
};
2020-08-06 09:49:33 -04:00
2020-08-10 07:35:15 -04:00
if (kind === LinkKind.Permalink) {
const { roomKind, roomLink, eventId } = parsePermalink(identifier);
2020-06-10 20:53:49 -04:00
2020-08-10 07:35:15 -04:00
return {
kind,
...parsedLink,
roomKind,
roomLink,
eventId,
};
2020-06-10 20:53:49 -04:00
}
2020-08-10 07:35:15 -04:00
return {
kind,
...parsedLink,
};
}
2020-06-10 20:53:49 -04:00
2020-08-10 07:35:15 -04:00
/*
* 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 {
const params = new URLSearchParams();
const url = new URL(origin);
switch (link.kind) {
case LinkKind.GroupId:
case LinkKind.UserId:
case LinkKind.RoomId:
case LinkKind.Alias:
case LinkKind.Permalink:
forEach(link.arguments, (value, key) => {
if (value === undefined) {
// do nothing
} else if (key === "vias") {
(value as string[]).forEach((via) =>
params.append("via", via)
);
} else {
params.append(key, value.toString());
}
});
url.hash = `/${link.identifier}?${params.toString()}`;
}
return url;
2020-06-10 20:53:49 -04:00
}