wu-framework 1.0.5 → 1.1.0

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.
@@ -1,53 +1,168 @@
1
1
  /**
2
- * 🔌 WU-PLUGIN: EXTENSIBLE PLUGIN SYSTEM
2
+ * 🔌 WU-PLUGIN: SECURE PLUGIN SYSTEM
3
3
  *
4
- * Sistema de plugins para extender wu-framework sin modificar el core
4
+ * Sistema de plugins con sandboxing de seguridad
5
5
  * - Plugin lifecycle (install, beforeMount, afterMount, etc.)
6
- * - Dependency injection
7
- * - Plugin communication
6
+ * - Sandboxed API (plugins no tienen acceso completo al core)
7
+ * - Permission system
8
+ * - Timeout protection
8
9
  */
9
10
 
10
11
  export class WuPluginSystem {
11
12
  constructor(core) {
12
- this.core = core;
13
- this.plugins = new Map(); // name -> plugin instance
14
- this.hooks = new Map(); // hook name -> [callbacks]
13
+ this._core = core; // Privado - no expuesto a plugins
14
+ this.plugins = new Map();
15
+ this.hooks = new Map();
15
16
 
16
17
  // Hooks disponibles
17
18
  this.availableHooks = [
18
- 'beforeInit',
19
- 'afterInit',
20
- 'beforeMount',
21
- 'afterMount',
22
- 'beforeUnmount',
23
- 'afterUnmount',
24
- 'onError',
25
- 'onDestroy'
19
+ 'beforeInit', 'afterInit',
20
+ 'beforeMount', 'afterMount',
21
+ 'beforeUnmount', 'afterUnmount',
22
+ 'onError', 'onDestroy'
26
23
  ];
27
24
 
28
- // Inicializar hooks
25
+ // 🔐 Permisos disponibles
26
+ this.availablePermissions = [
27
+ 'mount', // Puede montar/desmontar apps
28
+ 'events', // Puede emitir/escuchar eventos
29
+ 'store', // Puede leer/escribir store
30
+ 'apps', // Puede ver lista de apps
31
+ 'config', // Puede modificar configuración
32
+ 'unsafe' // Acceso completo (peligroso)
33
+ ];
34
+
35
+ // 🔐 Timeout para hooks (evita que plugins bloqueen)
36
+ this.hookTimeout = 5000; // 5 segundos
37
+
29
38
  this.availableHooks.forEach(hook => {
30
39
  this.hooks.set(hook, []);
31
40
  });
41
+ }
42
+
43
+ /**
44
+ * 🔐 CREATE SANDBOXED API: Crea API limitada para el plugin
45
+ * @param {Array} permissions - Permisos del plugin
46
+ * @returns {Object} API sandboxeada
47
+ */
48
+ _createSandboxedApi(permissions) {
49
+ const api = {
50
+ // Info básica siempre disponible
51
+ version: this._core.version,
52
+ info: this._core.info,
53
+
54
+ // 📊 Métodos de solo lectura
55
+ getAppInfo: (appName) => {
56
+ const mounted = this._core.mounted.get(appName);
57
+ if (!mounted) return null;
58
+ return {
59
+ name: appName,
60
+ state: mounted.state,
61
+ timestamp: mounted.timestamp
62
+ };
63
+ },
64
+
65
+ getMountedApps: () => {
66
+ return Array.from(this._core.mounted.keys());
67
+ },
68
+
69
+ getStats: () => this._core.getStats()
70
+ };
71
+
72
+ // 🔐 Agregar métodos según permisos
73
+ if (permissions.includes('events') || permissions.includes('unsafe')) {
74
+ api.emit = (event, data) => this._core.eventBus.emit(event, data, { appName: 'plugin' });
75
+ api.on = (event, cb) => this._core.eventBus.on(event, cb);
76
+ api.off = (event, cb) => this._core.eventBus.off(event, cb);
77
+ }
78
+
79
+ if (permissions.includes('store') || permissions.includes('unsafe')) {
80
+ api.getState = (path) => this._core.store.get(path);
81
+ api.setState = (path, value) => this._core.store.set(path, value);
82
+ }
83
+
84
+ if (permissions.includes('mount') || permissions.includes('unsafe')) {
85
+ api.mount = (appName, container) => this._core.mount(appName, container);
86
+ api.unmount = (appName) => this._core.unmount(appName);
87
+ }
88
+
89
+ if (permissions.includes('config') || permissions.includes('unsafe')) {
90
+ api.configure = (config) => {
91
+ // Solo permitir configuración segura
92
+ const safeKeys = ['debug', 'logLevel'];
93
+ const safeConfig = {};
94
+ for (const key of safeKeys) {
95
+ if (key in config) safeConfig[key] = config[key];
96
+ }
97
+ Object.assign(this._core, safeConfig);
98
+ };
99
+ }
100
+
101
+ // 🚨 Acceso completo solo con permiso 'unsafe'
102
+ if (permissions.includes('unsafe')) {
103
+ api._unsafeCore = this._core;
104
+ console.warn('[WuPlugin] ⚠️ Plugin has unsafe access to core!');
105
+ }
106
+
107
+ // Congelar API para evitar modificaciones
108
+ return Object.freeze(api);
109
+ }
110
+
111
+ /**
112
+ * 🔐 VALIDATE PLUGIN: Validar estructura del plugin
113
+ * @param {Object} plugin
114
+ * @returns {boolean}
115
+ */
116
+ _validatePlugin(plugin) {
117
+ if (!plugin || typeof plugin !== 'object') {
118
+ throw new Error('[WuPlugin] Invalid plugin: must be an object');
119
+ }
120
+
121
+ if (!plugin.name || typeof plugin.name !== 'string') {
122
+ throw new Error('[WuPlugin] Invalid plugin: must have a name (string)');
123
+ }
124
+
125
+ if (plugin.name.length > 50) {
126
+ throw new Error('[WuPlugin] Invalid plugin: name too long (max 50 chars)');
127
+ }
128
+
129
+ // Validar que los hooks sean funciones
130
+ for (const hookName of this.availableHooks) {
131
+ if (plugin[hookName] && typeof plugin[hookName] !== 'function') {
132
+ throw new Error(`[WuPlugin] Invalid plugin: ${hookName} must be a function`);
133
+ }
134
+ }
135
+
136
+ // Validar permisos
137
+ if (plugin.permissions) {
138
+ if (!Array.isArray(plugin.permissions)) {
139
+ throw new Error('[WuPlugin] Invalid plugin: permissions must be an array');
140
+ }
141
+
142
+ for (const perm of plugin.permissions) {
143
+ if (!this.availablePermissions.includes(perm)) {
144
+ throw new Error(`[WuPlugin] Invalid permission: ${perm}`);
145
+ }
146
+ }
147
+ }
32
148
 
33
- console.log('[WuPlugin] 🔌 Plugin system initialized');
149
+ return true;
34
150
  }
35
151
 
36
152
  /**
37
- * 📦 USE: Instalar plugin
153
+ * 📦 USE: Instalar plugin con sandboxing
38
154
  * @param {Object|Function} plugin - Plugin o factory function
39
155
  * @param {Object} options - Opciones del plugin
40
156
  */
41
157
  use(plugin, options = {}) {
42
158
  // Si es una función, ejecutarla para obtener el plugin
159
+ // Nota: factory functions NO reciben acceso al core
43
160
  if (typeof plugin === 'function') {
44
- plugin = plugin(this.core, options);
161
+ plugin = plugin(options);
45
162
  }
46
163
 
47
164
  // Validar plugin
48
- if (!plugin.name) {
49
- throw new Error('[WuPlugin] Plugin must have a name property');
50
- }
165
+ this._validatePlugin(plugin);
51
166
 
52
167
  // Verificar si ya está instalado
53
168
  if (this.plugins.has(plugin.name)) {
@@ -55,15 +170,28 @@ export class WuPluginSystem {
55
170
  return;
56
171
  }
57
172
 
58
- // Ejecutar install del plugin
173
+ // Determinar permisos (por defecto: solo eventos)
174
+ const permissions = plugin.permissions || ['events'];
175
+
176
+ // 🔐 Crear API sandboxeada
177
+ const sandboxedApi = this._createSandboxedApi(permissions);
178
+
179
+ // Ejecutar install del plugin con API sandboxeada
59
180
  if (plugin.install) {
60
- plugin.install(this.core, options);
181
+ try {
182
+ plugin.install(sandboxedApi, options);
183
+ } catch (error) {
184
+ console.error(`[WuPlugin] Error installing "${plugin.name}":`, error);
185
+ throw error;
186
+ }
61
187
  }
62
188
 
63
- // Registrar hooks del plugin
189
+ // Registrar hooks del plugin con protección
64
190
  this.availableHooks.forEach(hookName => {
65
191
  if (typeof plugin[hookName] === 'function') {
66
- this.registerHook(hookName, plugin[hookName].bind(plugin));
192
+ // Wrap el hook con timeout y try-catch
193
+ const wrappedHook = this._wrapHook(plugin[hookName].bind(plugin), plugin.name, hookName);
194
+ this.registerHook(hookName, wrappedHook);
67
195
  }
68
196
  });
69
197
 
@@ -71,31 +199,52 @@ export class WuPluginSystem {
71
199
  this.plugins.set(plugin.name, {
72
200
  plugin,
73
201
  options,
202
+ permissions,
203
+ sandboxedApi,
74
204
  installedAt: Date.now()
75
205
  });
76
206
 
77
- console.log(`[WuPlugin] ✅ Plugin "${plugin.name}" installed`);
207
+ console.log(`[WuPlugin] ✅ Plugin "${plugin.name}" installed (permissions: ${permissions.join(', ')})`);
208
+ }
209
+
210
+ /**
211
+ * 🔐 WRAP HOOK: Envolver hook con timeout y error handling
212
+ */
213
+ _wrapHook(hookFn, pluginName, hookName) {
214
+ return async (context) => {
215
+ const timeoutPromise = new Promise((_, reject) => {
216
+ setTimeout(() => {
217
+ reject(new Error(`Plugin "${pluginName}" hook "${hookName}" timed out after ${this.hookTimeout}ms`));
218
+ }, this.hookTimeout);
219
+ });
220
+
221
+ try {
222
+ // Race entre el hook y el timeout
223
+ return await Promise.race([
224
+ hookFn(context),
225
+ timeoutPromise
226
+ ]);
227
+ } catch (error) {
228
+ console.error(`[WuPlugin] Error in ${pluginName}.${hookName}:`, error);
229
+ // No propagar error para no romper otros plugins
230
+ return undefined;
231
+ }
232
+ };
78
233
  }
79
234
 
80
235
  /**
81
- * 🪝 REGISTER HOOK: Registrar callback en un hook
82
- * @param {string} hookName - Nombre del hook
83
- * @param {Function} callback - Función callback
236
+ * 🪝 REGISTER HOOK
84
237
  */
85
238
  registerHook(hookName, callback) {
86
239
  if (!this.hooks.has(hookName)) {
87
240
  console.warn(`[WuPlugin] Unknown hook: ${hookName}`);
88
241
  return;
89
242
  }
90
-
91
243
  this.hooks.get(hookName).push(callback);
92
244
  }
93
245
 
94
246
  /**
95
- * 🎯 CALL HOOK: Ejecutar todos los callbacks de un hook
96
- * @param {string} hookName - Nombre del hook
97
- * @param {*} context - Contexto a pasar a los callbacks
98
- * @returns {Promise<boolean>} false si algún callback cancela la operación
247
+ * 🎯 CALL HOOK
99
248
  */
100
249
  async callHook(hookName, context) {
101
250
  const callbacks = this.hooks.get(hookName) || [];
@@ -103,17 +252,11 @@ export class WuPluginSystem {
103
252
  for (const callback of callbacks) {
104
253
  try {
105
254
  const result = await callback(context);
106
-
107
- // Si retorna false, cancelar operación
108
255
  if (result === false) {
109
- console.log(`[WuPlugin] Hook ${hookName} cancelled by plugin`);
110
256
  return false;
111
257
  }
112
258
  } catch (error) {
113
259
  console.error(`[WuPlugin] Error in hook ${hookName}:`, error);
114
-
115
- // Llamar hook de error
116
- await this.callHook('onError', { hookName, error, context });
117
260
  }
118
261
  }
119
262
 
@@ -121,61 +264,46 @@ export class WuPluginSystem {
121
264
  }
122
265
 
123
266
  /**
124
- * 🗑️ UNINSTALL: Desinstalar plugin
125
- * @param {string} pluginName - Nombre del plugin
267
+ * 🗑️ UNINSTALL
126
268
  */
127
269
  uninstall(pluginName) {
128
270
  const pluginData = this.plugins.get(pluginName);
129
-
130
271
  if (!pluginData) {
131
272
  console.warn(`[WuPlugin] Plugin "${pluginName}" not found`);
132
273
  return;
133
274
  }
134
275
 
135
- const { plugin } = pluginData;
276
+ const { plugin, sandboxedApi } = pluginData;
136
277
 
137
- // Ejecutar uninstall si existe
138
278
  if (plugin.uninstall) {
139
- plugin.uninstall(this.core);
140
- }
141
-
142
- // Remover hooks del plugin
143
- this.availableHooks.forEach(hookName => {
144
- if (typeof plugin[hookName] === 'function') {
145
- const callbacks = this.hooks.get(hookName);
146
- const index = callbacks.indexOf(plugin[hookName]);
147
- if (index > -1) {
148
- callbacks.splice(index, 1);
149
- }
279
+ try {
280
+ plugin.uninstall(sandboxedApi);
281
+ } catch (error) {
282
+ console.error(`[WuPlugin] Error uninstalling "${pluginName}":`, error);
150
283
  }
151
- });
284
+ }
152
285
 
153
- // Remover plugin
154
286
  this.plugins.delete(pluginName);
155
-
156
287
  console.log(`[WuPlugin] ✅ Plugin "${pluginName}" uninstalled`);
157
288
  }
158
289
 
159
290
  /**
160
- * 📋 GET PLUGIN: Obtener plugin instalado
161
- * @param {string} pluginName - Nombre del plugin
162
- * @returns {Object}
291
+ * 📋 GET PLUGIN
163
292
  */
164
293
  getPlugin(pluginName) {
165
294
  return this.plugins.get(pluginName)?.plugin;
166
295
  }
167
296
 
168
297
  /**
169
- * 📊 GET STATS: Estadísticas de plugins
170
- * @returns {Object}
298
+ * 📊 GET STATS
171
299
  */
172
300
  getStats() {
173
301
  return {
174
302
  totalPlugins: this.plugins.size,
175
303
  plugins: Array.from(this.plugins.entries()).map(([name, data]) => ({
176
304
  name,
177
- installedAt: data.installedAt,
178
- hasUninstall: typeof data.plugin.uninstall === 'function'
305
+ permissions: data.permissions,
306
+ installedAt: data.installedAt
179
307
  })),
180
308
  hooks: Array.from(this.hooks.entries()).map(([name, callbacks]) => ({
181
309
  name,
@@ -185,23 +313,25 @@ export class WuPluginSystem {
185
313
  }
186
314
 
187
315
  /**
188
- * 🧹 CLEANUP: Limpiar todos los plugins
316
+ * 🧹 CLEANUP
189
317
  */
190
318
  cleanup() {
191
319
  for (const [name] of this.plugins) {
192
320
  this.uninstall(name);
193
321
  }
194
-
195
- console.log('[WuPlugin] 🧹 All plugins cleaned up');
196
322
  }
197
323
  }
198
324
 
199
325
  /**
200
- * 📦 PLUGIN HELPER: Helper para crear plugins fácilmente
326
+ * 📦 PLUGIN HELPER: Helper para crear plugins
327
+ * @param {Object} config - Configuración del plugin
328
+ * @param {string} config.name - Nombre del plugin
329
+ * @param {Array} config.permissions - Permisos requeridos
201
330
  */
202
331
  export const createPlugin = (config) => {
203
332
  return {
204
333
  name: config.name,
334
+ permissions: config.permissions || ['events'],
205
335
  install: config.install,
206
336
  uninstall: config.uninstall,
207
337
  beforeMount: config.beforeMount,
package/src/index.js CHANGED
@@ -103,7 +103,7 @@ if (typeof window !== 'undefined') {
103
103
 
104
104
  // Configurar propiedades si no existen
105
105
  if (!wu.version) {
106
- wu.version = '1.0.5';
106
+ wu.version = '1.0.6';
107
107
  console.log('🚀 Wu Framework loaded - Universal Microfrontends ready');
108
108
  wu.info = {
109
109
  name: 'Wu Framework',