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