mastercontroller 1.3.9 → 1.3.12
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 +6 -1
- package/.eslintrc.json +50 -0
- package/.github/workflows/ci.yml +317 -0
- package/.prettierrc +10 -0
- package/CHANGES.md +296 -0
- package/DEPLOYMENT.md +956 -0
- package/FIXES_APPLIED.md +378 -0
- package/FORTUNE_500_UPGRADE.md +863 -0
- package/MasterAction.js +10 -263
- package/MasterControl.js +226 -43
- package/MasterRequest.js +42 -1
- package/MasterRouter.js +42 -37
- package/PERFORMANCE_SECURITY_AUDIT.md +677 -0
- package/README.md +602 -71
- package/SENIOR_ENGINEER_AUDIT.md +2477 -0
- package/VERIFICATION_CHECKLIST.md +726 -0
- package/error/README.md +2452 -0
- package/monitoring/HealthCheck.js +347 -0
- package/monitoring/PrometheusExporter.js +416 -0
- package/monitoring/README.md +3112 -0
- package/package.json +64 -11
- package/security/MasterValidator.js +140 -10
- package/security/README.md +1805 -0
- package/security/adapters/RedisCSRFStore.js +428 -0
- package/security/adapters/RedisRateLimiter.js +462 -0
- package/security/adapters/RedisSessionStore.js +476 -0
- package/MasterCors.js.tmp +0 -0
- package/MasterHtml.js +0 -649
- package/MasterPipeline.js.tmp +0 -0
- package/MasterRequest.js.tmp +0 -0
- package/MasterRouter.js.tmp +0 -0
- package/MasterSocket.js.tmp +0 -0
- package/MasterTemp.js.tmp +0 -0
- package/MasterTemplate.js +0 -230
- package/MasterTimeout.js.tmp +0 -0
- package/TemplateOverwrite.js +0 -41
- package/TemplateOverwrite.js.tmp +0 -0
- package/error/ErrorBoundary.js +0 -353
- package/error/HydrationMismatch.js +0 -265
- package/error/MasterError.js +0 -240
- package/error/MasterError.js.tmp +0 -0
- package/error/MasterErrorRenderer.js +0 -536
- package/error/MasterErrorRenderer.js.tmp +0 -0
- package/error/SSRErrorHandler.js +0 -273
- package/ssr/hydration-client.js +0 -93
- package/ssr/runtime-ssr.cjs +0 -553
- package/ssr/ssr-shims.js +0 -73
package/MasterTemplate.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// version 0.0.5
|
|
2
|
-
// https://github.com/WebReflection/backtick-template
|
|
3
|
-
// https://stackoverflow.com/questions/29182244/convert-a-string-to-a-template-string
|
|
4
|
-
|
|
5
|
-
// Security - Template injection prevention
|
|
6
|
-
const { escapeHTML } = require('./security/MasterSanitizer');
|
|
7
|
-
const { logger } = require('./error/MasterErrorLogger');
|
|
8
|
-
|
|
9
|
-
var replace = ''.replace;
|
|
10
|
-
|
|
11
|
-
var ca = /[&<>'"]/g;
|
|
12
|
-
var es = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g;
|
|
13
|
-
|
|
14
|
-
var esca = {
|
|
15
|
-
'&': '&',
|
|
16
|
-
'<': '<',
|
|
17
|
-
'>': '>',
|
|
18
|
-
"'": ''',
|
|
19
|
-
'"': '"'
|
|
20
|
-
};
|
|
21
|
-
var unes = {
|
|
22
|
-
'&': '&',
|
|
23
|
-
'&': '&',
|
|
24
|
-
'<': '<',
|
|
25
|
-
'<': '<',
|
|
26
|
-
'>': '>',
|
|
27
|
-
'>': '>',
|
|
28
|
-
''': "'",
|
|
29
|
-
''': "'",
|
|
30
|
-
'"': '"',
|
|
31
|
-
'"': '"'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
class MasterTemplate{
|
|
35
|
-
|
|
36
|
-
_ = {};
|
|
37
|
-
$ = 0;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/*! (C) 2017-2018 Andrea Giammarchi - MIT Style License */
|
|
41
|
-
htmlBuilder( fn, $str, $object) {
|
|
42
|
-
'use strict';
|
|
43
|
-
|
|
44
|
-
try{
|
|
45
|
-
// reset cache every 32M
|
|
46
|
-
if (33554432 < this.$) {
|
|
47
|
-
this._ = {};
|
|
48
|
-
this.$ = 0;
|
|
49
|
-
}
|
|
50
|
-
var
|
|
51
|
-
hasTransformer = typeof fn === 'function',
|
|
52
|
-
str = hasTransformer ? $str : fn,
|
|
53
|
-
object = hasTransformer ? $object : $str,
|
|
54
|
-
_ = this._,
|
|
55
|
-
known = _.hasOwnProperty(str);
|
|
56
|
-
|
|
57
|
-
// Security: Validate template for dangerous patterns
|
|
58
|
-
if (!known) {
|
|
59
|
-
this.validateTemplate(str);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
var parsed = known ? _[str] : (_[str] = this.parse(str)),
|
|
63
|
-
chunks = parsed.chunks,
|
|
64
|
-
values = parsed.values,
|
|
65
|
-
strings
|
|
66
|
-
;
|
|
67
|
-
// add str length only if not known
|
|
68
|
-
if (!known)
|
|
69
|
-
this.$ += str.length;
|
|
70
|
-
if (hasTransformer) {
|
|
71
|
-
str = 'function' + (Math.random() * 1e5 | 0);
|
|
72
|
-
strings = [
|
|
73
|
-
str,
|
|
74
|
-
'with(this)return ' + str + '([' + chunks + ']' + (
|
|
75
|
-
values.length ? (',' + values.join(',')) : ''
|
|
76
|
-
) + ')'
|
|
77
|
-
];
|
|
78
|
-
} else {
|
|
79
|
-
strings = chunks.slice(0, 1);
|
|
80
|
-
for (var i = 1, length = chunks.length; i < length; i++)
|
|
81
|
-
strings.push(values[i - 1], chunks[i]);
|
|
82
|
-
strings = ['with(this)return ' + strings.join('+')];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return Function.apply(null, strings).apply(
|
|
86
|
-
object,
|
|
87
|
-
hasTransformer ? [fn] : []
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
catch(err){
|
|
91
|
-
console.log("error", err);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
parse(str) {
|
|
96
|
-
var
|
|
97
|
-
stringify = JSON.stringify,
|
|
98
|
-
open = 0, close = 0, counter = 0,
|
|
99
|
-
i = 0, length = str.length,
|
|
100
|
-
chunks = i < length ? [] : ['""'],
|
|
101
|
-
values = []
|
|
102
|
-
;
|
|
103
|
-
while (i < length) {
|
|
104
|
-
open = str.indexOf('${', i);
|
|
105
|
-
if (-1 < open) {
|
|
106
|
-
chunks.push(stringify(str.slice(i, open)));
|
|
107
|
-
open += 2;
|
|
108
|
-
close = open;
|
|
109
|
-
counter = 1;
|
|
110
|
-
while (close < length) {
|
|
111
|
-
switch (str.charAt(close++)) {
|
|
112
|
-
case '}': --counter; break;
|
|
113
|
-
case '{': ++counter; break;
|
|
114
|
-
}
|
|
115
|
-
if (counter < 1) {
|
|
116
|
-
values.push('(' + str.slice(open, close - 1) + ')');
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
i = close;
|
|
121
|
-
} else {
|
|
122
|
-
chunks.push(stringify(str.slice(i)));
|
|
123
|
-
i = length;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (chunks.length === values.length)
|
|
127
|
-
chunks.push('""');
|
|
128
|
-
return {chunks: chunks, values: values};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
escape(es) {
|
|
132
|
-
return replace.call(es, ca, this.pe);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
unescape(un) {
|
|
136
|
-
return replace.call(un, es, this.cape);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
pe(m) {
|
|
140
|
-
return esca[m];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
cape(m) {
|
|
144
|
-
return unes[m];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ==================== Security Methods ====================
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Validate template for dangerous patterns
|
|
151
|
-
* Prevents template injection attacks
|
|
152
|
-
*/
|
|
153
|
-
validateTemplate(template) {
|
|
154
|
-
if (!template || typeof template !== 'string') {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const isDevelopment = process.env.NODE_ENV !== 'production' && process.env.master === 'development';
|
|
159
|
-
|
|
160
|
-
// Dangerous patterns in templates
|
|
161
|
-
const dangerousPatterns = [
|
|
162
|
-
{ pattern: /\$\{.*__proto__/gi, name: 'Prototype pollution' },
|
|
163
|
-
{ pattern: /\$\{.*constructor.*\(/gi, name: 'Constructor access' },
|
|
164
|
-
{ pattern: /\$\{.*\beval\s*\(/gi, name: 'eval() usage' },
|
|
165
|
-
{ pattern: /\$\{.*Function\s*\(/gi, name: 'Function constructor' },
|
|
166
|
-
{ pattern: /\$\{.*require\s*\(/gi, name: 'require() usage' },
|
|
167
|
-
{ pattern: /\$\{.*import\s*\(/gi, name: 'import() usage' },
|
|
168
|
-
{ pattern: /\$\{.*process\./gi, name: 'Process access' },
|
|
169
|
-
{ pattern: /\$\{.*global\./gi, name: 'Global object access' },
|
|
170
|
-
{ pattern: /\$\{.*\bfs\./gi, name: 'File system access' },
|
|
171
|
-
{ pattern: /\$\{.*child_process/gi, name: 'Child process access' }
|
|
172
|
-
];
|
|
173
|
-
|
|
174
|
-
for (const { pattern, name } of dangerousPatterns) {
|
|
175
|
-
if (pattern.test(template)) {
|
|
176
|
-
logger.error({
|
|
177
|
-
code: 'MC_SECURITY_TEMPLATE_INJECTION',
|
|
178
|
-
message: `Dangerous template pattern detected: ${name}`,
|
|
179
|
-
pattern: pattern.toString(),
|
|
180
|
-
template: template.substring(0, 200) // Log first 200 chars only
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
if (isDevelopment) {
|
|
184
|
-
throw new Error(`[MasterController Security] Template injection attempt detected: ${name}\nPattern: ${pattern}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// In production, sanitize by removing the dangerous expression
|
|
188
|
-
template = template.replace(pattern, '${/* REMOVED: Security risk */}');
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return template;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Sanitize template variables before rendering
|
|
197
|
-
* Call this on user-provided data
|
|
198
|
-
*/
|
|
199
|
-
sanitizeVariable(value) {
|
|
200
|
-
if (value === null || value === undefined) {
|
|
201
|
-
return '';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (typeof value === 'string') {
|
|
205
|
-
return escapeHTML(value);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (typeof value === 'object') {
|
|
209
|
-
// Prevent prototype pollution
|
|
210
|
-
if (value.__proto__ || value.constructor) {
|
|
211
|
-
logger.warn({
|
|
212
|
-
code: 'MC_SECURITY_OBJECT_POLLUTION',
|
|
213
|
-
message: 'Attempted to pass object with prototype/constructor to template'
|
|
214
|
-
});
|
|
215
|
-
return '[Object]';
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Safely stringify
|
|
219
|
-
try {
|
|
220
|
-
return JSON.stringify(value);
|
|
221
|
-
} catch (e) {
|
|
222
|
-
return '[Object]';
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return String(value);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
module.exports = MasterTemplate;
|
package/MasterTimeout.js.tmp
DELETED
|
File without changes
|
package/TemplateOverwrite.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// version 0.0.1
|
|
2
|
-
var master = require('mastercontroller');
|
|
3
|
-
|
|
4
|
-
class TemplateOverwrite{
|
|
5
|
-
|
|
6
|
-
#templateFunc;
|
|
7
|
-
#isTemplate = false;
|
|
8
|
-
|
|
9
|
-
// Lazy-load master to avoid circular dependency (Google-style lazy initialization)
|
|
10
|
-
get _master() {
|
|
11
|
-
if (!this.__masterCache) {
|
|
12
|
-
this.__masterCache = require('./MasterControl');
|
|
13
|
-
}
|
|
14
|
-
return this.__masterCache;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
get isTemplate(){
|
|
18
|
-
return this.#isTemplate;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
template(func){
|
|
22
|
-
this.#isTemplate = true;
|
|
23
|
-
this.#templateFunc = func === undefined ? null : func;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
templateRender(data, type){
|
|
27
|
-
if(this.#templateFunc){
|
|
28
|
-
return this.#templateFunc(data, type);
|
|
29
|
-
}
|
|
30
|
-
else{
|
|
31
|
-
console.log("cannot call template render when no function has been declared. ")
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
close(response, code, content, end){
|
|
36
|
-
response.writeHead(code, content.type);
|
|
37
|
-
response.end(end);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
module.exports = { TemplateOverwrite };
|
package/TemplateOverwrite.js.tmp
DELETED
|
File without changes
|
package/error/ErrorBoundary.js
DELETED
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ErrorBoundary - Production error boundary system for Web Components
|
|
3
|
-
* Catches component errors without crashing entire application
|
|
4
|
-
* Version: 1.0.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* ErrorBoundary Web Component
|
|
9
|
-
* Usage:
|
|
10
|
-
* <error-boundary>
|
|
11
|
-
* <my-component></my-component>
|
|
12
|
-
* </error-boundary>
|
|
13
|
-
*/
|
|
14
|
-
class ErrorBoundary extends HTMLElement {
|
|
15
|
-
constructor() {
|
|
16
|
-
super();
|
|
17
|
-
this._hasError = false;
|
|
18
|
-
this._errorInfo = null;
|
|
19
|
-
this._originalContent = null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
connectedCallback() {
|
|
23
|
-
// Store original content
|
|
24
|
-
this._originalContent = this.innerHTML;
|
|
25
|
-
|
|
26
|
-
// Catch errors from child components
|
|
27
|
-
this.addEventListener('error', this._handleError.bind(this), true);
|
|
28
|
-
|
|
29
|
-
// Also catch unhandled promise rejections in child components
|
|
30
|
-
window.addEventListener('unhandledrejection', this._handleRejection.bind(this));
|
|
31
|
-
|
|
32
|
-
// Wrap all child custom elements with error catching
|
|
33
|
-
this._wrapChildComponents();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
disconnectedCallback() {
|
|
37
|
-
this.removeEventListener('error', this._handleError, true);
|
|
38
|
-
window.removeEventListener('unhandledrejection', this._handleRejection);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Wrap child component lifecycle methods with error handling
|
|
43
|
-
*/
|
|
44
|
-
_wrapChildComponents() {
|
|
45
|
-
const customElements = this.querySelectorAll('*');
|
|
46
|
-
|
|
47
|
-
customElements.forEach(el => {
|
|
48
|
-
if (!el.tagName.includes('-')) return;
|
|
49
|
-
|
|
50
|
-
// Wrap connectedCallback
|
|
51
|
-
if (el.connectedCallback && !el._errorBoundaryWrapped) {
|
|
52
|
-
const originalConnected = el.connectedCallback.bind(el);
|
|
53
|
-
el.connectedCallback = (...args) => {
|
|
54
|
-
try {
|
|
55
|
-
return originalConnected(...args);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
this._catchComponentError(error, el);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
el._errorBoundaryWrapped = true;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Wrap attributeChangedCallback
|
|
64
|
-
if (el.attributeChangedCallback && !el._errorBoundaryAttrWrapped) {
|
|
65
|
-
const originalAttrChanged = el.attributeChangedCallback.bind(el);
|
|
66
|
-
el.attributeChangedCallback = (...args) => {
|
|
67
|
-
try {
|
|
68
|
-
return originalAttrChanged(...args);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
this._catchComponentError(error, el);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
el._errorBoundaryAttrWrapped = true;
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Handle error events
|
|
80
|
-
*/
|
|
81
|
-
_handleError(event) {
|
|
82
|
-
// Only handle errors from child elements
|
|
83
|
-
if (!this.contains(event.target)) return;
|
|
84
|
-
|
|
85
|
-
event.preventDefault();
|
|
86
|
-
event.stopPropagation();
|
|
87
|
-
|
|
88
|
-
this._catchComponentError(event.error || new Error('Unknown error'), event.target);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Handle unhandled promise rejections
|
|
93
|
-
*/
|
|
94
|
-
_handleRejection(event) {
|
|
95
|
-
// Check if rejection came from a component within this boundary
|
|
96
|
-
if (event.reason && event.reason.component) {
|
|
97
|
-
const component = this.querySelector(event.reason.component);
|
|
98
|
-
if (component) {
|
|
99
|
-
this._catchComponentError(event.reason, component);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Catch and handle component errors
|
|
106
|
-
*/
|
|
107
|
-
_catchComponentError(error, component) {
|
|
108
|
-
if (this._hasError) return; // Already in error state
|
|
109
|
-
|
|
110
|
-
this._hasError = true;
|
|
111
|
-
this._errorInfo = {
|
|
112
|
-
error,
|
|
113
|
-
component: component ? component.tagName.toLowerCase() : 'unknown',
|
|
114
|
-
timestamp: new Date().toISOString(),
|
|
115
|
-
userAgent: navigator.userAgent,
|
|
116
|
-
url: window.location.href
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Log error
|
|
120
|
-
this._logError();
|
|
121
|
-
|
|
122
|
-
// Show fallback UI
|
|
123
|
-
this._showFallbackUI();
|
|
124
|
-
|
|
125
|
-
// Call custom error handler if provided
|
|
126
|
-
if (typeof this.onError === 'function') {
|
|
127
|
-
try {
|
|
128
|
-
this.onError(this._errorInfo);
|
|
129
|
-
} catch (handlerError) {
|
|
130
|
-
console.error('[ErrorBoundary] onError handler failed:', handlerError);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Dispatch custom event for external monitoring
|
|
135
|
-
this.dispatchEvent(new CustomEvent('error-boundary-catch', {
|
|
136
|
-
bubbles: true,
|
|
137
|
-
detail: this._errorInfo
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Log error to console and monitoring services
|
|
143
|
-
*/
|
|
144
|
-
_logError() {
|
|
145
|
-
console.error('[ErrorBoundary] Caught error:', this._errorInfo);
|
|
146
|
-
|
|
147
|
-
// Send to monitoring service if configured
|
|
148
|
-
if (window.masterControllerErrorReporter) {
|
|
149
|
-
try {
|
|
150
|
-
window.masterControllerErrorReporter({
|
|
151
|
-
type: 'error-boundary',
|
|
152
|
-
...this._errorInfo
|
|
153
|
-
});
|
|
154
|
-
} catch (reporterError) {
|
|
155
|
-
console.error('[ErrorBoundary] Error reporter failed:', reporterError);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Send to Sentry if available
|
|
160
|
-
if (window.Sentry) {
|
|
161
|
-
window.Sentry.captureException(this._errorInfo.error, {
|
|
162
|
-
tags: {
|
|
163
|
-
component: this._errorInfo.component,
|
|
164
|
-
errorBoundary: true
|
|
165
|
-
},
|
|
166
|
-
extra: this._errorInfo
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Show fallback UI
|
|
173
|
-
*/
|
|
174
|
-
_showFallbackUI() {
|
|
175
|
-
const fallbackTemplate = this.getAttribute('fallback-template');
|
|
176
|
-
const customMessage = this.getAttribute('error-message');
|
|
177
|
-
|
|
178
|
-
if (fallbackTemplate) {
|
|
179
|
-
// Use custom template
|
|
180
|
-
const template = document.querySelector(fallbackTemplate);
|
|
181
|
-
if (template) {
|
|
182
|
-
this.innerHTML = template.innerHTML;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Default fallback UI
|
|
188
|
-
const isDevelopment = this.hasAttribute('dev-mode');
|
|
189
|
-
|
|
190
|
-
this.innerHTML = `
|
|
191
|
-
<div class="error-boundary-fallback" style="
|
|
192
|
-
padding: 20px;
|
|
193
|
-
margin: 10px 0;
|
|
194
|
-
background: ${isDevelopment ? '#fee' : '#f9fafb'};
|
|
195
|
-
border: 2px solid ${isDevelopment ? '#f87171' : '#d1d5db'};
|
|
196
|
-
border-radius: 8px;
|
|
197
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
198
|
-
">
|
|
199
|
-
<div style="display: flex; align-items: start; gap: 12px;">
|
|
200
|
-
<div style="font-size: 24px;">${isDevelopment ? '❌' : '⚠️'}</div>
|
|
201
|
-
<div style="flex: 1;">
|
|
202
|
-
<h3 style="margin: 0 0 8px 0; color: ${isDevelopment ? '#dc2626' : '#374151'}; font-size: 18px; font-weight: 600;">
|
|
203
|
-
${customMessage || 'Something went wrong'}
|
|
204
|
-
</h3>
|
|
205
|
-
<p style="margin: 0 0 12px 0; color: #6b7280; font-size: 14px;">
|
|
206
|
-
${isDevelopment
|
|
207
|
-
? `Component "${this._errorInfo.component}" encountered an error.`
|
|
208
|
-
: 'We\'ve been notified and are working on it.'
|
|
209
|
-
}
|
|
210
|
-
</p>
|
|
211
|
-
${isDevelopment ? `
|
|
212
|
-
<details style="margin-top: 12px;">
|
|
213
|
-
<summary style="cursor: pointer; color: #3b82f6; font-weight: 600; font-size: 14px;">
|
|
214
|
-
View Error Details
|
|
215
|
-
</summary>
|
|
216
|
-
<pre style="
|
|
217
|
-
margin-top: 12px;
|
|
218
|
-
padding: 12px;
|
|
219
|
-
background: #1f2937;
|
|
220
|
-
color: #f3f4f6;
|
|
221
|
-
border-radius: 4px;
|
|
222
|
-
font-size: 12px;
|
|
223
|
-
overflow-x: auto;
|
|
224
|
-
font-family: 'Courier New', monospace;
|
|
225
|
-
">${this.escapeHtml(this._errorInfo.error.stack || this._errorInfo.error.message)}</pre>
|
|
226
|
-
</details>
|
|
227
|
-
` : ''}
|
|
228
|
-
<button
|
|
229
|
-
onclick="this.closest('.error-boundary-fallback').parentElement.dispatchEvent(new CustomEvent('error-boundary-retry', { bubbles: true }))"
|
|
230
|
-
style="
|
|
231
|
-
margin-top: 12px;
|
|
232
|
-
padding: 8px 16px;
|
|
233
|
-
background: #3b82f6;
|
|
234
|
-
color: white;
|
|
235
|
-
border: none;
|
|
236
|
-
border-radius: 6px;
|
|
237
|
-
font-weight: 600;
|
|
238
|
-
font-size: 14px;
|
|
239
|
-
cursor: pointer;
|
|
240
|
-
"
|
|
241
|
-
onmouseover="this.style.background='#2563eb'"
|
|
242
|
-
onmouseout="this.style.background='#3b82f6'"
|
|
243
|
-
>
|
|
244
|
-
Try Again
|
|
245
|
-
</button>
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
`;
|
|
250
|
-
|
|
251
|
-
// Handle retry button
|
|
252
|
-
this.addEventListener('error-boundary-retry', this._handleRetry.bind(this), { once: true });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Handle retry
|
|
257
|
-
*/
|
|
258
|
-
_handleRetry() {
|
|
259
|
-
this._hasError = false;
|
|
260
|
-
this._errorInfo = null;
|
|
261
|
-
this.innerHTML = this._originalContent;
|
|
262
|
-
|
|
263
|
-
// Re-wrap child components
|
|
264
|
-
this._wrapChildComponents();
|
|
265
|
-
|
|
266
|
-
// Dispatch retry event
|
|
267
|
-
this.dispatchEvent(new CustomEvent('error-boundary-retried', {
|
|
268
|
-
bubbles: true
|
|
269
|
-
}));
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Escape HTML for safe rendering
|
|
274
|
-
*/
|
|
275
|
-
escapeHtml(str) {
|
|
276
|
-
if (!str) return '';
|
|
277
|
-
return String(str)
|
|
278
|
-
.replace(/&/g, '&')
|
|
279
|
-
.replace(/</g, '<')
|
|
280
|
-
.replace(/>/g, '>')
|
|
281
|
-
.replace(/"/g, '"')
|
|
282
|
-
.replace(/'/g, ''');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Public API: Reset error state
|
|
287
|
-
*/
|
|
288
|
-
reset() {
|
|
289
|
-
this._handleRetry();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Public API: Get error info
|
|
294
|
-
*/
|
|
295
|
-
getErrorInfo() {
|
|
296
|
-
return this._errorInfo;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Public API: Check if has error
|
|
301
|
-
*/
|
|
302
|
-
hasError() {
|
|
303
|
-
return this._hasError;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Register the error boundary component
|
|
308
|
-
if (!customElements.get('error-boundary')) {
|
|
309
|
-
customElements.define('error-boundary', ErrorBoundary);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Global error handler setup
|
|
313
|
-
if (typeof window !== 'undefined') {
|
|
314
|
-
// Catch uncaught errors globally
|
|
315
|
-
window.addEventListener('error', (event) => {
|
|
316
|
-
console.error('[MasterController] Uncaught error:', event.error);
|
|
317
|
-
|
|
318
|
-
// Try to find nearest error boundary
|
|
319
|
-
if (event.target instanceof HTMLElement) {
|
|
320
|
-
let boundary = event.target.closest('error-boundary');
|
|
321
|
-
if (boundary) {
|
|
322
|
-
// Error will be handled by the boundary
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// No boundary found - log to monitoring service
|
|
328
|
-
if (window.masterControllerErrorReporter) {
|
|
329
|
-
window.masterControllerErrorReporter({
|
|
330
|
-
type: 'uncaught-error',
|
|
331
|
-
error: event.error,
|
|
332
|
-
message: event.message,
|
|
333
|
-
filename: event.filename,
|
|
334
|
-
lineno: event.lineno,
|
|
335
|
-
colno: event.colno
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// Catch unhandled promise rejections
|
|
341
|
-
window.addEventListener('unhandledrejection', (event) => {
|
|
342
|
-
console.error('[MasterController] Unhandled rejection:', event.reason);
|
|
343
|
-
|
|
344
|
-
if (window.masterControllerErrorReporter) {
|
|
345
|
-
window.masterControllerErrorReporter({
|
|
346
|
-
type: 'unhandled-rejection',
|
|
347
|
-
reason: event.reason
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export { ErrorBoundary };
|