@devskin/browser-sdk 1.0.41 → 1.0.42

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.
@@ -13475,11 +13475,12 @@
13475
13475
  },
13476
13476
  // Configuration options
13477
13477
  sampling: {
13478
- // Mouse interactions
13479
- mousemove: this.config.sampleRate !== undefined
13480
- ? Math.floor(100 / this.config.sampleRate)
13481
- : true,
13482
- // Mouse interactions
13478
+ // Mouse movement sampling - throttle to reduce bandwidth
13479
+ // e.g. 0.1 = capture 10% of mouse movements (Math.floor(1/0.1) = 10, meaning capture 1 every 10 events)
13480
+ mousemove: this.config.mouseMoveSampleRate !== undefined
13481
+ ? Math.max(1, Math.floor(1 / this.config.mouseMoveSampleRate))
13482
+ : 10, // Default: capture 1 every 10 mouse movements (10%)
13483
+ // Mouse interactions (clicks, etc) - always capture
13483
13484
  mouseInteraction: true,
13484
13485
  // Scroll events
13485
13486
  scroll: 150, // Throttle scroll events to every 150ms
@@ -13894,6 +13895,72 @@
13894
13895
  // private sessionRecorder: SessionRecorder | null = null; // Replaced by RRWebRecorder
13895
13896
  this.rrwebRecorder = null;
13896
13897
  }
