123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- 'use strict';
- // modify from https://github.com/poppinss/youch/blob/develop/src/Youch/index.js
- const fs = require('fs');
- const path = require('path');
- const cookie = require('cookie');
- const Mustache = require('mustache');
- const stackTrace = require('stack-trace');
- const util = require('util');
- const { detectErrorMessage } = require('./utils');
- const startingSlashRegex = /\\|\//;
- class ErrorView {
- constructor(ctx, error, template) {
- this.codeContext = 5;
- this._filterHeaders = [ 'cookie', 'connection' ];
- this.ctx = ctx;
- this.error = error;
- this.request = ctx.request;
- this.app = ctx.app;
- this.assets = new Map();
- this.viewTemplate = template;
- }
- /**
- * get html error page
- *
- * @return {String} html page
- *
- * @memberOf ErrorView
- */
- toHTML() {
- const stack = this.parseError();
- const data = this.serializeData(stack, (frame, index) => {
- const serializedFrame = this.serializeFrame(frame);
- serializedFrame.classes = this.getFrameClasses(frame, index);
- return serializedFrame;
- });
- data.request = this.serializeRequest();
- data.appInfo = this.serializeAppInfo();
- return this.complieView(this.viewTemplate, data);
- }
- /**
- * compile view
- *
- * @param {String} tpl - template
- * @param {Object} locals - data used by template
- *
- * @return {String} html
- *
- * @memberOf ErrorView
- */
- complieView(tpl, locals) {
- return Mustache.render(tpl, locals);
- }
- /**
- * check if the frame is node native file.
- *
- * @param {Frame} frame - current frame
- * @return {Boolean} bool
- *
- * @memberOf ErrorView
- */
- isNode(frame) {
- if (frame.isNative()) {
- return true;
- }
- const filename = frame.getFileName() || '';
- return !path.isAbsolute(filename) && filename[0] !== '.';
- }
- /**
- * check if the frame is app modules.
- *
- * @param {Object} frame - current frame
- * @return {Boolean} bool
- *
- * @memberOf ErrorView
- */
- isApp(frame) {
- if (this.isNode(frame)) {
- return false;
- }
- const filename = frame.getFileName() || '';
- return !filename.includes('node_modules' + path.sep);
- }
- /**
- * cache file asserts
- *
- * @param {String} key - assert key
- * @param {String} value - assert content
- *
- * @memberOf ErrorView
- */
- setAssets(key, value) {
- this.assets.set(key, value);
- }
- /**
- * get cache file asserts
- *
- * @param {String} key - assert key
- *
- * @memberOf ErrorView
- */
- getAssets(key) {
- this.assets.get(key);
- }
- /**
- * get frame source
- *
- * @param {Object} frame - current frame
- * @return {Object} frame source
- *
- * @memberOf ErrorView
- */
- getFrameSource(frame) {
- const filename = frame.getFileName();
- const lineNumber = frame.getLineNumber();
- let contents = this.getAssets(filename);
- if (!contents) {
- contents = fs.existsSync(filename) ? fs.readFileSync(filename, 'utf8') : '';
- this.setAssets(filename, contents);
- }
- const lines = contents.split(/\r?\n/);
- return {
- pre: lines.slice(Math.max(0, lineNumber - (this.codeContext + 1)), lineNumber - 1),
- line: lines[lineNumber - 1],
- post: lines.slice(lineNumber, lineNumber + this.codeContext),
- };
- }
- /**
- * parse error and return frame stack
- *
- * @return {Array} frame
- *
- * @memberOf ErrorView
- */
- parseError() {
- const stack = stackTrace.parse(this.error);
- return stack.map(frame => {
- if (!this.isNode(frame)) {
- frame.context = this.getFrameSource(frame);
- }
- return frame;
- });
- }
- /**
- * get stack context
- *
- * @param {Object} frame - current frame
- * @return {Object} context
- *
- * @memberOf ErrorView
- */
- getContext(frame) {
- if (!frame.context) {
- return {};
- }
- return {
- start: frame.getLineNumber() - (frame.context.pre || []).length,
- pre: frame.context.pre.join('\n'),
- line: frame.context.line,
- post: frame.context.post.join('\n'),
- };
- }
- /**
- * get frame classes, let view identify the frame
- *
- * @param {any} frame - current frame
- * @param {any} index - current index
- * @return {String} classes
- *
- * @memberOf ErrorView
- */
- getFrameClasses(frame, index) {
- const classes = [];
- if (index === 0) {
- classes.push('active');
- }
- if (!this.isApp(frame)) {
- classes.push('native-frame');
- }
- return classes.join(' ');
- }
- /**
- * serialize frame and return meaningful data
- *
- * @param {Object} frame - current frame
- * @return {Object} frame result
- *
- * @memberOf ErrorView
- */
- serializeFrame(frame) {
- const filename = frame.getFileName();
- const relativeFileName = filename.includes(process.cwd())
- ? filename.replace(process.cwd(), '').replace(startingSlashRegex, '')
- : filename;
- const extname = path.extname(filename).replace('.', '');
- return {
- extname,
- file: relativeFileName,
- method: frame.getFunctionName(),
- line: frame.getLineNumber(),
- column: frame.getColumnNumber(),
- context: this.getContext(frame),
- };
- }
- /**
- * serialize base data
- *
- * @param {Object} stack - frame stack
- * @param {Function} frameFomatter - frame fomatter function
- * @return {Object} data
- *
- * @memberOf ErrorView
- */
- serializeData(stack, frameFomatter) {
- const code = this.error.code || this.error.type;
- let message = detectErrorMessage(this.ctx, this.error);
- if (code) {
- message = `${message} (code: ${code})`;
- }
- return {
- code,
- message,
- name: this.error.name,
- status: this.error.status,
- frames: stack instanceof Array ? stack.filter(frame => frame.getFileName()).map(frameFomatter) : [],
- };
- }
- /**
- * serialize request object
- *
- * @return {Object} request object
- *
- * @memberOf ErrorView
- */
- serializeRequest() {
- const headers = [];
- Object.keys(this.request.headers).forEach(key => {
- if (this._filterHeaders.includes(key)) {
- return;
- }
- headers.push({
- key,
- value: this.request.headers[key],
- });
- });
- const parsedCookies = cookie.parse(this.request.headers.cookie || '');
- const cookies = Object.keys(parsedCookies).map(key => {
- return { key, value: parsedCookies[key] };
- });
- return {
- url: this.request.url,
- httpVersion: this.request.httpVersion,
- method: this.request.method,
- connection: this.request.headers.connection,
- headers,
- cookies,
- };
- }
- /**
- * serialize app info object
- *
- * @return {Object} egg app info
- *
- * @memberOf ErrorView
- */
- serializeAppInfo() {
- return {
- baseDir: this.app.config.baseDir,
- config: util.inspect(this.app.config),
- };
- }
- }
- module.exports = ErrorView;
|