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