matrix-public-archive/shared/lib/url-creator.js

175 lines
5.3 KiB
JavaScript

'use strict';
const urlJoin = require('url-join');
const assert = require('matrix-viewer-shared/lib/assert');
const { DIRECTION, TIME_PRECISION_VALUES } = require('matrix-viewer-shared/lib/reference-values');
function qsToUrlPiece(qs) {
if (qs.toString()) {
// We allow `$` to be unencoded in the query string because it's a valid character
// in a Matrix event ID
return `?${qs.toString().replace(/%24/g, '$')}`;
} else {
return '';
}
}
class URLCreator {
constructor(basePath) {
this._basePath = basePath;
}
permalinkForRoom(roomIdOrAlias) {
// We don't `encodeURIComponent(...)` because the URL looks nicer without encoded things
return `https://matrix.to/#/${roomIdOrAlias}`;
}
roomDirectoryUrl({ searchTerm, homeserver, paginationToken, direction } = {}) {
// You must provide both `paginationToken` and `direction` if either is defined
if (paginationToken || direction) {
assert(
[DIRECTION.forward, DIRECTION.backward].includes(direction),
'direction must be [f|b]'
);
assert(paginationToken);
}
let qs = new URLSearchParams();
if (searchTerm) {
qs.append('search', searchTerm);
}
if (homeserver) {
qs.append('homeserver', homeserver);
}
if (paginationToken) {
qs.append('page', paginationToken);
}
if (direction) {
qs.append('dir', direction);
}
return `${this._basePath}${qsToUrlPiece(qs)}`;
}
_getUrlPathForRoomIdOrAlias(roomIdOrAlias) {
let urlPath;
// We don't `encodeURIComponent(...)` because the URL looks nicer without encoded things
if (roomIdOrAlias.startsWith('#')) {
urlPath = `/r/${roomIdOrAlias.replace(/^#/, '')}`;
} else if (roomIdOrAlias.startsWith('!')) {
urlPath = `/roomid/${roomIdOrAlias.replace(/^!/, '')}`;
} else {
throw new Error(
'URLCreator._getUrlPathForRoomIdOrAlias(...): roomIdOrAlias should start with # (alias) or ! (room ID)'
);
}
return urlPath;
}
roomUrl(roomIdOrAlias, { viaServers = [] } = {}) {
assert(roomIdOrAlias);
assert(Array.isArray(viaServers));
let qs = new URLSearchParams();
[].concat(viaServers).forEach((viaServer) => {
qs.append('via', viaServer);
});
const urlPath = this._getUrlPathForRoomIdOrAlias(roomIdOrAlias);
return `${urlJoin(this._basePath, `${urlPath}`)}${qsToUrlPiece(qs)}`;
}
roomUrlForDate(
roomIdOrAlias,
date,
{ preferredPrecision = null, viaServers = [], scrollStartEventId } = {}
) {
assert(roomIdOrAlias);
assert(date);
assert(Array.isArray(viaServers));
// `preferredPrecision` is optional but if they gave a value, make sure it's something expected
if (preferredPrecision) {
assert(
Object.values(TIME_PRECISION_VALUES).includes(preferredPrecision),
`TimeSelectorViewModel: options.preferredPrecision must be one of ${JSON.stringify(
Object.values(TIME_PRECISION_VALUES)
)}`
);
}
let qs = new URLSearchParams();
[].concat(viaServers).forEach((viaServer) => {
qs.append('via', viaServer);
});
if (scrollStartEventId) {
qs.append('at', scrollStartEventId);
}
const urlPath = this._getUrlPathForRoomIdOrAlias(roomIdOrAlias);
// Gives the date in YYYY/mm/dd format.
// date.toISOString() -> 2022-02-16T23:20:04.709Z
const [datePiece, timePiece] = date.toISOString().split('T');
// Get the `2022/02/16` part of it
const urlDate = datePiece.replaceAll('-', '/');
// Get the `23:20:04` part of it (TIME_PRECISION_VALUES.seconds)
let urlTime = timePiece.split('.')[0];
if (preferredPrecision === TIME_PRECISION_VALUES.minutes) {
// We only want to replace the seconds part of the URL if its superfluous. `23:59:00`
// does not convey more information than `23:59` so we can safely remove it if the
// desired precision is in minutes.
urlTime = urlTime.replace(/:00$/, '');
}
const shouldIncludeTimeInUrl = !!preferredPrecision;
return `${urlJoin(
this._basePath,
`${urlPath}/date/${urlDate}${shouldIncludeTimeInUrl ? `T${urlTime}` : ''}`
)}${qsToUrlPiece(qs)}`;
}
jumpUrlForRoom(
roomIdOrAlias,
{
dir,
currentRangeStartTs,
currentRangeEndTs,
timelineStartEventId,
timelineEndEventId,
viaServers = [],
}
) {
assert(roomIdOrAlias);
assert(dir);
assert(typeof currentRangeStartTs === 'number');
assert(typeof currentRangeEndTs === 'number');
assert(Array.isArray(viaServers));
// `timelineStartEventId` and `timelineEndEventId` are optional because the
// timeline could be showing 0 events or we could be jumping with no knowledge of
// what was shown before.
let qs = new URLSearchParams();
qs.append('dir', dir);
qs.append('currentRangeStartTs', currentRangeStartTs);
qs.append('currentRangeEndTs', currentRangeEndTs);
if (timelineStartEventId) {
qs.append('timelineStartEventId', timelineStartEventId);
}
if (timelineEndEventId) {
qs.append('timelineEndEventId', timelineEndEventId);
}
[].concat(viaServers).forEach((viaServer) => {
qs.append('via', viaServer);
});
const urlPath = this._getUrlPathForRoomIdOrAlias(roomIdOrAlias);
return `${urlJoin(this._basePath, `${urlPath}/jump`)}${qsToUrlPiece(qs)}`;
}
}
module.exports = URLCreator;