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(