UNPKG

hapi-error

Version:

catch errors in your hapi application and display the appropriate error message/page

160 lines (138 loc) 6.07 kB
'use strict'; var Hoek = require('@hapi/hoek'); var pkg = require('../package.json'); // require package.json for attributes /** * isFunction checks if a given value is a function. * @param {Object} functionToCheck - the object we want to confirm is a function * @returns {Boolean} true|false */ function isFunction(functionToCheck) { const toString = Object.prototype.toString; return functionToCheck && toString.call(functionToCheck) === '[object Function]' || toString.call(functionToCheck) === '[object AsyncFunction]'; } /** * Merges in custom options to teh default config for each status code * @param {Object} config - the custom option object with status codes as keys * and objects with settings as values * @returns {Object} config to be used in plugin with defaults overwritten * and or added to */ function createConfig (config) { var mergedConfig = { templateName: 'error_template', statusCodes: { 401: { message: 'Please Login to view that page' }, 400: { message: 'Sorry, we do not have that page.' }, 404: { message: 'Sorry, that page is not available.' } } }; // Target status code configuration objects. var statusCodes = config.statusCodes || config; // Backwards compatibility. // Configure error template name. mergedConfig.templateName = config.templateName || mergedConfig.templateName; Object.keys(statusCodes).forEach(function (statusCode) { if (!mergedConfig.statusCodes[statusCode]) { mergedConfig.statusCodes[statusCode] = {}; } // Configure status code settings. Object.keys(statusCodes[statusCode]).forEach(function (setting) { mergedConfig.statusCodes[statusCode][setting] = statusCodes[statusCode][setting]; }); }); return mergedConfig; }; /** * Takes an error Object and Message and throws Hoek Error if not null * @param {String} error - error Object or null * @param {String|Object} [errorMessage] - Optional error message String/Object * @returns {Boolean} false. */ function handleError (error, errorMessage) { if (errorMessage) { return Hoek.assert(!error, errorMessage); } return Hoek.assert(!error, error); }; // export for use in files that do not have access to the request object exports.handleError = handleError; // e.g. database-specific getters/setters /** * register defines our errorHandler plugin following the standard hapi plugin * @param {Object} server - the server instance where the plugin is being used * @param {Object} options - any configuration options passed into the plugin * @returns {Function} reply.continue is called when the plugin is finished */ exports.plugin = { pkg: pkg, register: async function (server, options) { // creates config for handler to be used in 'onPreResponse' function var config = createConfig(options); // make handleError available on request server.ext('onRequest', function (request, reply) { request.handleError = handleError; // github.com/dwyl/hapi-error/issues/23 return reply.continue; }); // onPreResponse intercepts ALL errors server.ext('onPreResponse', function (request, reply) { var res = request.response; var req = request.raw.req; var msg = 'Sorry, something went wrong, please retrace your steps.'; var statusCode = 200; // default to "success" var accept = request.raw.req.headers.accept; var debug; // defined here to keep JSLint Happy. if (res.isBoom) { statusCode = res.output.payload.statusCode; debug = { method: req.method, // e.g GET/POST url: request.url.path, // the path the person requested headers: request.raw.req.headers, // all HTTP Headers info: request.info, // all additional request info (useful to debug) auth: request.auth, // any authentication details e.g. decoded JWT payload: request.payload, // the complete request payload received response: res.output.payload, // response before error intercepted stackTrace: res.stack // the stack trace of the error }; // ALWAYS Log the error server.log('error', debug); // github.com/dwyl/hapi-error/issues/22 // Header check, should take priority if (accept && accept.match(/json/)) { // support REST/JSON requests return reply.response(res.output.payload).code(statusCode); } // custom redirect https://github.com/dwyl/hapi-error/issues/5 var currentCodeConfig = config.statusCodes[statusCode]; if (currentCodeConfig && currentCodeConfig.redirect) { // if redirect is function invoke it with the request object if (isFunction(currentCodeConfig.redirect)) { const url = currentCodeConfig.redirect(request) return reply.redirect(url); } else { // if parameter is string, append redirect query var redirectString = request.url.pathname + request.url.search; return reply.redirect(currentCodeConfig.redirect + '?redirect=' + redirectString); } } if (currentCodeConfig && currentCodeConfig.message) { msg = isFunction(currentCodeConfig.message) ? currentCodeConfig.message(msg, request) : currentCodeConfig.message ; } res = Object.assign(debug, { errorTitle: res.output.payload.error, statusCode: statusCode, errorMessage: msg }); // next avoids TypeError if view rendering is not used in app e.g API! // see: https://github.com/dwyl/hapi-error/issues/49 if (!reply.view) { return reply.response(res).code(statusCode); } return reply.view(config.templateName, res).code(statusCode); // e.g 401 }; // end if (res.isBoom) return reply.continue; // continue processing the request }); } };