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.
- package/README.md +773 -366
- package/package.json +34 -9
- package/src/adapters/angular.d.ts +154 -0
- package/src/adapters/angular.js +642 -0
- package/src/adapters/index.js +157 -0
- package/src/adapters/lit.d.ts +120 -0
- package/src/adapters/lit.js +726 -0
- package/src/adapters/preact.d.ts +108 -0
- package/src/adapters/preact.js +665 -0
- package/src/adapters/react.d.ts +212 -0
- package/src/adapters/react.js +513 -0
- package/src/adapters/solid.d.ts +101 -0
- package/src/adapters/solid.js +591 -0
- package/src/adapters/svelte.d.ts +166 -0
- package/src/adapters/svelte.js +803 -0
- package/src/adapters/vanilla.d.ts +179 -0
- package/src/adapters/vanilla.js +791 -0
- package/src/adapters/vue.d.ts +299 -0
- package/src/adapters/vue.js +570 -0
- package/src/core/wu-cache.js +87 -15
- package/src/core/wu-event-bus.js +147 -58
- package/src/core/wu-manifest.js +139 -8
- package/src/core/wu-plugin.js +201 -71
- package/src/index.js +1 -1
package/src/core/wu-plugin.js
CHANGED
|
@@ -1,53 +1,168 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 🔌 WU-PLUGIN:
|
|
2
|
+
* 🔌 WU-PLUGIN: SECURE PLUGIN SYSTEM
|
|
3
3
|
*
|
|
4
|
-
* Sistema de plugins
|
|
4
|
+
* Sistema de plugins con sandboxing de seguridad
|
|
5
5
|
* - Plugin lifecycle (install, beforeMount, afterMount, etc.)
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
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.
|
|
13
|
-
this.plugins = new Map();
|
|
14
|
-
this.hooks = new Map();
|
|
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
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
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
|
-
//
|
|
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
|
-
|
|
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(
|
|
161
|
+
plugin = plugin(options);
|
|
45
162
|
}
|
|
46
163
|
|
|
47
164
|
// Validar plugin
|
|
48
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
178
|
-
|
|
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
|
|
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
|
|
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.
|
|
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',
|