@syntrologie/adapt-content 2.52.0 → 2.54.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/dist/runtime.d.ts CHANGED
@@ -29,24 +29,6 @@ export declare const executeRemoveClass: ActionExecutor<RemoveClassAction>;
29
29
  * Execute a setStyle action
30
30
  */
31
31
  export declare const executeSetStyle: ActionExecutor<SetStyleAction>;
32
- /**
33
- * Execute a hideBySelector action.
34
- *
35
- * Injects `selector { prop: value !important; ... }` into a dedicated
36
- * `<style>` element instead of resolving a live anchor and mutating its
37
- * inline style. This is the stylesheet-rule counterpart to executeSetStyle:
38
- * use it when the target may not exist in the DOM yet, may never appear in
39
- * some sessions, or gets torn down and re-inserted by the host page (chat
40
- * widgets, cookie banners, etc). A CSS rule matches those future insertions
41
- * for free — no polling, no MutationObserver, and critically, no
42
- * anchor-not-found wait blocking anything downstream (BUG-1786758064: a
43
- * `content:setStyle` suppression action retrying a `.crisp-client` anchor
44
- * that never appeared held up canvas render for ~20s).
45
- *
46
- * `!important` is required (unlike inline styles, which always win via
47
- * specificity) because a stylesheet rule competes with the host page's own
48
- * CSS on normal specificity/source-order terms.
49
- */
50
32
  export declare const executeHideBySelector: ActionExecutor<HideBySelectorAction>;
