Merge branch 'matrix-two' of github.com:matrix-org/matrix.to into matrixtwo/mvp
This commit is contained in:
commit
fc70e0c734
4
jest.config.js
Normal file
4
jest.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
@ -63,6 +63,7 @@
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/lodash": "^4.14.159",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
@ -73,6 +74,7 @@
|
||||
"node-sass": "^4.14.1",
|
||||
"prettier": "^2.0.5",
|
||||
"storybook-addon-designs": "^5.4.0",
|
||||
"ts-jest": "^26.1.4",
|
||||
"typescript": "~3.7.2"
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ export default {
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A1",
|
||||
url:
|
||||
"https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A1",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -25,7 +25,8 @@ export default {
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A1",
|
||||
url:
|
||||
"https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A1",
|
||||
},
|
||||
},
|
||||
decorators: [withDesign],
|
||||
|
@ -23,7 +23,8 @@ export default {
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=149%3A10756",
|
||||
url:
|
||||
"https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=149%3A10756",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -23,7 +23,8 @@ export default {
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=143%3A5853",
|
||||
url:
|
||||
"https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=143%3A5853",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
64
src/parser/parser.test.ts
Normal file
64
src/parser/parser.test.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/* eslint-disable no-fallthrough */
|
||||
|
||||
import {
|
||||
parseHash,
|
||||
parsePermalink,
|
||||
parseArgs,
|
||||
verifiers,
|
||||
identifyTypeFromRegex,
|
||||
toURL,
|
||||
} from "./parser";
|
||||
|
||||
import { LinkKind } from "./types";
|
||||
|
||||
const identifierType = (id: string): LinkKind =>
|
||||
identifyTypeFromRegex(id, verifiers, LinkKind.ParseFailed);
|
||||
|
||||
it("types identifiers correctly", () => {
|
||||
expect(identifierType("@user:matrix.org")).toEqual(LinkKind.UserId);
|
||||
expect(identifierType("!room:matrix.org")).toEqual(LinkKind.RoomId);
|
||||
expect(identifierType("!somewhere:example.org/$event:example.org")).toEqual(
|
||||
LinkKind.Permalink
|
||||
);
|
||||
expect(identifierType("+group:matrix.org")).toEqual(LinkKind.GroupId);
|
||||
expect(identifierType("#alias:matrix.org")).toEqual(LinkKind.Alias);
|
||||
});
|
||||
|
||||
it("types garbage as such", () => {
|
||||
expect(identifierType("sdfa;fdlkja")).toEqual(LinkKind.ParseFailed);
|
||||
expect(identifierType("$event$matrix.org")).toEqual(LinkKind.ParseFailed);
|
||||
expect(identifierType("/user:matrix.org")).toEqual(LinkKind.ParseFailed);
|
||||
});
|
||||
|
||||
it("parses args correctly", () => {
|
||||
expect(
|
||||
parseArgs("via=example.org&via=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 permalinks", () => {
|
||||
expect(parsePermalink("!somewhere:example.org/$event:example.org")).toEqual(
|
||||
{
|
||||
roomKind: LinkKind.RoomId,
|
||||
roomLink: "!somewhere:example.org",
|
||||
eventId: "$event:example.org",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("formats links correctly", () => {
|
||||
const 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 LinkKind.ParseFailed:
|
||||
fail("Parse failed");
|
||||
default:
|
||||
expect(toURL(origin, parse).toString()).toEqual(prefix + bigLink);
|
||||
}
|
||||
});
|
163
src/parser/parser.ts
Normal file
163
src/parser/parser.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import forEach from "lodash/forEach";
|
||||
|
||||
import {
|
||||
LinkKind,
|
||||
SafeLink,
|
||||
Link,
|
||||
LinkContent,
|
||||
Arguments,
|
||||
Permalink,
|
||||
} 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)
|
||||
* and parses into a Link. If the parse failed the result will
|
||||
* be ParseFailed
|
||||
*/
|
||||
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,
|
||||
};
|
||||
|
||||
if (kind === LinkKind.Permalink) {
|
||||
const { roomKind, roomLink, eventId } = parsePermalink(identifier);
|
||||
|
||||
return {
|
||||
kind,
|
||||
...parsedLink,
|
||||
roomKind,
|
||||
roomLink,
|
||||
eventId,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind,
|
||||
...parsedLink,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
54
src/parser/types.ts
Normal file
54
src/parser/types.ts
Normal file
@ -0,0 +1,54 @@
|
||||
export interface Arguments {
|
||||
vias: string[];
|
||||
// Schemeless http identifier
|
||||
client?: string;
|
||||
// MXID
|
||||
sharer?: string;
|
||||
}
|
||||
|
||||
export interface LinkContent {
|
||||
identifier: string;
|
||||
arguments: Arguments;
|
||||
originalLink: string;
|
||||
}
|
||||
|
||||
export enum LinkKind {
|
||||
Alias = "ALIAS",
|
||||
RoomId = "ROOM_ID",
|
||||
UserId = "USER_ID",
|
||||
Permalink = "PERMALINK",
|
||||
GroupId = "GROUP_ID",
|
||||
ParseFailed = "PARSE_FAILED",
|
||||
}
|
||||
|
||||
export interface Alias extends LinkContent {
|
||||
kind: LinkKind.Alias;
|
||||
}
|
||||
|
||||
export interface RoomId extends LinkContent {
|
||||
kind: LinkKind.RoomId;
|
||||
}
|
||||
|
||||
export interface UserId extends LinkContent {
|
||||
kind: LinkKind.UserId;
|
||||
}
|
||||
|
||||
export interface GroupId extends LinkContent {
|
||||
kind: LinkKind.GroupId;
|
||||
}
|
||||
|
||||
export interface Permalink extends LinkContent {
|
||||
kind: LinkKind.Permalink;
|
||||
roomKind: LinkKind.RoomId | LinkKind.Alias;
|
||||
roomLink: string;
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export interface ParseFailed {
|
||||
kind: LinkKind.ParseFailed;
|
||||
originalLink: string;
|
||||
}
|
||||
|
||||
export type SafeLink = Alias | RoomId | UserId | Permalink | GroupId;
|
||||
|
||||
export type Link = SafeLink | ParseFailed;
|
@ -16,6 +16,7 @@
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user