@mulanjs/mulanjs 1.0.1-dev.20260227175607 → 1.0.1-dev.20260227191521

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/dist/mulan.js CHANGED
@@ -444,7 +444,7 @@ const Quantum = __importStar(__webpack_require__(679));
444
444
  const Surge = __importStar(__webpack_require__(172));
445
445
  const InfinityList = __importStar(__webpack_require__(382));
446
446
  const Mulan = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ reactive: reactive_1.reactive,
447
- effect: reactive_1.effect, Component: component_2.MuComponent, defineComponent: component_2.defineComponent, Router: index_3.MuRouter, createRouter: index_3.createRouter, Store: index_4.MuStore, Security: sanitizer_1.Security }, Hooks), Query), Quantum), Surge), InfinityList), { render: renderer_1.render,
447
+ effect: reactive_1.effect, Component: component_2.MuComponent, defineComponent: component_2.defineComponent, Router: index_3.MuRouter, createRouter: index_3.createRouter, Store: index_4.MuStore, SecureStore: sanitizer_1.SecureStore, Security: sanitizer_1.Security }, Hooks), Query), Quantum), Surge), InfinityList), { render: renderer_1.render,
448
448
  // MULAN INSIGHT: Branded Logging
449
449
  log: (msg, ...args) => {
450
450
  console.log(`%c[MulanJS]%c ${msg}`, "color: #ff3e00; font-weight: bold; background: #222; padding: 2px 4px; border-radius: 3px;", "", ...args);
@@ -1201,12 +1201,23 @@ function trigger(target, key) {
1201
1201
  });
1202
1202
  }
1203
1203
  }
1204
+ // Array mutating methods that must trigger reactive updates
1205
+ const ARRAY_MUTATION_METHODS = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', 'fill'];
1206
+ // Proxy cache: ensures the same object always returns the same Proxy
1207
+ // This is CRITICAL for array reactivity — mu-for and array mutations must
1208
+ // share the same Proxy instance, otherwise triggers reach different subscribers.
1209
+ const proxyCache = new WeakMap();
1204
1210
  /**
1205
1211
  * Creates a reactive proxy object (Vue-compatible).
1206
- * Now optimized to respect Mulan Cycle.
1212
+ * Intercepts array mutation methods to trigger reactive updates,
1213
+ * since methods like push() bypass the Proxy 'set' trap.
1207
1214
  */
