wu-framework 1.1.15 → 1.1.17

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.
Files changed (88) hide show
  1. package/README.md +52 -20
  2. package/dist/wu-framework.cjs.js +1 -1
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15511 -15146
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +1 -1
  7. package/dist/wu-framework.esm.js.map +1 -1
  8. package/dist/wu-framework.umd.js +1 -1
  9. package/dist/wu-framework.umd.js.map +1 -1
  10. package/package.json +166 -161
  11. package/src/adapters/angular/ai.js +30 -30
  12. package/src/adapters/angular/index.d.ts +154 -154
  13. package/src/adapters/angular/index.js +932 -932
  14. package/src/adapters/angular.d.ts +3 -3
  15. package/src/adapters/angular.js +3 -3
  16. package/src/adapters/index.js +168 -168
  17. package/src/adapters/lit/ai.js +20 -20
  18. package/src/adapters/lit/index.d.ts +120 -120
  19. package/src/adapters/lit/index.js +721 -721
  20. package/src/adapters/lit.d.ts +3 -3
  21. package/src/adapters/lit.js +3 -3
  22. package/src/adapters/preact/ai.js +33 -33
  23. package/src/adapters/preact/index.d.ts +108 -108
  24. package/src/adapters/preact/index.js +661 -661
  25. package/src/adapters/preact.d.ts +3 -3
  26. package/src/adapters/preact.js +3 -3
  27. package/src/adapters/react/index.js +48 -54
  28. package/src/adapters/react.d.ts +3 -3
  29. package/src/adapters/react.js +3 -3
  30. package/src/adapters/shared.js +64 -64
  31. package/src/adapters/solid/ai.js +32 -32
  32. package/src/adapters/solid/index.d.ts +101 -101
  33. package/src/adapters/solid/index.js +586 -586
  34. package/src/adapters/solid.d.ts +3 -3
  35. package/src/adapters/solid.js +3 -3
  36. package/src/adapters/svelte/ai.js +31 -31
  37. package/src/adapters/svelte/index.d.ts +166 -166
  38. package/src/adapters/svelte/index.js +798 -798
  39. package/src/adapters/svelte.d.ts +3 -3
  40. package/src/adapters/svelte.js +3 -3
  41. package/src/adapters/vanilla/ai.js +30 -30
  42. package/src/adapters/vanilla/index.d.ts +179 -179
  43. package/src/adapters/vanilla/index.js +785 -785
  44. package/src/adapters/vanilla.d.ts +3 -3
  45. package/src/adapters/vanilla.js +3 -3
  46. package/src/adapters/vue/ai.js +52 -52
  47. package/src/adapters/vue/index.d.ts +299 -299
  48. package/src/adapters/vue/index.js +610 -610
  49. package/src/adapters/vue.d.ts +3 -3
  50. package/src/adapters/vue.js +3 -3
  51. package/src/ai/wu-ai-actions.js +261 -261
  52. package/src/ai/wu-ai-agent.js +546 -546
  53. package/src/ai/wu-ai-browser-primitives.js +354 -354
  54. package/src/ai/wu-ai-browser.js +380 -380
  55. package/src/ai/wu-ai-context.js +332 -332
  56. package/src/ai/wu-ai-conversation.js +613 -613
  57. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  58. package/src/ai/wu-ai-permissions.js +381 -381
  59. package/src/ai/wu-ai-provider.js +700 -700
  60. package/src/ai/wu-ai-schema.js +225 -225
  61. package/src/ai/wu-ai-triggers.js +396 -396
  62. package/src/ai/wu-ai.js +804 -804
  63. package/src/core/wu-app.js +236 -236
  64. package/src/core/wu-cache.js +498 -477
  65. package/src/core/wu-core.js +1412 -1398
  66. package/src/core/wu-error-boundary.js +396 -382
  67. package/src/core/wu-event-bus.js +390 -348
  68. package/src/core/wu-hooks.js +350 -350
  69. package/src/core/wu-html-parser.js +199 -190
  70. package/src/core/wu-iframe-sandbox.js +328 -328
  71. package/src/core/wu-loader.js +385 -273
  72. package/src/core/wu-logger.js +142 -134
  73. package/src/core/wu-manifest.js +532 -509
  74. package/src/core/wu-mcp-bridge.js +432 -432
  75. package/src/core/wu-overrides.js +510 -510
  76. package/src/core/wu-performance.js +228 -228
  77. package/src/core/wu-plugin.js +401 -348
  78. package/src/core/wu-prefetch.js +414 -414
  79. package/src/core/wu-proxy-sandbox.js +477 -476
  80. package/src/core/wu-sandbox.js +779 -779
  81. package/src/core/wu-script-executor.js +161 -113
  82. package/src/core/wu-snapshot-sandbox.js +227 -227
  83. package/src/core/wu-store.js +13 -3
  84. package/src/core/wu-strategies.js +256 -256
  85. package/src/core/wu-style-bridge.js +477 -477
  86. package/src/index.d.ts +317 -0
  87. package/src/index.js +234 -224
  88. package/src/utils/dependency-resolver.js +327 -327
