@kitbase/events 0.1.3 → 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/dist/index.cjs +472 -2
- 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 +467 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,11 @@ __export(index_exports, {
|
|
|
35
35
|
Kitbase: () => Kitbase,
|
|
36
36
|
KitbaseError: () => KitbaseError,
|
|
37
37
|
TimeoutError: () => TimeoutError,
|
|
38
|
-
ValidationError: () => ValidationError
|
|
38
|
+
ValidationError: () => ValidationError,
|
|
39
|
+
detectBot: () => detectBot,
|
|
40
|
+
getUserAgent: () => getUserAgent,
|
|
41
|
+
isBot: () => isBot,
|
|
42
|
+
isUserAgentBot: () => isUserAgentBot
|
|
39
43
|
});
|
|
40
44
|
module.exports = __toCommonJS(index_exports);
|
|
41
45
|
|
|
@@ -449,6 +453,269 @@ var EventQueue = class {
|
|
|
449
453
|
}
|
|
450
454
|
};
|
|
451
455
|
|
|
456
|
+
// src/botDetection.ts
|
|
457
|
+
var AUTOMATION_GLOBALS = [
|
|
458
|
+
"__webdriver_evaluate",
|
|
459
|
+
"__selenium_evaluate",
|
|
460
|
+
"__webdriver_script_function",
|
|
461
|
+
"__webdriver_unwrapped",
|
|
462
|
+
"__fxdriver_evaluate",
|
|
463
|
+
"__driver_evaluate",
|
|
464
|
+
"_Selenium_IDE_Recorder",
|
|
465
|
+
"_selenium",
|
|
466
|
+
"calledSelenium",
|
|
467
|
+
"$cdc_asdjflasutopfhvcZLmcfl_",
|
|
468
|
+
// Chrome DevTools Protocol marker
|
|
469
|
+
"__nightmare",
|
|
470
|
+
"domAutomation",
|
|
471
|
+
"domAutomationController"
|
|
472
|
+
];
|
|
473
|
+
var HEADLESS_PATTERNS = [
|
|
474
|
+
"headlesschrome",
|
|
475
|
+
"phantomjs",
|
|
476
|
+
"selenium",
|
|
477
|
+
"webdriver",
|
|
478
|
+
"puppeteer",
|
|
479
|
+
"playwright"
|
|
480
|
+
];
|
|
481
|
+
var HTTP_CLIENT_PATTERNS = [
|
|
482
|
+
"python",
|
|
483
|
+
"curl",
|
|
484
|
+
"wget",
|
|
485
|
+
"java/",
|
|
486
|
+
"go-http",
|
|
487
|
+
"node-fetch",
|
|
488
|
+
"axios",
|
|
489
|
+
"postman",
|
|
490
|
+
"insomnia",
|
|
491
|
+
"httpie",
|
|
492
|
+
"ruby",
|
|
493
|
+
"perl",
|
|
494
|
+
"scrapy",
|
|
495
|
+
"bot",
|
|
496
|
+
"spider",
|
|
497
|
+
"crawler",
|
|
498
|
+
"slurp",
|
|
499
|
+
"googlebot",
|
|
500
|
+
"bingbot",
|
|
501
|
+
"yandexbot",
|
|
502
|
+
"baiduspider",
|
|
503
|
+
"duckduckbot",
|
|
504
|
+
"facebookexternalhit",
|
|
505
|
+
"twitterbot",
|
|
506
|
+
"linkedinbot",
|
|
507
|
+
"whatsapp",
|
|
508
|
+
"telegram",
|
|
509
|
+
"discord",
|
|
510
|
+
"slack"
|
|
511
|
+
];
|
|
512
|
+
var DEFAULT_BOT_DETECTION_CONFIG = {
|
|
513
|
+
enabled: true,
|
|
514
|
+
checkWebdriver: true,
|
|
515
|
+
checkPhantomJS: true,
|
|
516
|
+
checkNightmare: true,
|
|
517
|
+
checkAutomationGlobals: true,
|
|
518
|
+
checkDocumentAttributes: true,
|
|
519
|
+
checkUserAgentHeadless: true,
|
|
520
|
+
checkUserAgentHttpClient: true
|
|
521
|
+
};
|
|
522
|
+
function isBrowser2() {
|
|
523
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
524
|
+
}
|
|
525
|
+
function getWindowProperty(key) {
|
|
526
|
+
try {
|
|
527
|
+
return window[key];
|
|
528
|
+
} catch {
|
|
529
|
+
return void 0;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function checkWebdriver() {
|
|
533
|
+
if (!isBrowser2()) return false;
|
|
534
|
+
try {
|
|
535
|
+
return window.navigator?.webdriver === true;
|
|
536
|
+
} catch {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function checkPhantomJS() {
|
|
541
|
+
if (!isBrowser2()) return false;
|
|
542
|
+
try {
|
|
543
|
+
return !!(getWindowProperty("callPhantom") || getWindowProperty("_phantom") || getWindowProperty("phantom"));
|
|
544
|
+
} catch {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function checkNightmare() {
|
|
549
|
+
if (!isBrowser2()) return false;
|
|
550
|
+
try {
|
|
551
|
+
return !!getWindowProperty("__nightmare");
|
|
552
|
+
} catch {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function checkAutomationGlobals() {
|
|
557
|
+
if (!isBrowser2()) return false;
|
|
558
|
+
try {
|
|
559
|
+
for (const global of AUTOMATION_GLOBALS) {
|
|
560
|
+
if (getWindowProperty(global) !== void 0) {
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return false;
|
|
565
|
+
} catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function checkDocumentAttributes() {
|
|
570
|
+
if (!isBrowser2()) return false;
|
|
571
|
+
try {
|
|
572
|
+
const docEl = document.documentElement;
|
|
573
|
+
if (!docEl) return false;
|
|
574
|
+
return !!(docEl.getAttribute("webdriver") || docEl.getAttribute("selenium") || docEl.getAttribute("driver"));
|
|
575
|
+
} catch {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function checkUserAgentHeadless() {
|
|
580
|
+
if (!isBrowser2()) return false;
|
|
581
|
+
try {
|
|
582
|
+
const ua = window.navigator?.userAgent?.toLowerCase() || "";
|
|
583
|
+
if (!ua) return false;
|
|
584
|
+
for (const pattern of HEADLESS_PATTERNS) {
|
|
585
|
+
if (ua.includes(pattern)) {
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
} catch {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function checkUserAgentHttpClient(additionalPatterns) {
|
|
595
|
+
if (!isBrowser2()) return false;
|
|
596
|
+
try {
|
|
597
|
+
const ua = window.navigator?.userAgent?.toLowerCase() || "";
|
|
598
|
+
if (!ua) return false;
|
|
599
|
+
for (const pattern of HTTP_CLIENT_PATTERNS) {
|
|
600
|
+
if (ua.includes(pattern)) {
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (additionalPatterns) {
|
|
605
|
+
for (const pattern of additionalPatterns) {
|
|
606
|
+
if (ua.includes(pattern.toLowerCase())) {
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return false;
|
|
612
|
+
} catch {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function checkMissingUserAgent() {
|
|
617
|
+
if (!isBrowser2()) return false;
|
|
618
|
+
try {
|
|
619
|
+
const ua = window.navigator?.userAgent;
|
|
620
|
+
return !ua || ua === "" || ua === "undefined" || ua.length < 10;
|
|
621
|
+
} catch {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function checkInvalidEnvironment() {
|
|
626
|
+
if (!isBrowser2()) return false;
|
|
627
|
+
try {
|
|
628
|
+
if (!window.navigator || !window.location || !window.document || typeof window.navigator !== "object" || typeof window.location !== "object" || typeof window.document !== "object") {
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
return false;
|
|
632
|
+
} catch {
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function detectBot(config = {}) {
|
|
637
|
+
const mergedConfig = { ...DEFAULT_BOT_DETECTION_CONFIG, ...config };
|
|
638
|
+
const checks = {
|
|
639
|
+
webdriver: mergedConfig.checkWebdriver ? checkWebdriver() : false,
|
|
640
|
+
phantomjs: mergedConfig.checkPhantomJS ? checkPhantomJS() : false,
|
|
641
|
+
nightmare: mergedConfig.checkNightmare ? checkNightmare() : false,
|
|
642
|
+
automationGlobals: mergedConfig.checkAutomationGlobals ? checkAutomationGlobals() : false,
|
|
643
|
+
documentAttributes: mergedConfig.checkDocumentAttributes ? checkDocumentAttributes() : false,
|
|
644
|
+
userAgentHeadless: mergedConfig.checkUserAgentHeadless ? checkUserAgentHeadless() : false,
|
|
645
|
+
userAgentHttpClient: mergedConfig.checkUserAgentHttpClient ? checkUserAgentHttpClient(config.additionalBotPatterns) : false,
|
|
646
|
+
missingUserAgent: checkMissingUserAgent(),
|
|
647
|
+
invalidEnvironment: checkInvalidEnvironment()
|
|
648
|
+
};
|
|
649
|
+
let reason;
|
|
650
|
+
if (checks.webdriver) {
|
|
651
|
+
reason = "WebDriver detected";
|
|
652
|
+
} else if (checks.phantomjs) {
|
|
653
|
+
reason = "PhantomJS detected";
|
|
654
|
+
} else if (checks.nightmare) {
|
|
655
|
+
reason = "Nightmare.js detected";
|
|
656
|
+
} else if (checks.automationGlobals) {
|
|
657
|
+
reason = "Automation tool globals detected";
|
|
658
|
+
} else if (checks.documentAttributes) {
|
|
659
|
+
reason = "Automation attributes on document element";
|
|
660
|
+
} else if (checks.userAgentHeadless) {
|
|
661
|
+
reason = "Headless browser user agent detected";
|
|
662
|
+
} else if (checks.userAgentHttpClient) {
|
|
663
|
+
reason = "HTTP client/bot user agent detected";
|
|
664
|
+
} else if (checks.missingUserAgent) {
|
|
665
|
+
reason = "Missing or invalid user agent";
|
|
666
|
+
} else if (checks.invalidEnvironment) {
|
|
667
|
+
reason = "Invalid browser environment";
|
|
668
|
+
}
|
|
669
|
+
const isBot2 = Object.values(checks).some(Boolean);
|
|
670
|
+
const result = {
|
|
671
|
+
isBot: isBot2,
|
|
672
|
+
reason,
|
|
673
|
+
checks
|
|
674
|
+
};
|
|
675
|
+
if (isBot2 && config.onBotDetected) {
|
|
676
|
+
try {
|
|
677
|
+
config.onBotDetected(result);
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
function isBot(config = {}) {
|
|
684
|
+
return detectBot(config).isBot;
|
|
685
|
+
}
|
|
686
|
+
function isUserAgentBot(userAgent, additionalPatterns) {
|
|
687
|
+
if (!userAgent || userAgent.length < 10) {
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
const ua = userAgent.toLowerCase();
|
|
691
|
+
for (const pattern of HEADLESS_PATTERNS) {
|
|
692
|
+
if (ua.includes(pattern)) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
for (const pattern of HTTP_CLIENT_PATTERNS) {
|
|
697
|
+
if (ua.includes(pattern)) {
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (additionalPatterns) {
|
|
702
|
+
for (const pattern of additionalPatterns) {
|
|
703
|
+
if (ua.includes(pattern.toLowerCase())) {
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
function getUserAgent() {
|
|
711
|
+
if (!isBrowser2()) return null;
|
|
712
|
+
try {
|
|
713
|
+
return window.navigator?.userAgent || null;
|
|
714
|
+
} catch {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
452
719
|
// src/client.ts
|
|
453
720
|
var DEFAULT_BASE_URL = "https://api.kitbase.dev";
|
|
454
721
|
var TIMEOUT = 3e4;
|
|
@@ -495,8 +762,13 @@ var Kitbase = class {
|
|
|
495
762
|
sessionStorageKey;
|
|
496
763
|
analyticsEnabled;
|
|
497
764
|
autoTrackPageViews;
|
|
765
|
+
autoTrackOutboundLinks;
|
|
498
766
|
userId = null;
|
|
499
767
|
unloadListenerAdded = false;
|
|
768
|
+
clickListenerAdded = false;
|
|
769
|
+
// Bot detection
|
|
770
|
+
botDetectionConfig;
|
|
771
|
+
botDetectionResult = null;
|
|
500
772
|
constructor(config) {
|
|
501
773
|
if (!config.token) {
|
|
502
774
|
throw new ValidationError("API token is required", "token");
|
|
@@ -515,6 +787,7 @@ var Kitbase = class {
|
|
|
515
787
|
this.sessionStorageKey = config.analytics?.sessionStorageKey ?? DEFAULT_SESSION_STORAGE_KEY;
|
|
516
788
|
this.analyticsEnabled = config.analytics?.autoTrackSessions ?? true;
|
|
517
789
|
this.autoTrackPageViews = config.analytics?.autoTrackPageViews ?? false;
|
|
790
|
+
this.autoTrackOutboundLinks = config.analytics?.autoTrackOutboundLinks ?? true;
|
|
518
791
|
if (this.analyticsEnabled) {
|
|
519
792
|
this.loadSession();
|
|
520
793
|
this.setupUnloadListener();
|
|
@@ -522,6 +795,9 @@ var Kitbase = class {
|
|
|
522
795
|
this.enableAutoPageViews();
|
|
523
796
|
}
|
|
524
797
|
}
|
|
798
|
+
if (this.autoTrackOutboundLinks && typeof window !== "undefined") {
|
|
799
|
+
this.setupOutboundLinkTracking();
|
|
800
|
+
}
|
|
525
801
|
this.offlineEnabled = config.offline?.enabled ?? false;
|
|
526
802
|
if (this.offlineEnabled) {
|
|
527
803
|
this.queue = new EventQueue(config.offline);
|
|
@@ -532,6 +808,21 @@ var Kitbase = class {
|
|
|
532
808
|
storageType: this.queue.getStorageType()
|
|
533
809
|
});
|
|
534
810
|
}
|
|
811
|
+
this.botDetectionConfig = {
|
|
812
|
+
...DEFAULT_BOT_DETECTION_CONFIG,
|
|
813
|
+
...config.botDetection
|
|
814
|
+
};
|
|
815
|
+
if (this.botDetectionConfig.enabled) {
|
|
816
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
817
|
+
if (this.botDetectionResult.isBot) {
|
|
818
|
+
this.log("Bot detected", {
|
|
819
|
+
reason: this.botDetectionResult.reason,
|
|
820
|
+
checks: this.botDetectionResult.checks
|
|
821
|
+
});
|
|
822
|
+
} else {
|
|
823
|
+
this.log("Bot detection enabled, no bot detected");
|
|
824
|
+
}
|
|
825
|
+
}
|
|
535
826
|
}
|
|
536
827
|
/**
|
|
537
828
|
* Initialize the anonymous ID from storage or generate a new one
|
|
@@ -752,6 +1043,82 @@ var Kitbase = class {
|
|
|
752
1043
|
return (Date.now() - startTime) / 1e3;
|
|
753
1044
|
}
|
|
754
1045
|
// ============================================================
|
|
1046
|
+
// Bot Detection
|
|
1047
|
+
// ============================================================
|
|
1048
|
+
/**
|
|
1049
|
+
* Check if the current visitor is detected as a bot
|
|
1050
|
+
*
|
|
1051
|
+
* @returns true if bot detected, false otherwise
|
|
1052
|
+
*
|
|
1053
|
+
* @example
|
|
1054
|
+
* ```typescript
|
|
1055
|
+
* if (kitbase.isBot()) {
|
|
1056
|
+
* console.log('Bot detected, tracking disabled');
|
|
1057
|
+
* }
|
|
1058
|
+
* ```
|
|
1059
|
+
*/
|
|
1060
|
+
isBot() {
|
|
1061
|
+
if (!this.botDetectionConfig.enabled) {
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
if (!this.botDetectionResult) {
|
|
1065
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
1066
|
+
}
|
|
1067
|
+
return this.botDetectionResult.isBot;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Get detailed bot detection result
|
|
1071
|
+
*
|
|
1072
|
+
* @returns Bot detection result with detailed checks, or null if detection not enabled
|
|
1073
|
+
*
|
|
1074
|
+
* @example
|
|
1075
|
+
* ```typescript
|
|
1076
|
+
* const result = kitbase.getBotDetectionResult();
|
|
1077
|
+
* if (result?.isBot) {
|
|
1078
|
+
* console.log('Bot detected:', result.reason);
|
|
1079
|
+
* console.log('Checks:', result.checks);
|
|
1080
|
+
* }
|
|
1081
|
+
* ```
|
|
1082
|
+
*/
|
|
1083
|
+
getBotDetectionResult() {
|
|
1084
|
+
if (!this.botDetectionConfig.enabled) {
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
if (!this.botDetectionResult) {
|
|
1088
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
1089
|
+
}
|
|
1090
|
+
return this.botDetectionResult;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Force re-run bot detection
|
|
1094
|
+
* Useful if you want to check again after page state changes
|
|
1095
|
+
*
|
|
1096
|
+
* @returns Updated bot detection result
|
|
1097
|
+
*
|
|
1098
|
+
* @example
|
|
1099
|
+
* ```typescript
|
|
1100
|
+
* const result = kitbase.redetectBot();
|
|
1101
|
+
* console.log('Is bot:', result.isBot);
|
|
1102
|
+
* ```
|
|
1103
|
+
*/
|
|
1104
|
+
redetectBot() {
|
|
1105
|
+
this.botDetectionResult = detectBot(this.botDetectionConfig);
|
|
1106
|
+
this.log("Bot detection re-run", {
|
|
1107
|
+
isBot: this.botDetectionResult.isBot,
|
|
1108
|
+
reason: this.botDetectionResult.reason
|
|
1109
|
+
});
|
|
1110
|
+
return this.botDetectionResult;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Check if bot blocking is currently active
|
|
1114
|
+
* When bot detection is enabled and a bot is detected, all events are blocked.
|
|
1115
|
+
*
|
|
1116
|
+
* @returns true if bots are being blocked from tracking
|
|
1117
|
+
*/
|
|
1118
|
+
isBotBlockingActive() {
|
|
1119
|
+
return this.botDetectionConfig.enabled === true && this.isBot();
|
|
1120
|
+
}
|
|
1121
|
+
// ============================================================
|
|
755
1122
|
// Offline Queue
|
|
756
1123
|
// ============================================================
|
|
757
1124
|
/**
|
|
@@ -829,6 +1196,14 @@ var Kitbase = class {
|
|
|
829
1196
|
*/
|
|
830
1197
|
async track(options) {
|
|
831
1198
|
this.validateTrackOptions(options);
|
|
1199
|
+
if (this.isBotBlockingActive()) {
|
|
1200
|
+
this.log("Event blocked - bot detected", { event: options.event });
|
|
1201
|
+
return {
|
|
1202
|
+
id: `blocked-bot-${Date.now()}`,
|
|
1203
|
+
event: options.event,
|
|
1204
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
832
1207
|
let duration;
|
|
833
1208
|
const startTime = this.timedEvents.get(options.event);
|
|
834
1209
|
if (startTime !== void 0) {
|
|
@@ -1053,6 +1428,97 @@ var Kitbase = class {
|
|
|
1053
1428
|
this.unloadListenerAdded = true;
|
|
1054
1429
|
this.log("Session lifecycle listeners added");
|
|
1055
1430
|
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Setup outbound link click tracking
|
|
1433
|
+
*/
|
|
1434
|
+
setupOutboundLinkTracking() {
|
|
1435
|
+
if (typeof window === "undefined" || this.clickListenerAdded) return;
|
|
1436
|
+
const handleClick = (event) => {
|
|
1437
|
+
const link = event.target?.closest?.("a");
|
|
1438
|
+
if (link) {
|
|
1439
|
+
this.handleLinkClick(link);
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
const handleKeydown = (event) => {
|
|
1443
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1444
|
+
const link = event.target?.closest?.("a");
|
|
1445
|
+
if (link) {
|
|
1446
|
+
this.handleLinkClick(link);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
document.addEventListener("click", handleClick);
|
|
1451
|
+
document.addEventListener("keydown", handleKeydown);
|
|
1452
|
+
this.clickListenerAdded = true;
|
|
1453
|
+
this.log("Outbound link tracking enabled");
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Handle link click for outbound tracking
|
|
1457
|
+
*/
|
|
1458
|
+
handleLinkClick(link) {
|
|
1459
|
+
if (!link.href) return;
|
|
1460
|
+
try {
|
|
1461
|
+
const linkUrl = new URL(link.href);
|
|
1462
|
+
if (linkUrl.protocol !== "http:" && linkUrl.protocol !== "https:") {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const currentHost = window.location.hostname;
|
|
1466
|
+
const linkHost = linkUrl.hostname;
|
|
1467
|
+
if (linkHost === currentHost) {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (this.isSameRootDomain(currentHost, linkHost)) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
this.trackOutboundLink({
|
|
1474
|
+
url: link.href,
|
|
1475
|
+
text: link.textContent?.trim() || ""
|
|
1476
|
+
}).catch((err) => this.log("Failed to track outbound link", err));
|
|
1477
|
+
} catch {
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Get root domain from hostname (e.g., blog.example.com -> example.com)
|
|
1482
|
+
*/
|
|
1483
|
+
getRootDomain(hostname) {
|
|
1484
|
+
const parts = hostname.replace(/^www\./, "").split(".");
|
|
1485
|
+
if (parts.length >= 2) {
|
|
1486
|
+
return parts.slice(-2).join(".");
|
|
1487
|
+
}
|
|
1488
|
+
return hostname;
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Check if two hostnames share the same root domain
|
|
1492
|
+
*/
|
|
1493
|
+
isSameRootDomain(host1, host2) {
|
|
1494
|
+
return this.getRootDomain(host1) === this.getRootDomain(host2);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Track an outbound link click
|
|
1498
|
+
*
|
|
1499
|
+
* @param options - Outbound link options
|
|
1500
|
+
* @returns Promise resolving to the track response
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* ```typescript
|
|
1504
|
+
* await kitbase.trackOutboundLink({
|
|
1505
|
+
* url: 'https://example.com',
|
|
1506
|
+
* text: 'Visit Example',
|
|
1507
|
+
* });
|
|
1508
|
+
* ```
|
|
1509
|
+
*/
|
|
1510
|
+
async trackOutboundLink(options) {
|
|
1511
|
+
const session = this.getOrCreateSession();
|
|
1512
|
+
return this.track({
|
|
1513
|
+
channel: ANALYTICS_CHANNEL,
|
|
1514
|
+
event: "outbound_link",
|
|
1515
|
+
tags: {
|
|
1516
|
+
__session_id: session.id,
|
|
1517
|
+
__url: options.url,
|
|
1518
|
+
__text: options.text || ""
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1056
1522
|
/**
|
|
1057
1523
|
* Get UTM parameters from URL
|
|
1058
1524
|
*/
|
|
@@ -1258,6 +1724,10 @@ var Kitbase = class {
|
|
|
1258
1724
|
Kitbase,
|
|
1259
1725
|
KitbaseError,
|
|
1260
1726
|
TimeoutError,
|
|
1261
|
-
ValidationError
|
|
1727
|
+
ValidationError,
|
|
1728
|
+
detectBot,
|
|
1729
|
+
getUserAgent,
|
|
1730
|
+
isBot,
|
|
1731
|
+
isUserAgentBot
|
|
1262
1732
|
});
|
|
1263
1733
|
//# sourceMappingURL=index.cjs.map
|