Expose arguments for `renderHydrogenToString` (pure function) so we can reproduce when error occurs while we render Hydrogen (#35)
`renderHydrogenToString` is a pure function (probably) which means it will give the same output given the same input. This means, that if we give it a certain input and an error occurs, we should be able to reproduce it again if we have the arguments. This PR exposes those arguments in the logged error so we can investigate what's going wrong. Added so we can investigate https://github.com/matrix-org/matrix-public-archive/issues/34 better and reproduce locally.
This commit is contained in:
parent
d508521171
commit
17f2c399dd
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
// via https://stackoverflow.com/a/42755876/796832
|
||||
|
||||
// Standard error extender from @deployable/errors
|
||||
class ExtendedError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = this.constructor.name;
|
||||
this.message = message;
|
||||
if (typeof Error.captureStackTrace === 'function') {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
} else {
|
||||
this.stack = new Error(message).stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A way to create a new error with a custom message but keep the stack trace of
|
||||
// the original error. Useful to give more context and why the action was tried
|
||||
// in the first place.
|
||||
//
|
||||
// For example, if you get a generic EACCES disk error of a certain file, you
|
||||
// want to know why and what context the disk was trying to be read. A
|
||||
// stack-trace is not human digestable and only gives the where in the code.
|
||||
// What I actually need to know is that I was trying to read the `ratelimit` key
|
||||
// from the config when this error occured.
|
||||
//
|
||||
// `new RethrownError('Failed to get the ratelimit key from the config', originalError)` (failed to read the disk)
|
||||
class RethrownError extends ExtendedError {
|
||||
constructor(message, error) {
|
||||
super(message);
|
||||
if (!error) throw new Error('RethrownError requires a message and error');
|
||||
this.original = error;
|
||||
this.newStack = this.stack;
|
||||
|
||||
// The number of lines that make up the message itself. We count this by the
|
||||
// number of `\n` and `+ 1` for the first line because it doesn't start with
|
||||
// new line.
|
||||
const messageLines = (this.message.match(/\n/g) || []).length + 1;
|
||||
console.log('messageLines', messageLines);
|
||||
|
||||
const indentedOriginalError = error.stack
|
||||
.split('\n')
|
||||
.map((line) => ` ${line}`)
|
||||
.join('\n');
|
||||
|
||||
this.stack =
|
||||
this.stack
|
||||
.split('\n')
|
||||
// We use `+ 1` here so that we include the first line of the stack to
|
||||
// people know where the error was thrown from.
|
||||
.slice(0, messageLines + 1)
|
||||
.join('\n') +
|
||||
'\n' +
|
||||
' --- Original Error ---\n' +
|
||||
indentedOriginalError;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RethrownError;
|
|
@ -7,82 +7,92 @@ const { readFile } = require('fs').promises;
|
|||
const crypto = require('crypto');
|
||||
const { parseHTML } = require('linkedom');
|
||||
|
||||
const RethrownError = require('./lib/rethrown-error');
|
||||
const config = require('./lib/config');
|
||||
|
||||
async function renderToString({ fromTimestamp, roomData, events, stateEventMap }) {
|
||||
assert(fromTimestamp);
|
||||
assert(roomData);
|
||||
assert(events);
|
||||
assert(stateEventMap);
|
||||
async function renderHydrogenToString({ fromTimestamp, roomData, events, stateEventMap }) {
|
||||
try {
|
||||
assert(fromTimestamp);
|
||||
assert(roomData);
|
||||
assert(events);
|
||||
assert(stateEventMap);
|
||||
|
||||
const dom = parseHTML(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<div id="app" class="hydrogen"></div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
const dom = parseHTML(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<div id="app" class="hydrogen"></div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
if (!dom.requestAnimationFrame) {
|
||||
dom.requestAnimationFrame = function (cb) {
|
||||
setTimeout(cb, 0);
|
||||
if (!dom.requestAnimationFrame) {
|
||||
dom.requestAnimationFrame = function (cb) {
|
||||
setTimeout(cb, 0);
|
||||
};
|
||||
}
|
||||
|
||||
// Define this for the SSR context
|
||||
dom.window.matrixPublicArchiveContext = {
|
||||
fromTimestamp,
|
||||
roomData,
|
||||
events,
|
||||
stateEventMap,
|
||||
config: {
|
||||
basePort: config.get('basePort'),
|
||||
basePath: config.get('basePath'),
|
||||
matrixServerUrl: config.get('matrixServerUrl'),
|
||||
},
|
||||
};
|
||||
// Serialize it for when we run this again client-side
|
||||
dom.document.body.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`
|
||||
<script type="text/javascript">
|
||||
window.matrixPublicArchiveContext = ${JSON.stringify(dom.window.matrixPublicArchiveContext)}
|
||||
</script>
|
||||
`
|
||||
);
|
||||
|
||||
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/hydrogen-vm-render-script.js'),
|
||||
'utf8'
|
||||
);
|
||||
const hydrogenRenderScript = new vm.Script(hydrogenRenderScriptCode, {
|
||||
filename: 'hydrogen-vm-render-script.js',
|
||||
});
|
||||
const vmResult = hydrogenRenderScript.runInContext(vmContext);
|
||||
// Wait for everything to render
|
||||
// (waiting on the promise returned from `hydrogen-render-script.js`)
|
||||
await vmResult;
|
||||
|
||||
const documentString = dom.document.body.toString();
|
||||
return documentString;
|
||||
} catch (err) {
|
||||
throw new RethrownError(
|
||||
`Failed to render Hydrogen to string. In order to reproduce, feed in these arguments into \`renderHydrogenToString(...)\`:\n renderToString arguments: ${JSON.stringify(
|
||||
arguments[0]
|
||||
)}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
// Define this for the SSR context
|
||||
dom.window.matrixPublicArchiveContext = {
|
||||
fromTimestamp,
|
||||
roomData,
|
||||
events,
|
||||
stateEventMap,
|
||||
config: {
|
||||
basePort: config.get('basePort'),
|
||||
basePath: config.get('basePath'),
|
||||
matrixServerUrl: config.get('matrixServerUrl'),
|
||||
},
|
||||
};
|
||||
// Serialize it for when we run this again client-side
|
||||
dom.document.body.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`
|
||||
<script type="text/javascript">
|
||||
window.matrixPublicArchiveContext = ${JSON.stringify(dom.window.matrixPublicArchiveContext)}
|
||||
</script>
|
||||
`
|
||||
);
|
||||
|
||||
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/hydrogen-vm-render-script.js'),
|
||||
'utf8'
|
||||
);
|
||||
const hydrogenRenderScript = new vm.Script(hydrogenRenderScriptCode, {
|
||||
filename: 'hydrogen-vm-render-script.js',
|
||||
});
|
||||
const vmResult = hydrogenRenderScript.runInContext(vmContext);
|
||||
// Wait for everything to render
|
||||
// (waiting on the promise returned from `hydrogen-render-script.js`)
|
||||
await vmResult;
|
||||
|
||||
const documentString = dom.document.body.toString();
|
||||
return documentString;
|
||||
}
|
||||
|
||||
module.exports = renderToString;
|
||||
module.exports = renderHydrogenToString;
|
||||
|
|
Loading…
Reference in New Issue