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