Files
cinny/src/app/plugins/lotus-permalink.ts
T

110 lines
4.1 KiB
TypeScript
Raw Normal View History

import { getHomeRoomPath, getSpacePath, withSearchParam } from '../pages/pathUtils';
import { _RoomSearchParams } from '../pages/paths';
import { MatrixToRoom, MatrixToRoomEvent } from './matrix-to';
/**
* [Gitea #130] Direct Lotus permalinks.
*
* `matrix.to` cannot be pointed at this deployment (its Cinny adapter is
* hard-coded to app.cinny.in and `web-instance[]` only works for Element), so
* next to every "Copy Link" there is a "Copy Lotus Link" that yields
* `https://<this deployment>/home/<room>/<event>?viaServers=…` — the same
* routes the app already serves cold (logged-out → login → back to the room).
*
* `baseUrl` is `getOriginBaseUrl(hashRouter)` so the result is right for any
* deployment and for the hash-router config.
*/
const trimSlash = (s: string): string => s.replace(/\/+$/, '');
const withVia = (path: string, viaServers?: string[]): string =>
viaServers && viaServers.length > 0
? withSearchParam<_RoomSearchParams>(path, { viaServers: viaServers.join(',') })
: path;
export const getLotusRoomPermalink = (
baseUrl: string,
roomIdOrAlias: string,
eventId?: string,
viaServers?: string[],
): string => `${trimSlash(baseUrl)}${withVia(getHomeRoomPath(roomIdOrAlias, eventId), viaServers)}`;
export const getLotusSpacePermalink = (
baseUrl: string,
spaceIdOrAlias: string,
viaServers?: string[],
): string => `${trimSlash(baseUrl)}${withVia(getSpacePath(spaceIdOrAlias), viaServers)}`;
const tryDecode = (value: string): string => {
try {
return decodeURIComponent(value);
} catch {
return value;
}
};
const isRoomIdOrAlias = (s: string): boolean => /^[!#][^/\s]+:[^/\s]+$/.test(s);
const isEventId = (s: string): boolean => /^\$[^/\s]+$/.test(s);
/**
* Parse a link into the same shape the matrix.to parsers return, when it
* points at THIS deployment's room/space routes:
*
* <base>/home/<room>/<event>?viaServers=a,b
* <base>/direct/<room>/<event>
* <base>/<space>/ (space lobby)
* <base>/<space>/<room>/<event> (room inside a space → the room)
*
* Anything else — settings pages, explore, a different origin — is undefined.
* `via` is accepted as an alias of `viaServers` because the matrix.to Cinny
* adapter emits it.
*/
export const parseLotusPermalink = (
baseUrl: string,
href: string,
): MatrixToRoom | MatrixToRoomEvent | undefined => {
const base = trimSlash(baseUrl);
if (!href.startsWith(`${base}/`)) return undefined;
const rest = href.slice(base.length);
let url: URL;
try {
url = new URL(rest, 'http://lotus.invalid');
} catch {
return undefined;
}
const segments = url.pathname
.split('/')
.filter((s) => s.length > 0)
.map(tryDecode);
const via = [...url.searchParams.getAll('viaServers'), ...url.searchParams.getAll('via')]
.flatMap((v) => v.split(','))
.map((v) => v.trim())
.filter((v) => v.length > 0);
const viaServers = via.length > 0 ? via : undefined;
let roomIdOrAlias: string | undefined;
let eventId: string | undefined;
if (segments[0] === 'home' || segments[0] === 'direct') {
[, roomIdOrAlias, eventId] = segments;
} else if (segments[0] && isRoomIdOrAlias(segments[0])) {
// /<space>/ or /<space>/<room>/<event>
roomIdOrAlias = segments[1] && isRoomIdOrAlias(segments[1]) ? segments[1] : segments[0];
if (segments[1] && isRoomIdOrAlias(segments[1])) [, , eventId] = segments;
}
if (!roomIdOrAlias || !isRoomIdOrAlias(roomIdOrAlias)) return undefined;
if (eventId !== undefined && !isEventId(eventId)) return undefined;
return eventId ? { roomIdOrAlias, eventId, viaServers } : { roomIdOrAlias, viaServers };
};
/** Rewrite a Lotus permalink as the matrix.to link the rest of the app already understands. */
export const lotusPermalinkToMatrixTo = (baseUrl: string, href: string): string | undefined => {
const parsed = parseLotusPermalink(baseUrl, href);
if (!parsed) return undefined;
const fragment =
'eventId' in parsed ? `${parsed.roomIdOrAlias}/${parsed.eventId}` : parsed.roomIdOrAlias;
const query = parsed.viaServers ? `?${parsed.viaServers.map((s) => `via=${s}`).join('&')}` : '';
return `https://matrix.to/#/${fragment}${query}`;
};