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.
@@ -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
- const loadTemplate =
66
- isVisual && !this.controller.templates.has(componentName)
67
- ? this.controller.fetchText(componentName, 'html', componentCategory)
68
- : Promise.resolve(null);
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 = !this.controller.classes.has(componentName)
71
- ? this.getClass(modulePath)
72
- : Promise.resolve(null);
81
+ const loadClass = (isFromBundle || this.controller.classes.has(componentName))
82
+ ? Promise.resolve(null)
83
+ : this.getClass(modulePath);
73
84
 
74
- const loadCSS =
75
- isVisual && !this.controller.requestedStyles.has(componentName)
76
- ? this.controller.fetchText(componentName, 'css', componentCategory)
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 inquirer from 'inquirer';
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
- app.use('/Slice/', express.static(path.join(__dirname, '..', 'node_modules', 'slicejs-web-framework', 'Slice')));
27
-
28
-
29
- // Middleware para servir archivos estáticos
30
- app.use(express.static(path.join(__dirname, `../${folderDeployed}`)));
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-web-framework",
3
- "version": "2.2.6",
3
+ "version": "2.2.8",
4
4
  "description": "",
5
5
  "engines": {
6
6
  "node": ">=20"