|
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