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