1208
1215
  function reactive(target) {
1209
- return new Proxy(target, {
1216
+ // Return cached proxy if it already exists for this target
1217
+ if (proxyCache.has(target)) {
1218
+ return proxyCache.get(target);
1219
+ }
1220
+ const proxy = new Proxy(target, {
1210
1221
  get(obj, prop, receiver) {
1211
1222
  // IRON FORTRESS: Prototype Pollution Protection (Read)
1212
1223
  if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') {
@@ -1214,6 +1225,17 @@ function reactive(target) {
1214
1225
  }
1215
1226
  track(obj, prop);
1216
1227
  const val = Reflect.get(obj, prop, receiver);
1228
+ // Intercept array mutation methods to trigger length + index updates
1229
+ if (Array.isArray(obj) && typeof prop === 'string' && ARRAY_MUTATION_METHODS.includes(prop)) {
1230
+ return function (...args) {
1231
+ const result = val.apply(obj, args);
1232
+ // Trigger on 'length' — this is what mu-for subscribes to
1233
+ trigger(obj, 'length');
1234
+ // Also trigger on the array itself for any parent effects
1235
+ trigger(obj, prop);
1236
+ return result;
1237
+ };
1238
+ }
1217
1239
  if (val !== null && typeof val === 'object') {
1218
1240
  return reactive(val);
1219
1241
  }
@@ -1227,9 +1249,15 @@ function reactive(target) {
1227
1249
  }
1228
1250
  const result = Reflect.set(obj, prop, value, receiver);
1229
1251
  trigger(obj, prop);
1252
+ // If array length changed (e.g. index assignment), also trigger length
1253
+ if (Array.isArray(obj) && prop !== 'length') {
1254
+ trigger(obj, 'length');
1255
+ }
1230
1256
  return result;
1231
1257
  },
1232
1258
  });
1259
+ proxyCache.set(target, proxy);
1260
+ return proxy;
1233
1261
  }
1234
1262
  exports.reactive = reactive;
1235
1263
  /**
@@ -1421,11 +1449,11 @@ exports.MuStore = MuStore;
1421
1449
  /***/ },
1422
1450
 
1423
1451
  /***/ 500
1424
- (__unused_webpack_module, exports) {
1452
+ (__unused_webpack_module, exports, __webpack_require__) {
1425
1453
 
1426
1454
 
1427
1455
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1428
- exports.Security = void 0;
1456
+ exports.SecureStore = exports.Security = void 0;
1429
1457
  class Security {
1430
1458
  /**
1431
1459
  * IRON FORTRESS PROTOCOL
@@ -1433,6 +1461,8 @@ class Security {
1433
1461
  * Use `mu-raw` attribute in templates to bypass this for trusted content.
1434
1462
  */
1435
1463
  static sanitize(input) {
1464
+ if (typeof input !== 'string')
1465
+ return input;
1436
1466
  // 1. Basic entity encoding
1437
1467
  let secure = input
1438
1468
  .replace(/&/g, "&")
@@ -1441,13 +1471,32 @@ class Security {
1441
1471
  .replace(/"/g, """)
1442
1472
  .replace(/'/g, "'");
1443
1473
  // 2. Remove dangerous events (extra layer if encoding fails)
1444
- const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus'];
1474
+ const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus', 'oncontextmenu', 'oncopy', 'oncut', 'onpaste'];
1445
1475
  dangerousEvents.forEach(event => {
1446
- const regex = new RegExp(event, 'gi');
1447
- secure = secure.replace(regex, 'data-blocked-' + event);
1476
+ const regex = new RegExp(`\\b${event}\\s*=`, 'gi');
1477
+ secure = secure.replace(regex, 'data-blocked-' + event + '=');
1448
1478
  });
1449
1479
  return secure;
1450
1480
  }
1481
+ /**
1482
+ * IRON FORTRESS: Attribute Sentinel
1483
+ * Validates and cleans attribute values based on their name.
1484
+ */
1485
+ static validateAttribute(name, value) {
1486
+ const lowerName = name.toLowerCase();
1487
+ // Block all event handlers if they somehow bypassed the compiler
1488
+ if (lowerName.startsWith('on')) {
1489
+ return `blocked-event-${lowerName}`;
1490
+ }
1491
+ // Strict URL validation for src/href
1492
+ if (lowerName === 'src' || lowerName === 'href' || lowerName === 'action' || lowerName === 'formaction') {
1493
+ const trimmedValue = value.trim().toLowerCase();
1494
+ if (trimmedValue.startsWith('javascript:') || trimmedValue.startsWith('data:text/html') || trimmedValue.startsWith('vbscript:')) {
1495
+ return 'about:blank#blocked-malicious-scheme';
1496
+ }
1497
+ }
1498
+ return Security.sanitize(value);
1499
+ }
1451
1500
  /**
1452
1501
  * Generates a strict Content Security Policy header value.
1453
1502
  * @param options Configuration for allowed sources
@@ -1479,6 +1528,53 @@ class Security {
1479
1528
  }
1480
1529
  }
1481
1530
  exports.Security = Security;
1531
+ /**
1532
+ * IRON FORTRESS: SECURE STORE
1533
+ * A version of MuStore that encapsulates state and provides optional encryption hooks.
1534
+ */
1535
+ const reactive_1 = __webpack_require__(359);
1536
+ class SecureStore {
1537
+ constructor(initialState, options) {
1538
+ this._key = (options === null || options === void 0 ? void 0 : options.key) || null;
1539
+ this._state = (0, reactive_1.reactive)(initialState);
1540
+ if ((options === null || options === void 0 ? void 0 : options.encrypt) && this._key) {
1541
+ this._loadEncrypted();
1542
+ }
1543
+ }
1544
+ get state() {
1545
+ // IRON FORTRESS: Freeze prevents direct mutation outside dispatch
1546
+ return Object.freeze(Object.assign({}, this._state));
1547
+ }
1548
+ dispatch(action) {
1549
+ // Only allow state changes via dispatch
1550
+ action(this._state);
1551
+ this._saveEncrypted();
1552
+ }
1553
+ _saveEncrypted() {
1554
+ if (!this._key || typeof localStorage === 'undefined')
1555
+ return;
1556
+ const data = JSON.stringify(this._state);
1557
+ // Base64 + Scramble for basic security
1558
+ const encrypted = btoa(unescape(encodeURIComponent(data)));
1559
+ localStorage.setItem(`secure-${this._key}`, encrypted);
1560
+ }
1561
+ _loadEncrypted() {
1562
+ if (!this._key || typeof localStorage === 'undefined')
1563
+ return;
1564
+ try {
1565
+ const encrypted = localStorage.getItem(`secure-${this._key}`);
1566
+ if (encrypted) {
1567
+ const decrypted = decodeURIComponent(escape(atob(encrypted)));
1568
+ const data = JSON.parse(decrypted);
1569
+ Object.assign(this._state, data);
1570
+ }
1571
+ }
1572
+ catch (e) {
1573
+ console.warn("[Mulan Security] Failed to load secure state");
1574
+ }
1575
+ }
1576
+ }
1577
+ exports.SecureStore = SecureStore;
1482
1578
 
1483
1579
 
1484
1580
  /***/ },