slicejs-web-framework 2.2.6 → 2.2.8
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/Slice/Components/Structural/Controller/Controller.js +353 -2
- package/Slice/Slice.js +40 -13
- package/api/index.js +152 -11
- package/api/middleware/securityMiddleware.js +253 -0
- package/package.json +1 -1
|
@@ -7,14 +7,365 @@ export default class Controller {
|
|
|
7
7
|
this.classes = new Map();
|
|
8
8
|
this.requestedStyles = new Set(); // ✅ CRÍTICO: Para tracking de CSS cargados
|
|
9
9
|
this.activeComponents = new Map();
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
// 🚀 OPTIMIZACIÓN: Índice inverso para búsqueda rápida de hijos
|
|
12
12
|
// parentSliceId → Set<childSliceId>
|
|
13
13
|
this.childrenIndex = new Map();
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
// 📦 Bundle system
|
|
16
|
+
this.loadedBundles = new Set();
|
|
17
|
+
this.bundleConfig = null;
|
|
18
|
+
this.criticalBundleLoaded = false;
|
|
19
|
+
|
|
15
20
|
this.idCounter = 0;
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* 📦 Initializes bundle system (called automatically when config is loaded)
|
|
25
|
+
*/
|
|
26
|
+
initializeBundles(config = null) {
|
|
27
|
+
if (config) {
|
|
28
|
+
this.bundleConfig = config;
|
|
29
|
+
|
|
30
|
+
// Register critical bundle components if available
|
|
31
|
+
if (config.bundles?.critical) {
|
|
32
|
+
// The critical bundle should already be loaded, register its components
|
|
33
|
+
this.loadedBundles.add('critical');
|
|
34
|
+
// Note: Critical bundle registration is handled by the auto-import
|
|
35
|
+
}
|
|
36
|
+
this.criticalBundleLoaded = true;
|
|
37
|
+
} else {
|
|
38
|
+
// No bundles available, will use individual component loading
|
|
39
|
+
this.bundleConfig = null;
|
|
40
|
+
this.criticalBundleLoaded = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 📦 Loads a bundle by name or category
|
|
46
|
+
*/
|
|
47
|
+
async loadBundle(bundleName) {
|
|
48
|
+
if (this.loadedBundles.has(bundleName)) {
|
|
49
|
+
return; // Already loaded
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const bundleInfo = this.bundleConfig?.bundles?.routes?.[bundleName];
|
|
54
|
+
|
|
55
|
+
if (!bundleInfo) {
|
|
56
|
+
console.warn(`Bundle ${bundleName} not found in configuration`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const bundlePath = `/bundles/${bundleInfo.file}`;
|
|
61
|
+
|
|
62
|
+
// Dynamic import of the bundle
|
|
63
|
+
const bundleModule = await import(bundlePath);
|
|
64
|
+
|
|
65
|
+
// Manually register components from the imported bundle
|
|
66
|
+
if (bundleModule.SLICE_BUNDLE) {
|
|
67
|
+
this.registerBundle(bundleModule.SLICE_BUNDLE);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.loadedBundles.add(bundleName);
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.warn(`Failed to load bundle ${bundleName}:`, error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 📦 Registers a bundle's components (called automatically by bundle files)
|
|
79
|
+
*/
|
|
80
|
+
registerBundleLegacy(bundle) {
|
|
81
|
+
const { components, metadata } = bundle;
|
|
82
|
+
|
|
83
|
+
console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
|
|
84
|
+
|
|
85
|
+
// Phase 1: Register templates and CSS for all components first
|
|
86
|
+
for (const [componentName, componentData] of Object.entries(components)) {
|
|
87
|
+
try {
|
|
88
|
+
// Register HTML template
|
|
89
|
+
if (componentData.html !== undefined && !this.templates.has(componentName)) {
|
|
90
|
+
const template = document.createElement('template');
|
|
91
|
+
template.innerHTML = componentData.html || '';
|
|
92
|
+
this.templates.set(componentName, template);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Register CSS styles
|
|
96
|
+
if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
|
|
97
|
+
// Use the existing stylesManager to register component styles
|
|
98
|
+
if (window.slice && window.slice.stylesManager) {
|
|
99
|
+
window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
|
|
100
|
+
this.requestedStyles.add(componentName);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.warn(`❌ Failed to register assets for ${componentName}:`, error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Phase 2: Evaluate all external file dependencies
|
|
109
|
+
const processedDeps = new Set();
|
|
110
|
+
for (const [componentName, componentData] of Object.entries(components)) {
|
|
111
|
+
if (componentData.dependencies) {
|
|
112
|
+
for (const [depName, depContent] of Object.entries(componentData.dependencies)) {
|
|
113
|
+
if (!processedDeps.has(depName)) {
|
|
114
|
+
try {
|
|
115
|
+
// Convert ES6 exports to global assignments
|
|
116
|
+
let processedContent = depContent
|
|
117
|
+
.replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
|
|
118
|
+
.replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
|
|
119
|
+
.replace(/export\s+var\s+(\w+)\s*=/g, 'window.$1 =')
|
|
120
|
+
.replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
|
|
121
|
+
.replace(/export\s+default\s+/g, 'window.defaultExport =')
|
|
122
|
+
.replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
|
|
123
|
+
return exports.split(',').map(exp => {
|
|
124
|
+
const cleanExp = exp.trim();
|
|
125
|
+
const varName = cleanExp.split(' as ')[0].trim();
|
|
126
|
+
return `window.${varName} = ${varName};`;
|
|
127
|
+
}).join('\n');
|
|
128
|
+
})
|
|
129
|
+
// Remove any remaining export keywords
|
|
130
|
+
.replace(/^\s*export\s+/gm, '');
|
|
131
|
+
|
|
132
|
+
// Evaluate the dependency
|
|
133
|
+
try {
|
|
134
|
+
new Function('slice', 'customElements', 'window', 'document', processedContent)
|
|
135
|
+
(window.slice, window.customElements, window, window.document);
|
|
136
|
+
} catch (evalError) {
|
|
137
|
+
console.warn(`❌ Failed to evaluate processed dependency ${depName}:`, evalError);
|
|
138
|
+
console.warn('Processed content preview:', processedContent.substring(0, 200));
|
|
139
|
+
// Try evaluating the original content as fallback
|
|
140
|
+
try {
|
|
141
|
+
new Function('slice', 'customElements', 'window', 'document', depContent)
|
|
142
|
+
(window.slice, window.customElements, window, window.document);
|
|
143
|
+
console.log(`✅ Fallback evaluation succeeded for ${depName}`);
|
|
144
|
+
} catch (fallbackError) {
|
|
145
|
+
console.warn(`❌ Fallback evaluation also failed for ${depName}:`, fallbackError);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
processedDeps.add(depName);
|
|
150
|
+
console.log(`📄 Dependency loaded: ${depName}`);
|
|
151
|
+
} catch (depError) {
|
|
152
|
+
console.warn(`⚠️ Failed to load dependency ${depName} for ${componentName}:`, depError);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Phase 3: Evaluate all component classes (now that dependencies are available)
|
|
160
|
+
for (const [componentName, componentData] of Object.entries(components)) {
|
|
161
|
+
// For JavaScript classes, we need to evaluate the code
|
|
162
|
+
if (componentData.js && !this.classes.has(componentName)) {
|
|
163
|
+
try {
|
|
164
|
+
// Create evaluation context with dependencies
|
|
165
|
+
let evalCode = componentData.js;
|
|
166
|
+
|
|
167
|
+
// Prepend dependencies to make them available
|
|
168
|
+
if (componentData.dependencies) {
|
|
169
|
+
const depCode = Object.entries(componentData.dependencies)
|
|
170
|
+
.map(([depName, depContent]) => {
|
|
171
|
+
// Convert ES6 exports to global assignments
|
|
172
|
+
return depContent
|
|
173
|
+
.replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
|
|
174
|
+
.replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
|
|
175
|
+
.replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
|
|
176
|
+
.replace(/export\s+default\s+/g, 'window.defaultExport =')
|
|
177
|
+
.replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
|
|
178
|
+
return exports.split(',').map(exp => {
|
|
179
|
+
const cleanExp = exp.trim();
|
|
180
|
+
return `window.${cleanExp} = ${cleanExp};`;
|
|
181
|
+
}).join('\n');
|
|
182
|
+
});
|
|
183
|
+
})
|
|
184
|
+
.join('\n\n');
|
|
185
|
+
|
|
186
|
+
evalCode = depCode + '\n\n' + evalCode;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Evaluate the complete code
|
|
190
|
+
const componentClass = new Function('slice', 'customElements', 'window', 'document', `
|
|
191
|
+
"use strict";
|
|
192
|
+
${evalCode}
|
|
193
|
+
return ${componentName};
|
|
194
|
+
`)(window.slice, window.customElements, window, window.document);
|
|
195
|
+
|
|
196
|
+
if (componentClass) {
|
|
197
|
+
this.classes.set(componentName, componentClass);
|
|
198
|
+
console.log(`📝 Class registered for: ${componentName}`);
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.warn(`❌ Failed to evaluate class for ${componentName}:`, error);
|
|
202
|
+
console.warn('Code that failed:', componentData.js.substring(0, 200) + '...');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 📦 New bundle registration method (simplified and robust)
|
|
212
|
+
*/
|
|
213
|
+
registerBundle(bundle) {
|
|
214
|
+
const { components, metadata } = bundle;
|
|
215
|
+
|
|
216
|
+
console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
|
|
217
|
+
|
|
218
|
+
// Phase 1: Register all templates and CSS first
|
|
219
|
+
for (const [componentName, componentData] of Object.entries(components)) {
|
|
220
|
+
try {
|
|
221
|
+
if (componentData.html !== undefined && !this.templates.has(componentName)) {
|
|
222
|
+
const template = document.createElement('template');
|
|
223
|
+
template.innerHTML = componentData.html || '';
|
|
224
|
+
this.templates.set(componentName, template);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
|
|
228
|
+
if (window.slice && window.slice.stylesManager) {
|
|
229
|
+
window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
|
|
230
|
+
this.requestedStyles.add(componentName);
|
|
231
|
+
console.log(`🎨 CSS registered for: ${componentName}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.warn(`❌ Failed to register assets for ${componentName}:`, error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Phase 2: Evaluate all external file dependencies first
|
|
240
|
+
const processedDeps = new Set();
|
|
241
|
+
for (const [componentName, componentData] of Object.entries(components)) {
|
|
242
|
+
if (componentData.externalDependencies) {
|
|
243
|
+
for (const [depName, depContent] of Object.entries(componentData.externalDependencies)) {
|
|
244
|
+
if (!processedDeps.has(depName)) {
|
|
245
|
+
try {
|
|
246
|
+
// Process ES6 exports to make the code evaluable
|
|
247
|
+
let processedContent = depContent
|
|
248
|
+
// Convert named exports: export const varName = ... → window.varName = ...
|
|
249
|
+
.replace(/export\s+const\s+(\w+)\s*=\s*/g, 'window.$1 = ')
|
|
250
|
+
.replace(/export\s+let\s+(\w+)\s*=\s*/g, 'window.$1 = ')
|
|
251
|
+
.replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
|
|
252
|
+
.replace(/export\s+default\s+/g, 'window.defaultExport = ')
|
|
253
|
+
// Handle export { var1, var2 } statements
|
|
254
|
+
.replace(/export\s*{\s*([^}]+)\s*}/g, (match, exportsStr) => {
|
|
255
|
+
const exports = exportsStr.split(',').map(exp => exp.trim().split(' as ')[0].trim());
|
|
256
|
+
return exports.map(varName => `window.${varName} = ${varName};`).join('\n');
|
|
257
|
+
})
|
|
258
|
+
// Remove any remaining export keywords
|
|
259
|
+
.replace(/^\s*export\s+/gm, '');
|
|
260
|
+
|
|
261
|
+
// Evaluate the processed content
|
|
262
|
+
new Function('slice', 'customElements', 'window', 'document', processedContent)
|
|
263
|
+
(window.slice, window.customElements, window, window.document);
|
|
264
|
+
|
|
265
|
+
processedDeps.add(depName);
|
|
266
|
+
console.log(`📄 External dependency loaded: ${depName}`);
|
|
267
|
+
} catch (depError) {
|
|
268
|
+
console.warn(`⚠️ Failed to load external dependency ${depName}:`, depError);
|
|
269
|
+
console.warn('Original content preview:', depContent.substring(0, 200));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Phase 3: Evaluate all component classes (external dependencies are now available)
|
|
277
|
+
for (const [componentName, componentData] of Object.entries(components)) {
|
|
278
|
+
if (componentData.js && !this.classes.has(componentName)) {
|
|
279
|
+
try {
|
|
280
|
+
// Simple evaluation
|
|
281
|
+
const componentClass = new Function('slice', 'customElements', 'window', 'document', `
|
|
282
|
+
${componentData.js}
|
|
283
|
+
return ${componentName};
|
|
284
|
+
`)(window.slice, window.customElements, window, window.document);
|
|
285
|
+
|
|
286
|
+
if (componentClass) {
|
|
287
|
+
this.classes.set(componentName, componentClass);
|
|
288
|
+
console.log(`📝 Class registered for: ${componentName}`);
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.warn(`❌ Failed to evaluate class for ${componentName}:`, error);
|
|
292
|
+
// Continue with other components instead of failing completely
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log(`✅ Bundle registration completed: ${metadata.componentCount} components processed`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 📦 Determines which bundle to load for a component
|
|
302
|
+
*/
|
|
303
|
+
getBundleForComponent(componentName) {
|
|
304
|
+
if (!this.bundleConfig?.bundles) return null;
|
|
305
|
+
|
|
306
|
+
// Check if component is in critical bundle
|
|
307
|
+
if (this.bundleConfig.bundles.critical?.components?.includes(componentName)) {
|
|
308
|
+
return 'critical';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Find component in route bundles
|
|
312
|
+
if (this.bundleConfig.bundles.routes) {
|
|
313
|
+
for (const [bundleName, bundleInfo] of Object.entries(this.bundleConfig.bundles.routes)) {
|
|
314
|
+
if (bundleInfo.components?.includes(componentName)) {
|
|
315
|
+
return bundleName;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 📦 Checks if a component is available from loaded bundles
|
|
325
|
+
*/
|
|
326
|
+
isComponentFromBundle(componentName) {
|
|
327
|
+
if (!this.bundleConfig?.bundles) return false;
|
|
328
|
+
|
|
329
|
+
// Check critical bundle
|
|
330
|
+
if (this.bundleConfig.bundles.critical?.components?.includes(componentName)) {
|
|
331
|
+
return this.criticalBundleLoaded;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Check route bundles
|
|
335
|
+
if (this.bundleConfig.bundles.routes) {
|
|
336
|
+
for (const [bundleName, bundleInfo] of Object.entries(this.bundleConfig.bundles.routes)) {
|
|
337
|
+
if (bundleInfo.components?.includes(componentName)) {
|
|
338
|
+
return this.loadedBundles.has(bundleName);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 📦 Gets component data from loaded bundles
|
|
348
|
+
*/
|
|
349
|
+
getComponentFromBundle(componentName) {
|
|
350
|
+
if (!this.bundleConfig?.bundles) return null;
|
|
351
|
+
|
|
352
|
+
// Find component in any loaded bundle
|
|
353
|
+
const allBundles = [
|
|
354
|
+
{ name: 'critical', data: this.bundleConfig.bundles.critical },
|
|
355
|
+
...Object.entries(this.bundleConfig.bundles.routes || {}).map(([name, data]) => ({ name, data }))
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
for (const { name: bundleName, data: bundleData } of allBundles) {
|
|
359
|
+
if (bundleData?.components?.includes(componentName) && this.loadedBundles.has(bundleName)) {
|
|
360
|
+
// Find the bundle file and extract component data
|
|
361
|
+
// This is a simplified version - in practice you'd need to access the loaded bundle data
|
|
362
|
+
return { bundleName, componentName };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
18
369
|
logActiveComponents() {
|
|
19
370
|
this.activeComponents.forEach((component) => {
|
|
20
371
|
let parent = component.parentComponent;
|
package/Slice/Slice.js
CHANGED
|
@@ -11,6 +11,8 @@ export default class Slice {
|
|
|
11
11
|
this.loggerConfig = sliceConfig.logger;
|
|
12
12
|
this.debuggerConfig = sliceConfig.debugger;
|
|
13
13
|
this.loadingConfig = sliceConfig.loading;
|
|
14
|
+
|
|
15
|
+
// 📦 Bundle system is initialized automatically via import in index.js
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
async getClass(module) {
|
|
@@ -46,8 +48,17 @@ export default class Slice {
|
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// 📦 Try to load from bundles first
|
|
52
|
+
const bundleName = this.controller.getBundleForComponent(componentName);
|
|
53
|
+
if (bundleName && !this.controller.loadedBundles.has(bundleName)) {
|
|
54
|
+
await this.controller.loadBundle(bundleName);
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
let componentCategory = this.controller.componentCategories.get(componentName);
|
|
50
58
|
|
|
59
|
+
// 📦 Check if component is already available from loaded bundles
|
|
60
|
+
const isFromBundle = this.controller.isComponentFromBundle(componentName);
|
|
61
|
+
|
|
51
62
|
if (componentCategory === 'Structural') {
|
|
52
63
|
this.logger.logError(
|
|
53
64
|
'Slice',
|
|
@@ -57,27 +68,31 @@ export default class Slice {
|
|
|
57
68
|
return null;
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
let isVisual = slice.paths.components[componentCategory].type === "Visual";
|
|
71
|
+
let isVisual = slice.paths.components[componentCategory].type === "Visual";
|
|
61
72
|
let modulePath = `${slice.paths.components[componentCategory].path}/${componentName}/${componentName}.js`;
|
|
62
73
|
|
|
63
74
|
// Load template, class, and CSS concurrently if needed
|
|
64
75
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
// 📦 Skip individual loading if component is available from bundles
|
|
77
|
+
const loadTemplate = (isFromBundle || !isVisual || this.controller.templates.has(componentName))
|
|
78
|
+
? Promise.resolve(null)
|
|
79
|
+
: this.controller.fetchText(componentName, 'html', componentCategory);
|
|
69
80
|
|
|
70
|
-
const loadClass =
|
|
71
|
-
?
|
|
72
|
-
:
|
|
81
|
+
const loadClass = (isFromBundle || this.controller.classes.has(componentName))
|
|
82
|
+
? Promise.resolve(null)
|
|
83
|
+
: this.getClass(modulePath);
|
|
73
84
|
|
|
74
|
-
const loadCSS =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
: Promise.resolve(null);
|
|
85
|
+
const loadCSS = (isFromBundle || !isVisual || this.controller.requestedStyles.has(componentName))
|
|
86
|
+
? Promise.resolve(null)
|
|
87
|
+
: this.controller.fetchText(componentName, 'css', componentCategory);
|
|
78
88
|
|
|
79
89
|
const [html, ComponentClass, css] = await Promise.all([loadTemplate, loadClass, loadCSS]);
|
|
80
90
|
|
|
91
|
+
// 📦 If component is from bundle but not in cache, it should have been registered by registerBundle
|
|
92
|
+
if (isFromBundle) {
|
|
93
|
+
console.log(`📦 Using bundled component: ${componentName}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
if (html || html === '') {
|
|
82
97
|
const template = document.createElement('template');
|
|
83
98
|
template.innerHTML = html;
|
|
@@ -219,4 +234,16 @@ async function init() {
|
|
|
219
234
|
|
|
220
235
|
}
|
|
221
236
|
|
|
222
|
-
await init();
|
|
237
|
+
await init();
|
|
238
|
+
|
|
239
|
+
// Initialize bundles if available
|
|
240
|
+
try {
|
|
241
|
+
const { initializeBundles } = await import('/bundles/bundle.config.js');
|
|
242
|
+
if (initializeBundles) {
|
|
243
|
+
await initializeBundles(window.slice);
|
|
244
|
+
console.log('📦 Bundles initialized automatically');
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
// Bundles not available, continue with individual component loading
|
|
248
|
+
console.log('📄 Using individual component loading (no bundles found)');
|
|
249
|
+
}
|
package/api/index.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
// api/index.js - Seguridad automática sin configuración
|
|
1
2
|
import express from 'express';
|
|
2
3
|
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
3
5
|
import { fileURLToPath } from 'url';
|
|
4
6
|
import { dirname } from 'path';
|
|
5
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
securityMiddleware,
|
|
9
|
+
sliceFrameworkProtection,
|
|
10
|
+
suspiciousRequestLogger
|
|
11
|
+
} from './middleware/securityMiddleware.js';
|
|
6
12
|
|
|
7
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
14
|
const __dirname = dirname(__filename);
|
|
9
15
|
import sliceConfig from '../src/sliceConfig.json' with { type: 'json' };
|
|
10
16
|
|
|
11
17
|
let server;
|
|
12
|
-
|
|
13
18
|
const app = express();
|
|
14
19
|
|
|
15
20
|
// Parsear argumentos de línea de comandos
|
|
@@ -22,12 +27,48 @@ const folderDeployed = 'src';
|
|
|
22
27
|
// Obtener puerto desde sliceConfig.json, con fallback a process.env.PORT
|
|
23
28
|
const PORT = sliceConfig.server?.port || process.env.PORT || 3001;
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
app.use(
|
|
30
|
+
// ==============================================
|
|
31
|
+
// MIDDLEWARES DE SEGURIDAD (APLICAR PRIMERO)
|
|
32
|
+
// ==============================================
|
|
33
|
+
|
|
34
|
+
// 1. Logger de peticiones sospechosas (solo observación, no bloquea)
|
|
35
|
+
app.use(suspiciousRequestLogger());
|
|
36
|
+
|
|
37
|
+
// 2. Protección del framework - TOTALMENTE AUTOMÁTICA
|
|
38
|
+
// Detecta automáticamente el dominio desde los headers
|
|
39
|
+
// Funciona en localhost, IP, y cualquier dominio
|
|
40
|
+
app.use(sliceFrameworkProtection());
|
|
41
|
+
|
|
42
|
+
// 3. Middleware de seguridad general
|
|
43
|
+
app.use(securityMiddleware({
|
|
44
|
+
allowedExtensions: [
|
|
45
|
+
'.js', '.css', '.html', '.json',
|
|
46
|
+
'.svg', '.png', '.jpg', '.jpeg', '.gif',
|
|
47
|
+
'.woff', '.woff2', '.ttf', '.ico'
|
|
48
|
+
],
|
|
49
|
+
blockedPaths: [
|
|
50
|
+
'/node_modules',
|
|
51
|
+
'/package.json',
|
|
52
|
+
'/package-lock.json',
|
|
53
|
+
'/.env',
|
|
54
|
+
'/.git',
|
|
55
|
+
'/api/middleware'
|
|
56
|
+
],
|
|
57
|
+
allowPublicAssets: true
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// ==============================================
|
|
61
|
+
// MIDDLEWARES DE APLICACIÓN
|
|
62
|
+
// ==============================================
|
|
63
|
+
|
|
64
|
+
// Middleware global para archivos JavaScript con MIME types correctos
|
|
65
|
+
app.use((req, res, next) => {
|
|
66
|
+
if (req.path.endsWith('.js')) {
|
|
67
|
+
// Forzar headers correctos para TODOS los archivos .js
|
|
68
|
+
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
69
|
+
}
|
|
70
|
+
next();
|
|
71
|
+
});
|
|
31
72
|
|
|
32
73
|
// Middleware para parsear JSON y formularios
|
|
33
74
|
app.use(express.json());
|
|
@@ -46,6 +87,90 @@ app.use((req, res, next) => {
|
|
|
46
87
|
}
|
|
47
88
|
});
|
|
48
89
|
|
|
90
|
+
// ==============================================
|
|
91
|
+
// ARCHIVOS ESTÁTICOS (DESPUÉS DE SEGURIDAD)
|
|
92
|
+
// ==============================================
|
|
93
|
+
|
|
94
|
+
// Función de utilidad para verificar si existe el directorio bundles
|
|
95
|
+
function bundlesDirectoryExists() {
|
|
96
|
+
const bundleDir = path.join(__dirname, `../${folderDeployed}`, 'bundles');
|
|
97
|
+
return fs.existsSync(bundleDir) && fs.statSync(bundleDir).isDirectory();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Capturar todas las peticiones a bundles para debugging
|
|
101
|
+
app.use('/bundles/', (req, res, next) => {
|
|
102
|
+
console.log(`🔍 Bundle request: ${req.method} ${req.originalUrl}`);
|
|
103
|
+
next();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Middleware personalizado para archivos de bundles con MIME types correctos
|
|
107
|
+
// ⚠️ DEBE IR ANTES del middleware general para tener prioridad
|
|
108
|
+
app.use('/bundles/', (req, res, next) => {
|
|
109
|
+
// Verificar si existe el directorio bundles
|
|
110
|
+
if (!bundlesDirectoryExists()) {
|
|
111
|
+
console.log(`ℹ️ Bundles directory does not exist, skipping bundle processing`);
|
|
112
|
+
return next(); // Continuar con el siguiente middleware
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Solo procesar archivos .js
|
|
116
|
+
if (req.path.endsWith('.js')) {
|
|
117
|
+
const filePath = path.join(__dirname, `../${folderDeployed}`, 'bundles', req.path);
|
|
118
|
+
console.log(`📂 Processing bundle: ${req.path} -> ${filePath}`);
|
|
119
|
+
|
|
120
|
+
// Verificar que el archivo existe
|
|
121
|
+
if (fs.existsSync(filePath)) {
|
|
122
|
+
try {
|
|
123
|
+
// Leer y servir el archivo con headers correctos
|
|
124
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
125
|
+
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
126
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); // No cachear para permitir actualizaciones en tiempo real
|
|
127
|
+
res.setHeader('Pragma', 'no-cache');
|
|
128
|
+
res.setHeader('Expires', '0');
|
|
129
|
+
console.log(`✅ Serving bundle: ${req.path} (${fileContent.length} bytes, ${Buffer.byteLength(fileContent, 'utf8')} bytes UTF-8)`);
|
|
130
|
+
return res.send(fileContent);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.log(`❌ Error reading bundle file: ${error.message}`);
|
|
133
|
+
return res.status(500).send('Error reading bundle file');
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
console.log(`❌ Bundle file not found: ${filePath}`);
|
|
137
|
+
// Listar archivos disponibles para debugging
|
|
138
|
+
try {
|
|
139
|
+
const bundleDir = path.join(__dirname, `../${folderDeployed}`, 'bundles');
|
|
140
|
+
if (fs.existsSync(bundleDir)) {
|
|
141
|
+
const files = fs.readdirSync(bundleDir);
|
|
142
|
+
console.log(`📁 Available files in bundles: ${files.join(', ')}`);
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.log(`❌ Could not list bundle directory: ${e.message}`);
|
|
146
|
+
}
|
|
147
|
+
return res.status(404).send('Bundle file not found');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Para archivos no .js, continuar con el middleware estático normal
|
|
152
|
+
next();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Servir otros archivos de bundles (CSS, etc.) con el middleware estático normal
|
|
156
|
+
// Solo si existe el directorio bundles
|
|
157
|
+
if (bundlesDirectoryExists()) {
|
|
158
|
+
app.use('/bundles/', express.static(path.join(__dirname, `../${folderDeployed}`, 'bundles')));
|
|
159
|
+
console.log(`📦 Bundles directory found, serving static files`);
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`ℹ️ Bundles directory not found, skipping static bundle serving`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Servir framework Slice.js
|
|
165
|
+
app.use('/Slice/', express.static(path.join(__dirname, '..', 'node_modules', 'slicejs-web-framework', 'Slice')));
|
|
166
|
+
|
|
167
|
+
// Servir archivos estáticos del proyecto
|
|
168
|
+
app.use(express.static(path.join(__dirname, `../${folderDeployed}`)));
|
|
169
|
+
|
|
170
|
+
// ==============================================
|
|
171
|
+
// RUTAS DE API
|
|
172
|
+
// ==============================================
|
|
173
|
+
|
|
49
174
|
// Ruta de ejemplo para API
|
|
50
175
|
app.get('/api/status', (req, res) => {
|
|
51
176
|
res.json({
|
|
@@ -54,13 +179,23 @@ app.get('/api/status', (req, res) => {
|
|
|
54
179
|
folder: folderDeployed,
|
|
55
180
|
timestamp: new Date().toISOString(),
|
|
56
181
|
framework: 'Slice.js',
|
|
57
|
-
version: '2.0.0'
|
|
182
|
+
version: '2.0.0',
|
|
183
|
+
security: {
|
|
184
|
+
enabled: true,
|
|
185
|
+
mode: 'automatic',
|
|
186
|
+
description: 'Zero-config security - works with any domain'
|
|
187
|
+
}
|
|
58
188
|
});
|
|
59
189
|
});
|
|
60
190
|
|
|
191
|
+
|
|
192
|
+
// ==============================================
|
|
193
|
+
// SPA FALLBACK
|
|
194
|
+
// ==============================================
|
|
195
|
+
|
|
61
196
|
// SPA fallback - servir index.html para rutas no encontradas
|
|
62
197
|
app.get('*', (req, res) => {
|
|
63
|
-
const indexPath = path.join(__dirname, `../${folderDeployed}`,"App", 'index.html');
|
|
198
|
+
const indexPath = path.join(__dirname, `../${folderDeployed}`, "App", 'index.html');
|
|
64
199
|
res.sendFile(indexPath, (err) => {
|
|
65
200
|
if (err) {
|
|
66
201
|
res.status(404).send(`
|
|
@@ -76,8 +211,14 @@ app.get('*', (req, res) => {
|
|
|
76
211
|
});
|
|
77
212
|
});
|
|
78
213
|
|
|
214
|
+
// ==============================================
|
|
215
|
+
// INICIO DEL SERVIDOR
|
|
216
|
+
// ==============================================
|
|
217
|
+
|
|
79
218
|
function startServer() {
|
|
80
219
|
server = app.listen(PORT, () => {
|
|
220
|
+
console.log(`🔒 Security middleware: active (zero-config, automatic)`);
|
|
221
|
+
console.log(`🚀 Slice.js server running on port ${PORT}`);
|
|
81
222
|
});
|
|
82
223
|
}
|
|
83
224
|
|
|
@@ -95,4 +236,4 @@ process.on('SIGTERM', () => {
|
|
|
95
236
|
// Iniciar servidor
|
|
96
237
|
startServer();
|
|
97
238
|
|
|
98
|
-
export default app;
|
|
239
|
+
export default app;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// api/middleware/securityMiddleware.js
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Middleware de seguridad para prevenir acceso directo malicioso
|
|
6
|
+
* pero permitir que la aplicación cargue sus dependencias normalmente
|
|
7
|
+
*/
|
|
8
|
+
export function securityMiddleware(options = {}) {
|
|
9
|
+
const {
|
|
10
|
+
allowedExtensions = ['.js', '.css', '.html', '.json', '.svg', '.png', '.jpg', '.jpeg', '.gif', '.woff', '.woff2', '.ttf'],
|
|
11
|
+
blockedPaths = [
|
|
12
|
+
'/node_modules',
|
|
13
|
+
'/package.json',
|
|
14
|
+
'/package-lock.json',
|
|
15
|
+
'/.env',
|
|
16
|
+
'/.git'
|
|
17
|
+
],
|
|
18
|
+
allowPublicAssets = true
|
|
19
|
+
} = options;
|
|
20
|
+
|
|
21
|
+
return (req, res, next) => {
|
|
22
|
+
const requestPath = req.path;
|
|
23
|
+
|
|
24
|
+
// 1. Bloquear acceso a rutas definitivamente sensibles (configuración, dependencias)
|
|
25
|
+
const isBlockedPath = blockedPaths.some(blocked =>
|
|
26
|
+
requestPath.startsWith(blocked) || requestPath.includes(blocked)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (isBlockedPath) {
|
|
30
|
+
console.warn(`🚫 Blocked access to sensitive path: ${requestPath}`);
|
|
31
|
+
return res.status(403).json({
|
|
32
|
+
error: 'Forbidden',
|
|
33
|
+
message: 'Access to this resource is not allowed',
|
|
34
|
+
path: requestPath
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Permitir acceso a assets públicos
|
|
39
|
+
if (allowPublicAssets) {
|
|
40
|
+
const publicPaths = ['/assets', '/public', '/images', '/styles'];
|
|
41
|
+
const isPublicAsset = publicPaths.some(publicPath =>
|
|
42
|
+
requestPath.startsWith(publicPath)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (isPublicAsset) {
|
|
46
|
+
return next();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. Validar extensiones de archivo
|
|
51
|
+
const fileExtension = path.extname(requestPath).toLowerCase();
|
|
52
|
+
|
|
53
|
+
if (fileExtension && !allowedExtensions.includes(fileExtension)) {
|
|
54
|
+
console.warn(`🚫 Blocked file type: ${requestPath}`);
|
|
55
|
+
return res.status(403).json({
|
|
56
|
+
error: 'Forbidden',
|
|
57
|
+
message: 'File type not allowed',
|
|
58
|
+
extension: fileExtension
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 4. Prevenir path traversal attacks
|
|
63
|
+
const normalizedPath = path.normalize(requestPath);
|
|
64
|
+
if (normalizedPath.includes('..') || normalizedPath.includes('~')) {
|
|
65
|
+
console.warn(`🚫 Path traversal attempt: ${requestPath}`);
|
|
66
|
+
return res.status(403).json({
|
|
67
|
+
error: 'Forbidden',
|
|
68
|
+
message: 'Invalid path',
|
|
69
|
+
path: requestPath
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Todo está bien, continuar
|
|
74
|
+
next();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Middleware específico para proteger archivos del framework Slice.js
|
|
80
|
+
* PERMITE acceso cuando viene desde la propia aplicación (Referer válido)
|
|
81
|
+
* BLOQUEA acceso directo desde navegador o herramientas externas
|
|
82
|
+
*/
|
|
83
|
+
export function sliceFrameworkProtection(options = {}) {
|
|
84
|
+
const {
|
|
85
|
+
port = 3000,
|
|
86
|
+
strictMode = false,
|
|
87
|
+
allowedDomains = [] // Dominios personalizados permitidos
|
|
88
|
+
} = options;
|
|
89
|
+
|
|
90
|
+
return (req, res, next) => {
|
|
91
|
+
const requestPath = req.path;
|
|
92
|
+
|
|
93
|
+
// Rutas del framework que requieren verificación
|
|
94
|
+
const frameworkPaths = [
|
|
95
|
+
'/Slice/Components/Structural',
|
|
96
|
+
'/Slice/Core',
|
|
97
|
+
'/Slice/Services'
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const isFrameworkFile = frameworkPaths.some(fwPath =>
|
|
101
|
+
requestPath.startsWith(fwPath)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (!isFrameworkFile) {
|
|
105
|
+
return next();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Verificar el origen de la petición
|
|
109
|
+
const referer = req.get('Referer') || req.get('Referrer');
|
|
110
|
+
const origin = req.get('Origin');
|
|
111
|
+
const host = req.get('Host');
|
|
112
|
+
|
|
113
|
+
// Construir lista de orígenes válidos dinámicamente
|
|
114
|
+
const validOrigins = [
|
|
115
|
+
`http://localhost:${port}`,
|
|
116
|
+
`http://127.0.0.1:${port}`,
|
|
117
|
+
`http://0.0.0.0:${port}`,
|
|
118
|
+
`https://localhost:${port}`,
|
|
119
|
+
...allowedDomains // Dominios personalizados del usuario
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
// Si hay un Host header, agregarlo automáticamente
|
|
123
|
+
if (host) {
|
|
124
|
+
validOrigins.push(`http://${host}`);
|
|
125
|
+
validOrigins.push(`https://${host}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Verificar si la petición viene de un origen válido
|
|
129
|
+
const hasValidReferer = referer && validOrigins.some(valid => referer.startsWith(valid));
|
|
130
|
+
const hasValidOrigin = origin && validOrigins.some(valid => origin === valid);
|
|
131
|
+
const isSameHost = host && referer && referer.includes(host);
|
|
132
|
+
|
|
133
|
+
// Permitir si viene desde la aplicación
|
|
134
|
+
if (hasValidReferer || hasValidOrigin || isSameHost) {
|
|
135
|
+
return next();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// En modo estricto, bloquear todo acceso sin referer válido
|
|
139
|
+
if (strictMode) {
|
|
140
|
+
console.warn(`🚫 Blocked direct framework file access: ${requestPath}`);
|
|
141
|
+
return res.status(403).json({
|
|
142
|
+
error: 'Framework Protection',
|
|
143
|
+
message: 'Direct access to Slice.js framework files is blocked',
|
|
144
|
+
tip: 'Framework files must be loaded through the application',
|
|
145
|
+
path: requestPath
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// En modo normal (desarrollo), permitir pero advertir
|
|
150
|
+
console.warn(`⚠️ Framework file accessed without valid referer: ${requestPath}`);
|
|
151
|
+
next();
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Middleware para logging de peticiones sospechosas
|
|
157
|
+
*/
|
|
158
|
+
export function suspiciousRequestLogger() {
|
|
159
|
+
const suspiciousPatterns = [
|
|
160
|
+
/\.\.\//, // Path traversal
|
|
161
|
+
/~/, // Home directory access
|
|
162
|
+
/\.env/, // Environment files
|
|
163
|
+
/\.git/, // Git files
|
|
164
|
+
/package\.json/, // Package files
|
|
165
|
+
/package-lock\.json/,
|
|
166
|
+
/node_modules/, // Dependencies
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
return (req, res, next) => {
|
|
170
|
+
const requestPath = req.path;
|
|
171
|
+
|
|
172
|
+
const isSuspicious = suspiciousPatterns.some(pattern =>
|
|
173
|
+
pattern.test(requestPath)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (isSuspicious) {
|
|
177
|
+
const clientIp = req.ip || req.connection.remoteAddress;
|
|
178
|
+
console.warn(`⚠️ Suspicious request: ${requestPath} from ${clientIp}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
next();
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Middleware para bloquear acceso directo vía navegador (typing en la URL)
|
|
187
|
+
* pero permitir peticiones desde scripts (fetch, import, etc.)
|
|
188
|
+
*/
|
|
189
|
+
export function directAccessProtection(options = {}) {
|
|
190
|
+
const { protectedPaths = [] } = options;
|
|
191
|
+
|
|
192
|
+
return (req, res, next) => {
|
|
193
|
+
const requestPath = req.path;
|
|
194
|
+
|
|
195
|
+
const isProtectedPath = protectedPaths.some(protectedPath =>
|
|
196
|
+
requestPath.startsWith(protectedPath)
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (!isProtectedPath) {
|
|
200
|
+
return next();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Detectar acceso directo:
|
|
204
|
+
// - No tiene Referer (usuario escribió la URL directamente)
|
|
205
|
+
// - Accept header indica navegación HTML
|
|
206
|
+
const referer = req.get('Referer');
|
|
207
|
+
const accept = req.get('Accept') || '';
|
|
208
|
+
|
|
209
|
+
const isDirectBrowserAccess = !referer && accept.includes('text/html');
|
|
210
|
+
|
|
211
|
+
if (isDirectBrowserAccess) {
|
|
212
|
+
console.warn(`🚫 Blocked direct browser access: ${requestPath}`);
|
|
213
|
+
return res.status(403).send(`
|
|
214
|
+
<!DOCTYPE html>
|
|
215
|
+
<html>
|
|
216
|
+
<head>
|
|
217
|
+
<title>Access Denied</title>
|
|
218
|
+
<style>
|
|
219
|
+
body {
|
|
220
|
+
font-family: system-ui;
|
|
221
|
+
max-width: 600px;
|
|
222
|
+
margin: 100px auto;
|
|
223
|
+
padding: 20px;
|
|
224
|
+
}
|
|
225
|
+
h1 { color: #d32f2f; }
|
|
226
|
+
code {
|
|
227
|
+
background: #f5f5f5;
|
|
228
|
+
padding: 2px 6px;
|
|
229
|
+
border-radius: 3px;
|
|
230
|
+
}
|
|
231
|
+
</style>
|
|
232
|
+
</head>
|
|
233
|
+
<body>
|
|
234
|
+
<h1>🚫 Direct Access Denied</h1>
|
|
235
|
+
<p>This file cannot be accessed directly.</p>
|
|
236
|
+
<p>Path: <code>${requestPath}</code></p>
|
|
237
|
+
<p>Framework files are automatically loaded by the application.</p>
|
|
238
|
+
<p><a href="/">← Return to application</a></p>
|
|
239
|
+
</body>
|
|
240
|
+
</html>
|
|
241
|
+
`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
next();
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export default {
|
|
249
|
+
securityMiddleware,
|
|
250
|
+
sliceFrameworkProtection,
|
|
251
|
+
suspiciousRequestLogger,
|
|
252
|
+
directAccessProtection
|
|
253
|
+
};
|