diff --git a/server/hydrogen-render/3-render-hydrogen-to-string-unsafe.js b/server/hydrogen-render/3-render-hydrogen-to-string-unsafe.js
index a8bc83b..ec8c280 100644
--- a/server/hydrogen-render/3-render-hydrogen-to-string-unsafe.js
+++ b/server/hydrogen-render/3-render-hydrogen-to-string-unsafe.js
@@ -20,12 +20,10 @@ const { parseHTML } = require('linkedom');
const config = require('../lib/config');
-async function _renderHydrogenToStringUnsafe({ fromTimestamp, roomData, events, stateEventMap }) {
- assert(fromTimestamp);
- assert(roomData);
- assert(events);
- assert(stateEventMap);
-
+// Setup the DOM context with any necessary shims/polyfills and ensure the VM
+// context global has everything that a normal document does so Hydrogen can
+// render.
+function createDomAndSetupVmContext() {
const dom = parseHTML(`
@@ -42,6 +40,36 @@ async function _renderHydrogenToStringUnsafe({ fromTimestamp, roomData, events,
};
}
+ const vmContext = vm.createContext(dom);
+ // Make the dom properties available in sub-`require(...)` calls
+ vmContext.global.window = dom.window;
+ vmContext.global.document = dom.document;
+ vmContext.global.Node = dom.Node;
+ vmContext.global.navigator = dom.navigator;
+ vmContext.global.DOMParser = dom.DOMParser;
+ // Make sure `webcrypto` exists since it was only introduced in Node.js v17
+ assert(crypto.webcrypto);
+ vmContext.global.crypto = crypto.webcrypto;
+
+ // So require(...) works in the vm
+ vmContext.global.require = require;
+ // So we can see logs from the underlying vm
+ vmContext.global.console = console;
+
+ return {
+ dom,
+ vmContext,
+ };
+}
+
+async function _renderHydrogenToStringUnsafe({ fromTimestamp, roomData, events, stateEventMap }) {
+ assert(fromTimestamp);
+ assert(roomData);
+ assert(events);
+ assert(stateEventMap);
+
+ const { dom, vmContext } = createDomAndSetupVmContext();
+
// Define this for the SSR context
dom.window.matrixPublicArchiveContext = {
fromTimestamp,
@@ -64,22 +92,6 @@ async function _renderHydrogenToStringUnsafe({ fromTimestamp, roomData, events,
`
);
- const vmContext = vm.createContext(dom);
- // Make the dom properties available in sub-`require(...)` calls
- vmContext.global.window = dom.window;
- vmContext.global.document = dom.document;
- vmContext.global.Node = dom.Node;
- vmContext.global.navigator = dom.navigator;
- vmContext.global.DOMParser = dom.DOMParser;
- // Make sure `webcrypto` exists since it was only introduced in Node.js v17
- assert(crypto.webcrypto);
- vmContext.global.crypto = crypto.webcrypto;
-
- // So require(...) works in the vm
- vmContext.global.require = require;
- // So we can see logs from the underlying vm
- vmContext.global.console = console;
-
const hydrogenRenderScriptCode = await readFile(
path.resolve(__dirname, '../../shared/4-hydrogen-vm-render-script.js'),
'utf8'
diff --git a/test/client-utils.js b/test/client-utils.js
index a119aa4..edb42cb 100644
--- a/test/client-utils.js
+++ b/test/client-utils.js
@@ -121,7 +121,6 @@ async function joinRoom({ client, roomId, viaServers }) {
client.homeserverUrl,
`/_matrix/client/v3/join/${roomId}?${qs.toString()}`
);
- console.log('test client joinRoomUrl', joinRoomUrl);
const joinRoomResponse = await fetchEndpointAsJson(joinRoomUrl, {
method: 'POST',
accessToken: client.accessToken,
diff --git a/test/e2e-tests.js b/test/e2e-tests.js
index c221160..193c8fc 100644
--- a/test/e2e-tests.js
+++ b/test/e2e-tests.js
@@ -129,6 +129,7 @@ describe('matrix-public-archive', () => {
let numMessagesSent = 0;
afterEach(() => {
if (interactive) {
+ // eslint-disable-next-line no-console
console.log('Interactive URL for test', archiveUrl);
}
@@ -317,6 +318,36 @@ describe('matrix-public-archive', () => {
},
});
+ // Test to make sure we can render the page when the reply is missing the
+ // event it's replying to (the relation).
+ const replyMissingRelationMessageText = `While the giant-impact theory explains many lines of evidence, some questions are still unresolved, most of which involve the Moon's composition.`;
+ const missingRelationEventId = '$someMissingEvent';
+ const replyMissingRelationMessageEventId = await sendMessageOnArchiveDate({
+ client,
+ roomId,
+ content: {
+ 'org.matrix.msc1767.message': [
+ {
+ body: '> <@ericgittertester:my.synapse.server> some missing message',
+ mimetype: 'text/plain',
+ },
+ {
+ body: `In reply to @ericgittertester:my.synapse.server
some missing message
${replyMissingRelationMessageText}`,
+ mimetype: 'text/html',
+ },
+ ],
+ body: `> <@ericgittertester:my.synapse.server> some missing message\n\n${replyMissingRelationMessageText}`,
+ msgtype: 'm.text',
+ format: 'org.matrix.custom.html',
+ formatted_body: `In reply to @ericgittertester:my.synapse.server
some missing message
${replyMissingRelationMessageText}`,
+ 'm.relates_to': {
+ 'm.in_reply_to': {
+ event_id: missingRelationEventId,
+ },
+ },
+ },
+ });
+
// Test reactions
const reactionText = '😅';
await sendEventOnArchiveDate({
@@ -384,6 +415,17 @@ describe('matrix-public-archive', () => {
replyMessageElement.outerHTML,
new RegExp(`.*${escapeStringRegexp(normalMessageEventId2)}.*`)
);
+
+ const replyMissingRelationMessageElement = dom.document.querySelector(
+ `[data-event-id="${replyMissingRelationMessageEventId}"]`
+ );
+ // Make sure the reply text is there.
+ // We don't care about the message we're replying to because it's missing on purpose.
+ assert.match(
+ replyMissingRelationMessageElement.outerHTML,
+ new RegExp(`.*${escapeStringRegexp(replyMissingRelationMessageText)}.*`)
+ );
+
// Make sure the reaction also exists
assert.match(
replyMessageElement.outerHTML,