@clianta/sdk 1.0.0 → 1.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.
@@ -1,14 +1,14 @@
1
1
  /*!
2
- * Clianta SDK v1.0.0
2
+ * Clianta SDK v1.1.0
3
3
  * (c) 2026 Clianta
4
4
  * Released under the MIT License.
5
5
  */
6
6
  /**
7
7
  * Clianta SDK - Configuration
8
- * @version 1.0.0
8
+ * @see SDK_VERSION in core/config.ts
9
9
  */
10
10
  /** SDK Version */
11
- const SDK_VERSION = '1.0.0';
11
+ const SDK_VERSION = '1.1.0';
12
12
  /** Default API endpoint based on environment */
13
13
  const getDefaultApiEndpoint = () => {
14
14
  if (typeof window === 'undefined')
@@ -28,6 +28,7 @@ const DEFAULT_PLUGINS = [
28
28
  'engagement',
29
29
  'downloads',
30
30
  'exitIntent',
31
+ 'popupForms',
31
32
  ];
32
33
  /** Default configuration values */
33
34
  const DEFAULT_CONFIG = {
@@ -42,9 +43,11 @@ const DEFAULT_CONFIG = {
42
43
  defaultConsent: { analytics: true, marketing: false, personalization: false },
43
44
  waitForConsent: false,
44
45
  storageKey: 'mb_consent',
46
+ anonymousMode: false,
45
47
  },
46
48
  cookieDomain: '',
47
49
  useCookies: false,
50
+ cookielessMode: false,
48
51
  };
49
52
  /** Storage keys */
50
53
  const STORAGE_KEYS = {
@@ -78,8 +81,8 @@ function mergeConfig(userConfig = {}) {
78
81
  }
79
82
 
80
83
  /**
81
- * MorrisB Tracking SDK - Debug Logger
82
- * @version 3.0.0
84
+ * Clianta SDK - Debug Logger
85
+ * @see SDK_VERSION in core/config.ts
83
86
  */
84
87
  const LOG_PREFIX = '[Clianta]';
85
88
  const LOG_STYLES = {
@@ -149,9 +152,9 @@ function createLogger(enabled = false) {
149
152
  const logger = createLogger(false);
150
153
 
151
154
  /**
152
- * MorrisB Tracking SDK - Transport Layer
155
+ * Clianta SDK - Transport Layer
153
156
  * Handles sending events to the backend with retry logic
154
- * @version 3.0.0
157
+ * @see SDK_VERSION in core/config.ts
155
158
  */
156
159
  const DEFAULT_TIMEOUT = 10000; // 10 seconds
157
160
  const DEFAULT_MAX_RETRIES = 3;
@@ -275,8 +278,8 @@ class Transport {
275
278
  }
276
279
 
277
280
  /**
278
- * MorrisB Tracking SDK - Utility Functions
279
- * @version 3.0.0
281
+ * Clianta SDK - Utility Functions
282
+ * @see SDK_VERSION in core/config.ts
280
283
  */
281
284
  // ============================================
282
285
  // UUID GENERATION
@@ -562,9 +565,9 @@ function getDeviceInfo() {
562
565
  }
563
566
 
564
567
  /**
565
- * MorrisB Tracking SDK - Event Queue
568
+ * Clianta SDK - Event Queue
566
569
  * Handles batching and flushing of events
567
- * @version 3.0.0
570
+ * @see SDK_VERSION in core/config.ts
568
571
  */
569
572
  const MAX_QUEUE_SIZE = 1000;
570
573
  /**
@@ -739,8 +742,8 @@ class EventQueue {
739
742
  }
740
743
 
741
744
  /**
742
- * MorrisB Tracking SDK - Plugin Base
743
- * @version 3.0.0
745
+ * Clianta SDK - Plugin Base
746
+ * @see SDK_VERSION in core/config.ts
744
747
  */
745
748
  /**
746
749
  * Base class for plugins
@@ -763,8 +766,8 @@ class BasePlugin {
763
766
  }
764
767
 
765
768
  /**
766
- * MorrisB Tracking SDK - Page View Plugin
767
- * @version 3.0.0
769
+ * Clianta SDK - Page View Plugin
770
+ * @see SDK_VERSION in core/config.ts
768
771
  */
769
772
  /**
770
773
  * Page View Plugin - Tracks page views
@@ -812,8 +815,8 @@ class PageViewPlugin extends BasePlugin {
812
815
  }
813
816
 
814
817
  /**
815
- * MorrisB Tracking SDK - Scroll Depth Plugin
816
- * @version 3.0.0
818
+ * Clianta SDK - Scroll Depth Plugin
819
+ * @see SDK_VERSION in core/config.ts
817
820
  */
818
821
  /**
819
822
  * Scroll Depth Plugin - Tracks scroll milestones
@@ -880,8 +883,8 @@ class ScrollPlugin extends BasePlugin {
880
883
  }
881
884
 
882
885
  /**
883
- * MorrisB Tracking SDK - Form Tracking Plugin
884
- * @version 3.0.0
886
+ * Clianta SDK - Form Tracking Plugin
887
+ * @see SDK_VERSION in core/config.ts
885
888
  */
886
889
  /**
887
890
  * Form Tracking Plugin - Auto-tracks form views, interactions, and submissions
@@ -988,8 +991,8 @@ class FormsPlugin extends BasePlugin {
988
991
  }
989
992
 
990
993
  /**
991
- * MorrisB Tracking SDK - Click Tracking Plugin
992
- * @version 3.0.0
994
+ * Clianta SDK - Click Tracking Plugin
995
+ * @see SDK_VERSION in core/config.ts
993
996
  */
994
997
  /**
995
998
  * Click Tracking Plugin - Tracks button and CTA clicks
@@ -1030,8 +1033,8 @@ class ClicksPlugin extends BasePlugin {
1030
1033
  }
1031
1034
 
1032
1035
  /**
1033
- * MorrisB Tracking SDK - Engagement Plugin
1034
- * @version 3.0.0
1036
+ * Clianta SDK - Engagement Plugin
1037
+ * @see SDK_VERSION in core/config.ts
1035
1038
  */
1036
1039
  /**
1037
1040
  * Engagement Plugin - Tracks user engagement and time on page
@@ -1112,8 +1115,8 @@ class EngagementPlugin extends BasePlugin {
1112
1115
  }
1113
1116
 
1114
1117
  /**
1115
- * MorrisB Tracking SDK - Downloads Plugin
1116
- * @version 3.0.0
1118
+ * Clianta SDK - Downloads Plugin
1119
+ * @see SDK_VERSION in core/config.ts
1117
1120
  */
1118
1121
  /**
1119
1122
  * Downloads Plugin - Tracks file downloads
@@ -1160,8 +1163,8 @@ class DownloadsPlugin extends BasePlugin {
1160
1163
  }
1161
1164
 
1162
1165
  /**
1163
- * MorrisB Tracking SDK - Exit Intent Plugin
1164
- * @version 3.0.0
1166
+ * Clianta SDK - Exit Intent Plugin
1167
+ * @see SDK_VERSION in core/config.ts
1165
1168
  */
1166
1169
  /**
1167
1170
  * Exit Intent Plugin - Detects when user intends to leave the page
@@ -1203,8 +1206,8 @@ class ExitIntentPlugin extends BasePlugin {
1203
1206
  }
1204
1207
 
1205
1208
  /**
1206
- * MorrisB Tracking SDK - Error Tracking Plugin
1207
- * @version 3.0.0
1209
+ * Clianta SDK - Error Tracking Plugin
1210
+ * @see SDK_VERSION in core/config.ts
1208
1211
  */
1209
1212
  /**
1210
1213
  * Error Tracking Plugin - Tracks JavaScript errors
@@ -1253,8 +1256,8 @@ class ErrorsPlugin extends BasePlugin {
1253
1256
  }
1254
1257
 
1255
1258
  /**
1256
- * MorrisB Tracking SDK - Performance Plugin
1257
- * @version 3.0.0
1259
+ * Clianta SDK - Performance Plugin
1260
+ * @see SDK_VERSION in core/config.ts
1258
1261
  */
1259
1262
  /**
1260
1263
  * Performance Plugin - Tracks page performance and Web Vitals
@@ -1360,8 +1363,420 @@ class PerformancePlugin extends BasePlugin {
1360
1363
  }
1361
1364
 
1362
1365
  /**
1363
- * MorrisB Tracking SDK - Plugins Index
1364
- * @version 3.0.0
1366
+ * Clianta Tracking SDK - Popup Forms Plugin
1367
+ * @see SDK_VERSION in core/config.ts
1368
+ *
1369
+ * Auto-loads and displays lead capture popups based on triggers
1370
+ */
1371
+ /**
1372
+ * Popup Forms Plugin - Fetches and displays lead capture forms
1373
+ */
1374
+ class PopupFormsPlugin extends BasePlugin {
1375
+ constructor() {
1376
+ super(...arguments);
1377
+ this.name = 'popupForms';
1378
+ this.forms = [];
1379
+ this.shownForms = new Set();
1380
+ this.scrollHandler = null;
1381
+ this.exitHandler = null;
1382
+ }
1383
+ async init(tracker) {
1384
+ super.init(tracker);
1385
+ if (typeof window === 'undefined')
1386
+ return;
1387
+ // Load shown forms from storage
1388
+ this.loadShownForms();
1389
+ // Fetch active forms
1390
+ await this.fetchForms();
1391
+ // Setup triggers
1392
+ this.setupTriggers();
1393
+ }
1394
+ destroy() {
1395
+ this.removeTriggers();
1396
+ super.destroy();
1397
+ }
1398
+ loadShownForms() {
1399
+ try {
1400
+ const stored = localStorage.getItem('clianta_shown_forms');
1401
+ if (stored) {
1402
+ const data = JSON.parse(stored);
1403
+ this.shownForms = new Set(data.forms || []);
1404
+ }
1405
+ }
1406
+ catch (e) {
1407
+ // Ignore storage errors
1408
+ }
1409
+ }
1410
+ saveShownForms() {
1411
+ try {
1412
+ localStorage.setItem('clianta_shown_forms', JSON.stringify({
1413
+ forms: Array.from(this.shownForms),
1414
+ timestamp: Date.now(),
1415
+ }));
1416
+ }
1417
+ catch (e) {
1418
+ // Ignore storage errors
1419
+ }
1420
+ }
1421
+ async fetchForms() {
1422
+ if (!this.tracker)
1423
+ return;
1424
+ const config = this.tracker.getConfig();
1425
+ const workspaceId = this.tracker.getWorkspaceId();
1426
+ const apiEndpoint = config.apiEndpoint || 'https://api.clianta.online';
1427
+ try {
1428
+ const url = encodeURIComponent(window.location.href);
1429
+ const response = await fetch(`${apiEndpoint}/api/public/lead-forms/${workspaceId}?url=${url}`);
1430
+ if (!response.ok)
1431
+ return;
1432
+ const data = await response.json();
1433
+ if (data.success && Array.isArray(data.data)) {
1434
+ this.forms = data.data.filter((form) => this.shouldShowForm(form));
1435
+ }
1436
+ }
1437
+ catch (error) {
1438
+ console.error('[Clianta] Failed to fetch forms:', error);
1439
+ }
1440
+ }
1441
+ shouldShowForm(form) {
1442
+ // Check show frequency
1443
+ if (form.showFrequency === 'once_per_visitor') {
1444
+ if (this.shownForms.has(form._id))
1445
+ return false;
1446
+ }
1447
+ else if (form.showFrequency === 'once_per_session') {
1448
+ const sessionKey = `clianta_form_${form._id}_shown`;
1449
+ if (sessionStorage.getItem(sessionKey))
1450
+ return false;
1451
+ }
1452
+ return true;
1453
+ }
1454
+ setupTriggers() {
1455
+ this.forms.forEach(form => {
1456
+ switch (form.trigger.type) {
1457
+ case 'delay':
1458
+ setTimeout(() => this.showForm(form), (form.trigger.value || 5) * 1000);
1459
+ break;
1460
+ case 'scroll':
1461
+ this.setupScrollTrigger(form);
1462
+ break;
1463
+ case 'exit_intent':
1464
+ this.setupExitIntentTrigger(form);
1465
+ break;
1466
+ case 'click':
1467
+ this.setupClickTrigger(form);
1468
+ break;
1469
+ }
1470
+ });
1471
+ }
1472
+ setupScrollTrigger(form) {
1473
+ const threshold = form.trigger.value || 50;
1474
+ this.scrollHandler = () => {
1475
+ const scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
1476
+ if (scrollPercent >= threshold) {
1477
+ this.showForm(form);
1478
+ if (this.scrollHandler) {
1479
+ window.removeEventListener('scroll', this.scrollHandler);
1480
+ }
1481
+ }
1482
+ };
1483
+ window.addEventListener('scroll', this.scrollHandler, { passive: true });
1484
+ }
1485
+ setupExitIntentTrigger(form) {
1486
+ this.exitHandler = (e) => {
1487
+ if (e.clientY <= 0) {
1488
+ this.showForm(form);
1489
+ if (this.exitHandler) {
1490
+ document.removeEventListener('mouseout', this.exitHandler);
1491
+ }
1492
+ }
1493
+ };
1494
+ document.addEventListener('mouseout', this.exitHandler);
1495
+ }
1496
+ setupClickTrigger(form) {
1497
+ if (!form.trigger.selector)
1498
+ return;
1499
+ const elements = document.querySelectorAll(form.trigger.selector);
1500
+ elements.forEach(el => {
1501
+ el.addEventListener('click', () => this.showForm(form));
1502
+ });
1503
+ }
1504
+ removeTriggers() {
1505
+ if (this.scrollHandler) {
1506
+ window.removeEventListener('scroll', this.scrollHandler);
1507
+ }
1508
+ if (this.exitHandler) {
1509
+ document.removeEventListener('mouseout', this.exitHandler);
1510
+ }
1511
+ }
1512
+ async showForm(form) {
1513
+ // Check if already shown in this session
1514
+ if (!this.shouldShowForm(form))
1515
+ return;
1516
+ // Mark as shown
1517
+ this.shownForms.add(form._id);
1518
+ this.saveShownForms();
1519
+ sessionStorage.setItem(`clianta_form_${form._id}_shown`, 'true');
1520
+ // Track view
1521
+ await this.trackFormView(form._id);
1522
+ // Render form
1523
+ this.renderForm(form);
1524
+ }
1525
+ async trackFormView(formId) {
1526
+ if (!this.tracker)
1527
+ return;
1528
+ const config = this.tracker.getConfig();
1529
+ const apiEndpoint = config.apiEndpoint || 'https://api.clianta.online';
1530
+ try {
1531
+ await fetch(`${apiEndpoint}/api/public/lead-forms/${formId}/view`, {
1532
+ method: 'POST',
1533
+ headers: { 'Content-Type': 'application/json' },
1534
+ });
1535
+ }
1536
+ catch (e) {
1537
+ // Ignore tracking errors
1538
+ }
1539
+ }
1540
+ renderForm(form) {
1541
+ // Create overlay
1542
+ const overlay = document.createElement('div');
1543
+ overlay.id = `clianta-form-overlay-${form._id}`;
1544
+ overlay.style.cssText = `
1545
+ position: fixed;
1546
+ top: 0;
1547
+ left: 0;
1548
+ right: 0;
1549
+ bottom: 0;
1550
+ background: rgba(0, 0, 0, 0.5);
1551
+ z-index: 999998;
1552
+ display: flex;
1553
+ align-items: center;
1554
+ justify-content: center;
1555
+ opacity: 0;
1556
+ transition: opacity 0.3s ease;
1557
+ `;
1558
+ // Create form container
1559
+ const container = document.createElement('div');
1560
+ container.id = `clianta-form-${form._id}`;
1561
+ const style = form.style || {};
1562
+ container.style.cssText = `
1563
+ background: ${style.backgroundColor || '#FFFFFF'};
1564
+ border-radius: ${style.borderRadius || 12}px;
1565
+ padding: 24px;
1566
+ max-width: 400px;
1567
+ width: 90%;
1568
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
1569
+ transform: translateY(20px);
1570
+ opacity: 0;
1571
+ transition: all 0.3s ease;
1572
+ `;
1573
+ // Build form HTML
1574
+ container.innerHTML = this.buildFormHTML(form);
1575
+ overlay.appendChild(container);
1576
+ document.body.appendChild(overlay);
1577
+ // Animate in
1578
+ requestAnimationFrame(() => {
1579
+ overlay.style.opacity = '1';
1580
+ container.style.transform = 'translateY(0)';
1581
+ container.style.opacity = '1';
1582
+ });
1583
+ // Setup event listeners
1584
+ this.setupFormEvents(form, overlay, container);
1585
+ }
1586
+ buildFormHTML(form) {
1587
+ const style = form.style || {};
1588
+ const primaryColor = style.primaryColor || '#10B981';
1589
+ const textColor = style.textColor || '#18181B';
1590
+ let fieldsHTML = form.fields.map(field => {
1591
+ const requiredMark = field.required ? '<span style="color: #EF4444;">*</span>' : '';
1592
+ if (field.type === 'textarea') {
1593
+ return `
1594
+ <div style="margin-bottom: 12px;">
1595
+ <label style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px; color: ${textColor};">
1596
+ ${field.label} ${requiredMark}
1597
+ </label>
1598
+ <textarea
1599
+ name="${field.name}"
1600
+ placeholder="${field.placeholder || ''}"
1601
+ ${field.required ? 'required' : ''}
1602
+ style="width: 100%; padding: 8px 12px; border: 1px solid #E4E4E7; border-radius: 6px; font-size: 14px; resize: vertical; min-height: 80px;"
1603
+ ></textarea>
1604
+ </div>
1605
+ `;
1606
+ }
1607
+ else if (field.type === 'checkbox') {
1608
+ return `
1609
+ <div style="margin-bottom: 12px;">
1610
+ <label style="display: flex; align-items: center; gap: 8px; font-size: 14px; color: ${textColor}; cursor: pointer;">
1611
+ <input
1612
+ type="checkbox"
1613
+ name="${field.name}"
1614
+ ${field.required ? 'required' : ''}
1615
+ style="width: 16px; height: 16px;"
1616
+ />
1617
+ ${field.label} ${requiredMark}
1618
+ </label>
1619
+ </div>
1620
+ `;
1621
+ }
1622
+ else {
1623
+ return `
1624
+ <div style="margin-bottom: 12px;">
1625
+ <label style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px; color: ${textColor};">
1626
+ ${field.label} ${requiredMark}
1627
+ </label>
1628
+ <input
1629
+ type="${field.type}"
1630
+ name="${field.name}"
1631
+ placeholder="${field.placeholder || ''}"
1632
+ ${field.required ? 'required' : ''}
1633
+ style="width: 100%; padding: 8px 12px; border: 1px solid #E4E4E7; border-radius: 6px; font-size: 14px; box-sizing: border-box;"
1634
+ />
1635
+ </div>
1636
+ `;
1637
+ }
1638
+ }).join('');
1639
+ return `
1640
+ <button id="clianta-form-close" style="
1641
+ position: absolute;
1642
+ top: 12px;
1643
+ right: 12px;
1644
+ background: none;
1645
+ border: none;
1646
+ font-size: 20px;
1647
+ cursor: pointer;
1648
+ color: #71717A;
1649
+ padding: 4px;
1650
+ ">&times;</button>
1651
+ <h2 style="font-size: 20px; font-weight: 700; margin-bottom: 8px; color: ${textColor};">
1652
+ ${form.headline || 'Stay in touch'}
1653
+ </h2>
1654
+ <p style="font-size: 14px; color: #71717A; margin-bottom: 16px;">
1655
+ ${form.subheadline || 'Get the latest updates'}
1656
+ </p>
1657
+ <form id="clianta-form-element">
1658
+ ${fieldsHTML}
1659
+ <button type="submit" style="
1660
+ width: 100%;
1661
+ padding: 10px 16px;
1662
+ background: ${primaryColor};
1663
+ color: white;
1664
+ border: none;
1665
+ border-radius: 6px;
1666
+ font-size: 14px;
1667
+ font-weight: 500;
1668
+ cursor: pointer;
1669
+ margin-top: 8px;
1670
+ ">
1671
+ ${form.submitButtonText || 'Subscribe'}
1672
+ </button>
1673
+ </form>
1674
+ `;
1675
+ }
1676
+ setupFormEvents(form, overlay, container) {
1677
+ // Close button
1678
+ const closeBtn = container.querySelector('#clianta-form-close');
1679
+ if (closeBtn) {
1680
+ closeBtn.addEventListener('click', () => this.closeForm(form._id, overlay, container));
1681
+ }
1682
+ // Overlay click
1683
+ overlay.addEventListener('click', (e) => {
1684
+ if (e.target === overlay) {
1685
+ this.closeForm(form._id, overlay, container);
1686
+ }
1687
+ });
1688
+ // Form submit
1689
+ const formElement = container.querySelector('#clianta-form-element');
1690
+ if (formElement) {
1691
+ formElement.addEventListener('submit', async (e) => {
1692
+ e.preventDefault();
1693
+ await this.handleSubmit(form, formElement, container);
1694
+ });
1695
+ }
1696
+ }
1697
+ closeForm(formId, overlay, container) {
1698
+ container.style.transform = 'translateY(20px)';
1699
+ container.style.opacity = '0';
1700
+ overlay.style.opacity = '0';
1701
+ setTimeout(() => {
1702
+ overlay.remove();
1703
+ }, 300);
1704
+ }
1705
+ async handleSubmit(form, formElement, container) {
1706
+ if (!this.tracker)
1707
+ return;
1708
+ const config = this.tracker.getConfig();
1709
+ const apiEndpoint = config.apiEndpoint || 'https://api.clianta.online';
1710
+ const visitorId = this.tracker.getVisitorId();
1711
+ // Collect form data
1712
+ const formData = new FormData(formElement);
1713
+ const data = {};
1714
+ formData.forEach((value, key) => {
1715
+ data[key] = value;
1716
+ });
1717
+ // Disable submit button
1718
+ const submitBtn = formElement.querySelector('button[type="submit"]');
1719
+ if (submitBtn) {
1720
+ submitBtn.disabled = true;
1721
+ submitBtn.innerHTML = 'Submitting...';
1722
+ }
1723
+ try {
1724
+ const response = await fetch(`${apiEndpoint}/api/public/lead-forms/${form._id}/submit`, {
1725
+ method: 'POST',
1726
+ headers: { 'Content-Type': 'application/json' },
1727
+ body: JSON.stringify({
1728
+ visitorId,
1729
+ data,
1730
+ url: window.location.href,
1731
+ }),
1732
+ });
1733
+ const result = await response.json();
1734
+ if (result.success) {
1735
+ // Show success message
1736
+ container.innerHTML = `
1737
+ <div style="text-align: center; padding: 20px;">
1738
+ <div style="width: 48px; height: 48px; background: #10B981; border-radius: 50%; margin: 0 auto 16px; display: flex; align-items: center; justify-content: center;">
1739
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
1740
+ <polyline points="20 6 9 17 4 12"></polyline>
1741
+ </svg>
1742
+ </div>
1743
+ <p style="font-size: 16px; font-weight: 500; color: #18181B;">
1744
+ ${form.successMessage || 'Thank you!'}
1745
+ </p>
1746
+ </div>
1747
+ `;
1748
+ // Track identify
1749
+ if (data.email) {
1750
+ this.tracker?.identify(data.email, data);
1751
+ }
1752
+ // Redirect if configured
1753
+ if (form.redirectUrl) {
1754
+ setTimeout(() => {
1755
+ window.location.href = form.redirectUrl;
1756
+ }, 1500);
1757
+ }
1758
+ // Close after delay
1759
+ setTimeout(() => {
1760
+ const overlay = document.getElementById(`clianta-form-overlay-${form._id}`);
1761
+ if (overlay) {
1762
+ this.closeForm(form._id, overlay, container);
1763
+ }
1764
+ }, 2000);
1765
+ }
1766
+ }
1767
+ catch (error) {
1768
+ console.error('[Clianta] Form submit error:', error);
1769
+ if (submitBtn) {
1770
+ submitBtn.disabled = false;
1771
+ submitBtn.innerHTML = form.submitButtonText || 'Subscribe';
1772
+ }
1773
+ }
1774
+ }
1775
+ }
1776
+
1777
+ /**
1778
+ * Clianta SDK - Plugins Index
1779
+ * Version is defined in core/config.ts as SDK_VERSION
1365
1780
  */
1366
1781
  /**
1367
1782
  * Get plugin instance by name
@@ -1386,17 +1801,256 @@ function getPlugin(name) {
1386
1801
  return new ErrorsPlugin();
1387
1802
  case 'performance':
1388
1803
  return new PerformancePlugin();
1804
+ case 'popupForms':
1805
+ return new PopupFormsPlugin();
1389
1806
  default:
1390
1807
  throw new Error(`Unknown plugin: ${name}`);
1391
1808
  }
1392
1809
  }
1393
1810
 
1394
1811
  /**
1395
- * MorrisB Tracking SDK - Main Tracker Class
1396
- * @version 3.0.0
1812
+ * Clianta SDK - Consent Storage
1813
+ * Handles persistence of consent state
1814
+ * @see SDK_VERSION in core/config.ts
1815
+ */
1816
+ const CONSENT_VERSION = 1;
1817
+ /**
1818
+ * Save consent state to storage
1819
+ */
1820
+ function saveConsent(state) {
1821
+ try {
1822
+ if (typeof localStorage === 'undefined')
1823
+ return false;
1824
+ const stored = {
1825
+ state,
1826
+ timestamp: Date.now(),
1827
+ version: CONSENT_VERSION,
1828
+ };
1829
+ localStorage.setItem(STORAGE_KEYS.CONSENT, JSON.stringify(stored));
1830
+ return true;
1831
+ }
1832
+ catch {
1833
+ return false;
1834
+ }
1835
+ }
1836
+ /**
1837
+ * Load consent state from storage
1838
+ */
1839
+ function loadConsent() {
1840
+ try {
1841
+ if (typeof localStorage === 'undefined')
1842
+ return null;
1843
+ const stored = localStorage.getItem(STORAGE_KEYS.CONSENT);
1844
+ if (!stored)
1845
+ return null;
1846
+ const parsed = JSON.parse(stored);
1847
+ // Validate version
1848
+ if (parsed.version !== CONSENT_VERSION) {
1849
+ clearConsent();
1850
+ return null;
1851
+ }
1852
+ return parsed;
1853
+ }
1854
+ catch {
1855
+ return null;
1856
+ }
1857
+ }
1858
+ /**
1859
+ * Clear consent state from storage
1860
+ */
1861
+ function clearConsent() {
1862
+ try {
1863
+ if (typeof localStorage === 'undefined')
1864
+ return false;
1865
+ localStorage.removeItem(STORAGE_KEYS.CONSENT);
1866
+ return true;
1867
+ }
1868
+ catch {
1869
+ return false;
1870
+ }
1871
+ }
1872
+ /**
1873
+ * Check if consent has been explicitly set
1874
+ */
1875
+ function hasStoredConsent() {
1876
+ return loadConsent() !== null;
1877
+ }
1878
+
1879
+ /**
1880
+ * Clianta SDK - Consent Manager
1881
+ * Manages consent state and event buffering for GDPR/CCPA compliance
1882
+ * @see SDK_VERSION in core/config.ts
1397
1883
  */
1398
1884
  /**
1399
- * Main MorrisB Tracker Class
1885
+ * Manages user consent state for tracking
1886
+ */
1887
+ class ConsentManager {
1888
+ constructor(config = {}) {
1889
+ this.eventBuffer = [];
1890
+ this.callbacks = [];
1891
+ this.hasExplicitConsent = false;
1892
+ this.config = {
1893
+ defaultConsent: { analytics: true, marketing: false, personalization: false },
1894
+ waitForConsent: false,
1895
+ storageKey: 'mb_consent',
1896
+ ...config,
1897
+ };
1898
+ // Load stored consent or use default
1899
+ const stored = loadConsent();
1900
+ if (stored) {
1901
+ this.state = stored.state;
1902
+ this.hasExplicitConsent = true;
1903
+ logger.debug('Loaded stored consent:', this.state);
1904
+ }
1905
+ else {
1906
+ this.state = this.config.defaultConsent || { analytics: true };
1907
+ this.hasExplicitConsent = false;
1908
+ logger.debug('Using default consent:', this.state);
1909
+ }
1910
+ // Register callback if provided
1911
+ if (config.onConsentChange) {
1912
+ this.callbacks.push(config.onConsentChange);
1913
+ }
1914
+ }
1915
+ /**
1916
+ * Grant consent for specified categories
1917
+ */
1918
+ grant(categories) {
1919
+ const previous = { ...this.state };
1920
+ this.state = { ...this.state, ...categories };
1921
+ this.hasExplicitConsent = true;
1922
+ saveConsent(this.state);
1923
+ logger.info('Consent granted:', categories);
1924
+ this.notifyChange(previous);
1925
+ }
1926
+ /**
1927
+ * Revoke consent for specified categories
1928
+ */
1929
+ revoke(categories) {
1930
+ const previous = { ...this.state };
1931
+ for (const category of categories) {
1932
+ this.state[category] = false;
1933
+ }
1934
+ this.hasExplicitConsent = true;
1935
+ saveConsent(this.state);
1936
+ logger.info('Consent revoked:', categories);
1937
+ this.notifyChange(previous);
1938
+ }
1939
+ /**
1940
+ * Update entire consent state
1941
+ */
1942
+ update(state) {
1943
+ const previous = { ...this.state };
1944
+ this.state = { ...state };
1945
+ this.hasExplicitConsent = true;
1946
+ saveConsent(this.state);
1947
+ logger.info('Consent updated:', this.state);
1948
+ this.notifyChange(previous);
1949
+ }
1950
+ /**
1951
+ * Reset consent to default (clear stored consent)
1952
+ */
1953
+ reset() {
1954
+ const previous = { ...this.state };
1955
+ this.state = this.config.defaultConsent || { analytics: true };
1956
+ this.hasExplicitConsent = false;
1957
+ this.eventBuffer = [];
1958
+ clearConsent();
1959
+ logger.info('Consent reset to defaults');
1960
+ this.notifyChange(previous);
1961
+ }
1962
+ /**
1963
+ * Get current consent state
1964
+ */
1965
+ getState() {
1966
+ return { ...this.state };
1967
+ }
1968
+ /**
1969
+ * Check if a specific consent category is granted
1970
+ */
1971
+ hasConsent(category) {
1972
+ return this.state[category] === true;
1973
+ }
1974
+ /**
1975
+ * Check if analytics consent is granted (most common check)
1976
+ */
1977
+ canTrack() {
1978
+ // If waiting for consent and no explicit consent given, cannot track
1979
+ if (this.config.waitForConsent && !this.hasExplicitConsent) {
1980
+ return false;
1981
+ }
1982
+ return this.state.analytics === true;
1983
+ }
1984
+ /**
1985
+ * Check if explicit consent has been given
1986
+ */
1987
+ hasExplicit() {
1988
+ return this.hasExplicitConsent;
1989
+ }
1990
+ /**
1991
+ * Check if there's stored consent
1992
+ */
1993
+ hasStored() {
1994
+ return hasStoredConsent();
1995
+ }
1996
+ /**
1997
+ * Buffer an event (for waitForConsent mode)
1998
+ */
1999
+ bufferEvent(event) {
2000
+ this.eventBuffer.push(event);
2001
+ logger.debug('Event buffered (waiting for consent):', event.eventName);
2002
+ }
2003
+ /**
2004
+ * Get and clear buffered events
2005
+ */
2006
+ flushBuffer() {
2007
+ const events = [...this.eventBuffer];
2008
+ this.eventBuffer = [];
2009
+ if (events.length > 0) {
2010
+ logger.debug(`Flushing ${events.length} buffered events`);
2011
+ }
2012
+ return events;
2013
+ }
2014
+ /**
2015
+ * Get buffered event count
2016
+ */
2017
+ getBufferSize() {
2018
+ return this.eventBuffer.length;
2019
+ }
2020
+ /**
2021
+ * Register a consent change callback
2022
+ */
2023
+ onChange(callback) {
2024
+ this.callbacks.push(callback);
2025
+ // Return unsubscribe function
2026
+ return () => {
2027
+ const index = this.callbacks.indexOf(callback);
2028
+ if (index > -1) {
2029
+ this.callbacks.splice(index, 1);
2030
+ }
2031
+ };
2032
+ }
2033
+ /**
2034
+ * Notify all callbacks of consent change
2035
+ */
2036
+ notifyChange(previous) {
2037
+ for (const callback of this.callbacks) {
2038
+ try {
2039
+ callback(this.state, previous);
2040
+ }
2041
+ catch (error) {
2042
+ logger.error('Consent change callback error:', error);
2043
+ }
2044
+ }
2045
+ }
2046
+ }
2047
+
2048
+ /**
2049
+ * Clianta SDK - Main Tracker Class
2050
+ * @see SDK_VERSION in core/config.ts
2051
+ */
2052
+ /**
2053
+ * Main Clianta Tracker Class
1400
2054
  */
1401
2055
  class Tracker {
1402
2056
  constructor(workspaceId, userConfig = {}) {
@@ -1410,21 +2064,81 @@ class Tracker {
1410
2064
  // Setup debug mode
1411
2065
  logger.enabled = this.config.debug;
1412
2066
  logger.info(`Initializing SDK v${SDK_VERSION}`, { workspaceId });
2067
+ // Initialize consent manager
2068
+ this.consentManager = new ConsentManager({
2069
+ ...this.config.consent,
2070
+ onConsentChange: (state, previous) => {
2071
+ this.onConsentChange(state, previous);
2072
+ },
2073
+ });
1413
2074
  // Initialize transport and queue
1414
2075
  this.transport = new Transport({ apiEndpoint: this.config.apiEndpoint });
1415
2076
  this.queue = new EventQueue(this.transport, {
1416
2077
  batchSize: this.config.batchSize,
1417
2078
  flushInterval: this.config.flushInterval,
1418
2079
  });
1419
- // Get or create visitor and session IDs
1420
- this.visitorId = getOrCreateVisitorId(this.config.useCookies);
1421
- this.sessionId = getOrCreateSessionId(this.config.sessionTimeout);
2080
+ // Get or create visitor and session IDs based on mode
2081
+ this.visitorId = this.createVisitorId();
2082
+ this.sessionId = this.createSessionId();
1422
2083
  logger.debug('IDs created', { visitorId: this.visitorId, sessionId: this.sessionId });
1423
2084
  // Initialize plugins
1424
2085
  this.initPlugins();
1425
2086
  this.isInitialized = true;
1426
2087
  logger.info('SDK initialized successfully');
1427
2088
  }
2089
+ /**
2090
+ * Create visitor ID based on storage mode
2091
+ */
2092
+ createVisitorId() {
2093
+ // Anonymous mode: use temporary ID until consent
2094
+ if (this.config.consent.anonymousMode && !this.consentManager.hasExplicit()) {
2095
+ const key = STORAGE_KEYS.VISITOR_ID + '_anon';
2096
+ let anonId = getSessionStorage(key);
2097
+ if (!anonId) {
2098
+ anonId = 'anon_' + generateUUID();
2099
+ setSessionStorage(key, anonId);
2100
+ }
2101
+ return anonId;
2102
+ }
2103
+ // Cookie-less mode: use sessionStorage only
2104
+ if (this.config.cookielessMode) {
2105
+ let visitorId = getSessionStorage(STORAGE_KEYS.VISITOR_ID);
2106
+ if (!visitorId) {
2107
+ visitorId = generateUUID();
2108
+ setSessionStorage(STORAGE_KEYS.VISITOR_ID, visitorId);
2109
+ }
2110
+ return visitorId;
2111
+ }
2112
+ // Normal mode
2113
+ return getOrCreateVisitorId(this.config.useCookies);
2114
+ }
2115
+ /**
2116
+ * Create session ID
2117
+ */
2118
+ createSessionId() {
2119
+ return getOrCreateSessionId(this.config.sessionTimeout);
2120
+ }
2121
+ /**
2122
+ * Handle consent state changes
2123
+ */
2124
+ onConsentChange(state, previous) {
2125
+ logger.debug('Consent changed:', { from: previous, to: state });
2126
+ // If analytics consent was just granted
2127
+ if (state.analytics && !previous.analytics) {
2128
+ // Upgrade from anonymous ID to persistent ID
2129
+ if (this.config.consent.anonymousMode) {
2130
+ this.visitorId = getOrCreateVisitorId(this.config.useCookies);
2131
+ logger.info('Upgraded from anonymous to persistent visitor ID');
2132
+ }
2133
+ // Flush buffered events
2134
+ const buffered = this.consentManager.flushBuffer();
2135
+ for (const event of buffered) {
2136
+ // Update event with new visitor ID
2137
+ event.visitorId = this.visitorId;
2138
+ this.queue.push(event);
2139
+ }
2140
+ }
2141
+ }
1428
2142
  /**
1429
2143
  * Initialize enabled plugins
1430
2144
  */
@@ -1468,6 +2182,17 @@ class Tracker {
1468
2182
  timestamp: new Date().toISOString(),
1469
2183
  sdkVersion: SDK_VERSION,
1470
2184
  };
2185
+ // Check consent before tracking
2186
+ if (!this.consentManager.canTrack()) {
2187
+ // Buffer event for later if waitForConsent is enabled
2188
+ if (this.config.consent.waitForConsent) {
2189
+ this.consentManager.bufferEvent(event);
2190
+ return;
2191
+ }
2192
+ // Otherwise drop the event
2193
+ logger.debug('Event dropped (no consent):', eventName);
2194
+ return;
2195
+ }
1471
2196
  this.queue.push(event);
1472
2197
  logger.debug('Event tracked:', eventName, properties);
1473
2198
  }
@@ -1507,11 +2232,13 @@ class Tracker {
1507
2232
  * Update consent state
1508
2233
  */
1509
2234
  consent(state) {
1510
- logger.info('Consent updated:', state);
1511
- // TODO: Implement consent management
1512
- // - Store consent state
1513
- // - Enable/disable tracking based on consent
1514
- // - Notify plugins
2235
+ this.consentManager.update(state);
2236
+ }
2237
+ /**
2238
+ * Get current consent state
2239
+ */
2240
+ getConsentState() {
2241
+ return this.consentManager.getState();
1515
2242
  }
1516
2243
  /**
1517
2244
  * Toggle debug mode
@@ -1556,10 +2283,49 @@ class Tracker {
1556
2283
  reset() {
1557
2284
  logger.info('Resetting visitor data');
1558
2285
  resetIds(this.config.useCookies);
1559
- this.visitorId = getOrCreateVisitorId(this.config.useCookies);
1560
- this.sessionId = getOrCreateSessionId(this.config.sessionTimeout);
2286
+ this.visitorId = this.createVisitorId();
2287
+ this.sessionId = this.createSessionId();
1561
2288
  this.queue.clear();
1562
2289
  }
2290
+ /**
2291
+ * Delete all stored user data (GDPR right-to-erasure)
2292
+ */
2293
+ deleteData() {
2294
+ logger.info('Deleting all user data (GDPR request)');
2295
+ // Clear queue
2296
+ this.queue.clear();
2297
+ // Reset consent
2298
+ this.consentManager.reset();
2299
+ // Clear all stored IDs
2300
+ resetIds(this.config.useCookies);
2301
+ // Clear session storage items
2302
+ if (typeof sessionStorage !== 'undefined') {
2303
+ try {
2304
+ sessionStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
2305
+ sessionStorage.removeItem(STORAGE_KEYS.VISITOR_ID + '_anon');
2306
+ sessionStorage.removeItem(STORAGE_KEYS.SESSION_ID);
2307
+ sessionStorage.removeItem(STORAGE_KEYS.SESSION_TIMESTAMP);
2308
+ }
2309
+ catch {
2310
+ // Ignore errors
2311
+ }
2312
+ }
2313
+ // Clear localStorage items
2314
+ if (typeof localStorage !== 'undefined') {
2315
+ try {
2316
+ localStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
2317
+ localStorage.removeItem(STORAGE_KEYS.CONSENT);
2318
+ localStorage.removeItem(STORAGE_KEYS.EVENT_QUEUE);
2319
+ }
2320
+ catch {
2321
+ // Ignore errors
2322
+ }
2323
+ }
2324
+ // Generate new IDs
2325
+ this.visitorId = this.createVisitorId();
2326
+ this.sessionId = this.createSessionId();
2327
+ logger.info('All user data deleted');
2328
+ }
1563
2329
  /**
1564
2330
  * Destroy tracker and cleanup
1565
2331
  */
@@ -1582,7 +2348,7 @@ class Tracker {
1582
2348
 
1583
2349
  /**
1584
2350
  * Clianta SDK - CRM API Client
1585
- * @version 1.0.0
2351
+ * @see SDK_VERSION in core/config.ts
1586
2352
  */
1587
2353
  /**
1588
2354
  * CRM API Client for managing contacts and opportunities
@@ -1756,7 +2522,7 @@ class CRMClient {
1756
2522
  /**
1757
2523
  * Clianta SDK
1758
2524
  * Professional CRM and tracking SDK for lead generation
1759
- * @version 1.0.0
2525
+ * @see SDK_VERSION in core/config.ts
1760
2526
  */
1761
2527
  // Global instance cache
1762
2528
  let globalInstance = null;
@@ -1773,6 +2539,16 @@ let globalInstance = null;
1773
2539
  * debug: true,
1774
2540
  * plugins: ['pageView', 'forms', 'scroll'],
1775
2541
  * });
2542
+ *
2543
+ * @example
2544
+ * // With consent configuration
2545
+ * const tracker = clianta('your-workspace-id', {
2546
+ * consent: {
2547
+ * waitForConsent: true,
2548
+ * anonymousMode: true,
2549
+ * },
2550
+ * cookielessMode: true, // GDPR-friendly mode
2551
+ * });
1776
2552
  */
1777
2553
  function clianta(workspaceId, config) {
1778
2554
  // Return existing instance if same workspace
@@ -1794,8 +2570,9 @@ if (typeof window !== 'undefined') {
1794
2570
  clianta,
1795
2571
  Tracker,
1796
2572
  CRMClient,
2573
+ ConsentManager,
1797
2574
  };
1798
2575
  }
1799
2576
 
1800
- export { CRMClient, SDK_VERSION, Tracker, clianta, clianta as default };
2577
+ export { CRMClient, ConsentManager, SDK_VERSION, Tracker, clianta, clianta as default };
1801
2578
  //# sourceMappingURL=clianta.esm.js.map