mastercontroller 1.3.5 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MasterActionFilters.js +2 -2
- package/MasterHtml.js +2 -2
- package/package.json +1 -1
- package/error/ErrorBoundary.js +0 -353
- package/error/HydrationMismatch.js +0 -265
- package/error/MasterBackendErrorHandler.js +0 -769
- package/error/MasterError.js +0 -240
- package/error/MasterError.js.tmp +0 -0
- package/error/MasterErrorHandler.js +0 -487
- package/error/MasterErrorLogger.js +0 -360
- package/error/MasterErrorMiddleware.js +0 -407
- package/error/MasterErrorRenderer.js +0 -536
- package/error/MasterErrorRenderer.js.tmp +0 -0
- package/error/SSRErrorHandler.js +0 -273
- package/log/mastercontroller.log +0 -6
- package/test/security/filters.test.js +0 -276
- package/test/security/https.test.js +0 -214
- package/test/security/path-traversal.test.js +0 -222
- package/test/security/xss.test.js +0 -190
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MasterErrorRenderer - Professional error page rendering system
|
|
3
|
-
*
|
|
4
|
-
* Inspired by Rails ActionDispatch::ExceptionWrapper and Django error views
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Environment-specific rendering (dev vs production)
|
|
8
|
-
* - Dynamic error pages with template data
|
|
9
|
-
* - Multiple error codes (401, 403, 404, 422, 429, 500, 503, etc.)
|
|
10
|
-
* - Content negotiation (HTML vs JSON)
|
|
11
|
-
* - Custom error handlers
|
|
12
|
-
* - Template-based error pages
|
|
13
|
-
*
|
|
14
|
-
* @version 1.0.0
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
const { logger } = require('./MasterErrorLogger');
|
|
20
|
-
|
|
21
|
-
class MasterErrorRenderer {
|
|
22
|
-
constructor() {
|
|
23
|
-
this.errorTemplates = new Map();
|
|
24
|
-
this.customHandlers = new Map();
|
|
25
|
-
this.templateDir = null;
|
|
26
|
-
this.environment = 'development';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Lazy-load master to avoid circular dependency (Google-style lazy initialization)
|
|
30
|
-
get _master() {
|
|
31
|
-
if (!this.__masterCache) {
|
|
32
|
-
this.__masterCache = require('../MasterControl');
|
|
33
|
-
}
|
|
34
|
-
return this.__masterCache;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Initialize error renderer
|
|
39
|
-
*
|
|
40
|
-
* @param {Object} options - Configuration options
|
|
41
|
-
* @param {String} options.templateDir - Directory for error templates (default: 'public/errors')
|
|
42
|
-
* @param {String} options.environment - Environment (development, production, test)
|
|
43
|
-
* @param {Boolean} options.showStackTrace - Show stack traces in dev (default: true in dev)
|
|
44
|
-
*/
|
|
45
|
-
init(options = {}) {
|
|
46
|
-
this.templateDir = options.templateDir || path.join(this._master.root, 'public/errors');
|
|
47
|
-
this.environment = options.environment || this._master.environmentType || 'development';
|
|
48
|
-
this.showStackTrace = options.showStackTrace !== undefined
|
|
49
|
-
? options.showStackTrace
|
|
50
|
-
: (this.environment === 'development');
|
|
51
|
-
|
|
52
|
-
// Create error templates directory if it doesn't exist
|
|
53
|
-
if (!fs.existsSync(this.templateDir)) {
|
|
54
|
-
fs.mkdirSync(this.templateDir, { recursive: true });
|
|
55
|
-
logger.info({
|
|
56
|
-
code: 'MC_ERROR_RENDERER_DIR_CREATED',
|
|
57
|
-
message: 'Created error templates directory',
|
|
58
|
-
dir: this.templateDir
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Load error templates
|
|
63
|
-
this._loadTemplates();
|
|
64
|
-
|
|
65
|
-
logger.info({
|
|
66
|
-
code: 'MC_ERROR_RENDERER_INIT',
|
|
67
|
-
message: 'Error renderer initialized',
|
|
68
|
-
templateDir: this.templateDir,
|
|
69
|
-
environment: this.environment,
|
|
70
|
-
showStackTrace: this.showStackTrace
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return this;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Render error page
|
|
78
|
-
*
|
|
79
|
-
* @param {Object} ctx - Request context
|
|
80
|
-
* @param {Number} statusCode - HTTP status code
|
|
81
|
-
* @param {Object} errorData - Error data
|
|
82
|
-
* @returns {String} - Rendered HTML or JSON
|
|
83
|
-
*/
|
|
84
|
-
render(ctx, statusCode, errorData = {}) {
|
|
85
|
-
const isApiRequest = this._isApiRequest(ctx);
|
|
86
|
-
|
|
87
|
-
if (isApiRequest) {
|
|
88
|
-
return this._renderJSON(statusCode, errorData);
|
|
89
|
-
} else {
|
|
90
|
-
return this._renderHTML(ctx, statusCode, errorData);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Send error response
|
|
96
|
-
*
|
|
97
|
-
* @param {Object} ctx - Request context
|
|
98
|
-
* @param {Number} statusCode - HTTP status code
|
|
99
|
-
* @param {Object} errorData - Error data
|
|
100
|
-
*/
|
|
101
|
-
send(ctx, statusCode, errorData = {}) {
|
|
102
|
-
const content = this.render(ctx, statusCode, errorData);
|
|
103
|
-
const isApiRequest = this._isApiRequest(ctx);
|
|
104
|
-
|
|
105
|
-
if (!ctx.response.headersSent) {
|
|
106
|
-
ctx.response.statusCode = statusCode;
|
|
107
|
-
ctx.response.setHeader('Content-Type', isApiRequest ? 'application/json' : 'text/html');
|
|
108
|
-
ctx.response.end(content);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Log error
|
|
112
|
-
logger.error({
|
|
113
|
-
code: errorData.code || 'MC_HTTP_ERROR',
|
|
114
|
-
message: errorData.message || this._getDefaultMessage(statusCode),
|
|
115
|
-
statusCode,
|
|
116
|
-
path: ctx.pathName || ctx.request.url,
|
|
117
|
-
method: ctx.type || ctx.request.method.toLowerCase(),
|
|
118
|
-
stack: errorData.stack
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Register custom error handler for specific status code
|
|
124
|
-
*
|
|
125
|
-
* @param {Number} statusCode - HTTP status code
|
|
126
|
-
* @param {Function} handler - Handler function (ctx, errorData) => String
|
|
127
|
-
*
|
|
128
|
-
* @example
|
|
129
|
-
* this._master.errorRenderer.registerHandler(404, (ctx, errorData) => {
|
|
130
|
-
* return `<html><body>Custom 404: ${errorData.message}</body></html>`;
|
|
131
|
-
* });
|
|
132
|
-
*/
|
|
133
|
-
registerHandler(statusCode, handler) {
|
|
134
|
-
if (typeof handler !== 'function') {
|
|
135
|
-
throw new Error('Handler must be a function');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
this.customHandlers.set(statusCode, handler);
|
|
139
|
-
|
|
140
|
-
logger.info({
|
|
141
|
-
code: 'MC_ERROR_HANDLER_REGISTERED',
|
|
142
|
-
message: 'Custom error handler registered',
|
|
143
|
-
statusCode
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return this;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Render HTML error page
|
|
151
|
-
*
|
|
152
|
-
* @private
|
|
153
|
-
*/
|
|
154
|
-
_renderHTML(ctx, statusCode, errorData) {
|
|
155
|
-
// Check for custom handler
|
|
156
|
-
if (this.customHandlers.has(statusCode)) {
|
|
157
|
-
try {
|
|
158
|
-
return this.customHandlers.get(statusCode)(ctx, errorData);
|
|
159
|
-
} catch (err) {
|
|
160
|
-
logger.error({
|
|
161
|
-
code: 'MC_ERROR_HANDLER_FAILED',
|
|
162
|
-
message: 'Custom error handler failed',
|
|
163
|
-
statusCode,
|
|
164
|
-
error: err.message
|
|
165
|
-
});
|
|
166
|
-
// Fall through to default rendering
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Check for template
|
|
171
|
-
const template = this._getTemplate(statusCode);
|
|
172
|
-
if (template) {
|
|
173
|
-
return this._renderTemplate(template, statusCode, errorData);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Fallback to default error page
|
|
177
|
-
return this._renderDefaultHTML(statusCode, errorData);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Render JSON error response
|
|
182
|
-
*
|
|
183
|
-
* @private
|
|
184
|
-
*/
|
|
185
|
-
_renderJSON(statusCode, errorData) {
|
|
186
|
-
const response = {
|
|
187
|
-
error: this._getDefaultMessage(statusCode),
|
|
188
|
-
statusCode: statusCode,
|
|
189
|
-
code: errorData.code || 'MC_HTTP_ERROR'
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
if (errorData.message) {
|
|
193
|
-
response.message = errorData.message;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (this.showStackTrace && errorData.stack) {
|
|
197
|
-
response.stack = errorData.stack;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (errorData.details) {
|
|
201
|
-
response.details = errorData.details;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (errorData.suggestions) {
|
|
205
|
-
response.suggestions = errorData.suggestions;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return JSON.stringify(response, null, 2);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Render template with data
|
|
213
|
-
*
|
|
214
|
-
* @private
|
|
215
|
-
*/
|
|
216
|
-
_renderTemplate(template, statusCode, errorData) {
|
|
217
|
-
let html = template;
|
|
218
|
-
|
|
219
|
-
const data = {
|
|
220
|
-
statusCode: statusCode,
|
|
221
|
-
title: this._getDefaultTitle(statusCode),
|
|
222
|
-
message: errorData.message || this._getDefaultMessage(statusCode),
|
|
223
|
-
description: errorData.description || '',
|
|
224
|
-
code: errorData.code || 'MC_HTTP_ERROR',
|
|
225
|
-
stack: this.showStackTrace && errorData.stack ? errorData.stack : null,
|
|
226
|
-
suggestions: errorData.suggestions || [],
|
|
227
|
-
path: errorData.path || '',
|
|
228
|
-
environment: this.environment,
|
|
229
|
-
showStackTrace: this.showStackTrace
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// Simple template rendering (replace {{key}} with values)
|
|
233
|
-
for (const [key, value] of Object.entries(data)) {
|
|
234
|
-
const regex = new RegExp(`{{${key}}}`, 'g');
|
|
235
|
-
html = html.replace(regex, value || '');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Handle conditionals {{#if showStackTrace}}...{{/if}}
|
|
239
|
-
html = this._processConditionals(html, data);
|
|
240
|
-
|
|
241
|
-
// Handle loops {{#each suggestions}}...{{/each}}
|
|
242
|
-
html = this._processLoops(html, data);
|
|
243
|
-
|
|
244
|
-
return html;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Process conditional blocks in template
|
|
249
|
-
*
|
|
250
|
-
* @private
|
|
251
|
-
*/
|
|
252
|
-
_processConditionals(html, data) {
|
|
253
|
-
const conditionalRegex = /{{#if\s+(\w+)}}([\s\S]*?){{\/if}}/g;
|
|
254
|
-
|
|
255
|
-
return html.replace(conditionalRegex, (match, condition, content) => {
|
|
256
|
-
return data[condition] ? content : '';
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Process loop blocks in template
|
|
262
|
-
*
|
|
263
|
-
* @private
|
|
264
|
-
*/
|
|
265
|
-
_processLoops(html, data) {
|
|
266
|
-
const loopRegex = /{{#each\s+(\w+)}}([\s\S]*?){{\/each}}/g;
|
|
267
|
-
|
|
268
|
-
return html.replace(loopRegex, (match, arrayName, content) => {
|
|
269
|
-
const array = data[arrayName];
|
|
270
|
-
if (!Array.isArray(array)) {
|
|
271
|
-
return '';
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return array.map(item => {
|
|
275
|
-
let itemHtml = content;
|
|
276
|
-
if (typeof item === 'string') {
|
|
277
|
-
itemHtml = itemHtml.replace(/{{this}}/g, item);
|
|
278
|
-
} else if (typeof item === 'object') {
|
|
279
|
-
for (const [key, value] of Object.entries(item)) {
|
|
280
|
-
itemHtml = itemHtml.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return itemHtml;
|
|
284
|
-
}).join('');
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Render default HTML error page
|
|
290
|
-
*
|
|
291
|
-
* @private
|
|
292
|
-
*/
|
|
293
|
-
_renderDefaultHTML(statusCode, errorData) {
|
|
294
|
-
const title = this._getDefaultTitle(statusCode);
|
|
295
|
-
const message = errorData.message || this._getDefaultMessage(statusCode);
|
|
296
|
-
const stack = this.showStackTrace && errorData.stack
|
|
297
|
-
? `<pre style="background: #f5f5f5; padding: 1em; overflow: auto;">${this._escapeHtml(errorData.stack)}</pre>`
|
|
298
|
-
: '';
|
|
299
|
-
|
|
300
|
-
const suggestions = errorData.suggestions && errorData.suggestions.length > 0
|
|
301
|
-
? `<div style="margin-top: 2em;">
|
|
302
|
-
<h3>Suggestions:</h3>
|
|
303
|
-
<ul>
|
|
304
|
-
${errorData.suggestions.map(s => `<li>${this._escapeHtml(s)}</li>`).join('')}
|
|
305
|
-
</ul>
|
|
306
|
-
</div>`
|
|
307
|
-
: '';
|
|
308
|
-
|
|
309
|
-
return `<!DOCTYPE html>
|
|
310
|
-
<html>
|
|
311
|
-
<head>
|
|
312
|
-
<title>${title}</title>
|
|
313
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
314
|
-
<style>
|
|
315
|
-
body {
|
|
316
|
-
background-color: #f8f9fa;
|
|
317
|
-
color: #212529;
|
|
318
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
319
|
-
margin: 0;
|
|
320
|
-
padding: 2em;
|
|
321
|
-
}
|
|
322
|
-
.container {
|
|
323
|
-
max-width: 800px;
|
|
324
|
-
margin: 0 auto;
|
|
325
|
-
}
|
|
326
|
-
.error-box {
|
|
327
|
-
background: white;
|
|
328
|
-
border-left: 4px solid #dc3545;
|
|
329
|
-
border-radius: 4px;
|
|
330
|
-
padding: 2em;
|
|
331
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
332
|
-
}
|
|
333
|
-
h1 {
|
|
334
|
-
margin: 0 0 0.5em 0;
|
|
335
|
-
color: #dc3545;
|
|
336
|
-
font-size: 2em;
|
|
337
|
-
}
|
|
338
|
-
.status-code {
|
|
339
|
-
font-size: 4em;
|
|
340
|
-
font-weight: bold;
|
|
341
|
-
color: #dc3545;
|
|
342
|
-
margin: 0;
|
|
343
|
-
}
|
|
344
|
-
.message {
|
|
345
|
-
font-size: 1.2em;
|
|
346
|
-
margin: 1em 0;
|
|
347
|
-
}
|
|
348
|
-
.code {
|
|
349
|
-
background: #f8f9fa;
|
|
350
|
-
padding: 0.5em 1em;
|
|
351
|
-
border-radius: 4px;
|
|
352
|
-
font-family: monospace;
|
|
353
|
-
font-size: 0.9em;
|
|
354
|
-
margin: 1em 0;
|
|
355
|
-
}
|
|
356
|
-
pre {
|
|
357
|
-
background: #f5f5f5;
|
|
358
|
-
padding: 1em;
|
|
359
|
-
overflow: auto;
|
|
360
|
-
border-radius: 4px;
|
|
361
|
-
}
|
|
362
|
-
.footer {
|
|
363
|
-
margin-top: 2em;
|
|
364
|
-
text-align: center;
|
|
365
|
-
color: #6c757d;
|
|
366
|
-
font-size: 0.9em;
|
|
367
|
-
}
|
|
368
|
-
ul {
|
|
369
|
-
line-height: 1.6;
|
|
370
|
-
}
|
|
371
|
-
</style>
|
|
372
|
-
</head>
|
|
373
|
-
<body>
|
|
374
|
-
<div class="container">
|
|
375
|
-
<div class="error-box">
|
|
376
|
-
<p class="status-code">${statusCode}</p>
|
|
377
|
-
<h1>${title}</h1>
|
|
378
|
-
<p class="message">${this._escapeHtml(message)}</p>
|
|
379
|
-
${errorData.code ? `<div class="code">Error Code: ${errorData.code}</div>` : ''}
|
|
380
|
-
${suggestions}
|
|
381
|
-
${stack}
|
|
382
|
-
</div>
|
|
383
|
-
<div class="footer">
|
|
384
|
-
<p>MasterController Framework • ${this.environment} environment</p>
|
|
385
|
-
</div>
|
|
386
|
-
</div>
|
|
387
|
-
</body>
|
|
388
|
-
</html>`;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Load error templates from disk
|
|
393
|
-
*
|
|
394
|
-
* @private
|
|
395
|
-
*/
|
|
396
|
-
_loadTemplates() {
|
|
397
|
-
const statusCodes = [400, 401, 403, 404, 405, 422, 429, 500, 502, 503, 504];
|
|
398
|
-
|
|
399
|
-
for (const code of statusCodes) {
|
|
400
|
-
const templatePath = path.join(this.templateDir, `${code}.html`);
|
|
401
|
-
|
|
402
|
-
if (fs.existsSync(templatePath)) {
|
|
403
|
-
try {
|
|
404
|
-
const template = fs.readFileSync(templatePath, 'utf8');
|
|
405
|
-
this.errorTemplates.set(code, template);
|
|
406
|
-
logger.info({
|
|
407
|
-
code: 'MC_ERROR_TEMPLATE_LOADED',
|
|
408
|
-
message: 'Error template loaded',
|
|
409
|
-
statusCode: code,
|
|
410
|
-
path: templatePath
|
|
411
|
-
});
|
|
412
|
-
} catch (err) {
|
|
413
|
-
logger.error({
|
|
414
|
-
code: 'MC_ERROR_TEMPLATE_LOAD_FAILED',
|
|
415
|
-
message: 'Failed to load error template',
|
|
416
|
-
statusCode: code,
|
|
417
|
-
error: err.message
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Get template for status code
|
|
426
|
-
*
|
|
427
|
-
* @private
|
|
428
|
-
*/
|
|
429
|
-
_getTemplate(statusCode) {
|
|
430
|
-
// Check exact match
|
|
431
|
-
if (this.errorTemplates.has(statusCode)) {
|
|
432
|
-
return this.errorTemplates.get(statusCode);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Check category (4xx -> 400, 5xx -> 500)
|
|
436
|
-
const category = Math.floor(statusCode / 100) * 100;
|
|
437
|
-
if (this.errorTemplates.has(category)) {
|
|
438
|
-
return this.errorTemplates.get(category);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Check if request is API request
|
|
446
|
-
*
|
|
447
|
-
* @private
|
|
448
|
-
*/
|
|
449
|
-
_isApiRequest(ctx) {
|
|
450
|
-
// Check Accept header
|
|
451
|
-
const accept = ctx.request.headers['accept'] || '';
|
|
452
|
-
if (accept.includes('application/json')) {
|
|
453
|
-
return true;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Check path
|
|
457
|
-
const path = ctx.pathName || ctx.request.url;
|
|
458
|
-
if (path.startsWith('api/') || path.startsWith('/api/')) {
|
|
459
|
-
return true;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Check Content-Type
|
|
463
|
-
const contentType = ctx.request.headers['content-type'] || '';
|
|
464
|
-
if (contentType.includes('application/json')) {
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Get default title for status code
|
|
473
|
-
*
|
|
474
|
-
* @private
|
|
475
|
-
*/
|
|
476
|
-
_getDefaultTitle(statusCode) {
|
|
477
|
-
const titles = {
|
|
478
|
-
400: 'Bad Request',
|
|
479
|
-
401: 'Unauthorized',
|
|
480
|
-
403: 'Forbidden',
|
|
481
|
-
404: 'Page Not Found',
|
|
482
|
-
405: 'Method Not Allowed',
|
|
483
|
-
422: 'Unprocessable Entity',
|
|
484
|
-
429: 'Too Many Requests',
|
|
485
|
-
500: 'Internal Server Error',
|
|
486
|
-
502: 'Bad Gateway',
|
|
487
|
-
503: 'Service Unavailable',
|
|
488
|
-
504: 'Gateway Timeout'
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
return titles[statusCode] || `Error ${statusCode}`;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Get default message for status code
|
|
496
|
-
*
|
|
497
|
-
* @private
|
|
498
|
-
*/
|
|
499
|
-
_getDefaultMessage(statusCode) {
|
|
500
|
-
const messages = {
|
|
501
|
-
400: 'The request could not be understood by the server due to malformed syntax.',
|
|
502
|
-
401: 'You need to be authenticated to access this resource.',
|
|
503
|
-
403: 'You don\'t have permission to access this resource.',
|
|
504
|
-
404: 'The page you were looking for doesn\'t exist.',
|
|
505
|
-
405: 'The method specified in the request is not allowed for this resource.',
|
|
506
|
-
422: 'The request was well-formed but contains invalid data.',
|
|
507
|
-
429: 'Too many requests. Please slow down and try again later.',
|
|
508
|
-
500: 'We\'re sorry, but something went wrong on our end.',
|
|
509
|
-
502: 'The server received an invalid response from the upstream server.',
|
|
510
|
-
503: 'The service is temporarily unavailable. Please try again later.',
|
|
511
|
-
504: 'The server did not receive a timely response from the upstream server.'
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
return messages[statusCode] || 'An error occurred while processing your request.';
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Escape HTML entities
|
|
519
|
-
*
|
|
520
|
-
* @private
|
|
521
|
-
*/
|
|
522
|
-
_escapeHtml(text) {
|
|
523
|
-
if (!text) return '';
|
|
524
|
-
return text
|
|
525
|
-
.toString()
|
|
526
|
-
.replace(/&/g, '&')
|
|
527
|
-
.replace(/</g, '<')
|
|
528
|
-
.replace(/>/g, '>')
|
|
529
|
-
.replace(/"/g, '"')
|
|
530
|
-
.replace(/'/g, ''');
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
module.exports = { MasterErrorRenderer };
|
|
535
|
-
|
|
536
|
-
module.exports = MasterErrorRenderer;
|
|
File without changes
|