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.
- package/README.md +52 -20
- package/dist/wu-framework.cjs.js +1 -1
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +15511 -15146
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +1 -1
- package/dist/wu-framework.esm.js.map +1 -1
- package/dist/wu-framework.umd.js +1 -1
- package/dist/wu-framework.umd.js.map +1 -1
- package/package.json +166 -161
- package/src/adapters/angular/ai.js +30 -30
- package/src/adapters/angular/index.d.ts +154 -154
- package/src/adapters/angular/index.js +932 -932
- package/src/adapters/angular.d.ts +3 -3
- package/src/adapters/angular.js +3 -3
- package/src/adapters/index.js +168 -168
- package/src/adapters/lit/ai.js +20 -20
- package/src/adapters/lit/index.d.ts +120 -120
- package/src/adapters/lit/index.js +721 -721
- package/src/adapters/lit.d.ts +3 -3
- package/src/adapters/lit.js +3 -3
- package/src/adapters/preact/ai.js +33 -33
- package/src/adapters/preact/index.d.ts +108 -108
- package/src/adapters/preact/index.js +661 -661
- package/src/adapters/preact.d.ts +3 -3
- package/src/adapters/preact.js +3 -3
- package/src/adapters/react/index.js +48 -54
- package/src/adapters/react.d.ts +3 -3
- package/src/adapters/react.js +3 -3
- package/src/adapters/shared.js +64 -64
- package/src/adapters/solid/ai.js +32 -32
- package/src/adapters/solid/index.d.ts +101 -101
- package/src/adapters/solid/index.js +586 -586
- package/src/adapters/solid.d.ts +3 -3
- package/src/adapters/solid.js +3 -3
- package/src/adapters/svelte/ai.js +31 -31
- package/src/adapters/svelte/index.d.ts +166 -166
- package/src/adapters/svelte/index.js +798 -798
- package/src/adapters/svelte.d.ts +3 -3
- package/src/adapters/svelte.js +3 -3
- package/src/adapters/vanilla/ai.js +30 -30
- package/src/adapters/vanilla/index.d.ts +179 -179
- package/src/adapters/vanilla/index.js +785 -785
- package/src/adapters/vanilla.d.ts +3 -3
- package/src/adapters/vanilla.js +3 -3
- package/src/adapters/vue/ai.js +52 -52
- package/src/adapters/vue/index.d.ts +299 -299
- package/src/adapters/vue/index.js +610 -610
- package/src/adapters/vue.d.ts +3 -3
- package/src/adapters/vue.js +3 -3
- package/src/ai/wu-ai-actions.js +261 -261
- package/src/ai/wu-ai-agent.js +546 -546
- package/src/ai/wu-ai-browser-primitives.js +354 -354
- package/src/ai/wu-ai-browser.js +380 -380
- package/src/ai/wu-ai-context.js +332 -332
- package/src/ai/wu-ai-conversation.js +613 -613
- package/src/ai/wu-ai-orchestrate.js +1021 -1021
- package/src/ai/wu-ai-permissions.js +381 -381
- package/src/ai/wu-ai-provider.js +700 -700
- package/src/ai/wu-ai-schema.js +225 -225
- package/src/ai/wu-ai-triggers.js +396 -396
- package/src/ai/wu-ai.js +804 -804
- package/src/core/wu-app.js +236 -236
- package/src/core/wu-cache.js +498 -477
- package/src/core/wu-core.js +1412 -1398
- package/src/core/wu-error-boundary.js +396 -382
- package/src/core/wu-event-bus.js +390 -348
- package/src/core/wu-hooks.js +350 -350
- package/src/core/wu-html-parser.js +199 -190
- package/src/core/wu-iframe-sandbox.js +328 -328
- package/src/core/wu-loader.js +385 -273
- package/src/core/wu-logger.js +142 -134
- package/src/core/wu-manifest.js +532 -509
- package/src/core/wu-mcp-bridge.js +432 -432
- package/src/core/wu-overrides.js +510 -510
- package/src/core/wu-performance.js +228 -228
- package/src/core/wu-plugin.js +401 -348
- package/src/core/wu-prefetch.js +414 -414
- package/src/core/wu-proxy-sandbox.js +477 -476
- package/src/core/wu-sandbox.js +779 -779
- package/src/core/wu-script-executor.js +161 -113
- package/src/core/wu-snapshot-sandbox.js +227 -227
- package/src/core/wu-store.js +13 -3
- package/src/core/wu-strategies.js +256 -256
- package/src/core/wu-style-bridge.js +477 -477
- package/src/index.d.ts +317 -0
- package/src/index.js +234 -224
- package/src/utils/dependency-resolver.js +327 -327
package/src/core/wu-plugin.js
CHANGED
|
@@ -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
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
throw
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
};
|