@devskin/browser-sdk 1.0.40 → 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.
@@ -13473,11 +13473,12 @@ class RRWebRecorder {
13473
13473
  },
13474
13474
  // Configuration options
13475
13475
  sampling: {
13476
- // Mouse interactions
13477
- mousemove: this.config.sampleRate !== undefined
13478
- ? Math.floor(100 / this.config.sampleRate)
13479
- : true,
13480
- // Mouse interactions
13476
+ // Mouse movement sampling - throttle to reduce bandwidth
13477
+ // e.g. 0.1 = capture 10% of mouse movements (Math.floor(1/0.1) = 10, meaning capture 1 every 10 events)
13478
+ mousemove: this.config.mouseMoveSampleRate !== undefined
13479
+ ? Math.max(1, Math.floor(1 / this.config.mouseMoveSampleRate))
13480
+ : 10, // Default: capture 1 every 10 mouse movements (10%)
13481
+ // Mouse interactions (clicks, etc) - always capture
13481
13482
  mouseInteraction: true,
13482
13483
  // Scroll events
13483
13484
  scroll: 150, // Throttle scroll events to every 150ms
@@ -13878,6 +13879,7 @@ class DevSkinSDK {
13878
13879
  this.anonymousId = null;
13879
13880
  this.sessionStartTime = 0;
13880
13881
  this.initialized = false;
13882
+ this.initializing = false;
13881
13883
  this.heartbeatInterval = null;
13882
13884
  // Collectors
13883
13885
  this.deviceCollector = null;
@@ -13891,14 +13893,83 @@ class DevSkinSDK {
13891
13893
  // private sessionRecorder: SessionRecorder | null = null; // Replaced by RRWebRecorder
13892
13894
  this.rrwebRecorder = null;
13893
13895
  }
13896
+ /**
13897
+ * Detect if the current visitor is a bot/crawler
13898
+ */
13899
+ isBot() {
13900
+ if (typeof navigator === 'undefined')
13901
+ return true;
13902
+ const botPatterns = [
13903
+ /bot/i,
13904
+ /crawler/i,
13905
+ /spider/i,
13906
+ /crawling/i,
13907
+ /google/i,
13908
+ /bing/i,
13909
+ /yahoo/i,
13910
+ /duckduckgo/i,
13911
+ /baiduspider/i,
13912
+ /yandex/i,
13913
+ /facebookexternalhit/i,
13914
+ /twitterbot/i,
13915
+ /rogerbot/i,
13916
+ /linkedinbot/i,
13917
+ /embedly/i,
13918
+ /quora link preview/i,
13919
+ /showyoubot/i,
13920
+ /outbrain/i,
13921
+ /pinterest/i,
13922
+ /slackbot/i,
13923
+ /vkShare/i,
13924
+ /W3C_Validator/i,
13925
+ /redditbot/i,
13926
+ /Applebot/i,
13927
+ /WhatsApp/i,
13928
+ /flipboard/i,
13929
+ /tumblr/i,
13930
+ /bitlybot/i,
13931
+ /SkypeUriPreview/i,
13932
+ /nuzzel/i,
13933
+ /Discordbot/i,
13934
+ /Google Page Speed/i,
13935
+ /Qwantify/i,
13936
+ /pinterestbot/i,
13937
+ /Bitrix link preview/i,
13938
+ /XING-contenttabreceiver/i,
13939
+ /Chrome-Lighthouse/i,
13940
+ /telegrambot/i,
13941
+ /Lighthouse/i,
13942
+ /GTmetrix/i,
13943
+ /PageSpeed/i,
13944
+ /HeadlessChrome/i,
13945
+ /PhantomJS/i,
13946
+ ];
13947
+ const userAgent = navigator.userAgent;
13948
+ // Check user agent against bot patterns
13949
+ if (botPatterns.some(pattern => pattern.test(userAgent))) {
13950
+ return true;
13951
+ }
13952
+ // Check for headless browsers
13953
+ if ('webdriver' in navigator && navigator.webdriver === true) {
13954
+ return true;
13955
+ }
13956
+ // Check for headless Chrome
13957
+ if (userAgent.includes('HeadlessChrome')) {
13958
+ return true;
13959
+ }
13960
+ return false;
13961
+ }
13894
13962
  /**
13895
13963
  * Initialize the DevSkin SDK
13964
+ * Uses requestIdleCallback to defer heavy initialization without blocking the page
13896
13965
  */
13897
13966
  init(config) {
13898
- if (this.initialized) {
13899
- console.warn('[DevSkin] SDK already initialized');
13967
+ if (this.initialized || this.initializing) {
13968
+ console.warn('[DevSkin] SDK already initialized or initializing');
13900
13969
  return;
13901
13970
  }
13971
+ // Mark as initializing to prevent duplicate init() calls
13972
+ this.initializing = true;
13902
13973
  this.config = Object.assign({ debug: false, captureWebVitals: true, captureNetworkRequests: true, captureErrors: true, captureUserAgent: true, captureLocation: true, captureDevice: true, heatmapOptions: {
13903
13974
  enabled: true,
13904
13975
  trackClicks: true,
@@ -13908,99 +13979,135 @@ class DevSkinSDK {
13908
13979
  if (this.config.debug) {
13909
13980
  console.log('[DevSkin] Initializing SDK with config:', this.config);
13910
13981
  }
13911
- // Initialize transport
13982
+ // Initialize lightweight components immediately (needed for session context)
13912
13983
  this.transport = new Transport(this.config);
13913
- // Generate anonymous ID if not exists
13914
13984
  this.anonymousId = this.getOrCreateAnonymousId();
13915
- // Initialize collectors BEFORE starting session (so getContextData works)
13916
13985
  this.deviceCollector = new DeviceCollector(this.config);
13917
13986
  this.locationCollector = new LocationCollector(this.config);
13918
13987
  this.browserCollector = new BrowserCollector(this.config);
13919
- // Start session (will now include device/browser/location data)
13920
- // Wait for session creation to complete before starting collectors
13921
- this.startSession().then(() => {
13922
- // Session created, now safe to start collectors that send data
13923
- var _a, _b;
13924
- if (this.config.captureWebVitals) {
13925
- this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13926
- this.performanceCollector.start();
13927
- }
13928
- if (this.config.captureErrors) {
13929
- this.errorCollector = new ErrorCollector(this.config, this.transport);
13930
- this.errorCollector.start();
13931
- }
13932
- if (this.config.captureNetworkRequests) {
13933
- this.networkCollector = new NetworkCollector(this.config, this.transport);
13934
- this.networkCollector.start();
13935
- }
13936
- // Initialize heatmap collector - SEMPRE habilitado
13937
- // Merge default heatmap config with user config
13938
- const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13939
- this.config.heatmapOptions = heatmapConfig;
13940
- this.heatmapCollector = new HeatmapCollector(this.config, this.transport, this.anonymousId, this.sessionId);
13941
- this.heatmapCollector.start();
13942
- // Initialize screenshot collector and capture page
13943
- this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13944
- this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13945
- if (this.config.debug) {
13946
- console.log('[DevSkin] Heatmap collection enabled (always on)');
13947
- }
13948
- // Initialize session recording with rrweb
13949
- if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13950
- // Use RRWebRecorder for complete DOM recording
13951
- // Pass sessionStartTime to ensure timestamp continuity across page navigations
13952
- this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13953
- enabled: true,
13954
- sampleRate: this.config.sessionRecording.sampling || 0.5,
13955
- blockClass: 'rr-block',
13956
- ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13957
- maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13958
- ? this.config.sessionRecording.maskAllInputs
13959
- : true,
13960
- maskInputOptions: {
13961
- password: true,
13962
- email: true,
13963
- tel: true,
13964
- },
13965
- recordCanvas: this.config.sessionRecording.recordCanvas || false,
13966
- collectFonts: true,
13967
- inlineStylesheet: true,
13968
- checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13969
- checkoutEveryNth: 200, // Every 200 events
13970
- }, (events) => {
13971
- var _a;
13972
- // Send rrweb events to backend
13973
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13974
- }, this.sessionStartTime // Pass session start time for timestamp continuity
13975
- );
13976
- // Start recording immediately (session already created)
13977
- this.rrwebRecorder.start();
13978
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
13979
- console.log('[DevSkin] RRWeb recording started for session:', this.sessionId);
13988
+ // Defer heavy initialization to avoid blocking page load/rendering
13989
+ const initHeavyCollectors = () => {
13990
+ // Start session (will now include device/browser/location data)
13991
+ // Wait for session creation to complete before starting collectors
13992
+ this.startSession().then(() => {
13993
+ // Session created, now safe to start collectors that send data
13994
+ var _a, _b, _c;
13995
+ if (this.config.captureWebVitals) {
13996
+ this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13997
+ this.performanceCollector.start();
13998
+ }
13999
+ if (this.config.captureErrors) {
14000
+ this.errorCollector = new ErrorCollector(this.config, this.transport);
14001
+ this.errorCollector.start();
14002
+ }
14003
+ if (this.config.captureNetworkRequests) {
14004
+ this.networkCollector = new NetworkCollector(this.config, this.transport);
14005
+ this.networkCollector.start();
14006
+ }
14007
+ // Initialize heatmap collector - SEMPRE habilitado
14008
+ // Merge default heatmap config with user config
14009
+ const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
14010
+ this.config.heatmapOptions = heatmapConfig;
14011
+ this.heatmapCollector = new HeatmapCollector(this.config, this.transport, this.anonymousId, this.sessionId);
14012
+ this.heatmapCollector.start();
14013
+ // Initialize screenshot collector and capture page
14014
+ this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
14015
+ this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
14016
+ if (this.config.debug) {
14017
+ console.log('[DevSkin] Heatmap collection enabled (always on)');
13980
14018
  }
14019
+ // Initialize session recording with rrweb
14020
+ if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
14021
+ // Skip recording for bots/crawlers to avoid impacting SEO
14022
+ const isBot = this.isBot();
14023
+ // Apply session sampling - decide if this session should be recorded
14024
+ const samplingRate = this.config.sessionRecording.sampling || 1.0;
14025
+ const shouldRecord = !isBot && Math.random() < samplingRate;
14026
+ if (shouldRecord) {
14027
+ // Use RRWebRecorder for complete DOM recording
14028
+ // Pass sessionStartTime to ensure timestamp continuity across page navigations
14029
+ this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
14030
+ enabled: true,
14031
+ mouseMoveSampleRate: 0.5, // Sample 10% of mouse movements to reduce bandwidth
14032
+ blockClass: 'rr-block',
14033
+ ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
14034
+ maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
14035
+ ? this.config.sessionRecording.maskAllInputs
14036
+ : true,
14037
+ maskInputOptions: {
14038
+ password: true,
14039
+ email: true,
14040
+ tel: true,
14041
+ },
14042
+ recordCanvas: this.config.sessionRecording.recordCanvas || false,
14043
+ collectFonts: true,
14044
+ inlineStylesheet: true,
14045
+ checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
14046
+ checkoutEveryNth: 200, // Every 200 events
14047
+ }, (events) => {
14048
+ var _a;
14049
+ // Send rrweb events to backend
14050
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
14051
+ }, this.sessionStartTime // Pass session start time for timestamp continuity
14052
+ );
14053
+ // Start recording immediately (session already created)
14054
+ this.rrwebRecorder.start();
14055
+ if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14056
+ console.log(`[DevSkin] RRWeb recording started for session: ${this.sessionId} (sampling: ${samplingRate * 100}%)`);
14057
+ }
14058
+ }
14059
+ else {
14060
+ if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14061
+ if (isBot) {
14062
+ console.log(`[DevSkin] Session ${this.sessionId} skipped recording (bot/crawler detected for SEO optimization)`);
14063
+ }
14064
+ else {
14065
+ console.log(`[DevSkin] Session ${this.sessionId} not sampled for recording (sampling: ${samplingRate * 100}%)`);
14066
+ }
14067
+ }
14068
+ }
14069
+ }
14070
+ // Track initial page view
14071
+ this.trackPageView();
14072
+ // Start heartbeat to update session duration every 30 seconds
14073
+ this.startHeartbeat();
14074
+ }).catch((err) => {
14075
+ console.error('[DevSkin] Failed to create session:', err);
14076
+ });
14077
+ // Track page visibility changes
14078
+ this.setupVisibilityTracking();
14079
+ // Track page unload
14080
+ this.setupUnloadTracking();
14081
+ // Mark as fully initialized only after everything is loaded
14082
+ this.initialized = true;
14083
+ this.initializing = false;
14084
+ };
14085
+ // Use requestIdleCallback to defer heavy initialization (non-blocking)
14086
+ // Falls back to setTimeout for browsers that don't support it
14087
+ if (typeof window !== 'undefined') {
14088
+ if ('requestIdleCallback' in window) {
14089
+ window.requestIdleCallback(initHeavyCollectors, { timeout: 2000 });
13981
14090
  }
13982
- // Track initial page view
13983
- this.trackPageView();
13984
- // Start heartbeat to update session duration every 30 seconds
13985
- this.startHeartbeat();
13986
- }).catch((err) => {
13987
- console.error('[DevSkin] Failed to create session:', err);
13988
- });
13989
- // Track page visibility changes
13990
- this.setupVisibilityTracking();
13991
- // Track page unload
13992
- this.setupUnloadTracking();
13993
- this.initialized = true;
14091
+ else {
14092
+ // Fallback for older browsers
14093
+ setTimeout(initHeavyCollectors, 1);
14094
+ }
14095
+ }
14096
+ else {
14097
+ // Node.js environment (SSR)
14098
+ initHeavyCollectors();
14099
+ }
13994
14100
  if (this.config.debug) {
13995
- console.log('[DevSkin] SDK initialized successfully');
14101
+ console.log('[DevSkin] SDK initialization started (heavy collectors loading in background)');
13996
14102
  }
13997
14103
  }
