2023-04-24 22:50:53 -06:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const assert = require('assert');
|
2023-05-09 23:50:12 -06:00
|
|
|
const urlJoin = require('url-join');
|
2023-04-24 22:50:53 -06:00
|
|
|
|
|
|
|
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');
|
2023-05-09 23:50:12 -06:00
|
|
|
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'),
|
2023-07-14 14:52:35 -06:00
|
|
|
opengraphImage: getAssetUrl('client/img/opengraph.png'),
|
2023-05-09 23:50:12 -06:00
|
|
|
};
|
|
|
|
return _assetUrls;
|
|
|
|
}
|
2023-04-24 22:50:53 -06:00
|
|
|
|
2023-05-01 14:13:16 -06:00
|
|
|
function renderPageHtml({
|
2023-04-24 22:50:53 -06:00
|
|
|
pageOptions,
|
|
|
|
// Make sure you sanitize this before passing it to us
|
|
|
|
bodyHtml,
|
|
|
|
vmRenderContext,
|
|
|
|
}) {
|
|
|
|
assert(vmRenderContext);
|
|
|
|
assert(pageOptions);
|
|
|
|
assert(pageOptions.title);
|
2023-05-04 21:46:09 -06:00
|
|
|
assert(pageOptions.description);
|
2023-04-24 22:50:53 -06:00
|
|
|
assert(pageOptions.entryPoint);
|
|
|
|
assert(pageOptions.cspNonce);
|
|
|
|
|
|
|
|
const { styles, scripts } = getDependenciesForEntryPointName(pageOptions.entryPoint);
|
|
|
|
|
|
|
|
// Serialize the state for when we run the Hydrogen render again client-side to
|
|
|
|
// re-hydrate the DOM
|
2023-07-14 14:52:35 -06:00
|
|
|
const serializedMatrixViewerContext = JSON.stringify({
|
2023-04-24 22:50:53 -06:00
|
|
|
...vmRenderContext,
|
|
|
|
});
|
|
|
|
|
|
|
|
const serializableSpans = getSerializableSpans();
|
|
|
|
const serializedSpans = JSON.stringify(serializableSpans);
|
|
|
|
|
|
|
|
// We shouldn't let some pages be indexed by search engines
|
|
|
|
let maybeNoIndexHtml = '';
|
|
|
|
if (!pageOptions.shouldIndex) {
|
2023-05-05 14:36:26 -06:00
|
|
|
maybeNoIndexHtml = `<meta name="robots" content="noindex, nofollow">`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should tell search engines that some pages are NSFW, see
|
|
|
|
// https://developers.google.com/search/docs/crawling-indexing/safesearch
|
|
|
|
let maybeAdultMeta = '';
|
|
|
|
if (pageOptions.blockedBySafeSearch) {
|
|
|
|
maybeAdultMeta = `<meta name="rating" content="adult">`;
|
2023-04-24 22:50:53 -06:00
|
|
|
}
|
|
|
|
|
2023-05-09 23:50:12 -06:00
|
|
|
const pageAssetUrls = getAssetUrls();
|
|
|
|
let metaImageUrl = urlJoin(basePath, pageAssetUrls.opengraphImage);
|
|
|
|
if (pageOptions.imageUrl) {
|
|
|
|
metaImageUrl = pageOptions.imageUrl;
|
|
|
|
}
|
|
|
|
|
2023-06-22 00:50:55 -06:00
|
|
|
let maybeRelCanonical = '';
|
|
|
|
if (pageOptions.canonicalUrl) {
|
|
|
|
maybeRelCanonical = sanitizeHtml(`<link rel="canonical" href="${pageOptions.canonicalUrl}">`);
|
|
|
|
}
|
|
|
|
|
2023-04-24 22:50:53 -06:00
|
|
|
const pageHtml = `
|
|
|
|
<!doctype html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
${maybeNoIndexHtml}
|
2023-05-05 14:36:26 -06:00
|
|
|
${maybeAdultMeta}
|
2023-04-24 22:50:53 -06:00
|
|
|
${sanitizeHtml(`<title>${pageOptions.title}</title>`)}
|
2023-05-04 21:46:09 -06:00
|
|
|
${sanitizeHtml(`<meta name="description" content="${pageOptions.description}">`)}
|
2023-05-09 23:50:12 -06:00
|
|
|
${sanitizeHtml(`<meta property="og:image" content="${metaImageUrl}">`)}
|
|
|
|
<link rel="icon" href="${pageAssetUrls.faviconIco}" sizes="any">
|
|
|
|
<link rel="icon" href="${pageAssetUrls.faviconSvg}" type="image/svg+xml">
|
2023-06-22 00:50:55 -06:00
|
|
|
${maybeRelCanonical}
|
2023-04-24 22:50:53 -06:00
|
|
|
${styles
|
|
|
|
.map(
|
|
|
|
(styleUrl) =>
|
|
|
|
`<link href="${styleUrl}" rel="stylesheet" nonce="${pageOptions.cspNonce}">`
|
|
|
|
)
|
|
|
|
.join('\n')}
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
${bodyHtml}
|
|
|
|
|
|
|
|
${
|
|
|
|
/**
|
|
|
|
* This inline snippet is used in to scroll the Hydrogen timeline to the
|
|
|
|
* right place immediately when the page loads instead of waiting for
|
|
|
|
* Hydrogen to load, hydrate and finally scroll.
|
|
|
|
*/ ''
|
|
|
|
}
|
|
|
|
<script type="text/javascript" nonce="${pageOptions.cspNonce}">
|
|
|
|
const qs = new URLSearchParams(window?.location?.search);
|
|
|
|
const atEventId = qs.get('at');
|
|
|
|
if (atEventId) {
|
|
|
|
const el = document.querySelector(\`[data-event-id="\${atEventId}"]\`);
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
el && el.scrollIntoView({ block: 'center' });
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const el = document.querySelector('.js-bottom-scroll-anchor');
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
el && el.scrollIntoView({ block: 'end' });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<script type="text/javascript" nonce="${pageOptions.cspNonce}">
|
2023-07-14 14:52:35 -06:00
|
|
|
window.matrixViewerContext = ${safeJson(serializedMatrixViewerContext)}
|
2023-04-24 22:50:53 -06:00
|
|
|
</script>
|
|
|
|
|
|
|
|
${scripts
|
|
|
|
.map(
|
|
|
|
(scriptUrl) =>
|
2023-04-25 02:54:49 -06:00
|
|
|
`<script type="module" src="${scriptUrl}" nonce="${pageOptions.cspNonce}"></script>`
|
2023-04-24 22:50:53 -06:00
|
|
|
)
|
|
|
|
.join('\n')}
|
|
|
|
<script type="text/javascript" nonce="${pageOptions.cspNonce}">
|
|
|
|
window.tracingSpansForRequest = ${safeJson(serializedSpans)};
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
`;
|
|
|
|
|
|
|
|
return pageHtml;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = renderPageHtml;
|