Remove federtaed argument

This commit is contained in:
Jorik Schellekens 2020-08-10 12:35:15 +01:00
parent 14e22d41dc
commit 5b2c36833c
3 changed files with 205 additions and 227 deletions

View file

@ -1,78 +1,64 @@
/* eslint-disable no-fallthrough */
import { import {
parseHash, parseHash,
parsePermalink, parsePermalink,
parseArgs, parseArgs,
verifiers, verifiers,
identifyTypeFromRegex, identifyTypeFromRegex,
toURL, toURL,
} from "./parser"; } from "./parser";
import { LinkDiscriminator } from "./types"; import { LinkKind } from "./types";
function identifierType(id: string) { const identifierType = (id: string): LinkKind =>
return identifyTypeFromRegex(id, verifiers, LinkDiscriminator.ParseFailed); identifyTypeFromRegex(id, verifiers, LinkKind.ParseFailed);
}
it("types identifiers correctly", () => { it("types identifiers correctly", () => {
expect(identifierType("@user:matrix.org")).toEqual(LinkDiscriminator.UserId); expect(identifierType("@user:matrix.org")).toEqual(LinkKind.UserId);
expect(identifierType("!room:matrix.org")).toEqual(LinkDiscriminator.RoomId); expect(identifierType("!room:matrix.org")).toEqual(LinkKind.RoomId);
expect(identifierType("!somewhere:example.org/$event:example.org")).toEqual( expect(identifierType("!somewhere:example.org/$event:example.org")).toEqual(
LinkDiscriminator.Permalink LinkKind.Permalink
); );
expect(identifierType("+group:matrix.org")).toEqual( expect(identifierType("+group:matrix.org")).toEqual(LinkKind.GroupId);
LinkDiscriminator.GroupId expect(identifierType("#alias:matrix.org")).toEqual(LinkKind.Alias);
);
expect(identifierType("#alias:matrix.org")).toEqual(LinkDiscriminator.Alias);
}); });
it("types garbage as such", () => { it("types garbage as such", () => {
expect(identifierType("sdfa;fdlkja")).toEqual(LinkDiscriminator.ParseFailed); expect(identifierType("sdfa;fdlkja")).toEqual(LinkKind.ParseFailed);
expect(identifierType("$event$matrix.org")).toEqual( expect(identifierType("$event$matrix.org")).toEqual(LinkKind.ParseFailed);
LinkDiscriminator.ParseFailed expect(identifierType("/user:matrix.org")).toEqual(LinkKind.ParseFailed);
);
expect(identifierType("/user:matrix.org")).toEqual(
LinkDiscriminator.ParseFailed
);
}); });
it("parses vias", () => { it("parses args correctly", () => {
expect( expect(
parseArgs("via=example.org&via=alt.example.org") parseArgs("via=example.org&via=alt.example.org")
).toHaveProperty("vias", ["example.org", "alt.example.org"]); ).toHaveProperty("vias", ["example.org", "alt.example.org"]);
}); expect(parseArgs("sharer=blah")).toHaveProperty("sharer", "blah");
expect(parseArgs("client=blah.com")).toHaveProperty("client", "blah.com");
it("parses sharer", () => {
expect(parseArgs("sharer=blah")).toHaveProperty("sharer", "blah");
});
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", () => { it("parses permalinks", () => {
expect(parsePermalink("!somewhere:example.org/$event:example.org")).toEqual({ expect(parsePermalink("!somewhere:example.org/$event:example.org")).toEqual(
roomKind: LinkDiscriminator.RoomId, {
roomLink: "!somewhere:example.org", roomKind: LinkKind.RoomId,
eventId: "$event:example.org", roomLink: "!somewhere:example.org",
}); eventId: "$event:example.org",
}
);
}); });
it("formats links correctly", () => { it("formats links correctly", () => {
const bigLink = const bigLink =
"!somewhere:example.org/$event:example.org?via=dfasdf&via=jfjafjaf"; "!somewhere:example.org/$event:example.org?via=dfasdf&via=jfjafjaf";
const origin = "https://matrix.org"; const origin = "https://matrix.org";
const prefix = origin + "/#/"; const prefix = origin + "/#/";
const parse = parseHash(bigLink); const parse = parseHash(bigLink);
switch (parse.kind) { switch (parse.kind) {
case LinkDiscriminator.ParseFailed: case LinkKind.ParseFailed:
fail("Parse failed"); fail("Parse failed");
default: default:
expect(toURL(origin, parse).toString()).toEqual(prefix + bigLink); expect(toURL(origin, parse).toString()).toEqual(prefix + bigLink);
} }
}); });