13998
14104
  /**
13999
14105
  * Track a custom event
14106
+ * Works immediately after init() even if heavy collectors are still loading
14000
14107
  */
14001
14108
  track(eventName, properties) {
14002
- var _a, _b;
14003
- if (!this.initialized) {
14109
+ var _a, _b, _c, _d;
14110
+ if (!this.transport) {
14004
14111
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14005
14112
  return;
14006
14113
  }
@@ -14008,15 +14115,15 @@ class DevSkinSDK {
14008
14115
  eventName: eventName,
14009
14116
  eventType: 'track',
14010
14117
  timestamp: new Date().toISOString(),
14011
- sessionId: this.sessionId,
14012
- userId: this.userId || undefined,
14013
- anonymousId: this.anonymousId || undefined,
14118
+ sessionId: (_a = this.sessionId) !== null && _a !== void 0 ? _a : undefined,
14119
+ userId: (_b = this.userId) !== null && _b !== void 0 ? _b : undefined,
14120
+ anonymousId: (_c = this.anonymousId) !== null && _c !== void 0 ? _c : undefined,
14014
14121
  properties: Object.assign(Object.assign({}, properties), this.getContextData()),
14015
14122
  pageUrl: window.location.href,
14016
14123
  pageTitle: document.title,
14017
14124
  };
14018
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendEvent(eventData);
14019
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14125
+ this.transport.sendEvent(eventData);
14126
+ if ((_d = this.config) === null || _d === void 0 ? void 0 : _d.debug) {
14020
14127
  console.log('[DevSkin] Event tracked:', eventData);
14021
14128
  }
14022
14129
  }
@@ -14045,23 +14152,24 @@ class DevSkinSDK {
14045
14152
  }
14046
14153
  /**
14047
14154
  * Identify a user
14155
+ * Works immediately after init() even if heavy collectors are still loading
14048
14156
  */
14049
14157
  identify(userId, traits) {
14050
- var _a, _b;
14051
- if (!this.initialized) {
14158
+ var _a, _b, _c;
14159
+ if (!this.transport) {
14052
14160
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14053
14161
  return;
14054
14162
  }
14055
14163
  this.userId = userId;
14056
14164
  const userData = {
14057
14165
  userId: userId,
14058
- anonymousId: this.anonymousId || undefined,
14166
+ anonymousId: (_a = this.anonymousId) !== null && _a !== void 0 ? _a : undefined,
14059
14167
  traits: Object.assign(Object.assign({}, traits), this.getContextData()),
14060
- sessionId: this.sessionId,
14168
+ sessionId: (_b = this.sessionId) !== null && _b !== void 0 ? _b : undefined,
14061
14169
  timestamp: new Date().toISOString(),
14062
14170
  };
14063
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.identifyUser(userData);
14064
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14171
+ this.transport.identifyUser(userData);
14172
+ if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14065
14173
  console.log('[DevSkin] User identified:', userData);
14066
14174
  }
14067
14175
  }