wu-framework 1.1.6 → 1.1.8

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 (90) hide show
  1. package/README.md +511 -977
  2. package/dist/wu-framework.cjs.js +3 -1
  3. package/dist/wu-framework.cjs.js.map +1 -0
  4. package/dist/wu-framework.dev.js +7533 -2761
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +3 -0
  7. package/dist/wu-framework.esm.js.map +1 -0
  8. package/dist/wu-framework.umd.js +3 -1
  9. package/dist/wu-framework.umd.js.map +1 -0
  10. package/integrations/astro/README.md +127 -0
  11. package/integrations/astro/WuApp.astro +63 -0
  12. package/integrations/astro/WuShell.astro +39 -0
  13. package/integrations/astro/index.js +68 -0
  14. package/integrations/astro/package.json +38 -0
  15. package/integrations/astro/types.d.ts +53 -0
  16. package/package.json +94 -74
  17. package/src/adapters/angular/ai.js +30 -0
  18. package/src/adapters/angular/index.d.ts +154 -0
  19. package/src/adapters/angular/index.js +932 -0
  20. package/src/adapters/angular.d.ts +3 -154
  21. package/src/adapters/angular.js +3 -813
  22. package/src/adapters/index.js +35 -24
  23. package/src/adapters/lit/ai.js +20 -0
  24. package/src/adapters/lit/index.d.ts +120 -0
  25. package/src/adapters/lit/index.js +721 -0
  26. package/src/adapters/lit.d.ts +3 -120
  27. package/src/adapters/lit.js +3 -726
  28. package/src/adapters/preact/ai.js +33 -0
  29. package/src/adapters/preact/index.d.ts +108 -0
  30. package/src/adapters/preact/index.js +661 -0
  31. package/src/adapters/preact.d.ts +3 -108
  32. package/src/adapters/preact.js +3 -665
  33. package/src/adapters/react/ai.js +135 -0
  34. package/src/adapters/react/index.d.ts +246 -0
  35. package/src/adapters/react/index.js +689 -0
  36. package/src/adapters/react.d.ts +3 -212
  37. package/src/adapters/react.js +3 -513
  38. package/src/adapters/shared.js +64 -0
  39. package/src/adapters/solid/ai.js +32 -0
  40. package/src/adapters/solid/index.d.ts +101 -0
  41. package/src/adapters/solid/index.js +586 -0
  42. package/src/adapters/solid.d.ts +3 -101
  43. package/src/adapters/solid.js +3 -591
  44. package/src/adapters/svelte/ai.js +31 -0
  45. package/src/adapters/svelte/index.d.ts +166 -0
  46. package/src/adapters/svelte/index.js +798 -0
  47. package/src/adapters/svelte.d.ts +3 -166
  48. package/src/adapters/svelte.js +3 -803
  49. package/src/adapters/vanilla/ai.js +30 -0
  50. package/src/adapters/vanilla/index.d.ts +179 -0
  51. package/src/adapters/vanilla/index.js +785 -0
  52. package/src/adapters/vanilla.d.ts +3 -179
  53. package/src/adapters/vanilla.js +3 -791
  54. package/src/adapters/vue/ai.js +52 -0
  55. package/src/adapters/vue/index.d.ts +299 -0
  56. package/src/adapters/vue/index.js +608 -0
  57. package/src/adapters/vue.d.ts +3 -299
  58. package/src/adapters/vue.js +3 -611
  59. package/src/ai/wu-ai-actions.js +261 -0
  60. package/src/ai/wu-ai-browser.js +663 -0
  61. package/src/ai/wu-ai-context.js +332 -0
  62. package/src/ai/wu-ai-conversation.js +554 -0
  63. package/src/ai/wu-ai-permissions.js +381 -0
  64. package/src/ai/wu-ai-provider.js +605 -0
  65. package/src/ai/wu-ai-schema.js +225 -0
  66. package/src/ai/wu-ai-triggers.js +396 -0
  67. package/src/ai/wu-ai.js +474 -0
  68. package/src/core/wu-app.js +50 -8
  69. package/src/core/wu-cache.js +1 -1
  70. package/src/core/wu-core.js +645 -677
  71. package/src/core/wu-html-parser.js +121 -211
  72. package/src/core/wu-iframe-sandbox.js +328 -0
  73. package/src/core/wu-mcp-bridge.js +647 -0
  74. package/src/core/wu-overrides.js +510 -0
  75. package/src/core/wu-prefetch.js +414 -0
  76. package/src/core/wu-proxy-sandbox.js +398 -75
  77. package/src/core/wu-sandbox.js +86 -268
  78. package/src/core/wu-script-executor.js +79 -182
  79. package/src/core/wu-snapshot-sandbox.js +149 -106
  80. package/src/core/wu-strategies.js +13 -0
  81. package/src/core/wu-style-bridge.js +0 -2
  82. package/src/index.js +139 -665
  83. package/dist/wu-framework.hex.js +0 -23
  84. package/dist/wu-framework.min.js +0 -1
  85. package/dist/wu-framework.obf.js +0 -1
  86. package/scripts/build-protected.js +0 -366
  87. package/scripts/build.js +0 -212
  88. package/scripts/rollup-plugin-hex.js +0 -143
  89. package/src/core/wu-registry.js +0 -60
  90. package/src/core/wu-sandbox-pool.js +0 -390