@@ -1,348 +1,401 @@
1
- /**
2
- * 🔌 WU-PLUGIN: SECURE PLUGIN SYSTEM
3
- *
4
- * Sistema de plugins con sandboxing de seguridad
5
- * - Plugin lifecycle (install, beforeMount, afterMount, etc.)
6
- * - Sandboxed API (plugins no tienen acceso completo al core)
7
- * - Permission system
8
- * - Timeout protection
9
- */
10
- import { logger } from './wu-logger.js';
11
-
12
-
13
- export class WuPluginSystem {
14
- constructor(core) {
15
- this._core = core; // Privado - no expuesto a plugins
16
- this.plugins = new Map();
17
- this.hooks = new Map();
18
-
19
- // Hooks disponibles
20
- this.availableHooks = [
21
- 'beforeInit', 'afterInit',
22
- 'beforeMount', 'afterMount',
23
- 'beforeUnmount', 'afterUnmount',
24
- 'onError', 'onDestroy'
25
- ];
26
-
27
- // 🔐 Permisos disponibles
28
- this.availablePermissions = [
29
- 'mount', // Puede montar/desmontar apps
30
- 'events', // Puede emitir/escuchar eventos
31
- 'store', // Puede leer/escribir store
32
- 'apps', // Puede ver lista de apps
33
- 'config', // Puede modificar configuración
34
- 'unsafe' // Acceso completo (peligroso)
35
- ];
36
-
37
- // 🔐 Timeout para hooks (evita que plugins bloqueen)
38
- this.hookTimeout = 5000; // 5 segundos
39
-
40
- this.availableHooks.forEach(hook => {
41
- this.hooks.set(hook, []);
42
- });
43
- }
44
-
45
- /**
46
- * 🔐 CREATE SANDBOXED API: Crea API limitada para el plugin
47
- * @param {Array} permissions - Permisos del plugin
48
- * @returns {Object} API sandboxeada
49
- */
50
- _createSandboxedApi(permissions) {
51
- const api = {
52
- // Info básica siempre disponible
53
- version: this._core.version,
54
- info: this._core.info,
55
-
56
- // 📊 Métodos de solo lectura
57
- getAppInfo: (appName) => {
58
- const mounted = this._core.mounted.get(appName);
59
- if (!mounted) return null;
60
- return {
61
- name: appName,
62
- state: mounted.state,
63
- timestamp: mounted.timestamp
64
- };
65
- },
66
-
67
- getMountedApps: () => {
68
- return Array.from(this._core.mounted.keys());
69
- },
70
-
71
- getStats: () => this._core.getStats()
72
- };
73
-
74
- // 🔐 Agregar métodos según permisos
75
- if (permissions.includes('events') || permissions.includes('unsafe')) {
76
- api.emit = (event, data) => this._core.eventBus.emit(event, data, { appName: 'plugin' });
77
- api.on = (event, cb) => this._core.eventBus.on(event, cb);
78
- api.off = (event, cb) => this._core.eventBus.off(event, cb);
79
- }
80
-
81
- if (permissions.includes('store') || permissions.includes('unsafe')) {
82
- api.getState = (path) => this._core.store.get(path);
83
- api.setState = (path, value) => this._core.store.set(path, value);
84
- }
85
-
86
- if (permissions.includes('mount') || permissions.includes('unsafe')) {
87
- api.mount = (appName, container) => this._core.mount(appName, container);
88
- api.unmount = (appName) => this._core.unmount(appName);
89
- }
90
-
91
- if (permissions.includes('config') || permissions.includes('unsafe')) {
92
- api.configure = (config) => {
93
- // Solo permitir configuración segura
94
- const safeKeys = ['debug', 'logLevel'];
95
- const safeConfig = {};
96
- for (const key of safeKeys) {
97
- if (key in config) safeConfig[key] = config[key];
98
- }
99
- Object.assign(this._core, safeConfig);
100
- };
101
- }
102
-
103
- // 🚨 Acceso completo solo con permiso 'unsafe'
104
- if (permissions.includes('unsafe')) {
105
- api._unsafeCore = this._core;
106
- logger.warn('[WuPlugin] ⚠️ Plugin has unsafe access to core!');
107
- }
108
-
109
- // Congelar API para evitar modificaciones
110
- return Object.freeze(api);
111
- }
112
-
113
- /**
114
- * 🔐 VALIDATE PLUGIN: Validar estructura del plugin
115
- * @param {Object} plugin
116
- * @returns {boolean}
117
- */
118
- _validatePlugin(plugin) {
119
- if (!plugin || typeof plugin !== 'object') {
120
- throw new Error('[WuPlugin] Invalid plugin: must be an object');
121
- }
122
-
123
- if (!plugin.name || typeof plugin.name !== 'string') {
124
- throw new Error('[WuPlugin] Invalid plugin: must have a name (string)');
125
- }
126
-
127
- if (plugin.name.length > 50) {
128
- throw new Error('[WuPlugin] Invalid plugin: name too long (max 50 chars)');
129
- }
130
-
131
- // Validar que los hooks sean funciones
132
- for (const hookName of this.availableHooks) {
133
- if (plugin[hookName] && typeof plugin[hookName] !== 'function') {
134
- throw new Error(`[WuPlugin] Invalid plugin: ${hookName} must be a function`);
135
- }
136
- }
137
-
138
- // Validar permisos
139
- if (plugin.permissions) {
140
- if (!Array.isArray(plugin.permissions)) {
141
- throw new Error('[WuPlugin] Invalid plugin: permissions must be an array');
142
- }
143
-
144
- for (const perm of plugin.permissions) {
145
- if (!this.availablePermissions.includes(perm)) {
146
- throw new Error(`[WuPlugin] Invalid permission: ${perm}`);
147
- }
148
- }
149
- }
150
-
151
- return true;
152
- }
153
-
154
- /**
155
- * 📦 USE: Instalar plugin con sandboxing
156
- * @param {Object|Function} plugin - Plugin o factory function
157
- * @param {Object} options - Opciones del plugin
158
- */
159
- use(plugin, options = {}) {
160
- // Si es una función, ejecutarla para obtener el plugin
161
- // Nota: factory functions NO reciben acceso al core
162
- if (typeof plugin === 'function') {
163
- plugin = plugin(options);
164
- }
165
-
166
- // Validar plugin
167
- this._validatePlugin(plugin);
168
-
169
- // Verificar si ya está instalado
170
- if (this.plugins.has(plugin.name)) {
171
- logger.warn(`[WuPlugin] Plugin "${plugin.name}" already installed`);
172
- return;
173
- }
174
-
175
- // Determinar permisos (por defecto: solo eventos)
176
- const permissions = plugin.permissions || ['events'];
177
-
178
- // 🔐 Crear API sandboxeada
179
- const sandboxedApi = this._createSandboxedApi(permissions);
180
-
181
- // Ejecutar install del plugin con API sandboxeada
182
- if (plugin.install) {
183
- try {
184
- plugin.install(sandboxedApi, options);
185
- } catch (error) {
186
- console.error(`[WuPlugin] Error installing "${plugin.name}":`, error);
187
- throw error;
188
- }
189
- }
190
-
191
- // Registrar hooks del plugin con protección
192
- this.availableHooks.forEach(hookName => {
193
- if (typeof plugin[hookName] === 'function') {
194
- // Wrap el hook con timeout y try-catch
195
- const wrappedHook = this._wrapHook(plugin[hookName].bind(plugin), plugin.name, hookName);
196
- this.registerHook(hookName, wrappedHook);
197
- }
198
- });
199
-
200
- // Guardar plugin
201
- this.plugins.set(plugin.name, {
202
- plugin,
203
- options,
204
- permissions,
205
- sandboxedApi,
206
- installedAt: Date.now()
207
- });
208
-
209
- logger.debug(`[WuPlugin] Plugin "${plugin.name}" installed (permissions: ${permissions.join(', ')})`);
210
- }
211
-
212
- /**
213
- * 🔐 WRAP HOOK: Envolver hook con timeout y error handling
214
- */
215
- _wrapHook(hookFn, pluginName, hookName) {
216
- return async (context) => {
217
- const timeoutPromise = new Promise((_, reject) => {
218
- setTimeout(() => {
219
- reject(new Error(`Plugin "${pluginName}" hook "${hookName}" timed out after ${this.hookTimeout}ms`));
220
- }, this.hookTimeout);
221
- });
222
-
223
- try {
224
- // Race entre el hook y el timeout
225
- return await Promise.race([
226
- hookFn(context),
227
- timeoutPromise
228
- ]);
229
- } catch (error) {
230
- console.error(`[WuPlugin] Error in ${pluginName}.${hookName}:`, error);
231
- // No propagar error para no romper otros plugins
232
- return undefined;
233
- }
234
- };
235
- }
236
-
237
- /**
238
- * 🪝 REGISTER HOOK
239
- */
240
- registerHook(hookName, callback) {
241
- if (!this.hooks.has(hookName)) {
242
- logger.warn(`[WuPlugin] Unknown hook: ${hookName}`);
243
- return;
244
- }
245
- this.hooks.get(hookName).push(callback);
246
- }
247
-
248
- /**
249
- * 🎯 CALL HOOK
250
- */
251
- async callHook(hookName, context) {
252
- const callbacks = this.hooks.get(hookName) || [];
253
-
254
- for (const callback of callbacks) {
255
- try {
256
- const result = await callback(context);
257
- if (result === false) {
258
- return false;
259
- }
260
- } catch (error) {
261
- console.error(`[WuPlugin] Error in hook ${hookName}:`, error);
262
- }
263
- }
264
-
265
- return true;
266
- }
267
-
268
- /**
269
- * 🗑️ UNINSTALL
270
- */
271
- uninstall(pluginName) {
272
- const pluginData = this.plugins.get(pluginName);
273
- if (!pluginData) {
274
- logger.warn(`[WuPlugin] Plugin "${pluginName}" not found`);
275
- return;
276
- }
277
-
278
- const { plugin, sandboxedApi } = pluginData;
279
-
280
- if (plugin.uninstall) {
281
- try {
282
- plugin.uninstall(sandboxedApi);
283
- } catch (error) {
284
- console.error(`[WuPlugin] Error uninstalling "${pluginName}":`, error);
285
- }
286
- }
287
-
288
- this.plugins.delete(pluginName);
289
- logger.debug(`[WuPlugin] ✅ Plugin "${pluginName}" uninstalled`);
290
- }
291
-
292
- /**
293
- * 📋 GET PLUGIN
294
- */
295
- getPlugin(pluginName) {
296
- return this.plugins.get(pluginName)?.plugin;
297
- }
298
-
299
- /**
300
- * 📊 GET STATS
301
- */
302
- getStats() {
303
- return {
304
- totalPlugins: this.plugins.size,
305
- plugins: Array.from(this.plugins.entries()).map(([name, data]) => ({
306
- name,
307
- permissions: data.permissions,
308
- installedAt: data.installedAt
309
- })),
310
- hooks: Array.from(this.hooks.entries()).map(([name, callbacks]) => ({
311
- name,
312
- callbacks: callbacks.length
313
- }))
314
- };
315
- }
316
-
317
- /**
318
- * 🧹 CLEANUP
319
- */
320
- cleanup() {
321
- for (const [name] of this.plugins) {
322
- this.uninstall(name);
323
- }
324
- }
325
- }
326
-
327
- /**
328
- * 📦 PLUGIN HELPER: Helper para crear plugins
329
- * @param {Object} config - Configuración del plugin
330
- * @param {string} config.name - Nombre del plugin
331
- * @param {Array} config.permissions - Permisos requeridos
332
- */
333
- export const createPlugin = (config) => {
334
- return {
335
- name: config.name,
336
- permissions: config.permissions || ['events'],
337
- install: config.install,
338
- uninstall: config.uninstall,
339
- beforeInit: config.beforeInit,
340
- afterInit: config.afterInit,
341
- beforeMount: config.beforeMount,
342
- afterMount: config.afterMount,
343
- beforeUnmount: config.beforeUnmount,
344
- afterUnmount: config.afterUnmount,
345
- onError: config.onError,
346
- onDestroy: config.onDestroy
347
- };
348
- };
1
+ /**
2
+ * 🔌 WU-PLUGIN: SECURE PLUGIN SYSTEM
3
+ *
4
+ * Sistema de plugins con sandboxing de seguridad
5
+ * - Plugin lifecycle (install, beforeMount, afterMount, etc.)
6
+ * - Sandboxed API (plugins no tienen acceso completo al core)
7
+ * - Permission system
8
+ * - Timeout protection
9
+ */
10
+ import { logger } from './wu-logger.js';
11
+
12
+
13
+ export class WuPluginSystem {
14
+ constructor(core, options = {}) {
15
+ this._core = core; // Privado - no expuesto a plugins
16
+ this.plugins = new Map();
17
+ this.hooks = new Map();
18
+
19
+ // Hooks disponibles
20
+ this.availableHooks = [
21
+ 'beforeInit', 'afterInit',
22
+ 'beforeMount', 'afterMount',
23
+ 'beforeUnmount', 'afterUnmount',
24
+ 'onError', 'onDestroy'
25
+ ];
26
+
27
+ // 🔐 Permisos disponibles
28
+ this.availablePermissions = [
29
+ 'mount', // Puede montar/desmontar apps
30
+ 'events', // Puede emitir/escuchar eventos
31
+ 'store', // Puede leer/escribir store
32
+ 'apps', // Puede ver lista de apps
33
+ 'config', // Puede modificar configuración
34
+ 'unsafe' // Acceso completo (peligroso)
35
+ ];
36
+
37
+ // 🔐 Timeout para hooks (evita que plugins bloqueen)
38
+ this.hookTimeout = options.hookTimeout || 5000; // Default: 5 segundos
39
+
40
+ this.availableHooks.forEach(hook => {
41
+ this.hooks.set(hook, []);
42
+ });
43
+ }
44
+
45
+ /**
46
+ * 🔐 CREATE SANDBOXED API: Crea API limitada para el plugin
47
+ * @param {Array} permissions - Permisos del plugin
48
+ * @returns {Object} API sandboxeada
49
+ */
50
+ _createSandboxedApi(permissions) {
51
+ const api = {
52
+ // Info básica siempre disponible
53
+ version: this._core.version,
54
+ info: this._core.info,
55
+
56
+ // 📊 Métodos de solo lectura
57
+ getAppInfo: (appName) => {
58
+ const mounted = this._core.mounted.get(appName);
59
+ if (!mounted) return null;
60
+ return {
61
+ name: appName,
62
+ state: mounted.state,
63
+ timestamp: mounted.timestamp
64
+ };
65
+ },
66
+
67
+ getMountedApps: () => {
68
+ return Array.from(this._core.mounted.keys());
69
+ },
70
+
71
+ getStats: () => this._core.getStats()
72
+ };
73
+
74
+ // 🔐 Agregar métodos según permisos
75
+ if (permissions.includes('events') || permissions.includes('unsafe')) {
76
+ api.emit = (event, data) => this._core.eventBus.emit(event, data, { appName: 'plugin' });
77
+ api.on = (event, cb) => this._core.eventBus.on(event, cb);
78
+ api.off = (event, cb) => this._core.eventBus.off(event, cb);
79
+ }
80
+
81
+ if (permissions.includes('store') || permissions.includes('unsafe')) {
82
+ api.getState = (path) => this._core.store.get(path);
83
+ api.setState = (path, value) => this._core.store.set(path, value);
84
+ }
85
+
86
+ if (permissions.includes('mount') || permissions.includes('unsafe')) {
87
+ api.mount = (appName, container) => this._core.mount(appName, container);
88
+ api.unmount = (appName) => this._core.unmount(appName);
89
+ }
90
+
91
+ if (permissions.includes('config') || permissions.includes('unsafe')) {
92
+ api.configure = (config) => {
93
+ // Solo permitir configuración segura
94
+ const safeKeys = ['debug', 'logLevel'];
95
+ const safeConfig = {};
96
+ for (const key of safeKeys) {
97
+ if (key in config) safeConfig[key] = config[key];
98
+ }
99
+ Object.assign(this._core, safeConfig);
100
+ };
101
+ }
102
+
103
+ // 🚨 Acceso completo solo con permiso 'unsafe'
104
+ if (permissions.includes('unsafe')) {
105
+ api._unsafeCore = this._core;
106
+ logger.warn('[WuPlugin] ⚠️ Plugin has unsafe access to core!');
107
+ }
108
+
109
+ // Congelar API recursivamente para evitar modificaciones en cualquier nivel
110
+ return WuPluginSystem._deepFreeze(api);
111
+ }
112
+
113
+ /**
114
+ * Deep-freeze an object and all its nested plain objects and arrays.
115
+ * Functions, DOM nodes, and other non-plain types are left untouched
116
+ * so they remain callable / functional. A WeakSet guards against
117
+ * circular references.
118
+ *
119
+ * @param {*} obj - The value to deep-freeze
120
+ * @param {WeakSet} [seen] - Internal tracker for circular references
121
+ * @returns {*} The same object, now deeply frozen
122
+ */
123
+ static _deepFreeze(obj, seen) {
124
+ if (obj === null || obj === undefined) {
125
+ return obj;
126
+ }
127
+
128
+ // Only freeze plain objects and arrays.
129
+ // Functions must stay invocable; DOM nodes, class instances, etc.
130
+ // should not be tampered with.
131
+ const dominated = typeof obj === 'object';
132
+ if (!dominated) {
133
+ return obj;
134
+ }
135
+
136
+ const isPlainObject =
137
+ Object.getPrototypeOf(obj) === Object.prototype ||
138
+ Object.getPrototypeOf(obj) === null;
139
+ const isArray = Array.isArray(obj);
140
+
141
+ if (!isPlainObject && !isArray) {
142
+ return obj;
143
+ }
144
+
145
+ // Circular-reference guard
146
+ if (!seen) {
147
+ seen = new WeakSet();
148
+ }
149
+ if (seen.has(obj)) {
150
+ return obj;
151
+ }
152
+ seen.add(obj);
153
+
154
+ // Recurse into own enumerable properties
155
+ const keys = Object.keys(obj);
156
+ for (let i = 0; i < keys.length; i++) {
157
+ const value = obj[keys[i]];
158
+ if (value !== null && value !== undefined && typeof value === 'object') {
159
+ WuPluginSystem._deepFreeze(value, seen);
160
+ }
161
+ }
162
+
163
+ return Object.freeze(obj);
164
+ }
165
+
166
+ /**
167
+ * 🔐 VALIDATE PLUGIN: Validar estructura del plugin
168
+ * @param {Object} plugin
169
+ * @returns {boolean}
170
+ */
171
+ _validatePlugin(plugin) {
172
+ if (!plugin || typeof plugin !== 'object') {
173
+ throw new Error('[WuPlugin] Invalid plugin: must be an object');
174
+ }
175
+
176
+ if (!plugin.name || typeof plugin.name !== 'string') {
177
+ throw new Error('[WuPlugin] Invalid plugin: must have a name (string)');
178
+ }
179
+
180
+ if (plugin.name.length > 50) {
181
+ throw new Error('[WuPlugin] Invalid plugin: name too long (max 50 chars)');
182
+ }
183
+
184
+ // Validar que los hooks sean funciones
185
+ for (const hookName of this.availableHooks) {
186
+ if (plugin[hookName] && typeof plugin[hookName] !== 'function') {
187
+ throw new Error(`[WuPlugin] Invalid plugin: ${hookName} must be a function`);
188
+ }
189
+ }
190
+
191
+ // Validar permisos
192
+ if (plugin.permissions) {
193
+ if (!Array.isArray(plugin.permissions)) {
194
+ throw new Error('[WuPlugin] Invalid plugin: permissions must be an array');
195
+ }
196
+
197
+ for (const perm of plugin.permissions) {
198
+ if (!this.availablePermissions.includes(perm)) {
199
+ throw new Error(`[WuPlugin] Invalid permission: ${perm}`);
200
+ }
201
+ }
202
+ }
203
+
204
+ return true;
205
+ }
206
+
207
+ /**
208
+ * 📦 USE: Instalar plugin con sandboxing
209
+ * @param {Object|Function} plugin - Plugin o factory function
210
+ * @param {Object} options - Opciones del plugin
211
+ */
212
+ use(plugin, options = {}) {
213
+ // Si es una función, ejecutarla para obtener el plugin
214
+ // Nota: factory functions NO reciben acceso al core
215
+ if (typeof plugin === 'function') {
216
+ plugin = plugin(options);
217
+ }
218
+
219
+ // Validar plugin
220
+ this._validatePlugin(plugin);
221
+
222
+ // Verificar si ya está instalado
223
+ if (this.plugins.has(plugin.name)) {
224
+ logger.warn(`[WuPlugin] Plugin "${plugin.name}" already installed`);
225
+ return;
226
+ }
227
+
228
+ // Determinar permisos (por defecto: solo eventos)
229
+ const permissions = plugin.permissions || ['events'];
230
+
231
+ // 🔐 Crear API sandboxeada
232
+ const sandboxedApi = this._createSandboxedApi(permissions);
233
+
234
+ // Ejecutar install del plugin con API sandboxeada
235
+ if (plugin.install) {
236
+ try {
237
+ plugin.install(sandboxedApi, options);
238
+ } catch (error) {
239
+ console.error(`[WuPlugin] Error installing "${plugin.name}":`, error);
240
+ throw error;
241
+ }
242
+ }
243
+
244
+ // Registrar hooks del plugin con protección
245
+ this.availableHooks.forEach(hookName => {
246
+ if (typeof plugin[hookName] === 'function') {
247
+ // Wrap el hook con timeout y try-catch
248
+ const wrappedHook = this._wrapHook(plugin[hookName].bind(plugin), plugin.name, hookName);
249
+ this.registerHook(hookName, wrappedHook);
250
+ }
251
+ });
252
+
253
+ // Guardar plugin
254
+ this.plugins.set(plugin.name, {
255
+ plugin,
256
+ options,
257
+ permissions,
258
+ sandboxedApi,
259
+ installedAt: Date.now()
260
+ });
261
+
262
+ logger.debug(`[WuPlugin] ✅ Plugin "${plugin.name}" installed (permissions: ${permissions.join(', ')})`);
263
+ }
264
+
265
+ /**
266
+ * 🔐 WRAP HOOK: Envolver hook con timeout y error handling
267
+ */
268
+ _wrapHook(hookFn, pluginName, hookName) {
269
+ return async (context) => {
270
+ const timeoutPromise = new Promise((_, reject) => {
271
+ setTimeout(() => {
272
+ reject(new Error(`Plugin "${pluginName}" hook "${hookName}" timed out after ${this.hookTimeout}ms`));
273
+ }, this.hookTimeout);
274
+ });
275
+
276
+ try {
277
+ // Race entre el hook y el timeout
278
+ return await Promise.race([
279
+ hookFn(context),
280
+ timeoutPromise
281
+ ]);
282
+ } catch (error) {
283
+ console.error(`[WuPlugin] Error in ${pluginName}.${hookName}:`, error);
284
+ // No propagar error para no romper otros plugins
285
+ return undefined;
286
+ }
287
+ };
288
+ }
289
+
290
+ /**
291
+ * 🪝 REGISTER HOOK
292
+ */
293
+ registerHook(hookName, callback) {
294
+ if (!this.hooks.has(hookName)) {
295
+ logger.warn(`[WuPlugin] Unknown hook: ${hookName}`);
296
+ return;
297
+ }
298
+ this.hooks.get(hookName).push(callback);
299
+ }
300
+
301
+ /**
302
+ * 🎯 CALL HOOK
303
+ */
304
+ async callHook(hookName, context) {
305
+ const callbacks = this.hooks.get(hookName) || [];
306
+
307
+ for (const callback of callbacks) {
308
+ try {
309
+ const result = await callback(context);
310
+ if (result === false) {
311
+ return false;
312
+ }
313
+ } catch (error) {
314
+ console.error(`[WuPlugin] Error in hook ${hookName}:`, error);
315
+ }
316
+ }
317
+
318
+ return true;
319
+ }
320
+
321
+ /**
322
+ * 🗑️ UNINSTALL
323
+ */
324
+ uninstall(pluginName) {
325
+ const pluginData = this.plugins.get(pluginName);
326
+ if (!pluginData) {
327
+ logger.warn(`[WuPlugin] Plugin "${pluginName}" not found`);
328
+ return;
329
+ }
330
+
331
+ const { plugin, sandboxedApi } = pluginData;
332
+
333
+ if (plugin.uninstall) {
334
+ try {
335
+ plugin.uninstall(sandboxedApi);
336
+ } catch (error) {
337
+ console.error(`[WuPlugin] Error uninstalling "${pluginName}":`, error);
338
+ }
339
+ }
340
+
341
+ this.plugins.delete(pluginName);
342
+ logger.debug(`[WuPlugin] ✅ Plugin "${pluginName}" uninstalled`);
343
+ }
344
+
345
+ /**
346
+ * 📋 GET PLUGIN
347
+ */
348
+ getPlugin(pluginName) {
349
+ return this.plugins.get(pluginName)?.plugin;
350
+ }
351
+
352
+ /**
353
+ * 📊 GET STATS
354
+ */
355
+ getStats() {
356
+ return {
357
+ totalPlugins: this.plugins.size,
358
+ plugins: Array.from(this.plugins.entries()).map(([name, data]) => ({
359
+ name,
360
+ permissions: data.permissions,
361
+ installedAt: data.installedAt
362
+ })),
363
+ hooks: Array.from(this.hooks.entries()).map(([name, callbacks]) => ({
364
+ name,
365
+ callbacks: callbacks.length
366
+ }))
367
+ };
368
+ }
369
+
370
+ /**
371
+ * 🧹 CLEANUP
372
+ */
373
+ cleanup() {
374
+ for (const [name] of this.plugins) {
375
+ this.uninstall(name);
376
+ }
377
+ }
378
+ }
379
+
380
+ /**
381
+ * 📦 PLUGIN HELPER: Helper para crear plugins
382
+ * @param {Object} config - Configuración del plugin
383
+ * @param {string} config.name - Nombre del plugin
384
+ * @param {Array} config.permissions - Permisos requeridos
385
+ */
386
+ export const createPlugin = (config) => {
387
+ return {
388
+ name: config.name,
389
+ permissions: config.permissions || ['events'],
390
+ install: config.install,
391
+ uninstall: config.uninstall,
392
+ beforeInit: config.beforeInit,
393
+ afterInit: config.afterInit,
394
+ beforeMount: config.beforeMount,
395
+ afterMount: config.afterMount,
396
+ beforeUnmount: config.beforeUnmount,
397
+ afterUnmount: config.afterUnmount,
398
+ onError: config.onError,
399
+ onDestroy: config.onDestroy
400
+ };
401
+ };