mastercontroller 1.2.12 → 1.2.14
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/.claude/settings.local.json +12 -0
- package/MasterAction.js +297 -73
- package/MasterControl.js +112 -19
- package/MasterHtml.js +101 -14
- package/MasterRouter.js +281 -66
- package/MasterTemplate.js +96 -3
- package/README.md +0 -44
- package/error/ErrorBoundary.js +353 -0
- package/error/HydrationMismatch.js +265 -0
- package/error/MasterBackendErrorHandler.js +769 -0
- package/{MasterError.js → error/MasterError.js} +2 -2
- package/error/MasterErrorHandler.js +487 -0
- package/error/MasterErrorLogger.js +360 -0
- package/error/MasterErrorMiddleware.js +407 -0
- package/error/SSRErrorHandler.js +273 -0
- package/monitoring/MasterCache.js +400 -0
- package/monitoring/MasterMemoryMonitor.js +188 -0
- package/monitoring/MasterProfiler.js +409 -0
- package/monitoring/PerformanceMonitor.js +233 -0
- package/package.json +3 -3
- package/security/CSPConfig.js +319 -0
- package/security/EventHandlerValidator.js +464 -0
- package/security/MasterSanitizer.js +429 -0
- package/security/MasterValidator.js +546 -0
- package/security/SecurityMiddleware.js +486 -0
- package/security/SessionSecurity.js +416 -0
- package/ssr/hydration-client.js +93 -0
- package/ssr/runtime-ssr.cjs +553 -0
- package/ssr/ssr-shims.js +73 -0
- package/examples/FileServingExample.js +0 -88
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MasterErrorMiddleware - Request/Response error handling middleware
|
|
3
|
+
* Version: 1.0.1
|
|
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.info({
|
|
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
|
+
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSRErrorHandler - Server-side rendering error handling
|
|
3
|
+
* Handles component render failures with graceful fallbacks
|
|
4
|
+
* Version: 1.0.1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { MasterControllerError } = require('./MasterErrorHandler');
|
|
8
|
+
|
|
9
|
+
const isDevelopment = process.env.NODE_ENV !== 'production' && process.env.master === 'development';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Render error component for development mode
|
|
13
|
+
*/
|
|
14
|
+
function renderErrorComponent(options = {}) {
|
|
15
|
+
const {
|
|
16
|
+
component,
|
|
17
|
+
error,
|
|
18
|
+
stack,
|
|
19
|
+
file,
|
|
20
|
+
line,
|
|
21
|
+
details
|
|
22
|
+
} = options;
|
|
23
|
+
|
|
24
|
+
const errorObj = new MasterControllerError({
|
|
25
|
+
code: 'MC_ERR_COMPONENT_RENDER_FAILED',
|
|
26
|
+
message: error || 'Component failed to render on server',
|
|
27
|
+
component,
|
|
28
|
+
file,
|
|
29
|
+
line,
|
|
30
|
+
details: details || stack,
|
|
31
|
+
originalError: options.originalError
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Return full HTML error page in development
|
|
35
|
+
return errorObj.toHTML();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render fallback component for production mode
|
|
40
|
+
*/
|
|
41
|
+
function renderFallback(componentName, options = {}) {
|
|
42
|
+
const { showSkeleton = true, customMessage } = options;
|
|
43
|
+
|
|
44
|
+
if (showSkeleton) {
|
|
45
|
+
return `
|
|
46
|
+
<div class="mc-fallback" data-component="${componentName}" style="
|
|
47
|
+
padding: 20px;
|
|
48
|
+
background: #f9fafb;
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
border: 1px dashed #d1d5db;
|
|
51
|
+
min-height: 100px;
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
color: #6b7280;
|
|
56
|
+
">
|
|
57
|
+
<div class="mc-fallback-content">
|
|
58
|
+
${customMessage || ''}
|
|
59
|
+
<div class="skeleton-loader" style="
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 20px;
|
|
62
|
+
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
|
|
63
|
+
background-size: 200% 100%;
|
|
64
|
+
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
65
|
+
border-radius: 4px;
|
|
66
|
+
"></div>
|
|
67
|
+
</div>
|
|
68
|
+
<style>
|
|
69
|
+
@keyframes skeleton-loading {
|
|
70
|
+
0% { background-position: 200% 0; }
|
|
71
|
+
100% { background-position: -200% 0; }
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</div>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Minimal fallback - just an empty div
|
|
79
|
+
return `<div class="mc-fallback" data-component="${componentName}"></div>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Safe component render wrapper
|
|
84
|
+
* Catches errors and returns either error page (dev) or fallback (prod)
|
|
85
|
+
*/
|
|
86
|
+
function safeRenderComponent(component, componentName, filePath) {
|
|
87
|
+
try {
|
|
88
|
+
// Try tempRender first
|
|
89
|
+
if (typeof component.tempRender === 'function') {
|
|
90
|
+
const startTime = Date.now();
|
|
91
|
+
const result = component.tempRender();
|
|
92
|
+
const renderTime = Date.now() - startTime;
|
|
93
|
+
|
|
94
|
+
// Warn about slow renders in development
|
|
95
|
+
if (isDevelopment && renderTime > 100) {
|
|
96
|
+
console.warn(
|
|
97
|
+
new MasterControllerError({
|
|
98
|
+
code: 'MC_ERR_SLOW_RENDER',
|
|
99
|
+
message: `Component rendering slowly on server (${renderTime}ms)`,
|
|
100
|
+
component: componentName,
|
|
101
|
+
file: filePath,
|
|
102
|
+
details: `Render time exceeded 100ms threshold. Consider optimizing this component.`
|
|
103
|
+
}).format()
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { success: true, html: result, renderTime };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Fallback to connectedCallback
|
|
111
|
+
if (typeof component.connectedCallback === 'function') {
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
component.connectedCallback();
|
|
114
|
+
const renderTime = Date.now() - startTime;
|
|
115
|
+
|
|
116
|
+
// Get the rendered HTML
|
|
117
|
+
const html = component.innerHTML || '';
|
|
118
|
+
return { success: true, html, renderTime };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// No render method found
|
|
122
|
+
throw new Error('No tempRender() or connectedCallback() method found');
|
|
123
|
+
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(
|
|
126
|
+
new MasterControllerError({
|
|
127
|
+
code: 'MC_ERR_COMPONENT_RENDER_FAILED',
|
|
128
|
+
message: `Failed to render component: ${error.message}`,
|
|
129
|
+
component: componentName,
|
|
130
|
+
file: filePath,
|
|
131
|
+
originalError: error
|
|
132
|
+
}).format()
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (isDevelopment) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
html: renderErrorComponent({
|
|
139
|
+
component: componentName,
|
|
140
|
+
error: error.message,
|
|
141
|
+
stack: error.stack,
|
|
142
|
+
file: filePath,
|
|
143
|
+
originalError: error
|
|
144
|
+
}),
|
|
145
|
+
renderTime: 0
|
|
146
|
+
};
|
|
147
|
+
} else {
|
|
148
|
+
// Log error for monitoring
|
|
149
|
+
logProductionError(error, componentName, filePath);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
html: renderFallback(componentName, { showSkeleton: true }),
|
|
154
|
+
renderTime: 0
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Log production errors for monitoring
|
|
162
|
+
*/
|
|
163
|
+
function logProductionError(error, component, file) {
|
|
164
|
+
const errorData = {
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
component,
|
|
167
|
+
file,
|
|
168
|
+
message: error.message,
|
|
169
|
+
stack: error.stack,
|
|
170
|
+
environment: 'production'
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Log to console (can be captured by monitoring services)
|
|
174
|
+
console.error('[MasterController Production Error]', JSON.stringify(errorData, null, 2));
|
|
175
|
+
|
|
176
|
+
// Hook for external logging services (Sentry, LogRocket, etc.)
|
|
177
|
+
if (global.masterControllerErrorHook) {
|
|
178
|
+
try {
|
|
179
|
+
global.masterControllerErrorHook(errorData);
|
|
180
|
+
} catch (hookError) {
|
|
181
|
+
console.error('[MasterController] Error hook failed:', hookError.message);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Wrap connectedCallback with try-catch
|
|
188
|
+
*/
|
|
189
|
+
function wrapConnectedCallback(element, componentName, filePath) {
|
|
190
|
+
if (!element || typeof element.connectedCallback !== 'function') {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const originalCallback = element.connectedCallback;
|
|
195
|
+
|
|
196
|
+
element.connectedCallback = function(...args) {
|
|
197
|
+
try {
|
|
198
|
+
return originalCallback.apply(this, args);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(
|
|
201
|
+
new MasterControllerError({
|
|
202
|
+
code: 'MC_ERR_COMPONENT_RENDER_FAILED',
|
|
203
|
+
message: `connectedCallback failed: ${error.message}`,
|
|
204
|
+
component: componentName,
|
|
205
|
+
file: filePath,
|
|
206
|
+
originalError: error
|
|
207
|
+
}).format()
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (isDevelopment) {
|
|
211
|
+
this.innerHTML = renderErrorComponent({
|
|
212
|
+
component: componentName,
|
|
213
|
+
error: error.message,
|
|
214
|
+
stack: error.stack,
|
|
215
|
+
file: filePath,
|
|
216
|
+
originalError: error
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
logProductionError(error, componentName, filePath);
|
|
220
|
+
this.innerHTML = renderFallback(componentName);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Check if component has required SSR methods
|
|
228
|
+
*/
|
|
229
|
+
function validateSSRComponent(componentClass, componentName, filePath) {
|
|
230
|
+
const warnings = [];
|
|
231
|
+
|
|
232
|
+
// Check for tempRender method
|
|
233
|
+
if (!componentClass.prototype.tempRender && !componentClass.prototype.connectedCallback) {
|
|
234
|
+
warnings.push(
|
|
235
|
+
new MasterControllerError({
|
|
236
|
+
code: 'MC_ERR_TEMPRENDER_MISSING',
|
|
237
|
+
message: 'Component missing tempRender() method for SSR',
|
|
238
|
+
component: componentName,
|
|
239
|
+
file: filePath,
|
|
240
|
+
details: `Add a tempRender() method to enable server-side rendering:\n\n tempRender() {\n return \`<div>Your HTML here</div>\`;\n }`
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Warn if only render() exists (client-only)
|
|
246
|
+
if (componentClass.prototype.render && !componentClass.prototype.tempRender) {
|
|
247
|
+
warnings.push(
|
|
248
|
+
new MasterControllerError({
|
|
249
|
+
code: 'MC_ERR_TEMPRENDER_MISSING',
|
|
250
|
+
message: 'Component has render() but no tempRender() - will not SSR',
|
|
251
|
+
component: componentName,
|
|
252
|
+
file: filePath,
|
|
253
|
+
details: 'For SSR, rename render() to tempRender() or add a separate tempRender() method'
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (warnings.length > 0 && isDevelopment) {
|
|
259
|
+
warnings.forEach(warning => console.warn(warning.format()));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return warnings.length === 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
renderErrorComponent,
|
|
267
|
+
renderFallback,
|
|
268
|
+
safeRenderComponent,
|
|
269
|
+
wrapConnectedCallback,
|
|
270
|
+
validateSSRComponent,
|
|
271
|
+
logProductionError,
|
|
272
|
+
isDevelopment
|
|
273
|
+
};
|