diff --git a/src/d2m/event-dispatcher.js b/src/d2m/event-dispatcher.js
index fdb6c93..b98abfc 100644
--- a/src/d2m/event-dispatcher.js
+++ b/src/d2m/event-dispatcher.js
@@ -35,6 +35,8 @@ const speedbump = sync.require("./actions/speedbump")
const retrigger = sync.require("./actions/retrigger")
/** @type {import("./actions/set-presence")} */
const setPresence = sync.require("./actions/set-presence")
+/** @type {import("../m2d/event-dispatcher")} */
+const matrixEventDispatcher = sync.require("../m2d/event-dispatcher")
/** @type {any} */ // @ts-ignore bad types from semaphore
const Semaphore = require("@chriscdn/promise-semaphore")
@@ -66,22 +68,19 @@ module.exports = {
const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
if (!roomID) return
- let stackLines = null
- if (e.stack) {
- stackLines = e.stack.split("\n")
- let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/"))
- if (cloudstormLine !== -1) {
- stackLines = stackLines.slice(0, cloudstormLine - 2)
- }
- }
-
const builder = new mxUtils.MatrixStringBuilder()
builder.addLine("\u26a0 Bridged event from Discord not delivered", "\u26a0 Bridged event from Discord not delivered")
builder.addLine(`Gateway event: ${gatewayMessage.t}`)
- builder.addLine(e.toString())
- if (stackLines) {
- builder.addLine(`Error trace:\n${stackLines.join("\n")}`, `Error trace
${stackLines.join("\n")}
`)
+
+ let errorIntroLine = e.toString()
+ if (e.cause) {
+ errorIntroLine += ` (cause: ${e.cause})`
}
+ builder.addLine(errorIntroLine)
+
+ const stack = matrixEventDispatcher.stringifyErrorStack(e)
+ builder.addLine(`Error trace:\n${stack}`, `Error trace
${stack}
`)
+
builder.addLine("", `Original payload
${util.inspect(gatewayMessage.d, false, 4, false)}
`)
await api.sendEvent(roomID, "m.room.message", {
...builder.get(),
diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js
index 5f8cb2b..1bc97de 100644
--- a/src/m2d/event-dispatcher.js
+++ b/src/m2d/event-dispatcher.js
@@ -28,6 +28,58 @@ const {reg} = require("../matrix/read-registration")
let lastReportedEvent = 0
+/**
+ * This function is adapted from Evan Kaufman's fantastic work.
+ * The original function and my adapted function are both MIT licensed.
+ * @url https://github.com/EvanK/npm-loggable-error/
+ * @param {number} [depth]
+ * @returns {string}
+*/
+function stringifyErrorStack(err, depth = 0) {
+ let collapsed = " ".repeat(depth);
+ if (!(err instanceof Error)) {
+ return collapsed + err
+ }
+
+ // add full stack trace if one exists, otherwise convert to string
+ let stackLines = ( err?.stack ?? `${err}` ).replace(/^/gm, " ".repeat(depth)).trim().split("\n")
+ let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/"))
+ if (cloudstormLine !== -1) {
+ stackLines = stackLines.slice(0, cloudstormLine - 2)
+ }
+ collapsed += stackLines.join("\n")
+
+ const props = Object.getOwnPropertyNames(err).filter(p => !["message", "stack"].includes(p))
+
+ // only break into object notation if we have addtl props to dump
+ if (props.length) {
+ const dedent = " ".repeat(depth);
+ const indent = " ".repeat(depth + 2);
+
+ collapsed += " {\n";
+
+ // loop and print each (indented) prop name
+ for (let property of props) {
+ collapsed += `${indent}[${property}]: `;
+
+ // if another error object, stringify it too
+ if (err[property] instanceof Error) {
+ collapsed += stringifyErrorStack(err[property], depth + 2).trimStart();
+ }
+ // otherwise stringify as JSON
+ else {
+ collapsed += JSON.stringify(err[property]);
+ }
+
+ collapsed += "\n";
+ }
+
+ collapsed += `${dedent}}\n`;
+ }
+
+ return collapsed;
+}
+
function guard(type, fn) {
return async function(event, ...args) {
try {
@@ -39,7 +91,12 @@ function guard(type, fn) {
if (Date.now() - lastReportedEvent < 5000) return
lastReportedEvent = Date.now()
- const cloudflareErrorTitle = e.toString().match(/.*?
discord\.com \| ([^<]*)<\/title>/s)?.[1]
+ let errorIntroLine = e.toString()
+ if (e.cause) {
+ errorIntroLine += ` (cause: ${e.cause})`
+ }
+
+ const cloudflareErrorTitle = errorIntroLine.match(/.*?discord\.com \| ([^<]*)<\/title>/s)?.[1]
if (cloudflareErrorTitle) {
return api.sendEvent(event.room_id, "m.room.message", {
msgtype: "m.text",
@@ -53,16 +110,16 @@ function guard(type, fn) {
})
}
- let stackLines = e.stack.split("\n")
+ const stack = stringifyErrorStack(e)
api.sendEvent(event.room_id, "m.room.message", {
msgtype: "m.text",
body: "\u26a0 Matrix event not delivered to Discord. See formatted content for full details.",
format: "org.matrix.custom.html",
formatted_body: "\u26a0 Matrix event not delivered to Discord"
+ `
Event type: ${type}`
- + `
${e.toString()}`
+ + `
${errorIntroLine}`
+ `
Error trace
`
- + `${stackLines.join("\n")}
`
+ + `${stack}
`
+ `Original payload
`
+ `${util.inspect(event, false, 4, false)}
`,
"moe.cadence.ooye.error": {
@@ -297,3 +354,5 @@ async event => {
db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(newPower[mxid] || 0, event.room_id, mxid)
}
}))
+
+module.exports.stringifyErrorStack = stringifyErrorStack
diff --git a/test/test.js b/test/test.js
index 35c12ed..2d02cbb 100644
--- a/test/test.js
+++ b/test/test.js
@@ -29,7 +29,7 @@ reg.ooye.bridge_origin = "https://bridge.example.org"
const sync = new HeatSync({watchFS: false})
const discord = {
- // @ts-ignore - only ignore guilds, because my data dump is missing random properties
+ // @ts-ignore - ignore guilds, because my data dump is missing random properties
guilds: new Map([
[data.guild.general.id, data.guild.general],
[data.guild.fna.id, data.guild.fna],
@@ -43,6 +43,7 @@ const discord = {
application: {
id: "684280192553844747"
},
+ // @ts-ignore - ignore channels, because my data dump is missing random properties
channels: new Map([
[data.channel.general.id, data.channel.general],
[data.channel.updates.id, data.channel.updates],
@@ -127,6 +128,13 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("./addbot.test")
require("../src/db/orm.test")
+ require("../src/web/server.test")
+ require("../src/web/routes/download-discord.test")
+ require("../src/web/routes/download-matrix.test")
+ require("../src/web/routes/guild.test")
+ require("../src/web/routes/guild-settings.test")
+ require("../src/web/routes/link.test")
+ require("../src/web/routes/log-in-with-matrix.test")
require("../src/discord/utils.test")
require("../src/matrix/kstate.test")
require("../src/matrix/api.test")
@@ -147,6 +155,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../src/d2m/converters/remove-reaction.test")
require("../src/d2m/converters/thread-to-announcement.test")
require("../src/d2m/converters/user-to-mxid.test")
+ require("../src/m2d/event-dispatcher.test")
require("../src/m2d/converters/diff-pins.test")
require("../src/m2d/converters/event-to-message.test")
require("../src/m2d/converters/emoji.test")
@@ -157,11 +166,4 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../src/discord/interactions/permissions.test")
require("../src/discord/interactions/privacy.test")
require("../src/discord/interactions/reactions.test")
- require("../src/web/server.test")
- require("../src/web/routes/download-discord.test")
- require("../src/web/routes/download-matrix.test")
- require("../src/web/routes/guild.test")
- require("../src/web/routes/guild-settings.test")
- require("../src/web/routes/link.test")
- require("../src/web/routes/log-in-with-matrix.test")
})()
diff --git a/test/web.js b/test/web.js
index 0595a96..eb2b876 100644
--- a/test/web.js
+++ b/test/web.js
@@ -96,7 +96,7 @@ class Router {
const router = new Router()
-passthrough.as = {router}
+passthrough.as = {router, on() {}}
module.exports.router = router
module.exports.test = test