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.
- package/README.md +511 -977
- package/dist/wu-framework.cjs.js +3 -1
- package/dist/wu-framework.cjs.js.map +1 -0
- package/dist/wu-framework.dev.js +7533 -2761
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +3 -0
- package/dist/wu-framework.esm.js.map +1 -0
- package/dist/wu-framework.umd.js +3 -1
- package/dist/wu-framework.umd.js.map +1 -0
- package/integrations/astro/README.md +127 -0
- package/integrations/astro/WuApp.astro +63 -0
- package/integrations/astro/WuShell.astro +39 -0
- package/integrations/astro/index.js +68 -0
- package/integrations/astro/package.json +38 -0
- package/integrations/astro/types.d.ts +53 -0
- package/package.json +94 -74
- package/src/adapters/angular/ai.js +30 -0
- package/src/adapters/angular/index.d.ts +154 -0
- package/src/adapters/angular/index.js +932 -0
- package/src/adapters/angular.d.ts +3 -154
- package/src/adapters/angular.js +3 -813
- package/src/adapters/index.js +35 -24
- package/src/adapters/lit/ai.js +20 -0
- package/src/adapters/lit/index.d.ts +120 -0
- package/src/adapters/lit/index.js +721 -0
- package/src/adapters/lit.d.ts +3 -120
- package/src/adapters/lit.js +3 -726
- package/src/adapters/preact/ai.js +33 -0
- package/src/adapters/preact/index.d.ts +108 -0
- package/src/adapters/preact/index.js +661 -0
- package/src/adapters/preact.d.ts +3 -108
- package/src/adapters/preact.js +3 -665
- package/src/adapters/react/ai.js +135 -0
- package/src/adapters/react/index.d.ts +246 -0
- package/src/adapters/react/index.js +689 -0
- package/src/adapters/react.d.ts +3 -212
- package/src/adapters/react.js +3 -513
- package/src/adapters/shared.js +64 -0
- package/src/adapters/solid/ai.js +32 -0
- package/src/adapters/solid/index.d.ts +101 -0
- package/src/adapters/solid/index.js +586 -0
- package/src/adapters/solid.d.ts +3 -101
- package/src/adapters/solid.js +3 -591
- package/src/adapters/svelte/ai.js +31 -0
- package/src/adapters/svelte/index.d.ts +166 -0
- package/src/adapters/svelte/index.js +798 -0
- package/src/adapters/svelte.d.ts +3 -166
- package/src/adapters/svelte.js +3 -803
- package/src/adapters/vanilla/ai.js +30 -0
- package/src/adapters/vanilla/index.d.ts +179 -0
- package/src/adapters/vanilla/index.js +785 -0
- package/src/adapters/vanilla.d.ts +3 -179
- package/src/adapters/vanilla.js +3 -791
- package/src/adapters/vue/ai.js +52 -0
- package/src/adapters/vue/index.d.ts +299 -0
- package/src/adapters/vue/index.js +608 -0
- package/src/adapters/vue.d.ts +3 -299
- package/src/adapters/vue.js +3 -611
- package/src/ai/wu-ai-actions.js +261 -0
- package/src/ai/wu-ai-browser.js +663 -0
- package/src/ai/wu-ai-context.js +332 -0
- package/src/ai/wu-ai-conversation.js +554 -0
- package/src/ai/wu-ai-permissions.js +381 -0
- package/src/ai/wu-ai-provider.js +605 -0
- package/src/ai/wu-ai-schema.js +225 -0
- package/src/ai/wu-ai-triggers.js +396 -0
- package/src/ai/wu-ai.js +474 -0
- package/src/core/wu-app.js +50 -8
- package/src/core/wu-cache.js +1 -1
- package/src/core/wu-core.js +645 -677
- package/src/core/wu-html-parser.js +121 -211
- package/src/core/wu-iframe-sandbox.js +328 -0
- package/src/core/wu-mcp-bridge.js +647 -0
- package/src/core/wu-overrides.js +510 -0
- package/src/core/wu-prefetch.js +414 -0
- package/src/core/wu-proxy-sandbox.js +398 -75
- package/src/core/wu-sandbox.js +86 -268
- package/src/core/wu-script-executor.js +79 -182
- package/src/core/wu-snapshot-sandbox.js +149 -106
- package/src/core/wu-strategies.js +13 -0
- package/src/core/wu-style-bridge.js +0 -2
- package/src/index.js +139 -665
- package/dist/wu-framework.hex.js +0 -23
- package/dist/wu-framework.min.js +0 -1
- package/dist/wu-framework.obf.js +0 -1
- package/scripts/build-protected.js +0 -366
- package/scripts/build.js +0 -212
- package/scripts/rollup-plugin-hex.js +0 -143
- package/src/core/wu-registry.js +0 -60
- 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
|
+
}
|