View file

@ -1,93 +1,134 @@
import forEach from "lodash/forEach"; import forEach from "lodash/forEach";
import { import {
LinkDiscriminator, LinkKind,
SafeLink, SafeLink,
Link, Link,
LinkContent, LinkContent,
Arguments, Arguments,
Permalink,
} from "./types"; } 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<LinkKind.Alias | LinkKind.RoomId>[] = [
[/^#([^/:]+?):(.+)$/, LinkKind.Alias],
[/^!([^/:]+?):(.+)$/, LinkKind.RoomId],
];
export const verifiers: Verifier<LinkKind>[] = [
[/^[!#]([^/:]+?):(.+?)\/\$([^/:]+?):(.+?)$/, LinkKind.Permalink],
[/^@([^/:]+?):(.+)$/, LinkKind.UserId],
[/^\+([^/:]+?):(.+)$/, LinkKind.GroupId],
...roomVerifiers,
];
/*
* 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;
}
return verifiers.reduce<T | F>((kind, verifier) => {
if (kind !== fail) {
return kind;
}
if (identifier.match(verifier[0])) {
return verifier[1];
}
return kind;
}, fail);
}
/*
* Parses a permalink.
* Assumes the permalink is correct.
*/
export function parsePermalink(identifier: string): Permalink {
const [roomLink, eventId] = identifier.split("/");
const roomKind = identifyTypeFromRegex(
roomLink,
roomVerifiers,
// This is hacky but we're assuming identifier is a valid permalink
LinkKind.Alias
);
return {
roomKind,
roomLink,
eventId,
};
}
/*
* Repalces null with undefined
*/
function bottomExchange<T>(nullable: T | null): T | undefined {
if (nullable === null) return undefined;
return nullable;
}
/*
* parseArgs parses the <extra args> part of matrix.to links
*/
export function parseArgs(args: string): Arguments {
const params = new URLSearchParams(args);
return {
vias: params.getAll("via"),
client: bottomExchange(params.get("client")),
sharer: bottomExchange(params.get("sharer")),
};
}
/* /*
* parseLink takes a striped matrix.to 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 * and parses into a Link. If the parse failed the result will
* be ParseFailed * be ParseFailed
*/ */
export function parseHash(hash: string): Link { export function parseHash(hash: string): Link {
const [identifier, args] = hash.split("?"); const [identifier, args] = hash.split("?");
const kind = identifyTypeFromRegex( const kind = identifyTypeFromRegex(
identifier, identifier,
verifiers, verifiers,
LinkDiscriminator.ParseFailed LinkKind.ParseFailed
); );
let parsedLink: LinkContent = { const parsedLink: LinkContent = {
identifier, identifier,
arguments: parseArgs(args), arguments: parseArgs(args),
originalLink: hash, originalLink: hash,
}; };
if (kind === LinkDiscriminator.Permalink) { if (kind === LinkKind.Permalink) {
const { roomKind, roomLink, eventId } = parsePermalink(identifier); const { roomKind, roomLink, eventId } = parsePermalink(identifier);
return {
kind,
...parsedLink,
roomKind,
roomLink,
eventId,
};
}
return { return {
kind, kind,
...parsedLink, ...parsedLink,
roomKind,
roomLink,
eventId,
}; };
}
return {
kind,
...parsedLink,
};
}
/*
* Parses a permalink.
* Assumes the permalink is correct.
*/
export function parsePermalink(identifier: string) {
const [roomLink, eventId] = identifier.split("/");
const roomKind = identifyTypeFromRegex(
roomLink,
roomVerifiers,
// This is hacky but we're assuming identifier is a valid permalink
LinkDiscriminator.Alias
);
return {
roomKind,
roomLink,
eventId,
};
}
/*
* 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;
} }
/* /*
@ -96,72 +137,27 @@ function bottomExchange<T>(nullable: T | null): T | undefined {
* This is handy function in case the Link was constructed. * This is handy function in case the Link was constructed.
*/ */
export function toURL(origin: string, link: SafeLink): URL { export function toURL(origin: string, link: SafeLink): URL {
switch (link.kind) { const params = new URLSearchParams();
case LinkDiscriminator.GroupId: const url = new URL(origin);
case LinkDiscriminator.UserId: switch (link.kind) {
case LinkDiscriminator.RoomId: case LinkKind.GroupId:
case LinkDiscriminator.Alias: case LinkKind.UserId:
case LinkDiscriminator.Permalink: case LinkKind.RoomId:
const params = new URLSearchParams(); case LinkKind.Alias:
forEach(link.arguments, (value, key) => { case LinkKind.Permalink:
if (value === undefined) { forEach(link.arguments, (value, key) => {
// do nothing if (value === undefined) {
} else if (key === "vias") { // do nothing
(<string[]>(<unknown>value)).forEach((via) => } else if (key === "vias") {
params.append("via", via) (value as string[]).forEach((via) =>
); params.append("via", via)
} else { );
params.append(key, value.toString()); } else {
} params.append(key, value.toString());
}); }
});
const url = new URL(origin); url.hash = `/${link.identifier}?${params.toString()}`;
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 identifyTypeFromRegex<T, F>(
identifier: string,
verifiers: Verifier<T>[],
fail: F
): T | F {
if (identifier !== encodeURI(identifier)) {
return fail;
}
return verifiers.reduce<T | F>((discriminator, verifier) => {
if (discriminator !== fail) {
return discriminator;
} }
return url;
if (identifier.match(verifier[0])) {
return verifier[1];
}
return discriminator;
}, fail);
} }

View file

@ -1,56 +1,52 @@
export interface Arguments { export interface Arguments {
vias: string[]; vias: string[];
// Schemeless http identifier // Schemeless http identifier
client?: string; client?: string;
// Indicates whether a room exists on a federating server (assumed to be the // MXID
// default), or if the client must connect via the server identified by the sharer?: string;
// room ID or event ID
federated?: boolean;
// MXID
sharer?: string;
} }
export interface LinkContent { export interface LinkContent {
identifier: string; identifier: string;
arguments: Arguments; arguments: Arguments;
originalLink: string; originalLink: string;
} }
export enum LinkDiscriminator { export enum LinkKind {
Alias = "ALIAS", Alias = "ALIAS",
RoomId = "ROOM_ID", RoomId = "ROOM_ID",
UserId = "USER_ID", UserId = "USER_ID",
Permalink = "PERMALINK", Permalink = "PERMALINK",
GroupId = "GROUP_ID", GroupId = "GROUP_ID",
ParseFailed = "PARSE_FAILED", ParseFailed = "PARSE_FAILED",
} }
export interface Alias extends LinkContent { export interface Alias extends LinkContent {
kind: LinkDiscriminator.Alias; kind: LinkKind.Alias;
} }
export interface RoomId extends LinkContent { export interface RoomId extends LinkContent {
kind: LinkDiscriminator.RoomId; kind: LinkKind.RoomId;
} }
export interface UserId extends LinkContent { export interface UserId extends LinkContent {
kind: LinkDiscriminator.UserId; kind: LinkKind.UserId;
} }
export interface GroupId extends LinkContent { export interface GroupId extends LinkContent {
kind: LinkDiscriminator.GroupId; kind: LinkKind.GroupId;
} }
export interface Permalink extends LinkContent { export interface Permalink extends LinkContent {
kind: LinkDiscriminator.Permalink; kind: LinkKind.Permalink;
roomKind: LinkDiscriminator.RoomId | LinkDiscriminator.Alias; roomKind: LinkKind.RoomId | LinkKind.Alias;
roomLink: string; roomLink: string;
eventId: string; eventId: string;
} }
export interface ParseFailed { export interface ParseFailed {
kind: LinkDiscriminator.ParseFailed; kind: LinkKind.ParseFailed;
originalLink: string; originalLink: string;
} }
export type SafeLink = Alias | RoomId | UserId | Permalink | GroupId; export type SafeLink = Alias | RoomId | UserId | Permalink | GroupId;