78 lines
3.6 KiB
JavaScript
78 lines
3.6 KiB
JavaScript
import { joinURL, withQuery, withoutBase } from "ufo";
|
|
import { appendResponseHeader, getRequestHeaders, send, setResponseHeader, setResponseHeaders, setResponseStatus } from "h3";
|
|
import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
|
|
import { isJsonRequest } from "../utils/error.mjs";
|
|
import { generateErrorOverlayHTML } from "../utils/dev.mjs";
|
|
export default (async function errorhandler(error, event, { defaultHandler }) {
|
|
if (event.handled || isJsonRequest(event)) {
|
|
// let Nitro handle JSON errors
|
|
return;
|
|
}
|
|
// invoke default Nitro error handler (which will log appropriately if required)
|
|
const defaultRes = await defaultHandler(error, event, { json: true });
|
|
// let Nitro handle redirect if appropriate
|
|
const status = error.status || error.statusCode || 500;
|
|
if (status === 404 && defaultRes.status === 302) {
|
|
setResponseHeaders(event, defaultRes.headers);
|
|
setResponseStatus(event, defaultRes.status, defaultRes.statusText);
|
|
return send(event, JSON.stringify(defaultRes.body, null, 2));
|
|
}
|
|
if (import.meta.dev && typeof defaultRes.body !== "string" && Array.isArray(defaultRes.body.stack)) {
|
|
// normalize to string format expected by nuxt `error.vue`
|
|
defaultRes.body.stack = defaultRes.body.stack.join("\n");
|
|
}
|
|
const errorObject = defaultRes.body;
|
|
// remove proto/hostname/port from URL
|
|
const url = new URL(errorObject.url);
|
|
errorObject.url = withoutBase(url.pathname, useRuntimeConfig(event).app.baseURL) + url.search + url.hash;
|
|
// add default server message (keep sanitized for unhandled errors)
|
|
errorObject.message = error.unhandled ? errorObject.message || "Server Error" : error.message || errorObject.message || "Server Error";
|
|
// we will be rendering this error internally so we can pass along the error.data safely
|
|
errorObject.data ||= error.data;
|
|
errorObject.statusText ||= error.statusText || error.statusMessage;
|
|
delete defaultRes.headers["content-type"];
|
|
delete defaultRes.headers["content-security-policy"];
|
|
setResponseHeaders(event, defaultRes.headers);
|
|
// Access request headers
|
|
const reqHeaders = getRequestHeaders(event);
|
|
// Detect to avoid recursion in SSR rendering of errors
|
|
const isRenderingError = event.path.startsWith("/__nuxt_error") || !!reqHeaders["x-nuxt-error"];
|
|
// HTML response (via SSR)
|
|
const res = isRenderingError ? null : await useNitroApp().localFetch(withQuery(joinURL(useRuntimeConfig(event).app.baseURL, "/__nuxt_error"), errorObject), {
|
|
headers: {
|
|
...reqHeaders,
|
|
"x-nuxt-error": "true"
|
|
},
|
|
redirect: "manual"
|
|
}).catch(() => null);
|
|
if (event.handled) {
|
|
return;
|
|
}
|
|
// Fallback to static rendered error page
|
|
if (!res) {
|
|
const { template } = await import("../templates/error-500");
|
|
if (import.meta.dev) {
|
|
// TODO: Support `message` in template
|
|
errorObject.description = errorObject.message;
|
|
}
|
|
setResponseHeader(event, "Content-Type", "text/html;charset=UTF-8");
|
|
return send(event, template(errorObject));
|
|
}
|
|
const html = await res.text();
|
|
for (const [header, value] of res.headers.entries()) {
|
|
if (header === "set-cookie") {
|
|
appendResponseHeader(event, header, value);
|
|
continue;
|
|
}
|
|
setResponseHeader(event, header, value);
|
|
}
|
|
setResponseStatus(event, res.status && res.status !== 200 ? res.status : defaultRes.status, res.statusText || defaultRes.statusText);
|
|
if (import.meta.dev && !import.meta.test && typeof html === "string") {
|
|
const prettyResponse = await defaultHandler(error, event, { json: false });
|
|
if (typeof prettyResponse.body === "string") {
|
|
return send(event, html.replace("</body>", `${generateErrorOverlayHTML(prettyResponse.body, { startMinimized: 300 <= status && status < 500 })}</body>`));
|
|
}
|
|
}
|
|
return send(event, html);
|
|
});
|