diff --git a/client/img/matrix-public-archive-opengraph.png b/client/img/matrix-public-archive-opengraph.png
new file mode 100644
index 00000000..07946c6d
Binary files /dev/null and b/client/img/matrix-public-archive-opengraph.png differ
diff --git a/client/js/entry-client-room-directory.js b/client/js/entry-client-room-directory.js
index 1a1726b0..72645f99 100644
--- a/client/js/entry-client-room-directory.js
+++ b/client/js/entry-client-room-directory.js
@@ -8,3 +8,4 @@ import '../css/room-directory.css';
// over for all
import '../img/favicon.ico';
import '../img/favicon.svg';
+import '../img/matrix-public-archive-opengraph.png';
diff --git a/server/hydrogen-render/render-page-html.js b/server/hydrogen-render/render-page-html.js
index fced81dc..d209aa30 100644
--- a/server/hydrogen-render/render-page-html.js
+++ b/server/hydrogen-render/render-page-html.js
@@ -1,12 +1,32 @@
'use strict';
const assert = require('assert');
+const urlJoin = require('url-join');
const { getSerializableSpans } = require('../tracing/tracing-middleware');
const sanitizeHtml = require('../lib/sanitize-html');
const safeJson = require('../lib/safe-json');
const getDependenciesForEntryPointName = require('../lib/get-dependencies-for-entry-point-name');
-const getFaviconAssetUrls = require('../lib/get-favicon-asset-urls');
+const getAssetUrl = require('../lib/get-asset-url');
+
+const config = require('../lib/config');
+const basePath = config.get('basePath');
+assert(basePath);
+
+let _assetUrls;
+function getAssetUrls() {
+ // Probably not that much overhead but only calculate this once
+ if (_assetUrls) {
+ return _assetUrls;
+ }
+
+ _assetUrls = {
+ faviconIco: getAssetUrl('client/img/favicon.ico'),
+ faviconSvg: getAssetUrl('client/img/favicon.svg'),
+ opengraphImage: getAssetUrl('client/img/matrix-public-archive-opengraph.png'),
+ };
+ return _assetUrls;
+}
function renderPageHtml({
pageOptions,
@@ -45,7 +65,12 @@ function renderPageHtml({
maybeAdultMeta = ``;
}
- const faviconMap = getFaviconAssetUrls();
+ const pageAssetUrls = getAssetUrls();
+ let metaImageUrl = urlJoin(basePath, pageAssetUrls.opengraphImage);
+ if (pageOptions.imageUrl) {
+ metaImageUrl = pageOptions.imageUrl;
+ }
+
const pageHtml = `
@@ -55,8 +80,9 @@ function renderPageHtml({
${maybeAdultMeta}
${sanitizeHtml(`
${pageOptions.title}`)}
${sanitizeHtml(``)}
-
-
+ ${sanitizeHtml(``)}
+
+
${styles
.map(
(styleUrl) =>
diff --git a/server/lib/get-asset-url.js b/server/lib/get-asset-url.js
new file mode 100644
index 00000000..b62c5b5f
--- /dev/null
+++ b/server/lib/get-asset-url.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const path = require('path').posix;
+
+function getAssetUrl(inputAssetPath) {
+ // Lazy-load the manifest so we only require it on first call hopefully after the Vite
+ // client build completes. `require(...)` calls are cached so it should be fine to
+ // look this up over and over.
+ //
+ // We have to disable the `no-missing-require` because the file is built via the Vite client build.
+ // eslint-disable-next-line n/no-missing-require, n/no-unpublished-require
+ const manfiest = require('../../dist/manifest.json');
+
+ const assetEntry = manfiest[inputAssetPath];
+ if (!assetEntry) {
+ throw new Error(`Could not find asset with path "${inputAssetPath}" in \`dist/manifest.json\``);
+ }
+
+ const outputAssetPath = path.join('/', assetEntry.file);
+
+ return outputAssetPath;
+}
+
+module.exports = getAssetUrl;
diff --git a/server/lib/get-favicon-asset-urls.js b/server/lib/get-favicon-asset-urls.js
deleted file mode 100644
index 292a446e..00000000
--- a/server/lib/get-favicon-asset-urls.js
+++ /dev/null
@@ -1,30 +0,0 @@
-'use strict';
-
-const path = require('path').posix;
-
-let _faviconAssetUrls;
-function getFaviconAssetUrls() {
- // Probably not that much overhead but only calculate this once
- if (_faviconAssetUrls) {
- return _faviconAssetUrls;
- }
-
- // Lazy-load the manifest so we only require it on first call hopefully after the Vite
- // client build completes. `require(...)` calls are cached so it should be fine to
- // look this up over and over.
- //
- // We have to disable this because it's built via the Vite client build.
- // eslint-disable-next-line n/no-missing-require, n/no-unpublished-require
- const manifest = require('../../dist/manifest.json');
-
- const icoAssetPath = path.join('/', manifest['client/img/favicon.ico'].file);
- const svgAssetFile = path.join('/', manifest['client/img/favicon.svg'].file);
-
- _faviconAssetUrls = {
- ico: icoAssetPath,
- svg: svgAssetFile,
- };
- return _faviconAssetUrls;
-}
-
-module.exports = getFaviconAssetUrls;
diff --git a/server/routes/room-routes.js b/server/routes/room-routes.js
index a0f39898..b8255df4 100644
--- a/server/routes/room-routes.js
+++ b/server/routes/room-routes.js
@@ -26,6 +26,7 @@ const renderHydrogenVmRenderScriptToPageHtml = require('../hydrogen-render/rende
const setHeadersToPreloadAssets = require('../lib/set-headers-to-preload-assets');
const setHeadersForDateTemporalContext = require('../lib/set-headers-for-date-temporal-context');
const MatrixPublicArchiveURLCreator = require('matrix-public-archive-shared/lib/url-creator');
+const { mxcUrlToHttpThumbnail } = require('matrix-public-archive-shared/lib/mxc-url-to-http');
const checkTextForNsfw = require('matrix-public-archive-shared/lib/check-text-for-nsfw');
const {
MS_LOOKUP,
@@ -906,6 +907,13 @@ router.get(
const pageOptions = {
title: `${roomData.name} - Matrix Public Archive`,
description: `View the history of ${roomData.name} in the Matrix Public Archive`,
+ imageUrl:
+ roomData.avatarUrl &&
+ mxcUrlToHttpThumbnail({
+ mxcUrl: roomData.avatarUrl,
+ homeserverUrl: matrixServerUrl,
+ size: 256,
+ }),
blockedBySafeSearch: isNsfw,
entryPoint: 'client/js/entry-client-hydrogen.js',
locationHref: urlJoin(basePath, req.originalUrl),
diff --git a/shared/lib/mxc-url-to-http.js b/shared/lib/mxc-url-to-http.js
index 0bc563cf..d69281d6 100644
--- a/shared/lib/mxc-url-to-http.js
+++ b/shared/lib/mxc-url-to-http.js
@@ -13,15 +13,21 @@ function mxcUrlToHttp({ mxcUrl, homeserverUrl }) {
)}`;
}
-function mxcUrlToHttpThumbnail({ mxcUrl, homeserverUrl, size }) {
+const ALLOWED_RESIZE_METHODS = ['scale', 'crop'];
+function mxcUrlToHttpThumbnail({ mxcUrl, homeserverUrl, size, resizeMethod = 'scale' }) {
assert(mxcUrl, '`mxcUrl` must be provided to `mxcUrlToHttp(...)`');
assert(homeserverUrl, '`homeserverUrl` must be provided to `mxcUrlToHttp(...)`');
assert(size, '`size` must be provided to `mxcUrlToHttp(...)`');
+ assert(
+ ALLOWED_RESIZE_METHODS.includes(resizeMethod),
+ `\`resizeMethod\` must be ${JSON.stringify(ALLOWED_RESIZE_METHODS)}`
+ );
const [serverName, mediaId] = mxcUrl.substr('mxc://'.length).split('/');
let qs = new URLSearchParams();
qs.append('width', Math.round(size));
qs.append('height', Math.round(size));
+ qs.append('method', resizeMethod);
const url = homeserverUrl.replace(/\/$/, '');
return `${url}/_matrix/media/r0/thumbnail/${encodeURIComponent(serverName)}/${encodeURIComponent(