@@ -0,0 +1,510 @@
1
+ /**
2
+ * WU-OVERRIDES: Cookie-based URL overrides for QA/testing
3
+ *
4
+ * SECURITY MODEL:
5
+ * - DISABLED in production by default (must opt-in with allowOverrides: true)
6
+ * - Allowlist of trusted domains (only overrides to whitelisted hosts are accepted)
7
+ * - Visual indicator when overrides are active (prevents silent phishing)
8
+ *
9
+ * How it works:
10
+ * 1. QA sets a cookie: wu-override:cart=http://localhost:5173
11
+ * 2. During wu.init(), overrides are parsed from document.cookie
12
+ * 3. The URL for "cart" is replaced ONLY in that browser session
13
+ * 4. Everyone else sees the production URL
14
+ *
15
+ * Cookie format:
16
+ * wu-override:<appName>=<url>
17
+ *
18
+ * @example
19
+ * // Enable in init (required in production)
20
+ * wu.init({
21
+ * apps: [...],
22
+ * overrides: {
23
+ * enabled: true,
24
+ * allowedDomains: ['*.company.com', 'localhost', '*.vercel.app'],
25
+ * showIndicator: true
26
+ * }
27
+ * });
28
+ *
29
+ * // Programmatic API
30
+ * wu.override('cart', 'http://localhost:5173');
31
+ * wu.removeOverride('cart');
32
+ * wu.getOverrides();
33
+ * wu.clearOverrides();
34
+ */
35
+
36
+ import { logger } from './wu-logger.js';
37
+
38
+ const COOKIE_PREFIX = 'wu-override:';
39
+
40
+ export class WuOverrides {
41
+ constructor(config = {}) {
42
+ // In-memory cache of active overrides (synced with cookies)
43
+ this._overrides = new Map();
44
+
45
+ // Security config
46
+ this._allowedDomains = config.allowedDomains || [];
47
+ this._showIndicator = config.showIndicator ?? true;
48
+ this._indicatorElement = null;
49
+
50
+ // Determine enabled state:
51
+ // - If explicitly passed → use that value (respect user intent)
52
+ // - If not passed → auto-detect from environment
53
+ if (config.enabled !== undefined) {
54
+ this._enabled = config.enabled;
55
+ } else {
56
+ this._enabled = this._isDevEnvironment();
57
+ }
58
+
59
+ // Parse existing cookies on construction (only if enabled)
60
+ if (this._enabled) {
61
+ this._parseFromCookies();
62
+ }
63
+ }
64
+
65
+ // ─── Security: Environment Detection ─────────────────────────
66
+
67
+ /**
68
+ * Detect if we're in a development environment.
69
+ * Overrides are auto-enabled in dev, disabled in production.
70
+ */
71
+ _isDevEnvironment() {
72
+ if (typeof window === 'undefined') return false;
73
+
74
+ const hostname = window.location?.hostname || '';
75
+ const port = window.location?.port || '';
76
+
77
+ // localhost, 127.0.0.1, or non-standard ports = development
78
+ return (
79
+ hostname === 'localhost' ||
80
+ hostname === '127.0.0.1' ||
81
+ hostname === '0.0.0.0' ||
82
+ hostname.endsWith('.local') ||
83
+ (port !== '' && port !== '80' && port !== '443')
84
+ );
85
+ }
86
+
87
+ // ─── Security: Domain Allowlist ──────────────────────────────
88
+
89
+ /**
90
+ * Check if a URL's domain is in the allowlist.
91
+ * If no allowlist is configured, all valid URLs are accepted (dev-mode behavior).
92
+ * If an allowlist IS configured, only matching domains pass.
93
+ *
94
+ * @param {string} url
95
+ * @returns {boolean}
96
+ */
97
+ _isDomainAllowed(url) {
98
+ // No allowlist = allow everything (but only if overrides are enabled)
99
+ if (this._allowedDomains.length === 0) return true;
100
+
101
+ const hostname = this._extractHostname(url);
102
+ if (!hostname) return false;
103
+
104
+ for (const pattern of this._allowedDomains) {
105
+ if (this._matchDomain(hostname, pattern)) return true;
106
+ }
107
+
108
+ return false;
109
+ }
110
+
111
+ /**
112
+ * Extract hostname from a URL string.
113
+ */
114
+ _extractHostname(url) {
115
+ try {
116
+ // Handle localhost:PORT shorthand
117
+ if (/^localhost(:\d+)?/.test(url)) return 'localhost';
118
+
119
+ // Handle protocol-relative
120
+ const normalized = url.startsWith('//') ? `https:${url}` : url;
121
+ const parsed = new URL(normalized);
122
+ return parsed.hostname;
123
+ } catch {
124
+ return null;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Match a hostname against a domain pattern.
130
+ * Supports wildcard: *.company.com matches sub.company.com
131
+ *
132
+ * @param {string} hostname - e.g., 'cart.staging.company.com'
133
+ * @param {string} pattern - e.g., '*.company.com' or 'localhost'
134
+ * @returns {boolean}
135
+ */
136
+ _matchDomain(hostname, pattern) {
137
+ // Exact match
138
+ if (hostname === pattern) return true;
139
+
140
+ // Wildcard match: *.company.com
141
+ if (pattern.startsWith('*.')) {
142
+ const suffix = pattern.substring(2); // 'company.com'
143
+ return hostname === suffix || hostname.endsWith('.' + suffix);
144
+ }
145
+
146
+ return false;
147
+ }
148
+
149
+ // ─── Cookie Parsing ──────────────────────────────────────────
150
+
151
+ /**
152
+ * Parse all wu-override cookies from document.cookie.
153
+ * Called automatically on construction and can be called manually to refresh.
154
+ *
155
+ * @returns {Map<string, string>} Map of appName → overrideUrl
156
+ */
157
+ _parseFromCookies() {
158
+ this._overrides.clear();
159
+
160
+ if (typeof document === 'undefined') return this._overrides;
161
+
162
+ if (!this._enabled) {
163
+ logger.wuDebug('[WuOverrides] Overrides disabled — skipping cookie parse');
164
+ return this._overrides;
165
+ }
166
+
167
+ const cookies = document.cookie;
168
+ if (!cookies) return this._overrides;
169
+
170
+ // Split cookies and find wu-override:* entries
171
+ const pairs = cookies.split(';');
172
+
173
+ for (const pair of pairs) {
174
+ const trimmed = pair.trim();
175
+
176
+ if (!trimmed.startsWith(COOKIE_PREFIX)) continue;
177
+
178
+ // wu-override:cart=http://localhost:5173
179
+ const eqIndex = trimmed.indexOf('=');
180
+ if (eqIndex === -1) continue;
181
+
182
+ const appName = trimmed.substring(COOKIE_PREFIX.length, eqIndex).trim();
183
+ const url = trimmed.substring(eqIndex + 1).trim();
184
+
185
+ if (!appName || !url) continue;
186
+
187
+ // Validate URL format
188
+ if (!this._isValidUrl(url)) {
189
+ logger.wuWarn(`[WuOverrides] Invalid override URL for "${appName}": ${url}`);
190
+ continue;
191
+ }
192
+
193
+ // Validate domain is allowed
194
+ if (!this._isDomainAllowed(url)) {
195
+ logger.wuWarn(
196
+ `[WuOverrides] BLOCKED: "${appName}" override to "${url}" — ` +
197
+ `domain not in allowedDomains. ` +
198
+ `Allowed: [${this._allowedDomains.join(', ')}]`
199
+ );
200
+ continue;
201
+ }
202
+
203
+ this._overrides.set(appName, url);
204
+ logger.wuDebug(`[WuOverrides] Parsed override: ${appName} → ${url}`);
205
+ }
206
+
207
+ if (this._overrides.size > 0) {
208
+ logger.wuInfo(
209
+ `[WuOverrides] ${this._overrides.size} active override(s): ` +
210
+ [...this._overrides.keys()].join(', ')
211
+ );
212
+ }
213
+
214
+ return this._overrides;
215
+ }
216
+
217
+ // ─── Apply Overrides ─────────────────────────────────────────
218
+
219
+ /**
220
+ * Apply overrides to an array of app configs.
221
+ * Mutates the url field of matching apps.
222
+ * Called by WuCore during init, before registerApp.
223
+ *
224
+ * @param {Array<{name: string, url: string}>} apps - App configs to process
225
+ * @returns {Array<{name: string, url: string, _originalUrl?: string}>} Same array, mutated
226
+ */
227
+ applyToApps(apps) {
228
+ if (!this._enabled || this._overrides.size === 0) return apps;
229
+
230
+ for (const app of apps) {
231
+ const overrideUrl = this._overrides.get(app.name);
232
+ if (overrideUrl) {
233
+ app._originalUrl = app.url;
234
+ app.url = overrideUrl;
235
+ logger.wuInfo(
236
+ `[WuOverrides] "${app.name}" overridden: ${app._originalUrl} → ${overrideUrl}`
237
+ );
238
+ }
239
+ }
240
+
241
+ // Show visual indicator if overrides were applied
242
+ if (this._showIndicator && this._overrides.size > 0) {
243
+ this._showOverrideIndicator();
244
+ }
245
+
246
+ return apps;
247
+ }
248
+
249
+ /**
250
+ * Get the override URL for a specific app, or null if none.
251
+ *
252
+ * @param {string} appName
253
+ * @returns {string|null}
254
+ */
255
+ getOverrideFor(appName) {
256
+ return this._overrides.get(appName) || null;
257
+ }
258
+
259
+ // ─── Programmatic API ────────────────────────────────────────
260
+
261
+ /**
262
+ * Set an override for an app. Writes a cookie and updates in-memory cache.
263
+ *
264
+ * @param {string} appName - App to override
265
+ * @param {string} url - Override URL (e.g., 'http://localhost:5173')
266
+ * @param {Object} [options]
267
+ * @param {number} [options.maxAge=86400] - Cookie max-age in seconds (default: 24h)
268
+ * @param {string} [options.path='/'] - Cookie path
269
+ */
270
+ set(appName, url, options = {}) {
271
+ if (!appName || !url) {
272
+ throw new Error('[WuOverrides] appName and url are required');
273
+ }
274
+
275
+ if (!this._enabled) {
276
+ throw new Error(
277
+ '[WuOverrides] Overrides are disabled in this environment. ' +
278
+ 'Enable with wu.init({ overrides: { enabled: true } })'
279
+ );
280
+ }
281
+
282
+ if (!this._isValidUrl(url)) {
283
+ throw new Error(`[WuOverrides] Invalid URL: ${url}`);
284
+ }
285
+
286
+ if (!this._isDomainAllowed(url)) {
287
+ throw new Error(
288
+ `[WuOverrides] Domain not allowed: "${this._extractHostname(url)}". ` +
289
+ `Allowed: [${this._allowedDomains.join(', ')}]`
290
+ );
291
+ }
292
+
293
+ const maxAge = options.maxAge ?? 86400; // 24 hours default
294
+ const path = options.path ?? '/';
295
+
296
+ // Set cookie
297
+ if (typeof document !== 'undefined') {
298
+ document.cookie =
299
+ `${COOKIE_PREFIX}${appName}=${url}; path=${path}; max-age=${maxAge}; SameSite=Lax`;
300
+ }
301
+
302
+ // Update in-memory cache
303
+ this._overrides.set(appName, url);
304
+
305
+ // Update visual indicator
306
+ if (this._showIndicator) {
307
+ this._showOverrideIndicator();
308
+ }
309
+
310
+ logger.wuInfo(`[WuOverrides] Override set: ${appName} → ${url} (expires in ${maxAge}s)`);
311
+ }
312
+
313
+ /**
314
+ * Remove an override for a specific app.
315
+ *
316
+ * @param {string} appName - App to remove override for
317
+ */
318
+ remove(appName) {
319
+ // Delete cookie by setting max-age=0
320
+ if (typeof document !== 'undefined') {
321
+ document.cookie = `${COOKIE_PREFIX}${appName}=; path=/; max-age=0`;
322
+ }
323
+
324
+ this._overrides.delete(appName);
325
+
326
+ // Update or remove indicator
327
+ if (this._showIndicator) {
328
+ if (this._overrides.size === 0) {
329
+ this._removeOverrideIndicator();
330
+ } else {
331
+ this._showOverrideIndicator();
332
+ }
333
+ }
334
+
335
+ logger.wuInfo(`[WuOverrides] Override removed: ${appName}`);
336
+ }
337
+
338
+ /**
339
+ * Remove all overrides.
340
+ */
341
+ clearAll() {
342
+ for (const appName of [...this._overrides.keys()]) {
343
+ this.remove(appName);
344
+ }
345
+
346
+ this._removeOverrideIndicator();
347
+
348
+ logger.wuInfo('[WuOverrides] All overrides cleared');
349
+ }
350
+
351
+ /**
352
+ * Get all active overrides as a plain object.
353
+ *
354
+ * @returns {Object} { appName: url, ... }
355
+ */
356
+ getAll() {
357
+ return Object.fromEntries(this._overrides);
358
+ }
359
+
360
+ /**
361
+ * Check if any overrides are active.
362
+ *
363
+ * @returns {boolean}
364
+ */
365
+ hasOverrides() {
366
+ return this._overrides.size > 0;
367
+ }
368
+
369
+ /**
370
+ * Check if overrides are enabled in this environment.
371
+ *
372
+ * @returns {boolean}
373
+ */
374
+ isEnabled() {
375
+ return this._enabled;
376
+ }
377
+
378
+ /**
379
+ * Configure the override system.
380
+ * Called by WuCore during init with config.overrides.
381
+ *
382
+ * @param {Object} config
383
+ * @param {boolean} [config.enabled]
384
+ * @param {string[]} [config.allowedDomains]
385
+ * @param {boolean} [config.showIndicator]
386
+ */
387
+ configure(config = {}) {
388
+ if (config.enabled !== undefined) {
389
+ this._enabled = config.enabled;
390
+ }
391
+ if (config.allowedDomains) {
392
+ this._allowedDomains = config.allowedDomains;
393
+ }
394
+ if (config.showIndicator !== undefined) {
395
+ this._showIndicator = config.showIndicator;
396
+ }
397
+
398
+ // Re-parse cookies with new config
399
+ if (this._enabled) {
400
+ this._parseFromCookies();
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Refresh overrides by re-parsing cookies.
406
+ * Useful if cookies were modified externally (DevTools, other tabs).
407
+ */
408
+ refresh() {
409
+ this._parseFromCookies();
410
+ }
411
+
412
+ // ─── Visual Indicator (anti-phishing) ────────────────────────
413
+
414
+ /**
415
+ * Show a fixed banner when overrides are active.
416
+ * This prevents silent phishing — the user ALWAYS sees when
417
+ * microfrontends are being loaded from non-standard URLs.
418
+ */
419
+ _showOverrideIndicator() {
420
+ if (typeof document === 'undefined') return;
421
+
422
+ // Remove existing indicator first
423
+ this._removeOverrideIndicator();
424
+
425
+ const indicator = document.createElement('div');
426
+ indicator.id = 'wu-override-indicator';
427
+
428
+ const overrideList = [...this._overrides.entries()]
429
+ .map(([name, url]) => `${name} → ${url}`)
430
+ .join(' | ');
431
+
432
+ indicator.textContent = `WU OVERRIDE ACTIVE: ${overrideList}`;
433
+
434
+ indicator.style.cssText = [
435
+ 'position: fixed',
436
+ 'bottom: 0',
437
+ 'left: 0',
438
+ 'right: 0',
439
+ 'z-index: 2147483647',
440
+ 'background: #f59e0b',
441
+ 'color: #000',
442
+ 'font-family: monospace',
443
+ 'font-size: 12px',
444
+ 'font-weight: bold',
445
+ 'padding: 6px 12px',
446
+ 'text-align: center',
447
+ 'cursor: pointer',
448
+ 'user-select: none',
449
+ 'box-shadow: 0 -2px 8px rgba(0,0,0,0.2)'
450
+ ].join(';');
451
+
452
+ // Click to dismiss (but override stays active)
453
+ indicator.addEventListener('click', () => {
454
+ indicator.style.display = 'none';
455
+ });
456
+
457
+ // Double-click to clear all overrides
458
+ indicator.addEventListener('dblclick', (e) => {
459
+ e.preventDefault();
460
+ this.clearAll();
461
+ });
462
+
463
+ indicator.title = 'Click to hide | Double-click to clear all overrides';
464
+
465
+ document.body.appendChild(indicator);
466
+ this._indicatorElement = indicator;
467
+ }
468
+
469
+ /**
470
+ * Remove the visual indicator.
471
+ */
472
+ _removeOverrideIndicator() {
473
+ if (this._indicatorElement) {
474
+ this._indicatorElement.remove();
475
+ this._indicatorElement = null;
476
+ }
477
+ // Also remove by ID in case element reference was lost
478
+ if (typeof document !== 'undefined') {
479
+ const existing = document.getElementById('wu-override-indicator');
480
+ if (existing) existing.remove();
481
+ }
482
+ }
483
+
484
+ // ─── Validation ──────────────────────────────────────────────
485
+
486
+ _isValidUrl(url) {
487
+ // Accept http://, https://, and // protocol-relative
488
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
489
+ return true;
490
+ }
491
+ // Accept localhost shorthand like localhost:3000
492
+ if (/^localhost(:\d+)?/.test(url)) {
493
+ return true;
494
+ }
495
+ return false;
496
+ }
497
+
498
+ // ─── Stats ───────────────────────────────────────────────────
499
+
500
+ getStats() {
501
+ return {
502
+ enabled: this._enabled,
503
+ activeOverrides: this._overrides.size,
504
+ overrides: this.getAll(),
505
+ allowedDomains: this._allowedDomains,
506
+ showIndicator: this._showIndicator,
507
+ environment: this._isDevEnvironment() ? 'development' : 'production'
508
+ };
509
+ }
510
+ }