13898
+ /**
13899
+ * Detect if the current visitor is a bot/crawler
13900
+ */
13901
+ isBot() {
13902
+ if (typeof navigator === 'undefined')
13903
+ return true;
13904
+ const botPatterns = [
13905
+ /bot/i,
13906
+ /crawler/i,
13907
+ /spider/i,
13908
+ /crawling/i,
13909
+ /google/i,
13910
+ /bing/i,
13911
+ /yahoo/i,
13912
+ /duckduckgo/i,
13913
+ /baiduspider/i,
13914
+ /yandex/i,
13915
+ /facebookexternalhit/i,
13916
+ /twitterbot/i,
13917
+ /rogerbot/i,
13918
+ /linkedinbot/i,
13919
+ /embedly/i,
13920
+ /quora link preview/i,
13921
+ /showyoubot/i,
13922
+ /outbrain/i,
13923
+ /pinterest/i,
13924
+ /slackbot/i,
13925
+ /vkShare/i,
13926
+ /W3C_Validator/i,
13927
+ /redditbot/i,
13928
+ /Applebot/i,
13929
+ /WhatsApp/i,
13930
+ /flipboard/i,
13931
+ /tumblr/i,
13932
+ /bitlybot/i,
13933
+ /SkypeUriPreview/i,
13934
+ /nuzzel/i,
13935
+ /Discordbot/i,
13936
+ /Google Page Speed/i,
13937
+ /Qwantify/i,
13938
+ /pinterestbot/i,
13939
+ /Bitrix link preview/i,
13940
+ /XING-contenttabreceiver/i,
13941
+ /Chrome-Lighthouse/i,
13942
+ /telegrambot/i,
13943
+ /Lighthouse/i,
13944
+ /GTmetrix/i,
13945
+ /PageSpeed/i,
13946
+ /HeadlessChrome/i,
13947
+ /PhantomJS/i,
13948
+ ];
13949
+ const userAgent = navigator.userAgent;
13950
+ // Check user agent against bot patterns
13951
+ if (botPatterns.some(pattern => pattern.test(userAgent))) {
13952
+ return true;
13953
+ }
13954
+ // Check for headless browsers
13955
+ if ('webdriver' in navigator && navigator.webdriver === true) {
13956
+ return true;
13957
+ }
13958
+ // Check for headless Chrome
13959
+ if (userAgent.includes('HeadlessChrome')) {
13960
+ return true;
13961
+ }
13962
+ return false;
13963
+ }
13897
13964
  /**
13898
13965
  * Initialize the DevSkin SDK
13899
13966
  * Uses requestIdleCallback to defer heavy initialization without blocking the page
@@ -13926,7 +13993,7 @@
13926
13993
  // Wait for session creation to complete before starting collectors
13927
13994
  this.startSession().then(() => {
13928
13995
  // Session created, now safe to start collectors that send data
13929
- var _a, _b;
13996
+ var _a, _b, _c;
13930
13997
  if (this.config.captureWebVitals) {
13931
13998
  this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13932
13999
  this.performanceCollector.start();
@@ -13953,36 +14020,53 @@
13953
14020
  }
13954
14021
  // Initialize session recording with rrweb
13955
14022
  if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13956
- // Use RRWebRecorder for complete DOM recording
13957
- // Pass sessionStartTime to ensure timestamp continuity across page navigations
13958
- this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13959
- enabled: true,
13960
- sampleRate: this.config.sessionRecording.sampling || 0.5,
13961
- blockClass: 'rr-block',
13962
- ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13963
- maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13964
- ? this.config.sessionRecording.maskAllInputs
13965
- : true,
13966
- maskInputOptions: {
13967
- password: true,
13968
- email: true,
13969
- tel: true,
13970
- },
13971
- recordCanvas: this.config.sessionRecording.recordCanvas || false,
13972
- collectFonts: true,
13973
- inlineStylesheet: true,
13974
- checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13975
- checkoutEveryNth: 200, // Every 200 events
13976
- }, (events) => {
13977
- var _a;
13978
- // Send rrweb events to backend
13979
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13980
- }, this.sessionStartTime // Pass session start time for timestamp continuity
13981
- );
13982
- // Start recording immediately (session already created)
13983
- this.rrwebRecorder.start();
13984
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
13985
- console.log('[DevSkin] RRWeb recording started for session:', this.sessionId);
14023
+ // Skip recording for bots/crawlers to avoid impacting SEO
14024
+ const isBot = this.isBot();
14025
+ // Apply session sampling - decide if this session should be recorded
14026
+ const samplingRate = this.config.sessionRecording.sampling || 1.0;
14027
+ const shouldRecord = !isBot && Math.random() < samplingRate;
14028
+ if (shouldRecord) {
14029
+ // Use RRWebRecorder for complete DOM recording
14030
+ // Pass sessionStartTime to ensure timestamp continuity across page navigations
14031
+ this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
14032
+ enabled: true,
14033
+ mouseMoveSampleRate: 0.5, // Sample 10% of mouse movements to reduce bandwidth
14034
+ blockClass: 'rr-block',
14035
+ ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
14036
+ maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
14037
+ ? this.config.sessionRecording.maskAllInputs
14038
+ : true,
14039
+ maskInputOptions: {
14040
+ password: true,
14041
+ email: true,
14042
+ tel: true,
14043
+ },
14044
+ recordCanvas: this.config.sessionRecording.recordCanvas || false,
14045
+ collectFonts: true,
14046
+ inlineStylesheet: true,
14047
+ checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
14048
+ checkoutEveryNth: 200, // Every 200 events
14049
+ }, (events) => {
14050
+ var _a;
14051
+ // Send rrweb events to backend
14052
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
14053
+ }, this.sessionStartTime // Pass session start time for timestamp continuity
14054
+ );
14055
+ // Start recording immediately (session already created)
14056
+ this.rrwebRecorder.start();
14057
+ if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14058
+ console.log(`[DevSkin] RRWeb recording started for session: ${this.sessionId} (sampling: ${samplingRate * 100}%)`);
14059
+ }
14060
+ }
14061
+ else {
14062
+ if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14063
+ if (isBot) {
14064
+ console.log(`[DevSkin] Session ${this.sessionId} skipped recording (bot/crawler detected for SEO optimization)`);
14065
+ }
14066
+ else {
14067
+ console.log(`[DevSkin] Session ${this.sessionId} not sampled for recording (sampling: ${samplingRate * 100}%)`);
14068
+ }
14069
+ }
13986
14070
  }
13987
14071
  }
13988
14072
  // Track initial page view