cuoral-ionic 0.0.9 → 0.1.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/README.md +120 -12
- package/dist/cuoral.d.ts +1 -0
- package/dist/cuoral.d.ts.map +1 -1
- package/dist/cuoral.js +12 -5
- package/dist/index.esm.js +219 -18
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +219 -18
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +8 -0
- package/dist/intelligence.d.ts.map +1 -1
- package/dist/intelligence.js +201 -8
- package/dist/modal.d.ts +2 -1
- package/dist/modal.d.ts.map +1 -1
- package/dist/modal.js +6 -5
- package/package.json +1 -1
- package/src/cuoral.ts +16 -5
- package/src/intelligence.ts +231 -8
- package/src/modal.ts +7 -5
package/dist/index.js
CHANGED
|
@@ -382,10 +382,11 @@ class CuoralBridge {
|
|
|
382
382
|
* Handles full-screen modal display with floating button
|
|
383
383
|
*/
|
|
384
384
|
class CuoralModal {
|
|
385
|
-
constructor(widgetUrl, showFloatingButton = true) {
|
|
385
|
+
constructor(widgetUrl, showFloatingButton = true, primaryColor = '#007AFF') {
|
|
386
386
|
this.isOpen = false;
|
|
387
387
|
this.widgetUrl = widgetUrl;
|
|
388
388
|
this.showFloatingButton = showFloatingButton;
|
|
389
|
+
this.primaryColor = primaryColor;
|
|
389
390
|
}
|
|
390
391
|
/**
|
|
391
392
|
* Update the widget URL (e.g., to include new session_id)
|
|
@@ -453,12 +454,12 @@ class CuoralModal {
|
|
|
453
454
|
width: '56px',
|
|
454
455
|
height: '56px',
|
|
455
456
|
borderRadius: '50%',
|
|
456
|
-
backgroundColor:
|
|
457
|
+
backgroundColor: this.primaryColor,
|
|
457
458
|
display: 'flex',
|
|
458
459
|
alignItems: 'center',
|
|
459
460
|
justifyContent: 'center',
|
|
460
461
|
cursor: 'pointer',
|
|
461
|
-
boxShadow:
|
|
462
|
+
boxShadow: `0 4px 12px ${this.primaryColor}66`, // 40% opacity
|
|
462
463
|
zIndex: '999999',
|
|
463
464
|
transition: 'transform 0.2s, box-shadow 0.2s'
|
|
464
465
|
});
|
|
@@ -466,13 +467,13 @@ class CuoralModal {
|
|
|
466
467
|
this.floatingButton.addEventListener('mouseenter', () => {
|
|
467
468
|
if (this.floatingButton) {
|
|
468
469
|
this.floatingButton.style.transform = 'scale(1.1)';
|
|
469
|
-
this.floatingButton.style.boxShadow =
|
|
470
|
+
this.floatingButton.style.boxShadow = `0 6px 16px ${this.primaryColor}80`; // 50% opacity
|
|
470
471
|
}
|
|
471
472
|
});
|
|
472
473
|
this.floatingButton.addEventListener('mouseleave', () => {
|
|
473
474
|
if (this.floatingButton) {
|
|
474
475
|
this.floatingButton.style.transform = 'scale(1)';
|
|
475
|
-
this.floatingButton.style.boxShadow =
|
|
476
|
+
this.floatingButton.style.boxShadow = `0 4px 12px ${this.primaryColor}66`; // 40% opacity
|
|
476
477
|
}
|
|
477
478
|
});
|
|
478
479
|
// Click to open modal
|
|
@@ -671,11 +672,13 @@ class CuoralIntelligence {
|
|
|
671
672
|
// Session replay state
|
|
672
673
|
this.rrwebStopFn = null;
|
|
673
674
|
this.rrwebEvents = [];
|
|
675
|
+
this.rrwebEmit = null; // Store emit function for custom events
|
|
674
676
|
this.customEvents = [];
|
|
675
677
|
this.sessionReplayTimer = null;
|
|
676
678
|
this.clickTimestamps = new Map();
|
|
677
679
|
this.rageClickThreshold = 5; // 5 rapid clicks
|
|
678
680
|
this.rageClickWindowMs = 2000; // Within 2 seconds
|
|
681
|
+
this.shadowDOMNodeMap = new Map(); // Map elements to unique IDs
|
|
679
682
|
this.sessionId = sessionId;
|
|
680
683
|
}
|
|
681
684
|
/**
|
|
@@ -1185,27 +1188,65 @@ class CuoralIntelligence {
|
|
|
1185
1188
|
}
|
|
1186
1189
|
/**
|
|
1187
1190
|
* Setup session replay with rrweb
|
|
1191
|
+
* Captures everything for maximum replay fidelity
|
|
1188
1192
|
*/
|
|
1189
1193
|
setupSessionReplay() {
|
|
1190
1194
|
if (!this.sessionId) {
|
|
1191
1195
|
console.warn('[Cuoral Intelligence] Session replay requires a session ID');
|
|
1192
1196
|
return;
|
|
1193
1197
|
}
|
|
1194
|
-
// Start rrweb recording
|
|
1198
|
+
// Start rrweb recording with aggressive capture settings
|
|
1195
1199
|
this.rrwebStopFn = rrweb__namespace.record({
|
|
1196
1200
|
emit: (event) => {
|
|
1197
1201
|
this.rrwebEvents.push(event);
|
|
1202
|
+
this.rrwebEmit = this.rrwebEmit || ((e) => this.rrwebEvents.push(e)); // Store emit reference
|
|
1198
1203
|
},
|
|
1199
|
-
|
|
1204
|
+
// Take full snapshots more frequently to catch dynamic content
|
|
1205
|
+
checkoutEveryNms: 30000, // Every 30 seconds (was 60s)
|
|
1206
|
+
// Capture everything - minimize sampling throttling
|
|
1200
1207
|
sampling: {
|
|
1201
|
-
scroll:
|
|
1202
|
-
media: 800
|
|
1203
|
-
input: 'last', // Only record final input value (privacy)
|
|
1208
|
+
scroll: 50, // Capture scroll very frequently (was 150)
|
|
1209
|
+
media: 200, // Capture media interactions frequently (was 800)
|
|
1210
|
+
input: 'last', // Only record final input value (privacy for passwords)
|
|
1211
|
+
mousemove: true, // Capture all mouse movements
|
|
1212
|
+
mouseInteraction: true, // Capture all mouse interactions
|
|
1213
|
+
},
|
|
1214
|
+
// Privacy: Only mask sensitive inputs (passwords, credit cards)
|
|
1215
|
+
// Everything else is captured for maximum replay fidelity
|
|
1216
|
+
maskAllInputs: false, // Don't mask all inputs
|
|
1217
|
+
maskInputOptions: {
|
|
1218
|
+
password: true, // Mask password fields
|
|
1219
|
+
email: false, // Capture emails
|
|
1220
|
+
text: false, // Capture text inputs
|
|
1221
|
+
textarea: false, // Capture textareas
|
|
1222
|
+
select: false, // Capture select dropdowns
|
|
1223
|
+
// Mask credit card patterns
|
|
1224
|
+
},
|
|
1225
|
+
// Capture all content types
|
|
1226
|
+
recordCanvas: true, // Capture canvas elements (charts, games, etc.)
|
|
1227
|
+
// Inline everything for perfect replay
|
|
1228
|
+
inlineStylesheet: true, // Inline all external stylesheets
|
|
1229
|
+
inlineImages: true, // Inline images as base64 (larger but more reliable)
|
|
1230
|
+
collectFonts: true, // Capture custom fonts
|
|
1231
|
+
// Block/ignore classes - customers can add these to sensitive elements
|
|
1232
|
+
blockClass: 'cuoral-block', // Add to elements that should be blocked (replaced with placeholder)
|
|
1233
|
+
ignoreClass: 'cuoral-ignore', // Add to elements that should be ignored (not captured)
|
|
1234
|
+
// Capture mutations aggressively
|
|
1235
|
+
slimDOMOptions: {
|
|
1236
|
+
// Don't slim anything - capture full fidelity
|
|
1237
|
+
script: false, // Keep scripts
|
|
1238
|
+
comment: false, // Keep comments
|
|
1239
|
+
headFavicon: false, // Keep favicons
|
|
1240
|
+
headWhitespace: false, // Keep whitespace
|
|
1241
|
+
headMetaSocial: false, // Keep meta tags
|
|
1242
|
+
headMetaRobots: false, // Keep robots meta
|
|
1243
|
+
headMetaHttpEquiv: false, // Keep http-equiv
|
|
1244
|
+
headMetaAuthorship: false, // Keep authorship
|
|
1245
|
+
headMetaDescKeywords: false, // Keep description/keywords
|
|
1204
1246
|
},
|
|
1205
|
-
maskAllInputs: true, // Mask sensitive inputs (privacy)
|
|
1206
|
-
blockClass: 'cuoral-block',
|
|
1207
|
-
ignoreClass: 'cuoral-ignore',
|
|
1208
1247
|
});
|
|
1248
|
+
// Setup Shadow DOM observer to capture Ionic components
|
|
1249
|
+
this.setupShadowDOMObserver();
|
|
1209
1250
|
// Setup custom event tracking
|
|
1210
1251
|
this.setupClickTracking();
|
|
1211
1252
|
this.setupScrollTracking();
|
|
@@ -1357,6 +1398,159 @@ class CuoralIntelligence {
|
|
|
1357
1398
|
});
|
|
1358
1399
|
});
|
|
1359
1400
|
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Setup Shadow DOM observer to capture Ionic component content
|
|
1403
|
+
* Emits custom rrweb events with Shadow DOM snapshots for viewer reconstruction
|
|
1404
|
+
*/
|
|
1405
|
+
setupShadowDOMObserver() {
|
|
1406
|
+
let shadowNodeIdCounter = 1;
|
|
1407
|
+
let captureTimeout = null;
|
|
1408
|
+
const captureShadowDOM = (immediate = false) => {
|
|
1409
|
+
// Debounce rapid calls (e.g., during scroll)
|
|
1410
|
+
if (!immediate && captureTimeout) {
|
|
1411
|
+
clearTimeout(captureTimeout);
|
|
1412
|
+
}
|
|
1413
|
+
const doCapture = () => {
|
|
1414
|
+
if (!this.rrwebEmit)
|
|
1415
|
+
return;
|
|
1416
|
+
const shadowDOMData = [];
|
|
1417
|
+
const elementsWithShadowDOM = document.querySelectorAll('*');
|
|
1418
|
+
elementsWithShadowDOM.forEach((element) => {
|
|
1419
|
+
if (element.shadowRoot) {
|
|
1420
|
+
try {
|
|
1421
|
+
// Assign unique ID to element if it doesn't have one
|
|
1422
|
+
if (!this.shadowDOMNodeMap.has(element)) {
|
|
1423
|
+
this.shadowDOMNodeMap.set(element, shadowNodeIdCounter++);
|
|
1424
|
+
}
|
|
1425
|
+
const nodeId = this.shadowDOMNodeMap.get(element);
|
|
1426
|
+
const tagName = element.tagName.toLowerCase();
|
|
1427
|
+
// Capture Shadow DOM HTML
|
|
1428
|
+
const shadowHTML = element.shadowRoot.innerHTML;
|
|
1429
|
+
// Capture ALL styles from Shadow DOM
|
|
1430
|
+
const styleSheets = [];
|
|
1431
|
+
// 1. Capture inline <style> tags
|
|
1432
|
+
const shadowStyles = element.shadowRoot.querySelectorAll('style');
|
|
1433
|
+
shadowStyles.forEach((styleEl) => {
|
|
1434
|
+
if (styleEl.textContent) {
|
|
1435
|
+
styleSheets.push(styleEl.textContent);
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
// 2. Capture adoptedStyleSheets (modern CSS API)
|
|
1439
|
+
if (element.shadowRoot.adoptedStyleSheets) {
|
|
1440
|
+
try {
|
|
1441
|
+
element.shadowRoot.adoptedStyleSheets.forEach((sheet) => {
|
|
1442
|
+
try {
|
|
1443
|
+
const rules = Array.from(sheet.cssRules).map(rule => rule.cssText).join('\n');
|
|
1444
|
+
if (rules)
|
|
1445
|
+
styleSheets.push(rules);
|
|
1446
|
+
}
|
|
1447
|
+
catch (e) {
|
|
1448
|
+
// CORS blocked stylesheet
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
catch (e) {
|
|
1453
|
+
// Browser doesn't support adoptedStyleSheets
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
// 3. Capture external <link> stylesheets in Shadow DOM
|
|
1457
|
+
const shadowLinks = element.shadowRoot.querySelectorAll('link[rel="stylesheet"]');
|
|
1458
|
+
shadowLinks.forEach((linkEl) => {
|
|
1459
|
+
const href = linkEl.href;
|
|
1460
|
+
if (href) {
|
|
1461
|
+
// Store link href so dashboard can also load it
|
|
1462
|
+
styleSheets.push(`/* @import url("${href}"); */`);
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
// 4. Capture CSS custom properties from host element
|
|
1466
|
+
// Ionic components use CSS variables extensively (--ion-color-*, etc.)
|
|
1467
|
+
const computedStyle = window.getComputedStyle(element);
|
|
1468
|
+
const cssVariables = [];
|
|
1469
|
+
// Get all CSS custom properties from host
|
|
1470
|
+
for (let i = 0; i < computedStyle.length; i++) {
|
|
1471
|
+
const propertyName = computedStyle[i];
|
|
1472
|
+
if (propertyName.startsWith('--')) {
|
|
1473
|
+
const propertyValue = computedStyle.getPropertyValue(propertyName).trim();
|
|
1474
|
+
if (propertyValue) {
|
|
1475
|
+
cssVariables.push(`${propertyName}: ${propertyValue};`);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
// Add CSS variables as a :host rule
|
|
1480
|
+
if (cssVariables.length > 0) {
|
|
1481
|
+
styleSheets.push(`:host { ${cssVariables.join(' ')} }`);
|
|
1482
|
+
}
|
|
1483
|
+
// Get element's CSS selector for reconstruction
|
|
1484
|
+
const selector = this.getElementSelector(element);
|
|
1485
|
+
// Capture element attributes (Ionic uses color="primary", mode="ios", etc.)
|
|
1486
|
+
const attributes = {};
|
|
1487
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
1488
|
+
const attr = element.attributes[i];
|
|
1489
|
+
// Skip internal attributes
|
|
1490
|
+
if (!attr.name.startsWith('data-cuoral') && !attr.name.startsWith('ng-')) {
|
|
1491
|
+
attributes[attr.name] = attr.value;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
// Create Shadow DOM snapshot
|
|
1495
|
+
shadowDOMData.push({
|
|
1496
|
+
nodeId,
|
|
1497
|
+
tagName,
|
|
1498
|
+
selector,
|
|
1499
|
+
attributes, // NEW: Include element attributes
|
|
1500
|
+
shadowHTML,
|
|
1501
|
+
styleSheets,
|
|
1502
|
+
timestamp: Date.now(),
|
|
1503
|
+
});
|
|
1504
|
+
// Also set data attribute as fallback
|
|
1505
|
+
element.setAttribute('data-cuoral-shadow-id', String(nodeId));
|
|
1506
|
+
}
|
|
1507
|
+
catch (error) {
|
|
1508
|
+
// Shadow DOM might be closed
|
|
1509
|
+
console.warn('[Cuoral Intelligence] Failed to capture Shadow DOM:', error);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
// Emit custom event with Shadow DOM data
|
|
1514
|
+
if (shadowDOMData.length > 0 && this.rrwebEmit) {
|
|
1515
|
+
this.rrwebEmit({
|
|
1516
|
+
type: 5, // CustomEvent type in rrweb
|
|
1517
|
+
data: {
|
|
1518
|
+
tag: 'cuoral-shadow-dom',
|
|
1519
|
+
payload: {
|
|
1520
|
+
shadows: shadowDOMData,
|
|
1521
|
+
url: window.location.href,
|
|
1522
|
+
},
|
|
1523
|
+
},
|
|
1524
|
+
timestamp: Date.now(),
|
|
1525
|
+
});
|
|
1526
|
+
console.log(`[Cuoral Intelligence] Captured ${shadowDOMData.length} Shadow DOM elements`);
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
if (immediate) {
|
|
1530
|
+
doCapture();
|
|
1531
|
+
}
|
|
1532
|
+
else {
|
|
1533
|
+
// Debounce: wait 500ms after last call
|
|
1534
|
+
captureTimeout = setTimeout(doCapture, 500);
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
// Run immediately on init
|
|
1538
|
+
captureShadowDOM(true);
|
|
1539
|
+
// Observe DOM mutations to catch dynamically added Shadow DOM elements
|
|
1540
|
+
const observer = new MutationObserver(() => {
|
|
1541
|
+
captureShadowDOM(false); // Debounced
|
|
1542
|
+
});
|
|
1543
|
+
observer.observe(document.body, {
|
|
1544
|
+
childList: true,
|
|
1545
|
+
subtree: true,
|
|
1546
|
+
});
|
|
1547
|
+
// Capture on scroll events (debounced)
|
|
1548
|
+
window.addEventListener('scroll', () => captureShadowDOM(false), { passive: true });
|
|
1549
|
+
// Also listen for Ionic scroll events
|
|
1550
|
+
document.addEventListener('ionScroll', () => captureShadowDOM(false), { passive: true });
|
|
1551
|
+
// Periodic capture as fallback (every 5 seconds now, since we have scroll capture)
|
|
1552
|
+
setInterval(() => captureShadowDOM(true), 5000);
|
|
1553
|
+
}
|
|
1360
1554
|
/**
|
|
1361
1555
|
* Track custom business events (flows, features, etc.)
|
|
1362
1556
|
*/
|
|
@@ -1485,10 +1679,7 @@ class Cuoral {
|
|
|
1485
1679
|
if (options.lastName)
|
|
1486
1680
|
params.set('last_name', options.lastName);
|
|
1487
1681
|
const widgetUrl = `${baseUrl}?${params.toString()}`;
|
|
1488
|
-
//
|
|
1489
|
-
if (this.options.useModal) {
|
|
1490
|
-
this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
|
|
1491
|
-
}
|
|
1682
|
+
// Note: Modal will be created in initialize() after fetching primary color from backend
|
|
1492
1683
|
// Initialize bridge and recorder
|
|
1493
1684
|
this.bridge = new CuoralBridge({
|
|
1494
1685
|
widgetUrl,
|
|
@@ -1509,6 +1700,7 @@ class Cuoral {
|
|
|
1509
1700
|
*/
|
|
1510
1701
|
async initialize() {
|
|
1511
1702
|
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
1703
|
+
// This also fetches the organization's primary color
|
|
1512
1704
|
await this.initializeIntelligence();
|
|
1513
1705
|
console.log('[Cuoral] Initialize - Session ID:', localStorage.getItem('__x_loadID'));
|
|
1514
1706
|
// Setup localStorage listener to detect when widget changes session
|
|
@@ -1517,7 +1709,7 @@ class Cuoral {
|
|
|
1517
1709
|
// Recreate modal if it was destroyed (e.g., after clearSession)
|
|
1518
1710
|
if (this.options.useModal && !this.modal) {
|
|
1519
1711
|
const widgetUrl = this.getWidgetUrl();
|
|
1520
|
-
this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
|
|
1712
|
+
this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton, this.options.primaryColor);
|
|
1521
1713
|
}
|
|
1522
1714
|
// Update modal URL with session ID
|
|
1523
1715
|
if (this.modal) {
|
|
@@ -1542,6 +1734,15 @@ class Cuoral {
|
|
|
1542
1734
|
}
|
|
1543
1735
|
// Fetch session configuration
|
|
1544
1736
|
const config = await this.fetchSessionConfiguration(sessionId);
|
|
1737
|
+
// Extract organization's primary color from config if available
|
|
1738
|
+
if (!this.options.primaryColor && config?.color) {
|
|
1739
|
+
this.options.primaryColor = config.color;
|
|
1740
|
+
console.log('[Cuoral] Using organization primary color:', this.options.primaryColor);
|
|
1741
|
+
}
|
|
1742
|
+
else if (!this.options.primaryColor) {
|
|
1743
|
+
this.options.primaryColor = '#007AFF';
|
|
1744
|
+
console.log('[Cuoral] Using default primary color');
|
|
1745
|
+
}
|
|
1545
1746
|
// If session was invalid/expired, try to initiate a new one
|
|
1546
1747
|
if (!config && !localStorage.getItem('__x_loadID')) {
|
|
1547
1748
|
sessionId = await this.initiateSession();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/intelligence.d.ts
CHANGED
|
@@ -11,11 +11,13 @@ export declare class CuoralIntelligence {
|
|
|
11
11
|
private originalXMLHttpRequest;
|
|
12
12
|
private rrwebStopFn;
|
|
13
13
|
private rrwebEvents;
|
|
14
|
+
private rrwebEmit;
|
|
14
15
|
private customEvents;
|
|
15
16
|
private sessionReplayTimer;
|
|
16
17
|
private clickTimestamps;
|
|
17
18
|
private rageClickThreshold;
|
|
18
19
|
private rageClickWindowMs;
|
|
20
|
+
private shadowDOMNodeMap;
|
|
19
21
|
constructor(sessionId: string);
|
|
20
22
|
/**
|
|
21
23
|
* Update the session ID (e.g., when user logs in)
|
|
@@ -75,6 +77,7 @@ export declare class CuoralIntelligence {
|
|
|
75
77
|
private setupNativeErrorCapture;
|
|
76
78
|
/**
|
|
77
79
|
* Setup session replay with rrweb
|
|
80
|
+
* Captures everything for maximum replay fidelity
|
|
78
81
|
*/
|
|
79
82
|
private setupSessionReplay;
|
|
80
83
|
/**
|
|
@@ -89,6 +92,11 @@ export declare class CuoralIntelligence {
|
|
|
89
92
|
* Setup form tracking
|
|
90
93
|
*/
|
|
91
94
|
private setupFormTracking;
|
|
95
|
+
/**
|
|
96
|
+
* Setup Shadow DOM observer to capture Ionic component content
|
|
97
|
+
* Emits custom rrweb events with Shadow DOM snapshots for viewer reconstruction
|
|
98
|
+
*/
|
|
99
|
+
private setupShadowDOMObserver;
|
|
92
100
|
/**
|
|
93
101
|
* Track custom business events (flows, features, etc.)
|
|
94
102
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligence.d.ts","sourceRoot":"","sources":["../src/intelligence.ts"],"names":[],"mappings":"AA0EA,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAWZ;IAEF,OAAO,CAAC,MAAM,CAKZ;IAEF,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,qBAAqB,CAAc;IAC3C,OAAO,CAAC,kBAAkB,CAAM;IAChC,OAAO,CAAC,aAAa,CAAa;IAGlC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,sBAAsB,CAAa;IAG3C,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,iBAAiB,CAAQ;
|
|
1
|
+
{"version":3,"file":"intelligence.d.ts","sourceRoot":"","sources":["../src/intelligence.ts"],"names":[],"mappings":"AA0EA,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAWZ;IAEF,OAAO,CAAC,MAAM,CAKZ;IAEF,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,qBAAqB,CAAc;IAC3C,OAAO,CAAC,kBAAkB,CAAM;IAChC,OAAO,CAAC,aAAa,CAAa;IAGlC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,sBAAsB,CAAa;IAG3C,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,SAAS,CAAuC;IACxD,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,gBAAgB,CAAmC;gBAE/C,SAAS,EAAE,MAAM;IAI7B;;OAEG;IACI,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAkBlD;;OAEG;IACI,IAAI,IAAI,IAAI;IAgBnB;;OAEG;IACI,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAc1D;;OAEG;IACI,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAS7E;;OAEG;IACI,KAAK,IAAI,IAAI;IAMpB;;OAEG;IACI,OAAO,IAAI,IAAI;IA0CtB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAoFjC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA8H9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;OAEG;IACH,OAAO,CAAC,YAAY;IAyCpB;;OAEG;IACH,OAAO,CAAC,UAAU;IAqFlB;;OAEG;YACW,aAAa;IAiC3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA8E1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAoD1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwC3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA6DzB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA2K9B;;OAEG;IACI,gBAAgB,CACrB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EACpC,eAAe,CAAC,EAAE,MAAM,EACxB,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI;IAgBP;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAwB/B;;OAEG;YACW,sBAAsB;IAkBpC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAe1B;;OAEG;IACH,OAAO,CAAC,WAAW;CAOpB"}
|
package/dist/intelligence.js
CHANGED
|
@@ -35,11 +35,13 @@ export class CuoralIntelligence {
|
|
|
35
35
|
// Session replay state
|
|
36
36
|
this.rrwebStopFn = null;
|
|
37
37
|
this.rrwebEvents = [];
|
|
38
|
+
this.rrwebEmit = null; // Store emit function for custom events
|
|
38
39
|
this.customEvents = [];
|
|
39
40
|
this.sessionReplayTimer = null;
|
|
40
41
|
this.clickTimestamps = new Map();
|
|
41
42
|
this.rageClickThreshold = 5; // 5 rapid clicks
|
|
42
43
|
this.rageClickWindowMs = 2000; // Within 2 seconds
|
|
44
|
+
this.shadowDOMNodeMap = new Map(); // Map elements to unique IDs
|
|
43
45
|
this.sessionId = sessionId;
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
@@ -549,27 +551,65 @@ export class CuoralIntelligence {
|
|
|
549
551
|
}
|
|
550
552
|
/**
|
|
551
553
|
* Setup session replay with rrweb
|
|
554
|
+
* Captures everything for maximum replay fidelity
|
|
552
555
|
*/
|
|
553
556
|
setupSessionReplay() {
|
|
554
557
|
if (!this.sessionId) {
|
|
555
558
|
console.warn('[Cuoral Intelligence] Session replay requires a session ID');
|
|
556
559
|
return;
|
|
557
560
|
}
|
|
558
|
-
// Start rrweb recording
|
|
561
|
+
// Start rrweb recording with aggressive capture settings
|
|
559
562
|
this.rrwebStopFn = rrweb.record({
|
|
560
563
|
emit: (event) => {
|
|
561
564
|
this.rrwebEvents.push(event);
|
|
565
|
+
this.rrwebEmit = this.rrwebEmit || ((e) => this.rrwebEvents.push(e)); // Store emit reference
|
|
562
566
|
},
|
|
563
|
-
|
|
567
|
+
// Take full snapshots more frequently to catch dynamic content
|
|
568
|
+
checkoutEveryNms: 30000, // Every 30 seconds (was 60s)
|
|
569
|
+
// Capture everything - minimize sampling throttling
|
|
564
570
|
sampling: {
|
|
565
|
-
scroll:
|
|
566
|
-
media: 800
|
|
567
|
-
input: 'last', // Only record final input value (privacy)
|
|
571
|
+
scroll: 50, // Capture scroll very frequently (was 150)
|
|
572
|
+
media: 200, // Capture media interactions frequently (was 800)
|
|
573
|
+
input: 'last', // Only record final input value (privacy for passwords)
|
|
574
|
+
mousemove: true, // Capture all mouse movements
|
|
575
|
+
mouseInteraction: true, // Capture all mouse interactions
|
|
576
|
+
},
|
|
577
|
+
// Privacy: Only mask sensitive inputs (passwords, credit cards)
|
|
578
|
+
// Everything else is captured for maximum replay fidelity
|
|
579
|
+
maskAllInputs: false, // Don't mask all inputs
|
|
580
|
+
maskInputOptions: {
|
|
581
|
+
password: true, // Mask password fields
|
|
582
|
+
email: false, // Capture emails
|
|
583
|
+
text: false, // Capture text inputs
|
|
584
|
+
textarea: false, // Capture textareas
|
|
585
|
+
select: false, // Capture select dropdowns
|
|
586
|
+
// Mask credit card patterns
|
|
587
|
+
},
|
|
588
|
+
// Capture all content types
|
|
589
|
+
recordCanvas: true, // Capture canvas elements (charts, games, etc.)
|
|
590
|
+
// Inline everything for perfect replay
|
|
591
|
+
inlineStylesheet: true, // Inline all external stylesheets
|
|
592
|
+
inlineImages: true, // Inline images as base64 (larger but more reliable)
|
|
593
|
+
collectFonts: true, // Capture custom fonts
|
|
594
|
+
// Block/ignore classes - customers can add these to sensitive elements
|
|
595
|
+
blockClass: 'cuoral-block', // Add to elements that should be blocked (replaced with placeholder)
|
|
596
|
+
ignoreClass: 'cuoral-ignore', // Add to elements that should be ignored (not captured)
|
|
597
|
+
// Capture mutations aggressively
|
|
598
|
+
slimDOMOptions: {
|
|
599
|
+
// Don't slim anything - capture full fidelity
|
|
600
|
+
script: false, // Keep scripts
|
|
601
|
+
comment: false, // Keep comments
|
|
602
|
+
headFavicon: false, // Keep favicons
|
|
603
|
+
headWhitespace: false, // Keep whitespace
|
|
604
|
+
headMetaSocial: false, // Keep meta tags
|
|
605
|
+
headMetaRobots: false, // Keep robots meta
|
|
606
|
+
headMetaHttpEquiv: false, // Keep http-equiv
|
|
607
|
+
headMetaAuthorship: false, // Keep authorship
|
|
608
|
+
headMetaDescKeywords: false, // Keep description/keywords
|
|
568
609
|
},
|
|
569
|
-
maskAllInputs: true, // Mask sensitive inputs (privacy)
|
|
570
|
-
blockClass: 'cuoral-block',
|
|
571
|
-
ignoreClass: 'cuoral-ignore',
|
|
572
610
|
});
|
|
611
|
+
// Setup Shadow DOM observer to capture Ionic components
|
|
612
|
+
this.setupShadowDOMObserver();
|
|
573
613
|
// Setup custom event tracking
|
|
574
614
|
this.setupClickTracking();
|
|
575
615
|
this.setupScrollTracking();
|
|
@@ -721,6 +761,159 @@ export class CuoralIntelligence {
|
|
|
721
761
|
});
|
|
722
762
|
});
|
|
723
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* Setup Shadow DOM observer to capture Ionic component content
|
|
766
|
+
* Emits custom rrweb events with Shadow DOM snapshots for viewer reconstruction
|
|
767
|
+
*/
|
|
768
|
+
setupShadowDOMObserver() {
|
|
769
|
+
let shadowNodeIdCounter = 1;
|
|
770
|
+
let captureTimeout = null;
|
|
771
|
+
const captureShadowDOM = (immediate = false) => {
|
|
772
|
+
// Debounce rapid calls (e.g., during scroll)
|
|
773
|
+
if (!immediate && captureTimeout) {
|
|
774
|
+
clearTimeout(captureTimeout);
|
|
775
|
+
}
|
|
776
|
+
const doCapture = () => {
|
|
777
|
+
if (!this.rrwebEmit)
|
|
778
|
+
return;
|
|
779
|
+
const shadowDOMData = [];
|
|
780
|
+
const elementsWithShadowDOM = document.querySelectorAll('*');
|
|
781
|
+
elementsWithShadowDOM.forEach((element) => {
|
|
782
|
+
if (element.shadowRoot) {
|
|
783
|
+
try {
|
|
784
|
+
// Assign unique ID to element if it doesn't have one
|
|
785
|
+
if (!this.shadowDOMNodeMap.has(element)) {
|
|
786
|
+
this.shadowDOMNodeMap.set(element, shadowNodeIdCounter++);
|
|
787
|
+
}
|
|
788
|
+
const nodeId = this.shadowDOMNodeMap.get(element);
|
|
789
|
+
const tagName = element.tagName.toLowerCase();
|
|
790
|
+
// Capture Shadow DOM HTML
|
|
791
|
+
const shadowHTML = element.shadowRoot.innerHTML;
|
|
792
|
+
// Capture ALL styles from Shadow DOM
|
|
793
|
+
const styleSheets = [];
|
|
794
|
+
// 1. Capture inline <style> tags
|
|
795
|
+
const shadowStyles = element.shadowRoot.querySelectorAll('style');
|
|
796
|
+
shadowStyles.forEach((styleEl) => {
|
|
797
|
+
if (styleEl.textContent) {
|
|
798
|
+
styleSheets.push(styleEl.textContent);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
// 2. Capture adoptedStyleSheets (modern CSS API)
|
|
802
|
+
if (element.shadowRoot.adoptedStyleSheets) {
|
|
803
|
+
try {
|
|
804
|
+
element.shadowRoot.adoptedStyleSheets.forEach((sheet) => {
|
|
805
|
+
try {
|
|
806
|
+
const rules = Array.from(sheet.cssRules).map(rule => rule.cssText).join('\n');
|
|
807
|
+
if (rules)
|
|
808
|
+
styleSheets.push(rules);
|
|
809
|
+
}
|
|
810
|
+
catch (e) {
|
|
811
|
+
// CORS blocked stylesheet
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
catch (e) {
|
|
816
|
+
// Browser doesn't support adoptedStyleSheets
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
// 3. Capture external <link> stylesheets in Shadow DOM
|
|
820
|
+
const shadowLinks = element.shadowRoot.querySelectorAll('link[rel="stylesheet"]');
|
|
821
|
+
shadowLinks.forEach((linkEl) => {
|
|
822
|
+
const href = linkEl.href;
|
|
823
|
+
if (href) {
|
|
824
|
+
// Store link href so dashboard can also load it
|
|
825
|
+
styleSheets.push(`/* @import url("${href}"); */`);
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
// 4. Capture CSS custom properties from host element
|
|
829
|
+
// Ionic components use CSS variables extensively (--ion-color-*, etc.)
|
|
830
|
+
const computedStyle = window.getComputedStyle(element);
|
|
831
|
+
const cssVariables = [];
|
|
832
|
+
// Get all CSS custom properties from host
|
|
833
|
+
for (let i = 0; i < computedStyle.length; i++) {
|
|
834
|
+
const propertyName = computedStyle[i];
|
|
835
|
+
if (propertyName.startsWith('--')) {
|
|
836
|
+
const propertyValue = computedStyle.getPropertyValue(propertyName).trim();
|
|
837
|
+
if (propertyValue) {
|
|
838
|
+
cssVariables.push(`${propertyName}: ${propertyValue};`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
// Add CSS variables as a :host rule
|
|
843
|
+
if (cssVariables.length > 0) {
|
|
844
|
+
styleSheets.push(`:host { ${cssVariables.join(' ')} }`);
|
|
845
|
+
}
|
|
846
|
+
// Get element's CSS selector for reconstruction
|
|
847
|
+
const selector = this.getElementSelector(element);
|
|
848
|
+
// Capture element attributes (Ionic uses color="primary", mode="ios", etc.)
|
|
849
|
+
const attributes = {};
|
|
850
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
851
|
+
const attr = element.attributes[i];
|
|
852
|
+
// Skip internal attributes
|
|
853
|
+
if (!attr.name.startsWith('data-cuoral') && !attr.name.startsWith('ng-')) {
|
|
854
|
+
attributes[attr.name] = attr.value;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
// Create Shadow DOM snapshot
|
|
858
|
+
shadowDOMData.push({
|
|
859
|
+
nodeId,
|
|
860
|
+
tagName,
|
|
861
|
+
selector,
|
|
862
|
+
attributes, // NEW: Include element attributes
|
|
863
|
+
shadowHTML,
|
|
864
|
+
styleSheets,
|
|
865
|
+
timestamp: Date.now(),
|
|
866
|
+
});
|
|
867
|
+
// Also set data attribute as fallback
|
|
868
|
+
element.setAttribute('data-cuoral-shadow-id', String(nodeId));
|
|
869
|
+
}
|
|
870
|
+
catch (error) {
|
|
871
|
+
// Shadow DOM might be closed
|
|
872
|
+
console.warn('[Cuoral Intelligence] Failed to capture Shadow DOM:', error);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
// Emit custom event with Shadow DOM data
|
|
877
|
+
if (shadowDOMData.length > 0 && this.rrwebEmit) {
|
|
878
|
+
this.rrwebEmit({
|
|
879
|
+
type: 5, // CustomEvent type in rrweb
|
|
880
|
+
data: {
|
|
881
|
+
tag: 'cuoral-shadow-dom',
|
|
882
|
+
payload: {
|
|
883
|
+
shadows: shadowDOMData,
|
|
884
|
+
url: window.location.href,
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
timestamp: Date.now(),
|
|
888
|
+
});
|
|
889
|
+
console.log(`[Cuoral Intelligence] Captured ${shadowDOMData.length} Shadow DOM elements`);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
if (immediate) {
|
|
893
|
+
doCapture();
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
// Debounce: wait 500ms after last call
|
|
897
|
+
captureTimeout = setTimeout(doCapture, 500);
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
// Run immediately on init
|
|
901
|
+
captureShadowDOM(true);
|
|
902
|
+
// Observe DOM mutations to catch dynamically added Shadow DOM elements
|
|
903
|
+
const observer = new MutationObserver(() => {
|
|
904
|
+
captureShadowDOM(false); // Debounced
|
|
905
|
+
});
|
|
906
|
+
observer.observe(document.body, {
|
|
907
|
+
childList: true,
|
|
908
|
+
subtree: true,
|
|
909
|
+
});
|
|
910
|
+
// Capture on scroll events (debounced)
|
|
911
|
+
window.addEventListener('scroll', () => captureShadowDOM(false), { passive: true });
|
|
912
|
+
// Also listen for Ionic scroll events
|
|
913
|
+
document.addEventListener('ionScroll', () => captureShadowDOM(false), { passive: true });
|
|
914
|
+
// Periodic capture as fallback (every 5 seconds now, since we have scroll capture)
|
|
915
|
+
setInterval(() => captureShadowDOM(true), 5000);
|
|
916
|
+
}
|
|
724
917
|
/**
|
|
725
918
|
* Track custom business events (flows, features, etc.)
|
|
726
919
|
*/
|
package/dist/modal.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ export declare class CuoralModal {
|
|
|
9
9
|
private isOpen;
|
|
10
10
|
private widgetUrl;
|
|
11
11
|
private showFloatingButton;
|
|
12
|
+
private primaryColor;
|
|
12
13
|
private lastLoadedUrl?;
|
|
13
|
-
constructor(widgetUrl: string, showFloatingButton?: boolean);
|
|
14
|
+
constructor(widgetUrl: string, showFloatingButton?: boolean, primaryColor?: string);
|
|
14
15
|
/**
|
|
15
16
|
* Update the widget URL (e.g., to include new session_id)
|
|
16
17
|
* Forces complete iframe recreation if URL changed to clear all cached state
|
package/dist/modal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,aAAa,CAAC,CAAS;gBAEnB,SAAS,EAAE,MAAM,EAAE,kBAAkB,UAAO;
|
|
1
|
+
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAC,CAAS;gBAEnB,SAAS,EAAE,MAAM,EAAE,kBAAkB,UAAO,EAAE,YAAY,SAAY;IAMlF;;;OAGG;IACI,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA4C5C;;OAEG;IACI,UAAU,IAAI,IAAI;IAOzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkD5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAmGnB;;OAEG;IACI,IAAI,IAAI,IAAI;IAsBnB;;OAEG;IACI,KAAK,IAAI,IAAI;IAqBpB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACI,OAAO,IAAI,IAAI;IAUtB;;OAEG;IACI,SAAS,IAAI,iBAAiB,GAAG,SAAS;CAGlD"}
|