mastercontroller 1.2.11 → 1.2.13

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.
@@ -0,0 +1,407 @@
1
+ /**
2
+ * MasterErrorMiddleware - Request/Response error handling middleware
3
+ * Version: 1.0.0
4
+ */
5
+
6
+ const { handleControllerError, handleRoutingError, sendErrorResponse } = require('./MasterBackendErrorHandler');
7
+ const { logger } = require('./MasterErrorLogger');
8
+
9
+ const isDevelopment = process.env.NODE_ENV !== 'production' && process.env.master === 'development';
10
+
11
+ /**
12
+ * Global error handler middleware
13
+ * Wrap all controller actions with this
14
+ */
15
+ function errorHandlerMiddleware(handler, controllerName, actionName) {
16
+ return async function wrappedHandler(requestObject) {
17
+ const startTime = Date.now();
18
+
19
+ try {
20
+ // Execute the actual handler
21
+ const result = await Promise.resolve(handler.call(this, requestObject));
22
+
23
+ // Log successful request in development
24
+ if (isDevelopment) {
25
+ const duration = Date.now() - startTime;
26
+ logger.debug({
27
+ code: 'MC_INFO_REQUEST_SUCCESS',
28
+ message: `${controllerName}#${actionName} completed`,
29
+ context: {
30
+ duration,
31
+ path: requestObject.pathName,
32
+ method: requestObject.type
33
+ }
34
+ });
35
+ }
36
+
37
+ return result;
38
+
39
+ } catch (error) {
40
+ const duration = Date.now() - startTime;
41
+
42
+ // Handle the error
43
+ const mcError = handleControllerError(
44
+ error,
45
+ controllerName,
46
+ actionName,
47
+ requestObject.pathName
48
+ );
49
+
50
+ // Send error response
51
+ sendErrorResponse(
52
+ requestObject.response,
53
+ mcError,
54
+ requestObject.pathName
55
+ );
56
+
57
+ // Log to monitoring
58
+ logger.error({
59
+ code: mcError.code,
60
+ message: mcError.message,
61
+ controller: controllerName,
62
+ action: actionName,
63
+ route: requestObject.pathName,
64
+ method: requestObject.type,
65
+ duration,
66
+ originalError: error,
67
+ stack: error.stack
68
+ });
69
+
70
+ // Don't re-throw - error has been handled
71
+ return null;
72
+ }
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Request logging middleware
78
+ */
79
+ function requestLoggerMiddleware() {
80
+ return function(requestObject, next) {
81
+ const startTime = Date.now();
82
+
83
+ logger.info({
84
+ code: 'MC_INFO_REQUEST_START',
85
+ message: `${requestObject.type} ${requestObject.pathName}`,
86
+ context: {
87
+ method: requestObject.type,
88
+ path: requestObject.pathName,
89
+ params: requestObject.params,
90
+ query: requestObject.query,
91
+ ip: requestObject.request.connection?.remoteAddress
92
+ }
93
+ });
94
+
95
+ // Continue to next middleware
96
+ if (typeof next === 'function') {
97
+ next();
98
+ }
99
+ };
100
+ }
101
+
102
+ /**
103
+ * 404 handler middleware
104
+ */
105
+ function notFoundMiddleware(requestObject) {
106
+ const mcError = handleRoutingError(
107
+ requestObject.pathName,
108
+ [] // Would need to pass available routes here
109
+ );
110
+
111
+ sendErrorResponse(
112
+ requestObject.response,
113
+ mcError,
114
+ requestObject.pathName
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Extract user code context from stack trace
120
+ */
121
+ function extractUserCodeContext(stack) {
122
+ if (!stack) return null;
123
+
124
+ const lines = stack.split('\n');
125
+ const userFiles = [];
126
+ const frameworkFiles = [];
127
+
128
+ for (const line of lines) {
129
+ // Skip the error message line
130
+ if (!line.trim().startsWith('at ')) continue;
131
+
132
+ // Extract file path from stack line
133
+ const match = line.match(/\((.+?):(\d+):(\d+)\)|at (.+?):(\d+):(\d+)/);
134
+ if (!match) continue;
135
+
136
+ const filePath = match[1] || match[4];
137
+ const lineNum = match[2] || match[5];
138
+ const colNum = match[3] || match[6];
139
+
140
+ if (!filePath) continue;
141
+
142
+ // Categorize as user code or framework code
143
+ const isFramework = filePath.includes('node_modules/mastercontroller');
144
+ const isNodeInternal = filePath.includes('node:internal') || filePath.includes('/lib/internal/');
145
+
146
+ if (isNodeInternal) continue;
147
+
148
+ const fileInfo = {
149
+ file: filePath,
150
+ line: lineNum,
151
+ column: colNum,
152
+ location: `${filePath}:${lineNum}:${colNum}`
153
+ };
154
+
155
+ if (isFramework) {
156
+ frameworkFiles.push(fileInfo);
157
+ } else {
158
+ userFiles.push(fileInfo);
159
+ }
160
+ }
161
+
162
+ return {
163
+ userFiles,
164
+ frameworkFiles,
165
+ triggeringFile: userFiles[0] || frameworkFiles[0] || null
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Uncaught exception handler
171
+ */
172
+ function setupGlobalErrorHandlers() {
173
+ // Handle uncaught exceptions
174
+ process.on('uncaughtException', (error) => {
175
+ console.error('[MasterController] Uncaught Exception:', error);
176
+
177
+ // Extract context from stack trace
178
+ const context = extractUserCodeContext(error.stack);
179
+
180
+ // Build enhanced error message
181
+ let enhancedMessage = `Uncaught exception: ${error.message}`;
182
+
183
+ if (context && context.triggeringFile) {
184
+ enhancedMessage += `\n\n🔍 Error Location: ${context.triggeringFile.location}`;
185
+ }
186
+
187
+ if (context && context.userFiles.length > 0) {
188
+ enhancedMessage += `\n\n📂 Your Code Involved:`;
189
+ context.userFiles.forEach((file, i) => {
190
+ if (i < 3) { // Show first 3 user files
191
+ enhancedMessage += `\n ${i + 1}. ${file.location}`;
192
+ }
193
+ });
194
+ }
195
+
196
+ if (context && context.frameworkFiles.length > 0) {
197
+ enhancedMessage += `\n\n🔧 Framework Files Involved:`;
198
+ context.frameworkFiles.forEach((file, i) => {
199
+ if (i < 2) { // Show first 2 framework files
200
+ enhancedMessage += `\n ${i + 1}. ${file.location}`;
201
+ }
202
+ });
203
+ }
204
+
205
+ console.error(enhancedMessage);
206
+
207
+ logger.fatal({
208
+ code: 'MC_ERR_UNCAUGHT_EXCEPTION',
209
+ message: enhancedMessage,
210
+ originalError: error,
211
+ stack: error.stack,
212
+ context: context
213
+ });
214
+
215
+ // Give logger time to write, then exit
216
+ setTimeout(() => {
217
+ process.exit(1);
218
+ }, 1000);
219
+ });
220
+
221
+ // Handle unhandled promise rejections
222
+ process.on('unhandledRejection', (reason, promise) => {
223
+ console.error('[MasterController] Unhandled Rejection:', reason);
224
+
225
+ // Extract context from stack trace if available
226
+ const context = reason?.stack ? extractUserCodeContext(reason.stack) : null;
227
+
228
+ // Build enhanced error message
229
+ let enhancedMessage = `Unhandled promise rejection: ${reason}`;
230
+
231
+ if (context && context.triggeringFile) {
232
+ enhancedMessage += `\n\n🔍 Error Location: ${context.triggeringFile.location}`;
233
+ }
234
+
235
+ if (context && context.userFiles.length > 0) {
236
+ enhancedMessage += `\n\n📂 Your Code Involved:`;
237
+ context.userFiles.forEach((file, i) => {
238
+ if (i < 3) { // Show first 3 user files
239
+ enhancedMessage += `\n ${i + 1}. ${file.location}`;
240
+ }
241
+ });
242
+ }
243
+
244
+ if (enhancedMessage !== `Unhandled promise rejection: ${reason}`) {
245
+ console.error(enhancedMessage);
246
+ }
247
+
248
+ logger.error({
249
+ code: 'MC_ERR_UNHANDLED_REJECTION',
250
+ message: enhancedMessage,
251
+ originalError: reason,
252
+ stack: reason?.stack,
253
+ context: context
254
+ });
255
+ });
256
+
257
+ // Handle warnings
258
+ process.on('warning', (warning) => {
259
+ if (isDevelopment) {
260
+ console.warn('[MasterController] Warning:', warning);
261
+ }
262
+
263
+ logger.warn({
264
+ code: 'MC_WARN_PROCESS_WARNING',
265
+ message: warning.message,
266
+ context: {
267
+ name: warning.name,
268
+ stack: warning.stack
269
+ }
270
+ });
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Safe file reader with error handling
276
+ */
277
+ function safeReadFile(fs, filePath, encoding = 'utf8') {
278
+ try {
279
+ return {
280
+ success: true,
281
+ content: fs.readFileSync(filePath, encoding),
282
+ error: null
283
+ };
284
+ } catch (error) {
285
+ const { handleFileReadError } = require('./MasterBackendErrorHandler');
286
+ const mcError = handleFileReadError(error, filePath);
287
+
288
+ return {
289
+ success: false,
290
+ content: null,
291
+ error: mcError
292
+ };
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Safe file existence check
298
+ */
299
+ function safeFileExists(fs, filePath) {
300
+ try {
301
+ return fs.existsSync(filePath);
302
+ } catch (error) {
303
+ logger.warn({
304
+ code: 'MC_WARN_FILE_CHECK',
305
+ message: `Could not check if file exists: ${filePath}`,
306
+ originalError: error
307
+ });
308
+ return false;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Wrap controller class with error handling
314
+ */
315
+ function wrapController(ControllerClass, controllerName) {
316
+ const wrappedMethods = {};
317
+
318
+ // Get all methods from the controller
319
+ const methodNames = Object.getOwnPropertyNames(ControllerClass.prototype);
320
+
321
+ methodNames.forEach(methodName => {
322
+ if (methodName === 'constructor') return;
323
+
324
+ const originalMethod = ControllerClass.prototype[methodName];
325
+
326
+ if (typeof originalMethod === 'function') {
327
+ // Wrap each method with error handling
328
+ wrappedMethods[methodName] = errorHandlerMiddleware(
329
+ originalMethod,
330
+ controllerName,
331
+ methodName
332
+ );
333
+ }
334
+ });
335
+
336
+ // Create new class with wrapped methods
337
+ const WrappedController = class extends ControllerClass {
338
+ constructor(...args) {
339
+ super(...args);
340
+
341
+ // Apply wrapped methods
342
+ Object.keys(wrappedMethods).forEach(methodName => {
343
+ this[methodName] = wrappedMethods[methodName].bind(this);
344
+ });
345
+ }
346
+ };
347
+
348
+ return WrappedController;
349
+ }
350
+
351
+ /**
352
+ * Performance tracking middleware
353
+ */
354
+ function performanceMiddleware() {
355
+ const requests = new Map();
356
+
357
+ return {
358
+ start(requestId, requestObject) {
359
+ requests.set(requestId, {
360
+ startTime: Date.now(),
361
+ path: requestObject.pathName,
362
+ method: requestObject.type
363
+ });
364
+ },
365
+
366
+ end(requestId) {
367
+ const req = requests.get(requestId);
368
+ if (!req) return;
369
+
370
+ const duration = Date.now() - req.startTime;
371
+
372
+ if (duration > 1000) {
373
+ logger.warn({
374
+ code: 'MC_WARN_SLOW_REQUEST',
375
+ message: `Slow request detected (${duration}ms)`,
376
+ context: {
377
+ duration,
378
+ path: req.path,
379
+ method: req.method
380
+ }
381
+ });
382
+ }
383
+
384
+ requests.delete(requestId);
385
+ },
386
+
387
+ getStats() {
388
+ return {
389
+ activeRequests: requests.size,
390
+ requests: Array.from(requests.values())
391
+ };
392
+ }
393
+ };
394
+ }
395
+
396
+ const performanceTracker = performanceMiddleware();
397
+
398
+ module.exports = {
399
+ errorHandlerMiddleware,
400
+ requestLoggerMiddleware,
401
+ notFoundMiddleware,
402
+ setupGlobalErrorHandlers,
403
+ safeReadFile,
404
+ safeFileExists,
405
+ wrapController,
406
+ performanceTracker
407
+ };
package/MasterHtml.js CHANGED
@@ -1,4 +1,4 @@
1
- // version 0.0.23
1
+ // version 0.0.24
2
2
 
3
3
  var master = require('./MasterControl');
4
4
  var fs = require('fs');
@@ -7,6 +7,14 @@ var toolClass = require('./MasterTools');
7
7
  var temp = new tempClass();
8
8
  var tools = new toolClass();
9
9
 
10
+ // Enhanced error handling
11
+ const { handleTemplateError } = require('./MasterBackendErrorHandler');
12
+ const { safeReadFile, safeFileExists } = require('./MasterErrorMiddleware');
13
+ const { logger } = require('./MasterErrorLogger');
14
+
15
+ // Security - Sanitization
16
+ const { sanitizeTemplateHTML, sanitizeUserHTML, escapeHTML } = require('./MasterSanitizer');
17
+
10
18
  class html {
11
19
 
12
20
  javaScriptSerializer(name, obj){
@@ -17,21 +25,41 @@ class html {
17
25
 
18
26
  // render partial views
19
27
  renderPartial(path, data){
20
-
28
+ try {
29
+ var partialViewUrl = `/app/views/${path}`;
30
+ var fullPath = master.router.currentRoute.root + partialViewUrl;
31
+
32
+ const fileResult = safeReadFile(fs, fullPath);
33
+
34
+ if (!fileResult.success) {
35
+ logger.warn({
36
+ code: 'MC_ERR_VIEW_NOT_FOUND',
37
+ message: `Partial view not found: ${path}`,
38
+ file: fullPath
39
+ });
40
+ return `<!-- Partial view not found: ${path} -->`;
41
+ }
21
42
 
22
- var partialViewUrl = `/app/views/${path}`;
23
- var filepartialView = fs.readFileSync(master.router.currentRoute.root + partialViewUrl, 'utf8');
43
+ var partialView = null;
44
+ if(master.overwrite.isTemplate){
45
+ partialView = master.overwrite.templateRender(data, "renderPartialView");
46
+ }
47
+ else{
48
+ partialView = temp.htmlBuilder(fileResult.content, data);
49
+ }
24
50
 
25
- var partialView = null;
26
- if(master.overwrite.isTemplate){
27
- partialView = master.overwrite.templateRender(data, "renderPartialView");
28
- }
29
- else{
30
- partialView = temp.htmlBuilder(filepartialView, data);
51
+ return partialView;
52
+ } catch (error) {
53
+ const mcError = handleTemplateError(error, path, data);
54
+ logger.error({
55
+ code: mcError.code,
56
+ message: mcError.message,
57
+ file: path,
58
+ originalError: error
59
+ });
60
+ return `<!-- Error rendering partial: ${path} -->`;
31
61
  }
32
62
 
33
- return partialView;
34
-
35
63
  }
36
64
 
37
65
  // render all your link tags styles given the folder location
@@ -447,7 +475,7 @@ class html {
447
475
  return rangeField;
448
476
  }
449
477
 
450
- // allows you to add data object to params
478
+ // allows you to add data object to params
451
479
  addDataToParams(data){
452
480
 
453
481
  //loop through data and add it to new oobjects prototype
@@ -457,7 +485,66 @@ class html {
457
485
  master.view.extend(newObj);
458
486
  }
459
487
  }
460
-
488
+
489
+ // ==================== Security Methods ====================
490
+
491
+ /**
492
+ * Sanitize user-generated HTML content
493
+ * Use this for any HTML that comes from user input
494
+ * @param {string} html - HTML content to sanitize
495
+ * @returns {string} - Sanitized HTML
496
+ */
497
+ sanitizeHTML(html) {
498
+ return sanitizeUserHTML(html);
499
+ }
500
+
501
+ /**
502
+ * Escape HTML special characters
503
+ * Use this to display user input as text (not HTML)
504
+ * @param {string} text - Text to escape
505
+ * @returns {string} - Escaped text safe for display
506
+ */
507
+ escapeHTML(text) {
508
+ return escapeHTML(text);
509
+ }
510
+
511
+ /**
512
+ * Render user content safely
513
+ * Sanitizes HTML and wraps in container
514
+ * @param {string} content - User-generated content
515
+ * @param {string} containerTag - HTML tag to wrap content (default: div)
516
+ * @param {object} attrs - Attributes for container
517
+ * @returns {string} - Safe HTML
518
+ */
519
+ renderUserContent(content, containerTag = 'div', attrs = {}) {
520
+ const sanitized = sanitizeUserHTML(content);
521
+
522
+ let attrStr = '';
523
+ for (const [key, value] of Object.entries(attrs)) {
524
+ attrStr += ` ${key}="${escapeHTML(String(value))}"`;
525
+ }
526
+
527
+ return `<${containerTag}${attrStr}>${sanitized}</${containerTag}>`;
528
+ }
529
+
530
+ /**
531
+ * Create safe text node content
532
+ * @param {string} text - Text content
533
+ * @returns {string} - HTML-escaped text
534
+ */
535
+ textNode(text) {
536
+ return escapeHTML(text);
537
+ }
538
+
539
+ /**
540
+ * Create safe attribute value
541
+ * @param {string} value - Attribute value
542
+ * @returns {string} - Escaped and quoted value
543
+ */
544
+ safeAttr(value) {
545
+ return `"${escapeHTML(String(value))}"`;
546
+ }
547
+
461
548
  }
462
549
 
463
550
  master.extendView("html", html);