107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
const MATRIX_TO_BASE = 'https://matrix.to';
|
|
|
|
export const getMatrixToUser = (userId: string): string => `${MATRIX_TO_BASE}/#/${userId}`;
|
|
|
|
const withViaServers = (fragment: string, viaServers: string[]): string =>
|
|
`${fragment}?${viaServers.map((server) => `via=${server}`).join('&')}`;
|
|
|
|
export const getMatrixToRoom = (roomIdOrAlias: string, viaServers?: string[]): string => {
|
|
let fragment = roomIdOrAlias;
|
|
|
|
if (Array.isArray(viaServers) && viaServers.length > 0) {
|
|
fragment = withViaServers(fragment, viaServers);
|
|
}
|
|
|
|
return `${MATRIX_TO_BASE}/#/${fragment}`;
|
|
};
|
|
|
|
export const getMatrixToRoomEvent = (
|
|
roomId: string,
|
|
eventId: string,
|
|
viaServers?: string[]
|
|
): string => {
|
|
let fragment = `${roomId}/${eventId}`;
|
|
|
|
if (Array.isArray(viaServers) && viaServers.length > 0) {
|
|
fragment = withViaServers(fragment, viaServers);
|
|
}
|
|
|
|
return `${MATRIX_TO_BASE}/#/${fragment}`;
|
|
};
|
|
|
|
export type MatrixToRoom = {
|
|
roomIdOrAlias: string;
|
|
viaServers?: string[];
|
|
};
|
|
|
|
export type MatrixToRoomEvent = MatrixToRoom & {
|
|
eventId: string;
|
|
};
|
|
|
|
const MATRIX_TO = /^https?:\/\/matrix\.to\S*$/;
|
|
export const testMatrixTo = (href: string): boolean => MATRIX_TO.test(href);
|
|
|
|
// Matrix room IDs start with `!` (and aliases with `#`) — characters that
|
|
// some URL builders percent-encode in path segments. Go's `id.MatrixURI`
|
|
// builder (mautrix-go id/matrixuri.go) uses `url.PathEscape`, which emits
|
|
// `%21` for `!` — so every matrix.to URL produced by a mautrix bridge
|
|
// arrives here as `https://matrix.to/#/%21abc:server`. Our regexes below
|
|
// match literal `!`/`#` only, so without a decode pass every bridge-
|
|
// generated permalink would silently fail to parse — both the in-chat
|
|
// linkifier (`plugins/react-custom-html-parser.tsx`) and the widget
|
|
// «open-matrix-to» action would drop the URL on the floor.
|
|
//
|
|
// Element Web does the same `decodeURIComponent` step before parsing in
|
|
// `apps/web/src/utils/permalinks/Permalinks.ts::parsePermalink`; we
|
|
// mirror that contract here. `decodeURIComponent` throws synchronously on
|
|
// malformed `%XX` sequences (e.g. lone `%`), so wrap it; a malformed URL
|
|
// is dropped the same way as a non-matching one (undefined).
|
|
const tryDecodeHref = (href: string): string => {
|
|
try {
|
|
return decodeURIComponent(href);
|
|
} catch {
|
|
return href;
|
|
}
|
|
};
|
|
|
|
const MATRIX_TO_USER = /^https?:\/\/matrix\.to\/#\/(@[^:\s]+:[^?/\s]+)\/?$/;
|
|
const MATRIX_TO_ROOM = /^https?:\/\/matrix\.to\/#\/([#!][^?/\s]+)\/?(\?[\S]*)?$/;
|
|
const MATRIX_TO_ROOM_EVENT =
|
|
/^https?:\/\/matrix\.to\/#\/([#!][^?/\s]+)\/(\$[^?/\s]+)\/?(\?[\S]*)?$/;
|
|
|
|
export const parseMatrixToUser = (href: string): string | undefined => {
|
|
const match = tryDecodeHref(href).match(MATRIX_TO_USER);
|
|
if (!match) return undefined;
|
|
const userId = match[1];
|
|
return userId;
|
|
};
|
|
|
|
export const parseMatrixToRoom = (href: string): MatrixToRoom | undefined => {
|
|
const match = tryDecodeHref(href).match(MATRIX_TO_ROOM);
|
|
if (!match) return undefined;
|
|
|
|
const roomIdOrAlias = match[1];
|
|
const viaSearchStr = match[2];
|
|
const viaServers = new URLSearchParams(viaSearchStr).getAll('via');
|
|
|
|
return {
|
|
roomIdOrAlias,
|
|
viaServers: viaServers.length === 0 ? undefined : viaServers,
|
|
};
|
|
};
|
|
|
|
export const parseMatrixToRoomEvent = (href: string): MatrixToRoomEvent | undefined => {
|
|
const match = tryDecodeHref(href).match(MATRIX_TO_ROOM_EVENT);
|
|
if (!match) return undefined;
|
|
|
|
const roomIdOrAlias = match[1];
|
|
const eventId = match[2];
|
|
const viaSearchStr = match[3];
|
|
const viaServers = new URLSearchParams(viaSearchStr).getAll('via');
|
|
|
|
return {
|
|
roomIdOrAlias,
|
|
eventId,
|
|
viaServers: viaServers.length === 0 ? undefined : viaServers,
|
|
};
|
|
};
|