51
33
  /**
52
34
  * All executors provided by this app.
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EAEd,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,aAAa,EACd,MAAM,SAAS,CAAC;AAMjB;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,gBAAgB,CAsI9D,CAAC;AAmBF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,aAAa,CAsCxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,aAAa,CA6DxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CAmC1D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,iBAAiB,CAmChE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CAgE1D,CAAC;AAmBF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,oBAAoB,CA+DtE,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;EAQZ,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMnB,CAAC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EAEd,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,aAAa,EACd,MAAM,SAAS,CAAC;AAMjB;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,gBAAgB,CAsI9D,CAAC;AAmBF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,aAAa,CAsCxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,aAAa,CA6DxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CAmC1D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,iBAAiB,CAmChE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CAgE1D,CAAC;AAuDF,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,oBAAoB,CAwDtE,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;EAQZ,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMnB,CAAC"}
package/dist/runtime.js CHANGED
@@ -447,6 +447,11 @@ function hasCssBreakout(selector, styles) {
447
447
  if (CSS_BREAKOUT_CHARS.test(selector)) return true;
448
448
  return Object.values(styles).some((value) => CSS_BREAKOUT_CHARS.test(value));
449
449
  }
450
+ var HIDE_BY_SELECTOR_RULE_ID = "syntro-hide-by-selector";
451
+ function hideBySelectorRule(selector, styles) {
452
+ const declarations = Object.entries(styles).map(([prop, value]) => `${prop}: ${value} !important;`).join(" ");
453
+ return `:is(${selector}):not(#${HIDE_BY_SELECTOR_RULE_ID}) { ${declarations} }`;
454
+ }
450
455
  var executeHideBySelector = async (action, context) => {
451
456
  if (context.matchesAnchorRoute && !context.matchesAnchorRoute(action.anchorId)) {
452
457
  return { cleanup: () => {
@@ -461,10 +466,9 @@ var executeHideBySelector = async (action, context) => {
461
466
  return { cleanup: () => {
462
467
  } };
463
468
  }
464
- const declarations = Object.entries(action.styles).map(([prop, value]) => `${prop}: ${value} !important;`).join(" ");
465
469
  const styleEl = document.createElement("style");
466
470
  styleEl.setAttribute("data-syntro-action-id", context.generateId());
467
- styleEl.textContent = `${action.anchorId.selector} { ${declarations} }`;
471
+ styleEl.textContent = hideBySelectorRule(action.anchorId.selector, action.styles);
468
472
  document.head.appendChild(styleEl);
469
473
  context.publishEvent("action.applied", {
470
474
  id: context.generateId(),
@@ -486,8 +490,7 @@ var executeHideBySelector = async (action, context) => {
486
490
  );
487
491
  return;
488
492
  }
489
- const newDeclarations = Object.entries(newStyles).map(([prop, value]) => `${prop}: ${value} !important;`).join(" ");
490
- styleEl.textContent = `${action.anchorId.selector} { ${newDeclarations} }`;
493
+ styleEl.textContent = hideBySelectorRule(action.anchorId.selector, newStyles);
491
494
  }
492
495
  }
493
496
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/reconciliation-guard.ts", "../src/sanitizer.ts", "../src/runtime.ts"],
4
- "sourcesContent": ["/**\n * Reconciliation Guard - MutationObserver defense against React DOM removal.\n *\n * When the Syntrologie SDK inserts DOM nodes into a React-managed subtree\n * (via content:insertHtml), React's reconciliation will silently remove them\n * on the next render because they don't exist in React's virtual DOM.\n *\n * This guard watches for the removal of our inserted container and re-inserts\n * it using a debounced retry mechanism with a maximum retry count to prevent\n * infinite loops (e.g., in React StrictMode which double-invokes effects).\n */\n\nexport interface ReconciliationGuardOptions {\n /** Maximum re-insertion attempts before giving up. Default: 3 */\n maxRetries?: number;\n /** Debounce interval in ms to coalesce rapid removals. Default: 50 */\n debounceMs?: number;\n}\n\n/**\n * Watch for a container element being removed from the DOM by an external\n * framework (React, Vue, etc.) and call `reinsertFn` to re-insert it.\n *\n * @param container The element we inserted (has data-syntro-action-id)\n * @param anchor The anchor element our container is positioned relative to\n * @param reinsertFn Called when the container is removed \u2014 should re-insert it\n * @param opts Configuration for retry limits and debounce timing\n * @returns Cleanup function that disconnects the observer\n */\nexport function guardAgainstReconciliation(\n container: HTMLElement,\n anchor: HTMLElement,\n reinsertFn: () => void,\n opts?: ReconciliationGuardOptions\n): () => void {\n const maxRetries = opts?.maxRetries ?? 3;\n const debounceMs = opts?.debounceMs ?? 50;\n\n // Find the nearest parent to observe. Prefer container's parent, then anchor's.\n const observeTarget = container.parentElement ?? anchor.parentElement;\n if (!observeTarget) return () => {};\n\n let retries = 0;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let disconnected = false;\n\n const observer = new MutationObserver((mutations) => {\n if (disconnected) return;\n\n for (const mutation of mutations) {\n for (const removed of mutation.removedNodes) {\n // Check if the removed node is our container\n if (removed !== container) continue;\n\n if (retries >= maxRetries) {\n observer.disconnect();\n disconnected = true;\n return;\n }\n\n // Debounce to coalesce rapid React re-renders\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n if (disconnected) return;\n // If the anchor has been removed from the DOM (SPA navigation),\n // the page has been torn down \u2014 stop fighting React's reconciler.\n // Re-inserting into a dying subtree triggers removeChild crashes.\n if (!anchor.isConnected) {\n observer.disconnect();\n disconnected = true;\n return;\n }\n retries++;\n try {\n reinsertFn();\n } catch {\n // Re-insertion failed \u2014 stop trying\n observer.disconnect();\n disconnected = true;\n }\n }, debounceMs);\n\n return; // Found our container, no need to check further\n }\n }\n });\n\n observer.observe(observeTarget, { childList: true, subtree: true });\n\n return () => {\n disconnected = true;\n observer.disconnect();\n if (debounceTimer) clearTimeout(debounceTimer);\n };\n}\n", "/**\n * HTML Sanitizer\n *\n * Sanitizes HTML to prevent XSS attacks.\n * Uses native Sanitizer API when available, falls back to whitelist approach.\n */\n\nconst ALLOWED_TAGS = new Set([\n 'b',\n 'strong',\n 'i',\n 'em',\n 'u',\n 'span',\n 'div',\n 'p',\n 'br',\n 'ul',\n 'ol',\n 'li',\n 'code',\n 'pre',\n 'small',\n 'sup',\n 'sub',\n 'a',\n 'button',\n // SVG elements (for inline Lucide icons in config HTML)\n 'svg',\n 'path',\n 'circle',\n 'line',\n 'polyline',\n 'polygon',\n 'rect',\n 'g',\n]);\n\n/**\n * Syntro's own registered custom elements (`<syntro-pdp>`, tile widgets\u2026) are\n * placeable from config HTML (WI-1, 2026-07-29 WooCommerce PDP parity plan):\n * config HTML is oracle-verified (FEAT-1784879212) and an unregistered custom\n * tag is inert, so the element family is safe to pass \u2014 with the SAME\n * attribute scrubbing (on* handlers, dangerous URLs) as every allowed tag.\n * Non-syntro custom elements stay banned.\n */\nconst SYNTRO_ELEMENT_RE = /^syntro-[a-z][a-z0-9-]*$/;\nconst CONTAINS_SYNTRO_ELEMENT_RE = /<syntro-[a-z]/i;\n\nfunction isAllowedTag(tag: string): boolean {\n return ALLOWED_TAGS.has(tag) || SYNTRO_ELEMENT_RE.test(tag);\n}\n\nfunction normalizeUrlAttr(value: string): string {\n const withoutControlCharacters = Array.from(value, (character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint <= 0x1f || codePoint === 0x7f ? '' : character;\n }).join('');\n return withoutControlCharacters.trim().toLowerCase();\n}\n\nfunction isDangerousUrlAttr(name: string, value: string): boolean {\n if (name !== 'href' && name !== 'src' && name !== 'formaction') return false;\n const normalized = normalizeUrlAttr(value);\n return (\n normalized.startsWith('javascript:') ||\n normalized.startsWith('vbscript:') ||\n normalized.startsWith('data:')\n );\n}\n\nexport function sanitizeHtml(html: string): string {\n // Try native Sanitizer API first \u2014 EXCEPT when the config places a\n // syntro-* element: the native API's default config strips unknown custom\n // elements and takes explicit allow-lists only, so the pattern-based\n // family rule must run through the fallback walker below.\n const hasNative =\n typeof (window as any).Sanitizer === 'function' && !CONTAINS_SYNTRO_ELEMENT_RE.test(html);\n if (hasNative) {\n try {\n const s = new (window as any).Sanitizer({});\n const frag = s.sanitizeToFragment(html);\n const div = document.createElement('div');\n div.append(frag);\n return div.innerHTML;\n } catch {\n // Fall through to manual sanitizer\n }\n }\n\n // Conservative fallback sanitizer\n const tpl = document.createElement('template');\n tpl.innerHTML = html;\n const root = tpl.content;\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);\n const toRemove: Element[] = [];\n\n while (walker.nextNode()) {\n const el = walker.currentNode as Element;\n const tag = el.tagName.toLowerCase();\n\n if (!isAllowedTag(tag)) {\n toRemove.push(el);\n continue;\n }\n\n // Remove dangerous attributes\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n const isEvent = name.startsWith('on');\n\n if (isEvent || isDangerousUrlAttr(name, attr.value)) {\n el.removeAttribute(attr.name);\n }\n }\n }\n\n // Strip SVG elements that contained script children (XSS vector)\n const svgs = Array.from(root.querySelectorAll('svg'));\n for (const svg of svgs) {\n if (toRemove.some((el) => svg.contains(el) && el.tagName.toLowerCase() === 'script')) {\n toRemove.push(svg);\n }\n }\n\n // Remove disallowed elements but keep their children\n for (const el of toRemove) {\n while (el.firstChild) {\n el.parentNode?.insertBefore(el.firstChild, el);\n }\n el.remove();\n }\n\n return tpl.innerHTML;\n}\n", "/**\n * Adaptive Content - Runtime Module\n *\n * DOM manipulation actions: insertHtml, setText, setAttr, addClass, removeClass, setStyle.\n * These follow the hostPatcher snapshot pattern for safe reversibility.\n */\n\nimport { guardAgainstReconciliation } from './reconciliation-guard';\nimport { sanitizeHtml } from './sanitizer';\nimport type {\n ActionExecutor,\n AddClassAction,\n ExecutorResult,\n HideBySelectorAction,\n InsertHtmlAction,\n RemoveClassAction,\n SetAttrAction,\n SetStyleAction,\n SetTextAction,\n} from './types';\n\n// ============================================================================\n// Executors\n// ============================================================================\n\n/**\n * Execute an insertHtml action\n */\nexport const executeInsertHtml: ActionExecutor<InsertHtmlAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Sanitize HTML content using context utility\n const sanitizedHtml = sanitizeHtml(action.html);\n\n // Dedup: if a container for this action already exists, remove it first.\n // Uses the action label as a stable identifier across re-applications.\n // Check both inside the anchor (prepend/append) and in the parent (before/after).\n const dedupAttr = 'data-syntro-insert-label';\n const label = action.label;\n if (label) {\n const searchRoot = anchorEl.parentElement ?? anchorEl;\n const existing = Array.from(searchRoot.querySelectorAll(`[${dedupAttr}]`)).find(\n (candidate) => candidate.getAttribute(dedupAttr) === label\n );\n if (existing) existing.remove();\n }\n\n // Create container for inserted content\n const container = document.createElement('div');\n container.setAttribute('data-syntro-action-id', context.generateId());\n if (label) container.setAttribute(dedupAttr, label);\n container.innerHTML = sanitizedHtml;\n\n // Keep track of original state for replace position\n let originalContent: string | null = null;\n\n switch (action.position) {\n case 'before':\n anchorEl.insertAdjacentElement('beforebegin', container);\n break;\n case 'after':\n anchorEl.insertAdjacentElement('afterend', container);\n break;\n case 'prepend':\n anchorEl.insertBefore(container, anchorEl.firstChild);\n break;\n case 'append':\n anchorEl.appendChild(container);\n break;\n case 'replace':\n originalContent = anchorEl.innerHTML;\n anchorEl.replaceWith(container);\n break;\n }\n\n // Deep-link click handler \u2014 opens canvas + publishes deep-link event\n let deepLinkHandler: (() => void) | null = null;\n if (action.deepLink) {\n const { tileId, itemId } = action.deepLink;\n deepLinkHandler = () => {\n const handle = (window as any).SynOS?.handle;\n if (handle) {\n handle.open();\n handle.runtime?.events?.publish('notification.deep_link', { tileId, itemId });\n }\n };\n container.style.cursor = 'pointer';\n container.addEventListener('click', deepLinkHandler);\n }\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:insertHtml',\n anchorId: action.anchorId,\n position: action.position,\n });\n\n // Guard against React reconciliation removing our container.\n // The reinsert function re-applies the same insertion strategy.\n const reinsertFn = () => {\n switch (action.position) {\n case 'before':\n anchorEl.insertAdjacentElement('beforebegin', container);\n break;\n case 'after':\n anchorEl.insertAdjacentElement('afterend', container);\n break;\n case 'prepend':\n anchorEl.insertBefore(container, anchorEl.firstChild);\n break;\n case 'append':\n anchorEl.appendChild(container);\n break;\n case 'replace':\n // Cannot re-insert for replace \u2014 anchor was already replaced\n break;\n }\n };\n\n const guardCleanup = guardAgainstReconciliation(container, anchorEl, reinsertFn);\n\n return {\n cleanup: () => {\n if (deepLinkHandler) {\n container.removeEventListener('click', deepLinkHandler);\n }\n guardCleanup();\n // Skip DOM mutations if nodes are already detached (SPA navigation)\n if (!container.isConnected) return;\n try {\n if (action.position === 'replace' && originalContent !== null) {\n // Restore original element\n const restoredEl = document.createElement(anchorEl.tagName);\n restoredEl.innerHTML = originalContent;\n // Copy attributes\n Array.from(anchorEl.attributes).forEach((attr) => {\n restoredEl.setAttribute(attr.name, attr.value);\n });\n container.replaceWith(restoredEl);\n } else {\n container.remove();\n }\n } catch {\n // DOM nodes already removed by host framework \u2014 safe to ignore\n }\n },\n updateFn: (changes) => {\n if ('html' in changes && typeof changes.html === 'string') {\n container.innerHTML = sanitizeHtml(changes.html);\n }\n },\n };\n};\n\n/**\n * Walk the DOM to find the deepest descendant that uniquely carries\n * text content. Avoids destroying sibling elements (icons, images)\n * when setting text on a container like a button.\n */\nfunction findTextTarget(el: Element): Element {\n if (el.children.length === 0) return el;\n const textChildren = Array.from(el.children).filter((child) => child.textContent?.trim());\n if (textChildren.length === 1) {\n const child = textChildren[0];\n if (child.textContent?.trim() === el.textContent?.trim()) {\n return findTextTarget(child);\n }\n }\n return el;\n}\n\n/**\n * Execute a setText action\n */\nexport const executeSetText: ActionExecutor<SetTextAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n const textTarget = findTextTarget(anchorEl);\n\n // Snapshot original text\n const originalText = textTarget.textContent ?? '';\n\n // Set new text\n textTarget.textContent = action.text;\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:setText',\n anchorId: action.anchorId,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n textTarget.textContent = originalText;\n },\n updateFn: (changes) => {\n if ('text' in changes && typeof changes.text === 'string') {\n textTarget.textContent = changes.text;\n }\n },\n };\n};\n\n/**\n * Execute a setAttr action\n */\nexport const executeSetAttr: ActionExecutor<SetAttrAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Block dangerous attributes (case-insensitive)\n const lowerAttr = action.attr.toLowerCase();\n if (lowerAttr.startsWith('on')) {\n throw new Error(`Dangerous attribute not allowed: ${action.attr}`);\n }\n\n // Block dangerous URIs in URL-bearing attributes\n const isUrlAttr = lowerAttr === 'href' || lowerAttr === 'src' || lowerAttr === 'formaction';\n if (isUrlAttr) {\n const lowerValue = action.value.trim().toLowerCase();\n if (\n lowerValue.startsWith('javascript:') ||\n lowerValue.startsWith('vbscript:') ||\n lowerValue.startsWith('data:text/html')\n ) {\n throw new Error(`Dangerous URL not allowed in ${action.attr}: ${action.value}`);\n }\n }\n\n // Snapshot original attribute value\n const originalValue = anchorEl.getAttribute(action.attr);\n const hadAttribute = anchorEl.hasAttribute(action.attr);\n\n // Set new attribute\n anchorEl.setAttribute(action.attr, action.value);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:setAttr',\n anchorId: action.anchorId,\n attr: action.attr,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n if (hadAttribute && originalValue !== null) {\n anchorEl.setAttribute(action.attr, originalValue);\n } else {\n anchorEl.removeAttribute(action.attr);\n }\n },\n updateFn: (changes) => {\n if ('value' in changes && typeof changes.value === 'string') {\n anchorEl.setAttribute(action.attr, changes.value);\n }\n },\n };\n};\n\n/**\n * Execute an addClass action\n */\nexport const executeAddClass: ActionExecutor<AddClassAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Check if class was already present\n const hadClass = anchorEl.classList.contains(action.className);\n\n // Add class\n anchorEl.classList.add(action.className);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:addClass',\n anchorId: action.anchorId,\n className: action.className,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n // Only remove if we added it\n if (!hadClass) {\n anchorEl.classList.remove(action.className);\n }\n },\n };\n};\n\n/**\n * Execute a removeClass action\n */\nexport const executeRemoveClass: ActionExecutor<RemoveClassAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Check if class was present\n const hadClass = anchorEl.classList.contains(action.className);\n\n // Remove class\n anchorEl.classList.remove(action.className);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:removeClass',\n anchorId: action.anchorId,\n className: action.className,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n // Only re-add if we removed it\n if (hadClass) {\n anchorEl.classList.add(action.className);\n }\n },\n };\n};\n\n/**\n * Execute a setStyle action\n */\nexport const executeSetStyle: ActionExecutor<SetStyleAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Snapshot original styles\n const originalStyles = new Map<string, string>();\n for (const prop of Object.keys(action.styles)) {\n const current = (anchorEl as HTMLElement).style.getPropertyValue(prop);\n originalStyles.set(prop, current);\n }\n\n // Apply new styles\n //\n // setProperty() silently no-ops on invalid CSS property names (camelCase keys\n // like `justifyContent`, typos, etc). Validation rejects these at config-load\n // time, but in case a malformed config slips through we read back the value\n // and warn so the failure isn't invisible. (BUG-1779388834)\n for (const [prop, value] of Object.entries(action.styles)) {\n const el = anchorEl as HTMLElement;\n el.style.setProperty(prop, value);\n if (value !== '' && el.style.getPropertyValue(prop) === '') {\n console.warn(\n `[adaptive-content] setStyle: '${prop}: ${value}' was silently dropped \u2014 ` +\n `'${prop}' is not a recognized CSS property. Use kebab-case (e.g. 'justify-content' not 'justifyContent').`\n );\n }\n }\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:setStyle',\n anchorId: action.anchorId,\n styles: Object.keys(action.styles),\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n // Restore original styles\n for (const [prop, originalValue] of originalStyles) {\n if (originalValue) {\n (anchorEl as HTMLElement).style.setProperty(prop, originalValue);\n } else {\n (anchorEl as HTMLElement).style.removeProperty(prop);\n }\n }\n },\n updateFn: (changes) => {\n if ('styles' in changes && typeof changes.styles === 'object' && changes.styles) {\n for (const [prop, value] of Object.entries(changes.styles as Record<string, string>)) {\n (anchorEl as HTMLElement).style.setProperty(prop, value);\n }\n }\n },\n };\n};\n\n// Characters that let a selector/style value break out of the single CSS\n// rule this executor writes as raw text into a live <style> element. The\n// authoring-time Zod schema (HideBySelectorZ in runtime-sdk's\n// actions/schema.ts) already rejects these \u2014 this is a second,\n// independent guard at the actual injection sink, so a config that\n// somehow bypasses schema validation (saved before that fix shipped, a\n// hand-crafted API call, a future authoring path that forgets to\n// validate) still can't splice arbitrary attacker-controlled CSS into the\n// host page's <head> (defacement, CSS-based data exfiltration via\n// attribute selectors, clickjacking overlays).\nconst CSS_BREAKOUT_CHARS = /[{}]|\\/\\*/;\n\nfunction hasCssBreakout(selector: string, styles: Record<string, string>): boolean {\n if (CSS_BREAKOUT_CHARS.test(selector)) return true;\n return Object.values(styles).some((value) => CSS_BREAKOUT_CHARS.test(value));\n}\n\n/**\n * Execute a hideBySelector action.\n *\n * Injects `selector { prop: value !important; ... }` into a dedicated\n * `<style>` element instead of resolving a live anchor and mutating its\n * inline style. This is the stylesheet-rule counterpart to executeSetStyle:\n * use it when the target may not exist in the DOM yet, may never appear in\n * some sessions, or gets torn down and re-inserted by the host page (chat\n * widgets, cookie banners, etc). A CSS rule matches those future insertions\n * for free \u2014 no polling, no MutationObserver, and critically, no\n * anchor-not-found wait blocking anything downstream (BUG-1786758064: a\n * `content:setStyle` suppression action retrying a `.crisp-client` anchor\n * that never appeared held up canvas render for ~20s).\n *\n * `!important` is required (unlike inline styles, which always win via\n * specificity) because a stylesheet rule competes with the host page's own\n * CSS on normal specificity/source-order terms.\n */\nexport const executeHideBySelector: ActionExecutor<HideBySelectorAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n // Same route-scoping every other content action gets via resolveAnchor \u2014\n // just without the DOM resolution/waiting that comes bundled with it.\n // Executors invoked outside the full ActionEngine (tests, custom shells)\n // won't have this on context; default to \"matches\" rather than silently\n // no-op-ing the action.\n if (context.matchesAnchorRoute && !context.matchesAnchorRoute(action.anchorId)) {\n return { cleanup: () => {} };\n }\n\n if (hasCssBreakout(action.anchorId.selector, action.styles)) {\n console.error(\n '[adaptive-content] Refusing to inject content:hideBySelector rule \u2014 selector or ' +\n 'style value contains \"{\", \"}\", or \"/*\" (CSS rule breakout). This should have been ' +\n 'rejected by the authoring-time schema (HideBySelectorZ); refusing at the injection ' +\n 'sink as a second guard.',\n action.anchorId.selector,\n action.styles\n );\n return { cleanup: () => {} };\n }\n\n const declarations = Object.entries(action.styles)\n .map(([prop, value]) => `${prop}: ${value} !important;`)\n .join(' ');\n\n const styleEl = document.createElement('style');\n styleEl.setAttribute('data-syntro-action-id', context.generateId());\n styleEl.textContent = `${action.anchorId.selector} { ${declarations} }`;\n document.head.appendChild(styleEl);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:hideBySelector',\n anchorId: action.anchorId,\n styles: Object.keys(action.styles),\n });\n\n return {\n cleanup: () => {\n styleEl.remove();\n },\n updateFn: (changes) => {\n if ('styles' in changes && typeof changes.styles === 'object' && changes.styles) {\n const newStyles = changes.styles as Record<string, string>;\n if (hasCssBreakout(action.anchorId.selector, newStyles)) {\n console.error(\n '[adaptive-content] Refusing to update content:hideBySelector rule \u2014 updated ' +\n 'style value contains \"{\", \"}\", or \"/*\" (CSS rule breakout).',\n newStyles\n );\n return;\n }\n const newDeclarations = Object.entries(newStyles)\n .map(([prop, value]) => `${prop}: ${value} !important;`)\n .join(' ');\n styleEl.textContent = `${action.anchorId.selector} { ${newDeclarations} }`;\n }\n },\n };\n};\n\n// ============================================================================\n// Executor Definitions for Registration\n// ============================================================================\n\n/**\n * All executors provided by this app.\n * These are registered with the runtime's ExecutorRegistry.\n */\nexport const executors = [\n { kind: 'content:insertHtml', executor: executeInsertHtml },\n { kind: 'content:setText', executor: executeSetText },\n { kind: 'content:setAttr', executor: executeSetAttr },\n { kind: 'content:addClass', executor: executeAddClass },\n { kind: 'content:removeClass', executor: executeRemoveClass },\n { kind: 'content:setStyle', executor: executeSetStyle },\n { kind: 'content:hideBySelector', executor: executeHideBySelector },\n] as const;\n\n/**\n * App runtime manifest.\n */\nexport const runtime = {\n id: 'adaptive-content',\n version: '1.0.0',\n name: 'Content',\n description: 'DOM manipulation for text, attributes, and styles',\n executors,\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Reconciliation Guard - MutationObserver defense against React DOM removal.\n *\n * When the Syntrologie SDK inserts DOM nodes into a React-managed subtree\n * (via content:insertHtml), React's reconciliation will silently remove them\n * on the next render because they don't exist in React's virtual DOM.\n *\n * This guard watches for the removal of our inserted container and re-inserts\n * it using a debounced retry mechanism with a maximum retry count to prevent\n * infinite loops (e.g., in React StrictMode which double-invokes effects).\n */\n\nexport interface ReconciliationGuardOptions {\n /** Maximum re-insertion attempts before giving up. Default: 3 */\n maxRetries?: number;\n /** Debounce interval in ms to coalesce rapid removals. Default: 50 */\n debounceMs?: number;\n}\n\n/**\n * Watch for a container element being removed from the DOM by an external\n * framework (React, Vue, etc.) and call `reinsertFn` to re-insert it.\n *\n * @param container The element we inserted (has data-syntro-action-id)\n * @param anchor The anchor element our container is positioned relative to\n * @param reinsertFn Called when the container is removed \u2014 should re-insert it\n * @param opts Configuration for retry limits and debounce timing\n * @returns Cleanup function that disconnects the observer\n */\nexport function guardAgainstReconciliation(\n container: HTMLElement,\n anchor: HTMLElement,\n reinsertFn: () => void,\n opts?: ReconciliationGuardOptions\n): () => void {\n const maxRetries = opts?.maxRetries ?? 3;\n const debounceMs = opts?.debounceMs ?? 50;\n\n // Find the nearest parent to observe. Prefer container's parent, then anchor's.\n const observeTarget = container.parentElement ?? anchor.parentElement;\n if (!observeTarget) return () => {};\n\n let retries = 0;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let disconnected = false;\n\n const observer = new MutationObserver((mutations) => {\n if (disconnected) return;\n\n for (const mutation of mutations) {\n for (const removed of mutation.removedNodes) {\n // Check if the removed node is our container\n if (removed !== container) continue;\n\n if (retries >= maxRetries) {\n observer.disconnect();\n disconnected = true;\n return;\n }\n\n // Debounce to coalesce rapid React re-renders\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n if (disconnected) return;\n // If the anchor has been removed from the DOM (SPA navigation),\n // the page has been torn down \u2014 stop fighting React's reconciler.\n // Re-inserting into a dying subtree triggers removeChild crashes.\n if (!anchor.isConnected) {\n observer.disconnect();\n disconnected = true;\n return;\n }\n retries++;\n try {\n reinsertFn();\n } catch {\n // Re-insertion failed \u2014 stop trying\n observer.disconnect();\n disconnected = true;\n }\n }, debounceMs);\n\n return; // Found our container, no need to check further\n }\n }\n });\n\n observer.observe(observeTarget, { childList: true, subtree: true });\n\n return () => {\n disconnected = true;\n observer.disconnect();\n if (debounceTimer) clearTimeout(debounceTimer);\n };\n}\n", "/**\n * HTML Sanitizer\n *\n * Sanitizes HTML to prevent XSS attacks.\n * Uses native Sanitizer API when available, falls back to whitelist approach.\n */\n\nconst ALLOWED_TAGS = new Set([\n 'b',\n 'strong',\n 'i',\n 'em',\n 'u',\n 'span',\n 'div',\n 'p',\n 'br',\n 'ul',\n 'ol',\n 'li',\n 'code',\n 'pre',\n 'small',\n 'sup',\n 'sub',\n 'a',\n 'button',\n // SVG elements (for inline Lucide icons in config HTML)\n 'svg',\n 'path',\n 'circle',\n 'line',\n 'polyline',\n 'polygon',\n 'rect',\n 'g',\n]);\n\n/**\n * Syntro's own registered custom elements (`<syntro-pdp>`, tile widgets\u2026) are\n * placeable from config HTML (WI-1, 2026-07-29 WooCommerce PDP parity plan):\n * config HTML is oracle-verified (FEAT-1784879212) and an unregistered custom\n * tag is inert, so the element family is safe to pass \u2014 with the SAME\n * attribute scrubbing (on* handlers, dangerous URLs) as every allowed tag.\n * Non-syntro custom elements stay banned.\n */\nconst SYNTRO_ELEMENT_RE = /^syntro-[a-z][a-z0-9-]*$/;\nconst CONTAINS_SYNTRO_ELEMENT_RE = /<syntro-[a-z]/i;\n\nfunction isAllowedTag(tag: string): boolean {\n return ALLOWED_TAGS.has(tag) || SYNTRO_ELEMENT_RE.test(tag);\n}\n\nfunction normalizeUrlAttr(value: string): string {\n const withoutControlCharacters = Array.from(value, (character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint <= 0x1f || codePoint === 0x7f ? '' : character;\n }).join('');\n return withoutControlCharacters.trim().toLowerCase();\n}\n\nfunction isDangerousUrlAttr(name: string, value: string): boolean {\n if (name !== 'href' && name !== 'src' && name !== 'formaction') return false;\n const normalized = normalizeUrlAttr(value);\n return (\n normalized.startsWith('javascript:') ||\n normalized.startsWith('vbscript:') ||\n normalized.startsWith('data:')\n );\n}\n\nexport function sanitizeHtml(html: string): string {\n // Try native Sanitizer API first \u2014 EXCEPT when the config places a\n // syntro-* element: the native API's default config strips unknown custom\n // elements and takes explicit allow-lists only, so the pattern-based\n // family rule must run through the fallback walker below.\n const hasNative =\n typeof (window as any).Sanitizer === 'function' && !CONTAINS_SYNTRO_ELEMENT_RE.test(html);\n if (hasNative) {\n try {\n const s = new (window as any).Sanitizer({});\n const frag = s.sanitizeToFragment(html);\n const div = document.createElement('div');\n div.append(frag);\n return div.innerHTML;\n } catch {\n // Fall through to manual sanitizer\n }\n }\n\n // Conservative fallback sanitizer\n const tpl = document.createElement('template');\n tpl.innerHTML = html;\n const root = tpl.content;\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);\n const toRemove: Element[] = [];\n\n while (walker.nextNode()) {\n const el = walker.currentNode as Element;\n const tag = el.tagName.toLowerCase();\n\n if (!isAllowedTag(tag)) {\n toRemove.push(el);\n continue;\n }\n\n // Remove dangerous attributes\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n const isEvent = name.startsWith('on');\n\n if (isEvent || isDangerousUrlAttr(name, attr.value)) {\n el.removeAttribute(attr.name);\n }\n }\n }\n\n // Strip SVG elements that contained script children (XSS vector)\n const svgs = Array.from(root.querySelectorAll('svg'));\n for (const svg of svgs) {\n if (toRemove.some((el) => svg.contains(el) && el.tagName.toLowerCase() === 'script')) {\n toRemove.push(svg);\n }\n }\n\n // Remove disallowed elements but keep their children\n for (const el of toRemove) {\n while (el.firstChild) {\n el.parentNode?.insertBefore(el.firstChild, el);\n }\n el.remove();\n }\n\n return tpl.innerHTML;\n}\n", "/**\n * Adaptive Content - Runtime Module\n *\n * DOM manipulation actions: insertHtml, setText, setAttr, addClass, removeClass, setStyle.\n * These follow the hostPatcher snapshot pattern for safe reversibility.\n */\n\nimport { guardAgainstReconciliation } from './reconciliation-guard';\nimport { sanitizeHtml } from './sanitizer';\nimport type {\n ActionExecutor,\n AddClassAction,\n ExecutorResult,\n HideBySelectorAction,\n InsertHtmlAction,\n RemoveClassAction,\n SetAttrAction,\n SetStyleAction,\n SetTextAction,\n} from './types';\n\n// ============================================================================\n// Executors\n// ============================================================================\n\n/**\n * Execute an insertHtml action\n */\nexport const executeInsertHtml: ActionExecutor<InsertHtmlAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Sanitize HTML content using context utility\n const sanitizedHtml = sanitizeHtml(action.html);\n\n // Dedup: if a container for this action already exists, remove it first.\n // Uses the action label as a stable identifier across re-applications.\n // Check both inside the anchor (prepend/append) and in the parent (before/after).\n const dedupAttr = 'data-syntro-insert-label';\n const label = action.label;\n if (label) {\n const searchRoot = anchorEl.parentElement ?? anchorEl;\n const existing = Array.from(searchRoot.querySelectorAll(`[${dedupAttr}]`)).find(\n (candidate) => candidate.getAttribute(dedupAttr) === label\n );\n if (existing) existing.remove();\n }\n\n // Create container for inserted content\n const container = document.createElement('div');\n container.setAttribute('data-syntro-action-id', context.generateId());\n if (label) container.setAttribute(dedupAttr, label);\n container.innerHTML = sanitizedHtml;\n\n // Keep track of original state for replace position\n let originalContent: string | null = null;\n\n switch (action.position) {\n case 'before':\n anchorEl.insertAdjacentElement('beforebegin', container);\n break;\n case 'after':\n anchorEl.insertAdjacentElement('afterend', container);\n break;\n case 'prepend':\n anchorEl.insertBefore(container, anchorEl.firstChild);\n break;\n case 'append':\n anchorEl.appendChild(container);\n break;\n case 'replace':\n originalContent = anchorEl.innerHTML;\n anchorEl.replaceWith(container);\n break;\n }\n\n // Deep-link click handler \u2014 opens canvas + publishes deep-link event\n let deepLinkHandler: (() => void) | null = null;\n if (action.deepLink) {\n const { tileId, itemId } = action.deepLink;\n deepLinkHandler = () => {\n const handle = (window as any).SynOS?.handle;\n if (handle) {\n handle.open();\n handle.runtime?.events?.publish('notification.deep_link', { tileId, itemId });\n }\n };\n container.style.cursor = 'pointer';\n container.addEventListener('click', deepLinkHandler);\n }\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:insertHtml',\n anchorId: action.anchorId,\n position: action.position,\n });\n\n // Guard against React reconciliation removing our container.\n // The reinsert function re-applies the same insertion strategy.\n const reinsertFn = () => {\n switch (action.position) {\n case 'before':\n anchorEl.insertAdjacentElement('beforebegin', container);\n break;\n case 'after':\n anchorEl.insertAdjacentElement('afterend', container);\n break;\n case 'prepend':\n anchorEl.insertBefore(container, anchorEl.firstChild);\n break;\n case 'append':\n anchorEl.appendChild(container);\n break;\n case 'replace':\n // Cannot re-insert for replace \u2014 anchor was already replaced\n break;\n }\n };\n\n const guardCleanup = guardAgainstReconciliation(container, anchorEl, reinsertFn);\n\n return {\n cleanup: () => {\n if (deepLinkHandler) {\n container.removeEventListener('click', deepLinkHandler);\n }\n guardCleanup();\n // Skip DOM mutations if nodes are already detached (SPA navigation)\n if (!container.isConnected) return;\n try {\n if (action.position === 'replace' && originalContent !== null) {\n // Restore original element\n const restoredEl = document.createElement(anchorEl.tagName);\n restoredEl.innerHTML = originalContent;\n // Copy attributes\n Array.from(anchorEl.attributes).forEach((attr) => {\n restoredEl.setAttribute(attr.name, attr.value);\n });\n container.replaceWith(restoredEl);\n } else {\n container.remove();\n }\n } catch {\n // DOM nodes already removed by host framework \u2014 safe to ignore\n }\n },\n updateFn: (changes) => {\n if ('html' in changes && typeof changes.html === 'string') {\n container.innerHTML = sanitizeHtml(changes.html);\n }\n },\n };\n};\n\n/**\n * Walk the DOM to find the deepest descendant that uniquely carries\n * text content. Avoids destroying sibling elements (icons, images)\n * when setting text on a container like a button.\n */\nfunction findTextTarget(el: Element): Element {\n if (el.children.length === 0) return el;\n const textChildren = Array.from(el.children).filter((child) => child.textContent?.trim());\n if (textChildren.length === 1) {\n const child = textChildren[0];\n if (child.textContent?.trim() === el.textContent?.trim()) {\n return findTextTarget(child);\n }\n }\n return el;\n}\n\n/**\n * Execute a setText action\n */\nexport const executeSetText: ActionExecutor<SetTextAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n const textTarget = findTextTarget(anchorEl);\n\n // Snapshot original text\n const originalText = textTarget.textContent ?? '';\n\n // Set new text\n textTarget.textContent = action.text;\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:setText',\n anchorId: action.anchorId,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n textTarget.textContent = originalText;\n },\n updateFn: (changes) => {\n if ('text' in changes && typeof changes.text === 'string') {\n textTarget.textContent = changes.text;\n }\n },\n };\n};\n\n/**\n * Execute a setAttr action\n */\nexport const executeSetAttr: ActionExecutor<SetAttrAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Block dangerous attributes (case-insensitive)\n const lowerAttr = action.attr.toLowerCase();\n if (lowerAttr.startsWith('on')) {\n throw new Error(`Dangerous attribute not allowed: ${action.attr}`);\n }\n\n // Block dangerous URIs in URL-bearing attributes\n const isUrlAttr = lowerAttr === 'href' || lowerAttr === 'src' || lowerAttr === 'formaction';\n if (isUrlAttr) {\n const lowerValue = action.value.trim().toLowerCase();\n if (\n lowerValue.startsWith('javascript:') ||\n lowerValue.startsWith('vbscript:') ||\n lowerValue.startsWith('data:text/html')\n ) {\n throw new Error(`Dangerous URL not allowed in ${action.attr}: ${action.value}`);\n }\n }\n\n // Snapshot original attribute value\n const originalValue = anchorEl.getAttribute(action.attr);\n const hadAttribute = anchorEl.hasAttribute(action.attr);\n\n // Set new attribute\n anchorEl.setAttribute(action.attr, action.value);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:setAttr',\n anchorId: action.anchorId,\n attr: action.attr,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n if (hadAttribute && originalValue !== null) {\n anchorEl.setAttribute(action.attr, originalValue);\n } else {\n anchorEl.removeAttribute(action.attr);\n }\n },\n updateFn: (changes) => {\n if ('value' in changes && typeof changes.value === 'string') {\n anchorEl.setAttribute(action.attr, changes.value);\n }\n },\n };\n};\n\n/**\n * Execute an addClass action\n */\nexport const executeAddClass: ActionExecutor<AddClassAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Check if class was already present\n const hadClass = anchorEl.classList.contains(action.className);\n\n // Add class\n anchorEl.classList.add(action.className);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:addClass',\n anchorId: action.anchorId,\n className: action.className,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n // Only remove if we added it\n if (!hadClass) {\n anchorEl.classList.remove(action.className);\n }\n },\n };\n};\n\n/**\n * Execute a removeClass action\n */\nexport const executeRemoveClass: ActionExecutor<RemoveClassAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Check if class was present\n const hadClass = anchorEl.classList.contains(action.className);\n\n // Remove class\n anchorEl.classList.remove(action.className);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:removeClass',\n anchorId: action.anchorId,\n className: action.className,\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n // Only re-add if we removed it\n if (hadClass) {\n anchorEl.classList.add(action.className);\n }\n },\n };\n};\n\n/**\n * Execute a setStyle action\n */\nexport const executeSetStyle: ActionExecutor<SetStyleAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n let anchorEl = context.resolveAnchor(action.anchorId);\n if (!anchorEl && context.waitForAnchor) {\n anchorEl = await context.waitForAnchor(action.anchorId, 3000);\n }\n if (!anchorEl) {\n console.warn(`[adaptive-content] Anchor not found after waiting: ${action.anchorId.selector}`);\n return { cleanup: () => {} };\n }\n\n // Snapshot original styles\n const originalStyles = new Map<string, string>();\n for (const prop of Object.keys(action.styles)) {\n const current = (anchorEl as HTMLElement).style.getPropertyValue(prop);\n originalStyles.set(prop, current);\n }\n\n // Apply new styles\n //\n // setProperty() silently no-ops on invalid CSS property names (camelCase keys\n // like `justifyContent`, typos, etc). Validation rejects these at config-load\n // time, but in case a malformed config slips through we read back the value\n // and warn so the failure isn't invisible. (BUG-1779388834)\n for (const [prop, value] of Object.entries(action.styles)) {\n const el = anchorEl as HTMLElement;\n el.style.setProperty(prop, value);\n if (value !== '' && el.style.getPropertyValue(prop) === '') {\n console.warn(\n `[adaptive-content] setStyle: '${prop}: ${value}' was silently dropped \u2014 ` +\n `'${prop}' is not a recognized CSS property. Use kebab-case (e.g. 'justify-content' not 'justifyContent').`\n );\n }\n }\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:setStyle',\n anchorId: action.anchorId,\n styles: Object.keys(action.styles),\n });\n\n return {\n cleanup: () => {\n if (!anchorEl.isConnected) return;\n // Restore original styles\n for (const [prop, originalValue] of originalStyles) {\n if (originalValue) {\n (anchorEl as HTMLElement).style.setProperty(prop, originalValue);\n } else {\n (anchorEl as HTMLElement).style.removeProperty(prop);\n }\n }\n },\n updateFn: (changes) => {\n if ('styles' in changes && typeof changes.styles === 'object' && changes.styles) {\n for (const [prop, value] of Object.entries(changes.styles as Record<string, string>)) {\n (anchorEl as HTMLElement).style.setProperty(prop, value);\n }\n }\n },\n };\n};\n\n// Characters that let a selector/style value break out of the single CSS\n// rule this executor writes as raw text into a live <style> element. The\n// authoring-time Zod schema (HideBySelectorZ in runtime-sdk's\n// actions/schema.ts) already rejects these \u2014 this is a second,\n// independent guard at the actual injection sink, so a config that\n// somehow bypasses schema validation (saved before that fix shipped, a\n// hand-crafted API call, a future authoring path that forgets to\n// validate) still can't splice arbitrary attacker-controlled CSS into the\n// host page's <head> (defacement, CSS-based data exfiltration via\n// attribute selectors, clickjacking overlays).\nconst CSS_BREAKOUT_CHARS = /[{}]|\\/\\*/;\n\nfunction hasCssBreakout(selector: string, styles: Record<string, string>): boolean {\n if (CSS_BREAKOUT_CHARS.test(selector)) return true;\n return Object.values(styles).some((value) => CSS_BREAKOUT_CHARS.test(value));\n}\n\n/**\n * Execute a hideBySelector action.\n *\n * Injects `selector { prop: value !important; ... }` into a dedicated\n * `<style>` element instead of resolving a live anchor and mutating its\n * inline style. This is the stylesheet-rule counterpart to executeSetStyle:\n * use it when the target may not exist in the DOM yet, may never appear in\n * some sessions, or gets torn down and re-inserted by the host page (chat\n * widgets, cookie banners, etc). A CSS rule matches those future insertions\n * for free \u2014 no polling, no MutationObserver, and critically, no\n * anchor-not-found wait blocking anything downstream (BUG-1786758064: a\n * `content:setStyle` suppression action retrying a `.crisp-client` anchor\n * that never appeared held up canvas render for ~20s).\n *\n * `!important` is required (unlike inline styles, which always win via\n * specificity) because a stylesheet rule competes with the host page's own\n * CSS on normal specificity/source-order terms.\n *\n * `!important` alone is not enough (BUG-1789078665): a vendor stylesheet\n * loaded AFTER ours with the same selector and its own `!important` \u2014\n * Crisp's `.crisp-client{display:block!important}`, arriving 8s+ after\n * boot on a WP-Rocket \"delay JavaScript\" host \u2014 ties on specificity and\n * importance, and the later source order wins. The rule is therefore\n * written as `:is(<selector>):not(#syntro-hide-by-selector)`: `:is()`\n * keeps a selector list intact, `:not(#\u2026)` adds id-level specificity, so\n * no class/attribute rule can out-rank it whenever it loads.\n */\nconst HIDE_BY_SELECTOR_RULE_ID = 'syntro-hide-by-selector';\n\nfunction hideBySelectorRule(selector: string, styles: Record<string, string>): string {\n const declarations = Object.entries(styles)\n .map(([prop, value]) => `${prop}: ${value} !important;`)\n .join(' ');\n return `:is(${selector}):not(#${HIDE_BY_SELECTOR_RULE_ID}) { ${declarations} }`;\n}\n\nexport const executeHideBySelector: ActionExecutor<HideBySelectorAction> = async (\n action,\n context\n): Promise<ExecutorResult> => {\n // Same route-scoping every other content action gets via resolveAnchor \u2014\n // just without the DOM resolution/waiting that comes bundled with it.\n // Executors invoked outside the full ActionEngine (tests, custom shells)\n // won't have this on context; default to \"matches\" rather than silently\n // no-op-ing the action.\n if (context.matchesAnchorRoute && !context.matchesAnchorRoute(action.anchorId)) {\n return { cleanup: () => {} };\n }\n\n if (hasCssBreakout(action.anchorId.selector, action.styles)) {\n console.error(\n '[adaptive-content] Refusing to inject content:hideBySelector rule \u2014 selector or ' +\n 'style value contains \"{\", \"}\", or \"/*\" (CSS rule breakout). This should have been ' +\n 'rejected by the authoring-time schema (HideBySelectorZ); refusing at the injection ' +\n 'sink as a second guard.',\n action.anchorId.selector,\n action.styles\n );\n return { cleanup: () => {} };\n }\n\n const styleEl = document.createElement('style');\n styleEl.setAttribute('data-syntro-action-id', context.generateId());\n styleEl.textContent = hideBySelectorRule(action.anchorId.selector, action.styles);\n document.head.appendChild(styleEl);\n\n context.publishEvent('action.applied', {\n id: context.generateId(),\n kind: 'content:hideBySelector',\n anchorId: action.anchorId,\n styles: Object.keys(action.styles),\n });\n\n return {\n cleanup: () => {\n styleEl.remove();\n },\n updateFn: (changes) => {\n if ('styles' in changes && typeof changes.styles === 'object' && changes.styles) {\n const newStyles = changes.styles as Record<string, string>;\n if (hasCssBreakout(action.anchorId.selector, newStyles)) {\n console.error(\n '[adaptive-content] Refusing to update content:hideBySelector rule \u2014 updated ' +\n 'style value contains \"{\", \"}\", or \"/*\" (CSS rule breakout).',\n newStyles\n );\n return;\n }\n styleEl.textContent = hideBySelectorRule(action.anchorId.selector, newStyles);\n }\n },\n };\n};\n\n// ============================================================================\n// Executor Definitions for Registration\n// ============================================================================\n\n/**\n * All executors provided by this app.\n * These are registered with the runtime's ExecutorRegistry.\n */\nexport const executors = [\n { kind: 'content:insertHtml', executor: executeInsertHtml },\n { kind: 'content:setText', executor: executeSetText },\n { kind: 'content:setAttr', executor: executeSetAttr },\n { kind: 'content:addClass', executor: executeAddClass },\n { kind: 'content:removeClass', executor: executeRemoveClass },\n { kind: 'content:setStyle', executor: executeSetStyle },\n { kind: 'content:hideBySelector', executor: executeHideBySelector },\n] as const;\n\n/**\n * App runtime manifest.\n */\nexport const runtime = {\n id: 'adaptive-content',\n version: '1.0.0',\n name: 'Content',\n description: 'DOM manipulation for text, attributes, and styles',\n executors,\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syntrologie/adapt-content",
3
- "version": "2.52.0",
3
+ "version": "2.54.0",
4
4
  "description": "Adaptive Content app - DOM manipulation actions for text, attributes, and styles",
5
5
  "license": "Proprietary",
6
6
  "private": false,