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