@kitbase/events 0.1.2 → 0.1.4
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 +332 -0
- package/dist/index.cjs +473 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +232 -1
- package/dist/index.d.ts +232 -1
- package/dist/index.js +468 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -408,6 +408,269 @@ var EventQueue = class {
|
|
|
408
408
|
}
|
|
409
409
|
};
|
|
410
410
|
|
|
411
|
+
// src/botDetection.ts
|
|
412
|
+
var AUTOMATION_GLOBALS = [
|
|
413
|
+
"__webdriver_evaluate",
|
|
414
|
+
"__selenium_evaluate",
|
|
415
|
+
"__webdriver_script_function",
|
|
416
|
+
"__webdriver_unwrapped",
|
|
417
|
+
"__fxdriver_evaluate",
|
|
418
|
+
"__driver_evaluate",
|
|
419
|
+
"_Selenium_IDE_Recorder",
|
|
420
|
+
"_selenium",
|
|
421
|
+
"calledSelenium",
|
|
422
|
+
"$cdc_asdjflasutopfhvcZLmcfl_",
|
|
423
|
+
// Chrome DevTools Protocol marker
|
|
424
|
+
"__nightmare",
|
|
425
|
+
"domAutomation",
|
|
426
|
+
"domAutomationController"
|
|
427
|
+
];
|
|
428
|
+
var HEADLESS_PATTERNS = [
|
|
429
|
+
"headlesschrome",
|
|
430
|
+
"phantomjs",
|
|
431
|
+
"selenium",
|
|
432
|
+
"webdriver",
|
|
433
|
+
"puppeteer",
|
|
434
|
+
"playwright"
|
|
435
|
+
];
|
|
436
|
+
var HTTP_CLIENT_PATTERNS = [
|
|
437
|
+
"python",
|
|
438
|
+
"curl",
|
|
439
|
+
"wget",
|
|
440
|
+
"java/",
|
|
441
|
+
"go-http",
|
|
442
|
+
"node-fetch",
|
|
443
|
+
"axios",
|
|
444
|
+
"postman",
|
|
445
|
+
"insomnia",
|
|
446
|
+
"httpie",
|
|
447
|
+
"ruby",
|
|
448
|
+
"perl",
|
|
449
|
+
"scrapy",
|
|
450
|
+
"bot",
|
|
451
|
+
"spider",
|
|
452
|
+
"crawler",
|
|
453
|
+
"slurp",
|
|
454
|
+
"googlebot",
|
|
455
|
+
"bingbot",
|
|
456
|
+
"yandexbot",
|
|
457
|
+
"baiduspider",
|
|
458
|
+
"duckduckbot",
|
|
459
|
+
"facebookexternalhit",
|
|
460
|
+
"twitterbot",
|
|
461
|
+
"linkedinbot",
|
|
462
|
+
"whatsapp",
|
|
463
|
+
"telegram",
|
|
464
|
+
"discord",
|
|
465
|
+
"slack"
|
|
466
|
+
];
|
|
467
|
+
var DEFAULT_BOT_DETECTION_CONFIG = {
|
|
468
|
+
enabled: true,
|
|
469
|
+
checkWebdriver: true,
|
|
470
|
+
checkPhantomJS: true,
|
|
471
|
+
checkNightmare: true,
|
|
472
|
+
checkAutomationGlobals: true,
|
|
473
|
+
checkDocumentAttributes: true,
|
|
474
|
+
checkUserAgentHeadless: true,
|
|
475
|
+
checkUserAgentHttpClient: true
|
|
476
|
+
};
|
|
477
|
+
function isBrowser2() {
|
|
478
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
479
|
+
}
|
|
480
|
+
function getWindowProperty(key) {
|
|
481
|
+
try {
|
|
482
|
+
return window[key];
|
|
483
|
+
} catch {
|
|
484
|
+
return void 0;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function checkWebdriver() {
|
|
488
|
+
if (!isBrowser2()) return false;
|
|
489
|
+
try {
|
|
490
|
+
return window.navigator?.webdriver === true;
|
|
491
|
+
} catch {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function checkPhantomJS() {
|
|
496
|
+
if (!isBrowser2()) return false;
|
|
497
|
+
try {
|
|
498
|
+
return !!(getWindowProperty("callPhantom") || getWindowProperty("_phantom") || getWindowProperty("phantom"));
|
|
499
|
+
} catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function checkNightmare() {
|
|
504
|
+
if (!isBrowser2()) return false;
|
|
505
|
+
try {
|
|
506
|
+
return !!getWindowProperty("__nightmare");
|
|
507
|
+
} catch {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function checkAutomationGlobals() {
|
|
512
|
+
if (!isBrowser2()) return false;
|
|
513
|
+
try {
|
|
514
|
+
for (const global of AUTOMATION_GLOBALS) {
|
|
515
|
+
if (getWindowProperty(global) !== void 0) {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return false;
|
|
520
|
+
} catch {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function checkDocumentAttributes() {
|
|
525
|
+
if (!isBrowser2()) return false;
|
|
526
|
+
try {
|
|
527
|
+
const docEl = document.documentElement;
|
|
528
|
+
if (!docEl) return false;
|
|
529
|
+
return !!(docEl.getAttribute("webdriver") || docEl.getAttribute("selenium") || docEl.getAttribute("driver"));
|
|
530
|
+
} catch {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function checkUserAgentHeadless() {
|
|
535
|
+
if (!isBrowser2()) return false;
|
|
536
|
+
try {
|
|
537
|
+
const ua = window.navigator?.userAgent?.toLowerCase() || "";
|
|
538
|
+
if (!ua) return false;
|
|
539
|
+
for (const pattern of HEADLESS_PATTERNS) {
|
|
540
|
+
if (ua.includes(pattern)) {
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return false;
|
|
545
|
+
} catch {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function checkUserAgentHttpClient(additionalPatterns) {
|
|
550
|
+
if (!isBrowser2()) return false;
|
|
551
|
+
try {
|
|
552
|
+
const ua = window.navigator?.userAgent?.toLowerCase() || "";
|
|
553
|
+
if (!ua) return false;
|
|
554
|
+
for (const pattern of HTTP_CLIENT_PATTERNS) {
|
|
555
|
+
if (ua.includes(pattern)) {
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (additionalPatterns) {
|
|
560
|
+
for (const pattern of additionalPatterns) {
|
|
561
|
+
if (ua.includes(pattern.toLowerCase())) {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return false;
|
|
567
|
+
} catch {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function checkMissingUserAgent() {
|
|
572
|
+
if (!isBrowser2()) return false;
|
|
573
|
+
try {
|
|
574
|
+
const ua = window.navigator?.userAgent;
|
|
575
|
+
return !ua || ua === "" || ua === "undefined" || ua.length < 10;
|
|
576
|
+
} catch {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function checkInvalidEnvironment() {
|
|
581
|
+
if (!isBrowser2()) return false;
|
|
582
|
+
try {
|
|
583
|
+
if (!window.navigator || !window.location || !window.document || typeof window.navigator !== "object" || typeof window.location !== "object" || typeof window.document !== "object") {
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
return false;
|
|
587
|
+
} catch {
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function detectBot(config = {}) {
|
|
592
|
+
const mergedConfig = { ...DEFAULT_BOT_DETECTION_CONFIG, ...config };
|
|
593
|
+
const checks = {
|
|
594
|
+
webdriver: mergedConfig.checkWebdriver ? checkWebdriver() : false,
|
|
595
|
+
phantomjs: mergedConfig.checkPhantomJS ? checkPhantomJS() : false,
|
|
596
|
+
nightmare: mergedConfig.checkNightmare ? checkNightmare() : false,
|
|
597
|
+
automationGlobals: mergedConfig.checkAutomationGlobals ? checkAutomationGlobals() : false,
|
|
598
|
+
documentAttributes: mergedConfig.checkDocumentAttributes ? checkDocumentAttributes() : false,
|
|
599
|
+
userAgentHeadless: mergedConfig.checkUserAgentHeadless ? checkUserAgentHeadless() : false,
|
|
600
|
+
userAgentHttpClient: mergedConfig.checkUserAgentHttpClient ? checkUserAgentHttpClient(config.additionalBotPatterns) : false,
|
|
601
|
+
missingUserAgent: checkMissingUserAgent(),
|
|
602
|
+
invalidEnvironment: checkInvalidEnvironment()
|
|
603
|
+
};
|
|
604
|
+
let reason;
|
|
605
|
+
if (checks.webdriver) {
|
|
606
|
+
reason = "WebDriver detected";
|
|
607
|
+
} else if (checks.phantomjs) {
|
|
608
|
+
reason = "PhantomJS detected";
|
|
609
|
+
} else if (checks.nightmare) {
|
|
610
|
+
reason = "Nightmare.js detected";
|
|
611
|
+
} else if (checks.automationGlobals) {
|
|
612
|
+
reason = "Automation tool globals detected";
|
|
613
|
+
} else if (checks.documentAttributes) {
|
|
614
|
+
reason = "Automation attributes on document element";
|
|
615
|
+
} else if (checks.userAgentHeadless) {
|
|
616
|
+
reason = "Headless browser user agent detected";
|
|
617
|
+
} else if (checks.userAgentHttpClient) {
|
|
618
|
+
reason = "HTTP client/bot user agent detected";
|
|
619
|
+
} else if (checks.missingUserAgent) {
|
|
620
|
+
reason = "Missing or invalid user agent";
|
|
621
|
+
} else if (checks.invalidEnvironment) {
|
|
622
|
+
reason = "Invalid browser environment";
|
|
623
|
+
}
|
|
624
|
+
const isBot2 = Object.values(checks).some(Boolean);
|
|
625
|
+
const result = {
|
|
626
|
+
isBot: isBot2,
|
|
627
|
+
reason,
|
|
628
|
+
checks
|
|
629
|
+
};
|
|
630
|
+
if (isBot2 && config.onBotDetected) {
|
|
631
|
+
try {
|
|
632
|
+
config.onBotDetected(result);
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
function isBot(config = {}) {
|
|
639
|
+
return detectBot(config).isBot;
|
|
640
|
+
}
|
|
641
|
+
function isUserAgentBot(userAgent, additionalPatterns) {
|
|
642
|
+
if (!userAgent || userAgent.length < 10) {
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
const ua = userAgent.toLowerCase();
|
|
646
|
+
for (const pattern of HEADLESS_PATTERNS) {
|
|
647
|
+
if (ua.includes(pattern)) {
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
for (const pattern of HTTP_CLIENT_PATTERNS) {
|
|
652
|
+
if (ua.includes(pattern)) {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (additionalPatterns) {
|
|
657
|
+
for (const pattern of additionalPatterns) {
|
|
658
|
+
if (ua.includes(pattern.toLowerCase())) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
function getUserAgent() {
|
|
666
|
+
if (!isBrowser2()) return null;
|
|
667
|
+
try {
|
|
668
|
+
return window.navigator?.userAgent || null;
|
|
669
|
+
} catch {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
411
674
|
// src/client.ts
|
|
412
675
|
var DEFAULT_BASE_URL = "https://api.kitbase.dev";
|
|
413
676
|
var TIMEOUT = 3e4;
|
|
@@ -454,14 +717,19 @@ var Kitbase = class {
|
|
|
454
717
|
sessionStorageKey;
|
|
455
718
|
analyticsEnabled;
|
|
456
719
|
autoTrackPageViews;
|
|
720
|
+
autoTrackOutboundLinks;
|
|
457
721
|
userId = null;
|
|
458
722
|
unloadListenerAdded = false;
|
|
723
|
+
clickListenerAdded = false;
|
|
724
|
+
// Bot detection
|
|
725
|
+
botDetectionConfig;
|
|
726
|
+
botDetectionResult = null;
|
|
459
727
|
constructor(config) {
|
|
460
728
|
if (!config.token) {
|
|
461
729
|
throw new ValidationError("API token is required", "token");
|
|
462
730
|
}
|
|
463
731
|
this.token = config.token;
|
|
464
|
-
this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
732
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
465
733
|
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
466
734
|
this.debugMode = config.debug ?? false;
|
|
467
735
|
if (config.storage === null) {
|
|
@@ -474,6 +742,7 @@ var Kitbase = class {
|
|
|
474
742
|
this.sessionStorageKey = config.analytics?.sessionStorageKey ?? DEFAULT_SESSION_STORAGE_KEY;
|
|
475
743
|
this.analyticsEnabled = config.analytics?.autoTrackSessions ?? true;
|
|
476
744
|
this.autoTrackPageViews = config.analytics?.autoTrackPageViews ?? false;
|
|
745
|
+
this.autoTrackOutboundLinks = config.analytics?.autoTrackOutboundLinks ?? true;
|
|
477
746
|
if (this.analyticsEnabled) {
|
|
478
747
|
this.loadSession();
|
|
479
748
|
this.setupUnloadListener();
|
|
@@ -481,6 +750,9 @@ var Kitbase = class {
|
|
|
481
750
|
this.enableAutoPageViews();
|
|
482
751
|
}
|
|
483
752
|
}
|
|
753
|
+
if (this.autoTrackOutboundLinks && typeof window !== "undefined") {
|
|
754
|
+
this.setupOutboundLinkTracking();
|
|
755
|
+
}
|
|
484
756
|
this.offlineEnabled = config.offline?.enabled ?? false;
|
|
485
757
|
if (this.offlineEnabled) {
|
|
486
758
|
this.queue = new EventQueue(config.offline);
|
|
@@ -491,6 +763,21 @@ var Kitbase = class {
|
|
|
491
763
|
storageType: this.queue.getStorageType()
|
|
492
764
|
});
|
|
493
765
|
}
|
|
766
|
+
this.botDetectionConfig = {
|
|
767
|
+
...DEFAULT_BOT_DETECTION_CONFIG,
|
|
768
|
+
...config.botDetection
|
|
769
|
+
};
|
|
770
|
+
if (this.botDetectionConfig.enabled) {
|
|
771
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
772
|
+
if (this.botDetectionResult.isBot) {
|
|
773
|
+
this.log("Bot detected", {
|
|
774
|
+
reason: this.botDetectionResult.reason,
|
|
775
|
+
checks: this.botDetectionResult.checks
|
|
776
|
+
});
|
|
777
|
+
} else {
|
|
778
|
+
this.log("Bot detection enabled, no bot detected");
|
|
779
|
+
}
|
|
780
|
+
}
|
|
494
781
|
}
|
|
495
782
|
/**
|
|
496
783
|
* Initialize the anonymous ID from storage or generate a new one
|
|
@@ -711,6 +998,82 @@ var Kitbase = class {
|
|
|
711
998
|
return (Date.now() - startTime) / 1e3;
|
|
712
999
|
}
|
|
713
1000
|
// ============================================================
|
|
1001
|
+
// Bot Detection
|
|
1002
|
+
// ============================================================
|
|
1003
|
+
/**
|
|
1004
|
+
* Check if the current visitor is detected as a bot
|
|
1005
|
+
*
|
|
1006
|
+
* @returns true if bot detected, false otherwise
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* ```typescript
|
|
1010
|
+
* if (kitbase.isBot()) {
|
|
1011
|
+
* console.log('Bot detected, tracking disabled');
|
|
1012
|
+
* }
|
|
1013
|
+
* ```
|
|
1014
|
+
*/
|
|
1015
|
+
isBot() {
|
|
1016
|
+
if (!this.botDetectionConfig.enabled) {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
if (!this.botDetectionResult) {
|
|
1020
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
1021
|
+
}
|
|
1022
|
+
return this.botDetectionResult.isBot;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Get detailed bot detection result
|
|
1026
|
+
*
|
|
1027
|
+
* @returns Bot detection result with detailed checks, or null if detection not enabled
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* const result = kitbase.getBotDetectionResult();
|
|
1032
|
+
* if (result?.isBot) {
|
|
1033
|
+
* console.log('Bot detected:', result.reason);
|
|
1034
|
+
* console.log('Checks:', result.checks);
|
|
1035
|
+
* }
|
|
1036
|
+
* ```
|
|
1037
|
+
*/
|
|
1038
|
+
getBotDetectionResult() {
|
|
1039
|
+
if (!this.botDetectionConfig.enabled) {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
if (!this.botDetectionResult) {
|
|
1043
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
1044
|
+
}
|
|
1045
|
+
return this.botDetectionResult;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Force re-run bot detection
|
|
1049
|
+
* Useful if you want to check again after page state changes
|
|
1050
|
+
*
|
|
1051
|
+
* @returns Updated bot detection result
|
|
1052
|
+
*
|
|
1053
|
+
* @example
|
|
1054
|
+
* ```typescript
|
|
1055
|
+
* const result = kitbase.redetectBot();
|
|
1056
|
+
* console.log('Is bot:', result.isBot);
|
|
1057
|
+
* ```
|
|
1058
|
+
*/
|
|
1059
|
+
redetectBot() {
|
|
1060
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
1061
|
+
this.log("Bot detection re-run", {
|
|
1062
|
+
isBot: this.botDetectionResult.isBot,
|
|
1063
|
+
reason: this.botDetectionResult.reason
|
|
1064
|
+
});
|
|
1065
|
+
return this.botDetectionResult;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Check if bot blocking is currently active
|
|
1069
|
+
* When bot detection is enabled and a bot is detected, all events are blocked.
|
|
1070
|
+
*
|
|
1071
|
+
* @returns true if bots are being blocked from tracking
|
|
1072
|
+
*/
|
|
1073
|
+
isBotBlockingActive() {
|
|
1074
|
+
return this.botDetectionConfig.enabled === true && this.isBot();
|
|
1075
|
+
}
|
|
1076
|
+
// ============================================================
|
|
714
1077
|
// Offline Queue
|
|
715
1078
|
// ============================================================
|
|
716
1079
|
/**
|
|
@@ -788,6 +1151,14 @@ var Kitbase = class {
|
|
|
788
1151
|
*/
|
|
789
1152
|
async track(options) {
|
|
790
1153
|
this.validateTrackOptions(options);
|
|
1154
|
+
if (this.isBotBlockingActive()) {
|
|
1155
|
+
this.log("Event blocked - bot detected", { event: options.event });
|
|
1156
|
+
return {
|
|
1157
|
+
id: `blocked-bot-${Date.now()}`,
|
|
1158
|
+
event: options.event,
|
|
1159
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
791
1162
|
let duration;
|
|
792
1163
|
const startTime = this.timedEvents.get(options.event);
|
|
793
1164
|
if (startTime !== void 0) {
|
|
@@ -1012,6 +1383,97 @@ var Kitbase = class {
|
|
|
1012
1383
|
this.unloadListenerAdded = true;
|
|
1013
1384
|
this.log("Session lifecycle listeners added");
|
|
1014
1385
|
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Setup outbound link click tracking
|
|
1388
|
+
*/
|
|
1389
|
+
setupOutboundLinkTracking() {
|
|
1390
|
+
if (typeof window === "undefined" || this.clickListenerAdded) return;
|
|
1391
|
+
const handleClick = (event) => {
|
|
1392
|
+
const link = event.target?.closest?.("a");
|
|
1393
|
+
if (link) {
|
|
1394
|
+
this.handleLinkClick(link);
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
const handleKeydown = (event) => {
|
|
1398
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1399
|
+
const link = event.target?.closest?.("a");
|
|
1400
|
+
if (link) {
|
|
1401
|
+
this.handleLinkClick(link);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
document.addEventListener("click", handleClick);
|
|
1406
|
+
document.addEventListener("keydown", handleKeydown);
|
|
1407
|
+
this.clickListenerAdded = true;
|
|
1408
|
+
this.log("Outbound link tracking enabled");
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Handle link click for outbound tracking
|
|
1412
|
+
*/
|
|
1413
|
+
handleLinkClick(link) {
|
|
1414
|
+
if (!link.href) return;
|
|
1415
|
+
try {
|
|
1416
|
+
const linkUrl = new URL(link.href);
|
|
1417
|
+
if (linkUrl.protocol !== "http:" && linkUrl.protocol !== "https:") {
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const currentHost = window.location.hostname;
|
|
1421
|
+
const linkHost = linkUrl.hostname;
|
|
1422
|
+
if (linkHost === currentHost) {
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (this.isSameRootDomain(currentHost, linkHost)) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
this.trackOutboundLink({
|
|
1429
|
+
url: link.href,
|
|
1430
|
+
text: link.textContent?.trim() || ""
|
|
1431
|
+
}).catch((err) => this.log("Failed to track outbound link", err));
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Get root domain from hostname (e.g., blog.example.com -> example.com)
|
|
1437
|
+
*/
|
|
1438
|
+
getRootDomain(hostname) {
|
|
1439
|
+
const parts = hostname.replace(/^www\./, "").split(".");
|
|
1440
|
+
if (parts.length >= 2) {
|
|
1441
|
+
return parts.slice(-2).join(".");
|
|
1442
|
+
}
|
|
1443
|
+
return hostname;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Check if two hostnames share the same root domain
|
|
1447
|
+
*/
|
|
1448
|
+
isSameRootDomain(host1, host2) {
|
|
1449
|
+
return this.getRootDomain(host1) === this.getRootDomain(host2);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Track an outbound link click
|
|
1453
|
+
*
|
|
1454
|
+
* @param options - Outbound link options
|
|
1455
|
+
* @returns Promise resolving to the track response
|
|
1456
|
+
*
|
|
1457
|
+
* @example
|
|
1458
|
+
* ```typescript
|
|
1459
|
+
* await kitbase.trackOutboundLink({
|
|
1460
|
+
* url: 'https://example.com',
|
|
1461
|
+
* text: 'Visit Example',
|
|
1462
|
+
* });
|
|
1463
|
+
* ```
|
|
1464
|
+
*/
|
|
1465
|
+
async trackOutboundLink(options) {
|
|
1466
|
+
const session = this.getOrCreateSession();
|
|
1467
|
+
return this.track({
|
|
1468
|
+
channel: ANALYTICS_CHANNEL,
|
|
1469
|
+
event: "outbound_link",
|
|
1470
|
+
tags: {
|
|
1471
|
+
__session_id: session.id,
|
|
1472
|
+
__url: options.url,
|
|
1473
|
+
__text: options.text || ""
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1015
1477
|
/**
|
|
1016
1478
|
* Get UTM parameters from URL
|
|
1017
1479
|
*/
|
|
@@ -1216,6 +1678,10 @@ export {
|
|
|
1216
1678
|
Kitbase,
|
|
1217
1679
|
KitbaseError,
|
|
1218
1680
|
TimeoutError,
|
|
1219
|
-
ValidationError
|
|
1681
|
+
ValidationError,
|
|
1682
|
+
detectBot,
|
|
1683
|
+
getUserAgent,
|
|
1684
|
+
isBot,
|
|
1685
|
+
isUserAgentBot
|
|
1220
1686
|
};
|
|
1221
1687
|
//# sourceMappingURL=index.js.map
|