wu-framework 1.1.16 → 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/LICENSE +39 -39
- package/README.md +440 -408
- package/dist/wu-framework.cjs.js +1 -1
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +446 -81
- 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/integrations/astro/README.md +127 -127
- package/integrations/astro/WuApp.astro +63 -63
- package/integrations/astro/WuShell.astro +39 -39
- package/integrations/astro/index.js +68 -68
- package/integrations/astro/package.json +38 -38
- package/integrations/astro/types.d.ts +53 -53
- package/package.json +7 -2
- package/src/adapters/svelte/index.js +1 -1
- package/src/adapters/vanilla/index.js +1 -1
- package/src/core/wu-cache.js +24 -3
- package/src/core/wu-core.js +15 -1
- package/src/core/wu-error-boundary.js +17 -3
- package/src/core/wu-event-bus.js +43 -1
- package/src/core/wu-html-parser.js +13 -4
- package/src/core/wu-loader.js +162 -50
- package/src/core/wu-logger.js +21 -13
- package/src/core/wu-manifest.js +23 -0
- package/src/core/wu-plugin.js +57 -4
- package/src/core/wu-proxy-sandbox.js +2 -1
- package/src/core/wu-script-executor.js +48 -0
- package/src/core/wu-store.js +13 -3
- package/src/index.d.ts +317 -0
- package/src/index.js +11 -1
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import type { AstroIntegration } from 'astro';
|
|
2
|
-
|
|
3
|
-
/** Configuration for a single wu micro-app. */
|
|
4
|
-
export interface WuAppConfig {
|
|
5
|
-
/** Unique name used to reference this micro-app. */
|
|
6
|
-
name: string;
|
|
7
|
-
/** URL or path from which the micro-app is loaded. */
|
|
8
|
-
url: string;
|
|
9
|
-
/** Loading strategy. Defaults to the framework default. */
|
|
10
|
-
strategy?: 'eager' | 'lazy' | 'manual';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Options accepted by the wu Astro integration. */
|
|
14
|
-
export interface WuIntegrationOptions {
|
|
15
|
-
/** Micro-apps to auto-register via wu.init(). */
|
|
16
|
-
apps?: WuAppConfig[];
|
|
17
|
-
/** Global sandbox isolation mode. */
|
|
18
|
-
sandbox?: 'module' | 'strict' | 'eval';
|
|
19
|
-
/** CDN URL for the wu-framework UMD bundle. When omitted the local package is used. */
|
|
20
|
-
cdn?: string;
|
|
21
|
-
/** Enable debug logging. */
|
|
22
|
-
debug?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Astro integration factory for wu-framework.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* import wu from '@wu-framework/astro';
|
|
30
|
-
* export default defineConfig({ integrations: [wu({ apps: [...] })] });
|
|
31
|
-
*/
|
|
32
|
-
export default function wuIntegration(
|
|
33
|
-
options?: WuIntegrationOptions
|
|
34
|
-
): AstroIntegration;
|
|
35
|
-
|
|
36
|
-
/** Props for the WuShell.astro layout component. */
|
|
37
|
-
export interface WuShellProps {
|
|
38
|
-
apps: WuAppConfig[];
|
|
39
|
-
sandbox?: 'module' | 'strict' | 'eval';
|
|
40
|
-
debug?: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Props for the WuApp.astro mount component. */
|
|
44
|
-
export interface WuAppProps {
|
|
45
|
-
/** Name of the registered wu micro-app. */
|
|
46
|
-
name: string;
|
|
47
|
-
/** Defer mounting until the element is visible (IntersectionObserver). */
|
|
48
|
-
lazy?: boolean;
|
|
49
|
-
/** CSS class(es) for the container div. */
|
|
50
|
-
class?: string;
|
|
51
|
-
/** Inline styles for the container div. */
|
|
52
|
-
style?: string;
|
|
53
|
-
}
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
|
|
3
|
+
/** Configuration for a single wu micro-app. */
|
|
4
|
+
export interface WuAppConfig {
|
|
5
|
+
/** Unique name used to reference this micro-app. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** URL or path from which the micro-app is loaded. */
|
|
8
|
+
url: string;
|
|
9
|
+
/** Loading strategy. Defaults to the framework default. */
|
|
10
|
+
strategy?: 'eager' | 'lazy' | 'manual';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Options accepted by the wu Astro integration. */
|
|
14
|
+
export interface WuIntegrationOptions {
|
|
15
|
+
/** Micro-apps to auto-register via wu.init(). */
|
|
16
|
+
apps?: WuAppConfig[];
|
|
17
|
+
/** Global sandbox isolation mode. */
|
|
18
|
+
sandbox?: 'module' | 'strict' | 'eval';
|
|
19
|
+
/** CDN URL for the wu-framework UMD bundle. When omitted the local package is used. */
|
|
20
|
+
cdn?: string;
|
|
21
|
+
/** Enable debug logging. */
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Astro integration factory for wu-framework.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* import wu from '@wu-framework/astro';
|
|
30
|
+
* export default defineConfig({ integrations: [wu({ apps: [...] })] });
|
|
31
|
+
*/
|
|
32
|
+
export default function wuIntegration(
|
|
33
|
+
options?: WuIntegrationOptions
|
|
34
|
+
): AstroIntegration;
|
|
35
|
+
|
|
36
|
+
/** Props for the WuShell.astro layout component. */
|
|
37
|
+
export interface WuShellProps {
|
|
38
|
+
apps: WuAppConfig[];
|
|
39
|
+
sandbox?: 'module' | 'strict' | 'eval';
|
|
40
|
+
debug?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Props for the WuApp.astro mount component. */
|
|
44
|
+
export interface WuAppProps {
|
|
45
|
+
/** Name of the registered wu micro-app. */
|
|
46
|
+
name: string;
|
|
47
|
+
/** Defer mounting until the element is visible (IntersectionObserver). */
|
|
48
|
+
lazy?: boolean;
|
|
49
|
+
/** CSS class(es) for the container div. */
|
|
50
|
+
class?: string;
|
|
51
|
+
/** Inline styles for the container div. */
|
|
52
|
+
style?: string;
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wu-framework",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"description": "Universal Microfrontends Framework - 8 frameworks, zero config, Shadow DOM isolation",
|
|
5
5
|
"main": "dist/wu-framework.cjs.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
36
36
|
"test": "vitest run",
|
|
37
37
|
"test:watch": "vitest",
|
|
38
|
-
"test:coverage": "vitest run --coverage"
|
|
38
|
+
"test:coverage": "vitest run --coverage",
|
|
39
|
+
"lint": "eslint src/",
|
|
40
|
+
"format": "prettier --write src/",
|
|
41
|
+
"format:check": "prettier --check src/"
|
|
39
42
|
},
|
|
40
43
|
"keywords": [
|
|
41
44
|
"microfrontends",
|
|
@@ -118,7 +121,9 @@
|
|
|
118
121
|
"@rollup/plugin-replace": "^5.0.5",
|
|
119
122
|
"@rollup/plugin-terser": "^0.4.4",
|
|
120
123
|
"@vitest/coverage-v8": "^4.0.18",
|
|
124
|
+
"eslint": "^10.0.2",
|
|
121
125
|
"jsdom": "^28.0.0",
|
|
126
|
+
"prettier": "^3.8.1",
|
|
122
127
|
"rollup": "^4.9.0",
|
|
123
128
|
"vitest": "^4.0.18"
|
|
124
129
|
},
|
|
@@ -695,7 +695,7 @@ function createWuSlotConfig() {
|
|
|
695
695
|
|
|
696
696
|
// Implementación JavaScript pura para usar sin .svelte
|
|
697
697
|
createInstance: (target, props) => {
|
|
698
|
-
|
|
698
|
+
const container = document.createElement('div');
|
|
699
699
|
container.className = 'wu-slot';
|
|
700
700
|
container.style.minHeight = '100px';
|
|
701
701
|
container.style.position = 'relative';
|
package/src/core/wu-cache.js
CHANGED
|
@@ -32,6 +32,9 @@ export class WuCache {
|
|
|
32
32
|
cooldownUntil: 0
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
// Rate limit notification flag (log only once per cooldown)
|
|
36
|
+
this._rateLimitNotified = false;
|
|
37
|
+
|
|
35
38
|
// Memory cache
|
|
36
39
|
this.memoryCache = new Map();
|
|
37
40
|
|
|
@@ -66,6 +69,7 @@ export class WuCache {
|
|
|
66
69
|
}
|
|
67
70
|
// Cooldown terminado
|
|
68
71
|
this.rateLimiting.inCooldown = false;
|
|
72
|
+
this._rateLimitNotified = false;
|
|
69
73
|
this.rateLimiting.operations = [];
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -91,7 +95,22 @@ export class WuCache {
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/**
|
|
94
|
-
*
|
|
98
|
+
* Handle rate-limited operations: log once per cooldown, return diagnostic object
|
|
99
|
+
* @param {string} operation - The operation that was rate limited ('get' or 'set')
|
|
100
|
+
* @param {string} key - The cache key that was rejected
|
|
101
|
+
* @returns {{ rateLimited: true, operation: string, key: string }}
|
|
102
|
+
*/
|
|
103
|
+
_onRateLimited(operation, key) {
|
|
104
|
+
if (!this._rateLimitNotified) {
|
|
105
|
+
const cooldownRemaining = Math.max(0, this.rateLimiting.cooldownUntil - Date.now());
|
|
106
|
+
logger.warn(`[WuCache] Rate limited: ${operation} for key "${key}" rejected. ${cooldownRemaining}ms remaining in cooldown.`);
|
|
107
|
+
this._rateLimitNotified = true;
|
|
108
|
+
}
|
|
109
|
+
return { rateLimited: true, operation, key };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* GET RATE LIMIT STATUS
|
|
95
114
|
*/
|
|
96
115
|
getRateLimitStatus() {
|
|
97
116
|
const now = Date.now();
|
|
@@ -115,7 +134,8 @@ export class WuCache {
|
|
|
115
134
|
get(key) {
|
|
116
135
|
// 🔐 Check rate limit
|
|
117
136
|
if (!this._checkRateLimit()) {
|
|
118
|
-
|
|
137
|
+
this._onRateLimited('get', key);
|
|
138
|
+
return null;
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
// 1. Buscar en memoria
|
|
@@ -162,7 +182,8 @@ export class WuCache {
|
|
|
162
182
|
set(key, value, ttl) {
|
|
163
183
|
// 🔐 Check rate limit
|
|
164
184
|
if (!this._checkRateLimit()) {
|
|
165
|
-
|
|
185
|
+
this._onRateLimited('set', key);
|
|
186
|
+
return false;
|
|
166
187
|
}
|
|
167
188
|
|
|
168
189
|
try {
|
package/src/core/wu-core.js
CHANGED
|
@@ -333,6 +333,20 @@ export class WuCore {
|
|
|
333
333
|
} catch (error) {
|
|
334
334
|
logger.wuError(`Mount attempt ${attempt + 1} failed for ${appName}:`, error);
|
|
335
335
|
|
|
336
|
+
// Cleanup sandbox to prevent orphaned shadow DOMs
|
|
337
|
+
try {
|
|
338
|
+
if (this.sandbox && this.sandbox.sandboxes && this.sandbox.sandboxes.has(appName)) {
|
|
339
|
+
const sb = this.sandbox.sandboxes.get(appName);
|
|
340
|
+
if (sb && sb.proxySandbox) {
|
|
341
|
+
sb.proxySandbox.deactivate();
|
|
342
|
+
}
|
|
343
|
+
this.sandbox.sandboxes.delete(appName);
|
|
344
|
+
logger.wuDebug(`Sandbox cleaned up after mount failure for ${appName}`);
|
|
345
|
+
}
|
|
346
|
+
} catch (cleanupError) {
|
|
347
|
+
logger.wuWarn(`Sandbox cleanup failed for ${appName}:`, cleanupError);
|
|
348
|
+
}
|
|
349
|
+
|
|
336
350
|
// Use error boundary for intelligent error handling
|
|
337
351
|
const errorResult = await this.errorBoundary.handle(error, {
|
|
338
352
|
appName,
|
|
@@ -671,7 +685,7 @@ export class WuCore {
|
|
|
671
685
|
* Intelligently resolves module paths with real-time validation
|
|
672
686
|
*/
|
|
673
687
|
async resolveModulePath(app) {
|
|
674
|
-
|
|
688
|
+
const entryFile = app.manifest?.entry || 'main.js';
|
|
675
689
|
const baseUrl = app.url.replace(/\/$/, ''); // Remove trailing slash
|
|
676
690
|
|
|
677
691
|
// Normalize path: Remove duplicated directories
|
|
@@ -314,19 +314,33 @@ export class WuErrorBoundary {
|
|
|
314
314
|
* @param {Object} context - Contexto
|
|
315
315
|
*/
|
|
316
316
|
logError(error, context) {
|
|
317
|
+
// Truncate stack to first 5 lines to prevent retaining large object references
|
|
318
|
+
const stack = error.stack ? error.stack.split('\n').slice(0, 5).join('\n') : '';
|
|
319
|
+
|
|
320
|
+
// Shallow-copy context to avoid retaining references to live objects
|
|
321
|
+
const safeContext = {};
|
|
322
|
+
for (const key of Object.keys(context)) {
|
|
323
|
+
const val = context[key];
|
|
324
|
+
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val === null) {
|
|
325
|
+
safeContext[key] = val;
|
|
326
|
+
} else {
|
|
327
|
+
safeContext[key] = String(val);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
317
331
|
const errorEntry = {
|
|
318
332
|
error: {
|
|
319
333
|
name: error.name,
|
|
320
334
|
message: error.message,
|
|
321
|
-
stack
|
|
335
|
+
stack
|
|
322
336
|
},
|
|
323
|
-
context,
|
|
337
|
+
context: safeContext,
|
|
324
338
|
timestamp: Date.now()
|
|
325
339
|
};
|
|
326
340
|
|
|
327
341
|
this.errorLog.push(errorEntry);
|
|
328
342
|
|
|
329
|
-
//
|
|
343
|
+
// Maintain log limit
|
|
330
344
|
if (this.errorLog.length > this.maxErrorLog) {
|
|
331
345
|
this.errorLog.shift();
|
|
332
346
|
}
|
package/src/core/wu-event-bus.js
CHANGED
|
@@ -10,6 +10,25 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { logger } from './wu-logger.js';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} WuEvent
|
|
15
|
+
* @property {string} name - Event name
|
|
16
|
+
* @property {*} data - Event payload
|
|
17
|
+
* @property {number} timestamp - Event timestamp
|
|
18
|
+
* @property {string} appName - Source app name
|
|
19
|
+
* @property {Object} meta - Additional metadata
|
|
20
|
+
* @property {boolean} verified - Whether origin was verified
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} WuEventBusConfig
|
|
25
|
+
* @property {number} [maxHistory=100] - Maximum events in history
|
|
26
|
+
* @property {boolean} [enableReplay=true] - Enable event replay
|
|
27
|
+
* @property {boolean} [enableWildcards=true] - Enable wildcard matching
|
|
28
|
+
* @property {boolean} [logEvents=false] - Log all events
|
|
29
|
+
* @property {boolean} [strictMode=false] - Reject unauthorized events
|
|
30
|
+
* @property {boolean} [validateOrigin=true] - Validate event origins
|
|
31
|
+
*/
|
|
13
32
|
|
|
14
33
|
export class WuEventBus {
|
|
15
34
|
constructor() {
|
|
@@ -20,16 +39,21 @@ export class WuEventBus {
|
|
|
20
39
|
this.authorizedApps = new Map(); // appName -> { token, permissions }
|
|
21
40
|
this.trustedEvents = new Set(['wu:*', 'system:*']); // Eventos del sistema
|
|
22
41
|
|
|
42
|
+
// Auto-detect production environment for strictMode default
|
|
43
|
+
const isProduction = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';
|
|
44
|
+
|
|
23
45
|
this.config = {
|
|
24
46
|
maxHistory: 100,
|
|
25
47
|
enableReplay: true,
|
|
26
48
|
enableWildcards: true,
|
|
27
49
|
logEvents: false,
|
|
28
50
|
// 🔐 Opciones de seguridad
|
|
29
|
-
strictMode:
|
|
51
|
+
strictMode: isProduction, // Auto-enabled in production, permissive in development
|
|
30
52
|
validateOrigin: true // Valida que appName sea una app registrada
|
|
31
53
|
};
|
|
32
54
|
|
|
55
|
+
this._permissiveWarned = false;
|
|
56
|
+
|
|
33
57
|
this.stats = {
|
|
34
58
|
emitted: 0,
|
|
35
59
|
subscriptions: 0,
|
|
@@ -127,6 +151,19 @@ export class WuEventBus {
|
|
|
127
151
|
return `wu_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
128
152
|
}
|
|
129
153
|
|
|
154
|
+
/**
|
|
155
|
+
* WARN PERMISSIVE MODE: Log a one-time warning when strictMode is off
|
|
156
|
+
* Alerts developers that events are flowing without authorization checks
|
|
157
|
+
*/
|
|
158
|
+
_warnPermissiveMode() {
|
|
159
|
+
if (this._permissiveWarned) return;
|
|
160
|
+
this._permissiveWarned = true;
|
|
161
|
+
logger.warn(
|
|
162
|
+
'[WuEventBus] strictMode is disabled. Events are emitted without authorization checks. ' +
|
|
163
|
+
'Enable strictMode for production by calling enableStrictMode() or setting NODE_ENV=production.'
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
130
167
|
/**
|
|
131
168
|
* 📢 EMIT: Emitir evento con validación de origen
|
|
132
169
|
* @param {string} eventName - Nombre del evento
|
|
@@ -136,6 +173,11 @@ export class WuEventBus {
|
|
|
136
173
|
emit(eventName, data, options = {}) {
|
|
137
174
|
const appName = options.appName || 'unknown';
|
|
138
175
|
|
|
176
|
+
// Warn once if running in permissive mode (strictMode off)
|
|
177
|
+
if (!this.config.strictMode) {
|
|
178
|
+
this._warnPermissiveMode();
|
|
179
|
+
}
|
|
180
|
+
|
|
139
181
|
// 🔐 Validar origen si está habilitado
|
|
140
182
|
if (this.config.validateOrigin && this.config.strictMode) {
|
|
141
183
|
if (!this._validateOrigin(eventName, appName, options.token)) {
|
|
@@ -62,19 +62,28 @@ export class WuHtmlParser {
|
|
|
62
62
|
return this._cache.get(cacheKey);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const
|
|
66
|
-
|
|
65
|
+
const parser = new DOMParser();
|
|
66
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
67
67
|
|
|
68
68
|
const inlineScripts = [];
|
|
69
69
|
const externalScripts = [];
|
|
70
70
|
const inlineStyles = [];
|
|
71
71
|
const externalStyles = [];
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
const ctx = {
|
|
74
74
|
inlineScripts, externalScripts,
|
|
75
75
|
inlineStyles, externalStyles,
|
|
76
76
|
baseUrl
|
|
77
|
-
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// DOMParser moves <style>, <link>, and some <script> tags to <head>.
|
|
80
|
+
// Extract resources from both head and body to capture everything.
|
|
81
|
+
if (doc.head) {
|
|
82
|
+
this._extractResources(doc.head, ctx);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const temp = doc.body || doc.documentElement;
|
|
86
|
+
this._extractResources(temp, ctx);
|
|
78
87
|
|
|
79
88
|
const result = {
|
|
80
89
|
dom: temp.innerHTML,
|