211 lines
6.6 KiB
JavaScript
211 lines
6.6 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const urlJoin = require('url-join');
|
|
const express = require('express');
|
|
const asyncHandler = require('../lib/express-async-handler');
|
|
const StatusError = require('../lib/status-error');
|
|
|
|
const { handleTracingMiddleware, getSerializableSpans } = require('../tracing/tracing-middleware');
|
|
const timeoutMiddleware = require('./timeout-middleware');
|
|
|
|
const fetchRoomData = require('../fetch-room-data');
|
|
const fetchEventsInRange = require('../fetch-events-in-range');
|
|
const renderHydrogenToString = require('../hydrogen-render/1-render-hydrogen-to-string');
|
|
const sanitizeHtml = require('../lib/sanitize-html');
|
|
const safeJson = require('../lib/safe-json');
|
|
|
|
const config = require('../lib/config');
|
|
const basePath = config.get('basePath');
|
|
assert(basePath);
|
|
const matrixAccessToken = config.get('matrixAccessToken');
|
|
assert(matrixAccessToken);
|
|
const archiveMessageLimit = config.get('archiveMessageLimit');
|
|
assert(archiveMessageLimit);
|
|
|
|
function parseArchiveRangeFromReq(req) {
|
|
const yyyy = parseInt(req.params.yyyy, 10);
|
|
// Month is the only zero-based index in this group
|
|
const mm = parseInt(req.params.mm, 10) - 1;
|
|
const dd = parseInt(req.params.dd, 10);
|
|
|
|
const hourRange = req.params.hourRange;
|
|
|
|
let fromHour = 0;
|
|
let toHour = 0;
|
|
if (hourRange) {
|
|
const hourMatches = hourRange.match(/^(\d\d?)-(\d\d?)$/);
|
|
|
|
if (!hourMatches) {
|
|
throw new StatusError(404, 'Hour was unable to be parsed');
|
|
}
|
|
|
|
fromHour = parseInt(hourMatches[1], 10);
|
|
toHour = parseInt(hourMatches[2], 10);
|
|
|
|
if (Number.isNaN(fromHour) || fromHour < 0 || fromHour > 23) {
|
|
throw new StatusError(404, 'From hour can only be in range 0-23');
|
|
}
|
|
}
|
|
|
|
const fromTimestamp = Date.UTC(yyyy, mm, dd, fromHour);
|
|
let toTimestamp = Date.UTC(yyyy, mm, dd + 1, fromHour);
|
|
if (hourRange) {
|
|
toTimestamp = Date.UTC(yyyy, mm, dd, toHour);
|
|
}
|
|
|
|
return {
|
|
fromTimestamp,
|
|
toTimestamp,
|
|
yyyy,
|
|
mm,
|
|
dd,
|
|
hourRange,
|
|
fromHour,
|
|
toHour,
|
|
};
|
|
}
|
|
|
|
function installRoutes(app) {
|
|
app.use(handleTracingMiddleware);
|
|
|
|
app.get(
|
|
'/health-check',
|
|
asyncHandler(async function (req, res) {
|
|
res.send('{ "ok": true }');
|
|
})
|
|
);
|
|
|
|
// We have to disable no-missing-require lint because it doesn't take into
|
|
// account `package.json`. `exports`, see
|
|
// https://github.com/mysticatea/eslint-plugin-node/issues/255
|
|
// eslint-disable-next-line node/no-missing-require
|
|
app.use(express.static(path.dirname(require.resolve('hydrogen-view-sdk/assets/main.js'))));
|
|
|
|
app.get(
|
|
'/hydrogen-styles.css',
|
|
asyncHandler(async function (req, res) {
|
|
res.set('Content-Type', 'text/css');
|
|
// We have to disable no-missing-require lint because it doesn't take into
|
|
// account `package.json`. `exports`, see
|
|
// https://github.com/mysticatea/eslint-plugin-node/issues/255
|
|
// eslint-disable-next-line node/no-missing-require
|
|
res.sendFile(require.resolve('hydrogen-view-sdk/assets/theme-element-light.css'));
|
|
})
|
|
);
|
|
|
|
// Our own archive app styles
|
|
app.get(
|
|
'/styles.css',
|
|
asyncHandler(async function (req, res) {
|
|
res.set('Content-Type', 'text/css');
|
|
res.sendFile(path.join(__dirname, '../../public/styles/styles.css'));
|
|
})
|
|
);
|
|
|
|
app.get(
|
|
'/matrix-public-archive.js',
|
|
asyncHandler(async function (req, res) {
|
|
res.set('Content-Type', 'text/css');
|
|
res.sendFile(path.join(__dirname, '../../dist/matrix-public-archive.es.js'));
|
|
})
|
|
);
|
|
|
|
app.get(
|
|
'/:roomIdOrAlias/event/:eventId',
|
|
asyncHandler(async function (req, res) {
|
|
// TODO: Fetch event to get `origin_server_ts` and redirect to
|
|
// /!roomId/2022/01/01?at=$eventId
|
|
res.send('todo');
|
|
})
|
|
);
|
|
|
|
// Based off of the Gitter archive routes,
|
|
// https://gitlab.com/gitterHQ/webapp/-/blob/14954e05c905e8c7cb675efebb89116c07cfaab5/server/handlers/app/archive.js#L190-297
|
|
app.get(
|
|
'/:roomIdOrAlias/date/:yyyy(\\d{4})/:mm(\\d{2})/:dd(\\d{2})/:hourRange(\\d\\d?-\\d\\d?)?',
|
|
timeoutMiddleware,
|
|
asyncHandler(async function (req, res) {
|
|
const roomIdOrAlias = req.params.roomIdOrAlias;
|
|
assert(roomIdOrAlias.startsWith('!') || roomIdOrAlias.startsWith('#'));
|
|
|
|
const { fromTimestamp, toTimestamp, hourRange, fromHour, toHour } =
|
|
parseArchiveRangeFromReq(req);
|
|
|
|
// If the hourRange is defined, we force the range to always be 1 hour. If
|
|
// the format isn't correct, redirect to the correct hour range
|
|
if (hourRange && toHour !== fromHour + 1) {
|
|
res.redirect(
|
|
urlJoin(
|
|
basePath,
|
|
roomIdOrAlias,
|
|
'date',
|
|
req.params.yyyy,
|
|
req.params.mm,
|
|
req.params.dd,
|
|
`${fromHour}-${fromHour + 1}`
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
// TODO: Highlight tile that matches ?at=$xxx
|
|
//const aroundId = req.query.at;
|
|
|
|
// Do these in parallel to avoid the extra time in sequential round-trips
|
|
// (we want to display the archive page faster)
|
|
const [roomData, { events, stateEventMap }] = await Promise.all([
|
|
fetchRoomData(matrixAccessToken, roomIdOrAlias),
|
|
fetchEventsInRange(
|
|
matrixAccessToken,
|
|
roomIdOrAlias,
|
|
fromTimestamp,
|
|
toTimestamp,
|
|
archiveMessageLimit
|
|
),
|
|
]);
|
|
|
|
if (events.length >= archiveMessageLimit) {
|
|
throw new Error('TODO: Redirect user to smaller hour range');
|
|
}
|
|
|
|
const hydrogenHtmlOutput = await renderHydrogenToString({
|
|
fromTimestamp,
|
|
roomData,
|
|
events,
|
|
stateEventMap,
|
|
});
|
|
|
|
const serializableSpans = getSerializableSpans();
|
|
const serializedSpans = JSON.stringify(serializableSpans);
|
|
|
|
const hydrogenStylesUrl = urlJoin(basePath, 'hydrogen-styles.css');
|
|
const stylesUrl = urlJoin(basePath, 'styles.css');
|
|
const jsBundleUrl = urlJoin(basePath, 'matrix-public-archive.js');
|
|
const pageHtml = `
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
${sanitizeHtml(`<title>${roomData.name} - Matrix Public Archive</title>`)}
|
|
<link href="${hydrogenStylesUrl}" rel="stylesheet">
|
|
<link href="${stylesUrl}" rel="stylesheet">
|
|
</head>
|
|
<body>
|
|
${hydrogenHtmlOutput}
|
|
<script type="text/javascript" src="${jsBundleUrl}"></script>
|
|
<script type="text/javascript">window.tracingSpansForRequest = ${safeJson(
|
|
serializedSpans
|
|
)};</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
res.set('Content-Type', 'text/html');
|
|
res.send(pageHtml);
|
|
})
|
|
);
|
|
}
|
|
|
|
module.exports = installRoutes;
|