|
1 | 1 | 'use strict' |
2 | 2 |
|
3 | | -const Youch = require('youch') |
4 | | -const ForTerminal = require('youch-terminal') |
| 3 | +const ErrorHandler = require('./error-handler') |
5 | 4 |
|
6 | | -/** |
7 | | - * Create a Youch instance for pretty error printing. |
8 | | - * This instance is used to format output for the |
9 | | - * console and for a web view. |
10 | | - * |
11 | | - * @param {Object} request - the request object |
12 | | - * @param {Object} error - object with error details |
13 | | - * |
14 | | - * @returns {Object} |
15 | | - */ |
16 | | -function createYouch ({ request, error, links = [] }) { |
17 | | - /** |
18 | | - * hapi’s request and error objects don’t match the |
19 | | - * expected structure in Youch. We need to adjust |
20 | | - * properties to display them correctly. |
21 | | - */ |
22 | | - request.url = request.path |
23 | | - request.httpVersion = request.raw.req.httpVersion |
24 | | - error.status = error.output.statusCode |
25 | | - |
26 | | - try { |
27 | | - const youch = new Youch(error, request) |
28 | | - |
29 | | - links.forEach(link => youch.addLink(link)) |
30 | | - |
31 | | - return youch |
32 | | - } catch (error) { |
33 | | - console.error(error) |
34 | | - throw error |
35 | | - } |
36 | | -} |
37 | | - |
38 | | -/** |
39 | | - * Check whether the incoming request requires a JSON response. |
40 | | - * This is true for requests where the "accept" header |
41 | | - * contains "json" or the agent is a CLI/GUI app. |
42 | | - * |
43 | | - * @param {Object} |
44 | | - * |
45 | | - * @returns {Boolean} |
46 | | - */ |
47 | | -function wantsJson ({ agent, accept }) { |
48 | | - return matches(agent, /curl|wget|postman|insomnia/i) || matches(accept, /json/) |
49 | | -} |
50 | | - |
51 | | -/** |
52 | | - * Helper function to test whether a given |
53 | | - * string matches a RegEx. |
54 | | - * |
55 | | - * @param {String} str |
56 | | - * @param {String} regex |
57 | | - * |
58 | | - * @returns {Boolean} |
59 | | - */ |
60 | | -function matches (str, regex) { |
61 | | - return str && str.match(regex) |
62 | | -} |
63 | | - |
64 | | -/** |
65 | | - * Returns a link to Google that includes |
66 | | - * the error message as the search |
67 | | - * term. The link is an SVG icon. |
68 | | - * |
69 | | - * @param {Object} error |
70 | | - * |
71 | | - * @returns {String} |
72 | | - */ |
73 | | -function googleIcon (error) { |
74 | | - return `<a rel="noopener noreferrer" target="_blank" href="https://google.com/search?q=${encodeURIComponent(error.message)}" title="Search Google for "${error.message}""> |
75 | | - <!-- Google icon by Picons.me, found at https://www.iconfinder.com/Picons --> |
76 | | - <!-- Free for commercial use --> |
77 | | - <svg width="24" height="24" viewBox="0 0 56.6934 56.6934" xmlns="http://www.w3.org/2000/svg"> |
78 | | - <path d="M51.981,24.4812c-7.7173-0.0038-15.4346-0.0019-23.1518-0.001c0.001,3.2009-0.0038,6.4018,0.0019,9.6017 c4.4693-0.001,8.9386-0.0019,13.407,0c-0.5179,3.0673-2.3408,5.8723-4.9258,7.5991c-1.625,1.0926-3.492,1.8018-5.4168,2.139 c-1.9372,0.3306-3.9389,0.3729-5.8713-0.0183c-1.9651-0.3921-3.8409-1.2108-5.4773-2.3649 c-2.6166-1.8383-4.6135-4.5279-5.6388-7.5549c-1.0484-3.0788-1.0561-6.5046,0.0048-9.5805 c0.7361-2.1679,1.9613-4.1705,3.5708-5.8002c1.9853-2.0324,4.5664-3.4853,7.3473-4.0811c2.3812-0.5083,4.8921-0.4113,7.2234,0.294 c1.9815,0.6016,3.8082,1.6874,5.3044,3.1163c1.5125-1.5039,3.0173-3.0164,4.527-4.5231c0.7918-0.811,1.624-1.5865,2.3908-2.4196 c-2.2928-2.1218-4.9805-3.8274-7.9172-4.9056C32.0723,4.0363,26.1097,3.995,20.7871,5.8372 C14.7889,7.8907,9.6815,12.3763,6.8497,18.0459c-0.9859,1.9536-1.7057,4.0388-2.1381,6.1836 C3.6238,29.5732,4.382,35.2707,6.8468,40.1378c1.6019,3.1768,3.8985,6.001,6.6843,8.215c2.6282,2.0958,5.6916,3.6439,8.9396,4.5078 c4.0984,1.0993,8.461,1.0743,12.5864,0.1355c3.7284-0.8581,7.256-2.6397,10.0725-5.24c2.977-2.7358,5.1006-6.3403,6.2249-10.2138 C52.5807,33.3171,52.7498,28.8064,51.981,24.4812z"/> |
79 | | - </svg> |
80 | | - </a>` |
81 | | -} |
82 | | - |
83 | | -/** |
84 | | - * Returns a link to Stack Overflow that |
85 | | - * includes the error message as the |
86 | | - * search term. The link is an SVG icon. |
87 | | - * |
88 | | - * @param {Object} error |
89 | | - * |
90 | | - * @returns {String} |
91 | | - */ |
92 | | -function stackOverflowIcon (error) { |
93 | | - return `<a rel="noopener noreferrer" target="_blank" href="https://stackoverflow.com/search?q=${encodeURIComponent(error.message)}" title="Search Stack Overflow for "${error.message}""> |
94 | | - <!-- Stack Overflow icon by Picons.me, found at https://www.iconfinder.com/Picons --> |
95 | | - <!-- Free for commercial use --> |
96 | | - <svg width="24" height="24" viewBox="-1163 1657.697 56.693 56.693" xmlns="http://www.w3.org/2000/svg"> |
97 | | - <rect height="4.1104" transform="matrix(-0.8613 -0.508 0.508 -0.8613 -2964.1831 2556.6357)" width="19.2465" x="-1142.8167" y="1680.7778"/><rect height="4.1105" transform="matrix(-0.9657 -0.2596 0.2596 -0.9657 -2672.0498 3027.386)" width="19.2462" x="-1145.7363" y="1688.085"/><rect height="4.1098" transform="matrix(-0.9958 -0.0918 0.0918 -0.9958 -2425.5647 3282.8535)" width="19.246" x="-1146.9451" y="1695.1263"/><rect height="4.111" width="19.2473" x="-1147.2625" y="1701.293"/><path d="M-1121.4579,1710.9474c0,0,0,0.9601-0.0323,0.9601v0.0156h-30.7953c0,0-0.9598,0-0.9598-0.0156h-0.0326v-20.03h3.2877 v16.8049h25.2446v-16.8049h3.2877V1710.9474z"/><rect height="4.111" transform="matrix(0.5634 0.8262 -0.8262 0.5634 892.9033 1662.7915)" width="19.247" x="-1136.5389" y="1674.2235"/><rect height="4.1108" transform="matrix(0.171 0.9853 -0.9853 0.171 720.9987 2489.031)" width="19.2461" x="-1128.3032" y="1670.9347"/> |
98 | | - </svg> |
99 | | - </a>` |
100 | | -} |
101 | | - |
102 | | -/** |
103 | | - * Render better error views during development. |
104 | | - * |
105 | | - * @param {Object} server - hapi server instance where the plugin is registered |
106 | | - * @param {Object} options - plugin options |
107 | | - */ |
108 | 5 | async function register (server, options) { |
109 | | - const defaults = { |
110 | | - showErrors: false, |
111 | | - toTerminal: true, |
112 | | - links: [ |
113 | | - (error) => googleIcon(error), |
114 | | - (error) => stackOverflowIcon(error) |
115 | | - ] |
116 | | - } |
117 | | - |
118 | | - const config = Object.assign({}, defaults, options) |
119 | | - config.links = Array.isArray(config.links) ? config.links : [config.links] |
| 6 | + const { showErrors = false, template, ...config } = options |
120 | 7 |
|
121 | | - if (!config.showErrors) { |
| 8 | + if (!showErrors) { |
122 | 9 | return |
123 | 10 | } |
124 | 11 |
|
125 | | - if (config.template) { |
| 12 | + if (template) { |
126 | 13 | server.dependency(['vision']) |
127 | 14 | } |
128 | 15 |
|
129 | | - server.ext('onPreResponse', async (request, h) => { |
130 | | - const error = request.response |
131 | | - |
132 | | - if (error.isBoom && error.output.statusCode === 500) { |
133 | | - const accept = request.raw.req.headers.accept |
134 | | - const agent = request.raw.req.headers['user-agent'] |
135 | | - const statusCode = error.output.statusCode |
136 | | - |
137 | | - const errorResponse = { |
138 | | - title: error.output.payload.error, |
139 | | - statusCode, |
140 | | - message: error.message, |
141 | | - method: request.raw.req.method, |
142 | | - url: request.url.path, |
143 | | - headers: request.raw.req.headers, |
144 | | - payload: request.raw.req.method !== 'GET' ? request.payload : '', |
145 | | - stacktrace: error.stack |
146 | | - } |
147 | | - |
148 | | - const youch = createYouch({ request, error, links: config.links }) |
149 | | - |
150 | | - // print a pretty error to terminal as well |
151 | | - if (config.toTerminal) { |
152 | | - const json = await youch.toJSON() |
153 | | - console.log(ForTerminal(json)) |
154 | | - } |
| 16 | + const errorHandler = new ErrorHandler({ template, ...config }) |
155 | 17 |
|
156 | | - if (wantsJson({ accept, agent })) { |
157 | | - const details = Object.assign({}, errorResponse, { |
158 | | - stacktrace: errorResponse.stacktrace.split('\n').map(line => line.trim()) |
159 | | - }) |
160 | | - |
161 | | - return h |
162 | | - .response(JSON.stringify(details, null, 2)) |
163 | | - .type('application/json') |
164 | | - .code(statusCode) |
165 | | - } |
166 | | - |
167 | | - // did the user explicitly specify an error template |
168 | | - // favor a user’s custom template over the default template |
169 | | - if (config.template) { |
170 | | - return h |
171 | | - .view(config.template, { request, error, ...errorResponse }) |
172 | | - .code(statusCode) |
173 | | - } |
174 | | - |
175 | | - const html = await youch.toHTML() |
176 | | - |
177 | | - return h |
178 | | - .response(html) |
179 | | - .type('text/html') |
180 | | - .code(statusCode) |
181 | | - } |
182 | | - |
183 | | - return h.continue |
| 18 | + server.ext('onPreResponse', async (request, h) => { |
| 19 | + return errorHandler.handle(request, h) |
184 | 20 | }) |
185 | 21 | } |
186 | 22 |
|
|
0 commit comments