@clianta/sdk 1.0.0 → 1.1.1

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