2021-09-30 05:14:56 +02:00
|
|
|
import { logger } from "@coder/logger"
|
|
|
|
import express from "express"
|
|
|
|
import { promises as fs } from "fs"
|
|
|
|
import path from "path"
|
|
|
|
import { WebsocketRequest } from "../../../typings/pluginapi"
|
|
|
|
import { HttpCode } from "../../common/http"
|
|
|
|
import { rootPath } from "../constants"
|
|
|
|
import { replaceTemplates } from "../http"
|
2021-11-09 22:39:54 +01:00
|
|
|
import { escapeHtml, getMediaMime } from "../util"
|
2021-09-30 05:14:56 +02:00
|
|
|
|
2021-11-20 00:14:13 +01:00
|
|
|
interface ErrorWithStatusCode {
|
|
|
|
statusCode: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ErrorWithCode {
|
|
|
|
code: string
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Error is network related. */
|
|
|
|
export const errorHasStatusCode = (error: any): error is ErrorWithStatusCode => {
|
|
|
|
return error && "statusCode" in error
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Error originates from file system. */
|
|
|
|
export const errorHasCode = (error: any): error is ErrorWithCode => {
|
|
|
|
return error && "code" in error
|
|
|
|
}
|
|
|
|
|
|
|
|
const notFoundCodes = [404, "ENOENT", "EISDIR"]
|
|
|
|
|
2021-09-30 05:14:56 +02:00
|
|
|
export const errorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
2021-11-20 00:14:13 +01:00
|
|
|
let statusCode = 500
|
|
|
|
|
|
|
|
if (errorHasStatusCode(err)) {
|
|
|
|
statusCode = err.statusCode
|
|
|
|
} else if (errorHasCode(err) && notFoundCodes.includes(err.code)) {
|
|
|
|
statusCode = HttpCode.NotFound
|
2021-09-30 05:14:56 +02:00
|
|
|
}
|
|
|
|
|
2021-11-20 00:14:13 +01:00
|
|
|
res.status(statusCode)
|
2021-09-30 05:14:56 +02:00
|
|
|
|
|
|
|
// Assume anything that explicitly accepts text/html is a user browsing a
|
|
|
|
// page (as opposed to an xhr request). Don't use `req.accepts()` since
|
|
|
|
// *every* request that I've seen (in Firefox and Chromium at least)
|
|
|
|
// includes `*/*` making it always truthy. Even for css/javascript.
|
|
|
|
if (req.headers.accept && req.headers.accept.includes("text/html")) {
|
|
|
|
const resourcePath = path.resolve(rootPath, "src/browser/pages/error.html")
|
|
|
|
res.set("Content-Type", getMediaMime(resourcePath))
|
|
|
|
const content = await fs.readFile(resourcePath, "utf8")
|
|
|
|
res.send(
|
|
|
|
replaceTemplates(req, content)
|
2021-11-20 00:14:13 +01:00
|
|
|
.replace(/{{ERROR_TITLE}}/g, statusCode.toString())
|
|
|
|
.replace(/{{ERROR_HEADER}}/g, statusCode.toString())
|
2021-11-09 22:39:54 +01:00
|
|
|
.replace(/{{ERROR_BODY}}/g, escapeHtml(err.message)),
|
2021-09-30 05:14:56 +02:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
res.json({
|
|
|
|
error: err.message,
|
|
|
|
...(err.details || {}),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
|
|
|
logger.error(`${err.message} ${err.stack}`)
|
|
|
|
;(req as WebsocketRequest).ws.end()
|
|
|
|
}
|