2018-01-04 17:36:47 +00:00
|
|
|
const Youch = require('@nuxtjs/youch')
|
2018-01-05 09:19:32 +00:00
|
|
|
const { join, resolve, relative, isAbsolute } = require('path')
|
2018-01-04 17:36:47 +00:00
|
|
|
const { readFile } = require('fs-extra')
|
|
|
|
|
|
|
|
module.exports = function errorMiddleware(err, req, res, next) {
|
|
|
|
// ensure statusCode, message and name fields
|
|
|
|
err.statusCode = err.statusCode || 500
|
|
|
|
err.message = err.message || 'Nuxt Server Error'
|
|
|
|
err.name = (!err.name || err.name === 'Error') ? 'NuxtServerError' : err.name
|
|
|
|
|
|
|
|
// We hide actual errors from end users, so show them on server logs
|
|
|
|
if (err.statusCode !== 404) {
|
|
|
|
console.error(err) // eslint-disable-line no-console
|
|
|
|
}
|
|
|
|
|
|
|
|
const sendResponse = (content, type = 'text/html') => {
|
|
|
|
// Set Headers
|
|
|
|
res.statusCode = err.statusCode
|
|
|
|
res.statusMessage = err.name
|
|
|
|
res.setHeader('Content-Type', type + '; charset=utf-8')
|
|
|
|
res.setHeader('Content-Length', Buffer.byteLength(content))
|
|
|
|
|
|
|
|
// Send Response
|
|
|
|
res.end(content, 'utf-8')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if request accepts JSON
|
|
|
|
const hasReqHeader = (header, includes) => req.headers[header] && req.headers[header].toLowerCase().includes(includes)
|
|
|
|
const isJson = hasReqHeader('accept', 'application/json') || hasReqHeader('user-agent', 'curl/')
|
|
|
|
|
|
|
|
// Use basic errors when debug mode is disabled
|
|
|
|
if (!this.options.debug) {
|
|
|
|
// Json format is compatible with Youch json responses
|
|
|
|
const json = {
|
|
|
|
status: err.statusCode,
|
|
|
|
message: err.message,
|
|
|
|
name: err.name
|
|
|
|
}
|
|
|
|
if (isJson) {
|
|
|
|
sendResponse(JSON.stringify(json, undefined, 2), 'text/json')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const html = this.resources.errorTemplate(json)
|
|
|
|
sendResponse(html)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show stack trace
|
2018-01-06 19:57:20 +00:00
|
|
|
const youch = new Youch(err, req, readSource.bind(this), this.options.router.base)
|
2018-01-04 17:36:47 +00:00
|
|
|
if (isJson) {
|
|
|
|
youch.toJSON().then(json => { sendResponse(JSON.stringify(json, undefined, 2), 'text/json') })
|
|
|
|
} else {
|
|
|
|
youch.toHTML().then(html => { sendResponse(html) })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function readSource(frame) {
|
|
|
|
// Remove webpack:/// & query string from the end
|
2018-01-05 09:19:32 +00:00
|
|
|
const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : null
|
2018-01-04 17:36:47 +00:00
|
|
|
frame.fileName = sanitizeName(frame.fileName)
|
|
|
|
|
2018-01-05 09:19:32 +00:00
|
|
|
// Return if fileName is unknown
|
|
|
|
if (!frame.fileName) {
|
2018-01-04 17:36:47 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Possible paths for file
|
|
|
|
const searchPath = [
|
2018-01-05 09:19:32 +00:00
|
|
|
this.options.srcDir,
|
2018-01-04 17:36:47 +00:00
|
|
|
this.options.rootDir,
|
|
|
|
join(this.options.buildDir, 'dist'),
|
|
|
|
this.options.buildDir
|
|
|
|
]
|
|
|
|
|
2018-01-05 09:19:32 +00:00
|
|
|
// Scan filesystem for real source
|
2018-01-04 17:36:47 +00:00
|
|
|
for (let pathDir of searchPath) {
|
|
|
|
let fullPath = resolve(pathDir, frame.fileName)
|
|
|
|
let source = await readFile(fullPath, 'utf-8').catch(() => null)
|
|
|
|
if (source) {
|
2018-01-05 09:19:32 +00:00
|
|
|
frame.contents = source
|
2018-01-04 17:36:47 +00:00
|
|
|
frame.fullPath = fullPath
|
2018-01-05 09:19:32 +00:00
|
|
|
if (isAbsolute(frame.fileName)) {
|
|
|
|
frame.fileName = relative(this.options.rootDir, fullPath)
|
|
|
|
}
|
2018-01-04 17:36:47 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-01-05 09:19:32 +00:00
|
|
|
|
|
|
|
// Fallback: use server bundle
|
|
|
|
if (!frame.contents) {
|
|
|
|
frame.contents = this.resources.serverBundle.files[frame.fileName]
|
|
|
|
}
|
2018-01-04 17:36:47 +00:00
|
|
|
}
|