@devskin/browser-sdk 1.0.39 → 1.0.41

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.
@@ -857,9 +857,11 @@ class NetworkCollector {
857
857
  }
858
858
 
859
859
  class HeatmapCollector {
860
- constructor(config, transport) {
860
+ constructor(config, transport, anonymousId, sessionId) {
861
861
  this.config = config;
862
862
  this.transport = transport;
863
+ this.anonymousId = anonymousId;
864
+ this.sessionId = sessionId;
863
865
  this.clickData = [];
864
866
  this.scrollData = [];
865
867
  this.mouseMoveData = [];
@@ -1003,21 +1005,21 @@ class HeatmapCollector {
1003
1005
  // Send clicks individually (backend expects one click event per item)
1004
1006
  if (this.clickData.length > 0) {
1005
1007
  this.clickData.forEach(click => {
1006
- this.transport.sendHeatmapData(Object.assign({ type: 'click' }, click));
1008
+ this.transport.sendHeatmapData(Object.assign({ type: 'click', anonymousId: this.anonymousId, sessionId: this.sessionId }, click));
1007
1009
  });
1008
1010
  this.clickData = [];
1009
1011
  }
1010
1012
  // Send scroll data individually
1011
1013
  if (this.scrollData.length > 0) {
1012
1014
  this.scrollData.forEach(scroll => {
1013
- this.transport.sendHeatmapData(Object.assign({ type: 'scroll' }, scroll));
1015
+ this.transport.sendHeatmapData(Object.assign({ type: 'scroll', anonymousId: this.anonymousId, sessionId: this.sessionId }, scroll));
1014
1016
  });
1015
1017
  this.scrollData = [];
1016
1018
  }
1017
1019
  // Send mouse moves individually
1018
1020
  if (this.mouseMoveData.length > 0) {
1019
1021
  this.mouseMoveData.forEach(move => {
1020
- this.transport.sendHeatmapData(Object.assign({ type: 'mousemove' }, move));
1022
+ this.transport.sendHeatmapData(Object.assign({ type: 'mousemove', anonymousId: this.anonymousId, sessionId: this.sessionId }, move));
1021
1023
  });
1022
1024
  this.mouseMoveData = [];
1023
1025
  }
@@ -13872,6 +13874,7 @@ class DevSkinSDK {
13872
13874
  this.anonymousId = null;
13873
13875
  this.sessionStartTime = 0;
13874
13876
  this.initialized = false;
13877
+ this.initializing = false;
13875
13878
  this.heartbeatInterval = null;
13876
13879
  // Collectors
13877
13880
  this.deviceCollector = null;
@@ -13887,12 +13890,15 @@ class DevSkinSDK {
13887
13890
  }
13888
13891
  /**
13889
13892
  * Initialize the DevSkin SDK
13893
+ * Uses requestIdleCallback to defer heavy initialization without blocking the page
13890
13894
  */
13891
13895
  init(config) {
13892
- if (this.initialized) {
13893
- console.warn('[DevSkin] SDK already initialized');
13896
+ if (this.initialized || this.initializing) {
13897
+ console.warn('[DevSkin] SDK already initialized or initializing');
13894
13898
  return;
13895
13899
  }
13900
+ // Mark as initializing to prevent duplicate init() calls
13901
+ this.initializing = true;
13896
13902
  this.config = Object.assign({ debug: false, captureWebVitals: true, captureNetworkRequests: true, captureErrors: true, captureUserAgent: true, captureLocation: true, captureDevice: true, heatmapOptions: {
13897
13903
  enabled: true,
13898
13904
  trackClicks: true,
@@ -13902,99 +13908,118 @@ class DevSkinSDK {
13902
13908
  if (this.config.debug) {
13903
13909
  console.log('[DevSkin] Initializing SDK with config:', this.config);
13904
13910
  }
13905
- // Initialize transport
13911
+ // Initialize lightweight components immediately (needed for session context)
13906
13912
  this.transport = new Transport(this.config);
13907
- // Generate anonymous ID if not exists
13908
13913
  this.anonymousId = this.getOrCreateAnonymousId();
13909
- // Initialize collectors BEFORE starting session (so getContextData works)
13910
13914
  this.deviceCollector = new DeviceCollector(this.config);
13911
13915
  this.locationCollector = new LocationCollector(this.config);
13912
13916
  this.browserCollector = new BrowserCollector(this.config);
13913
- // Start session (will now include device/browser/location data)
13914
- // Wait for session creation to complete before starting collectors
13915
- this.startSession().then(() => {
13916
- // Session created, now safe to start collectors that send data
13917
- var _a, _b;
13918
- if (this.config.captureWebVitals) {
13919
- this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13920
- this.performanceCollector.start();
13921
- }
13922
- if (this.config.captureErrors) {
13923
- this.errorCollector = new ErrorCollector(this.config, this.transport);
13924
- this.errorCollector.start();
13925
- }
13926
- if (this.config.captureNetworkRequests) {
13927
- this.networkCollector = new NetworkCollector(this.config, this.transport);
13928
- this.networkCollector.start();
13929
- }
13930
- // Initialize heatmap collector - SEMPRE habilitado
13931
- // Merge default heatmap config with user config
13932
- const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13933
- this.config.heatmapOptions = heatmapConfig;
13934
- this.heatmapCollector = new HeatmapCollector(this.config, this.transport);
13935
- this.heatmapCollector.start();
13936
- // Initialize screenshot collector and capture page
13937
- this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13938
- this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13939
- if (this.config.debug) {
13940
- console.log('[DevSkin] Heatmap collection enabled (always on)');
13941
- }
13942
- // Initialize session recording with rrweb
13943
- if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13944
- // Use RRWebRecorder for complete DOM recording
13945
- // Pass sessionStartTime to ensure timestamp continuity across page navigations
13946
- this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13947
- enabled: true,
13948
- sampleRate: this.config.sessionRecording.sampling || 0.5,
13949
- blockClass: 'rr-block',
13950
- ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13951
- maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13952
- ? this.config.sessionRecording.maskAllInputs
13953
- : true,
13954
- maskInputOptions: {
13955
- password: true,
13956
- email: true,
13957
- tel: true,
13958
- },
13959
- recordCanvas: this.config.sessionRecording.recordCanvas || false,
13960
- collectFonts: true,
13961
- inlineStylesheet: true,
13962
- checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13963
- checkoutEveryNth: 200, // Every 200 events
13964
- }, (events) => {
13965
- var _a;
13966
- // Send rrweb events to backend
13967
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13968
- }, this.sessionStartTime // Pass session start time for timestamp continuity
13969
- );
13970
- // Start recording immediately (session already created)
13971
- this.rrwebRecorder.start();
13972
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
13973
- console.log('[DevSkin] RRWeb recording started for session:', this.sessionId);
13917
+ // Defer heavy initialization to avoid blocking page load/rendering
13918
+ const initHeavyCollectors = () => {
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);
13980
+ }
13974
13981
  }
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
+ // Mark as fully initialized only after everything is loaded
13994
+ this.initialized = true;
13995
+ this.initializing = false;
13996
+ };
13997
+ // Use requestIdleCallback to defer heavy initialization (non-blocking)
13998
+ // Falls back to setTimeout for browsers that don't support it
13999
+ if (typeof window !== 'undefined') {
14000
+ if ('requestIdleCallback' in window) {
14001
+ window.requestIdleCallback(initHeavyCollectors, { timeout: 2000 });
13975
14002
  }
13976
- // Track initial page view
13977
- this.trackPageView();
13978
- // Start heartbeat to update session duration every 30 seconds
13979
- this.startHeartbeat();
13980
- }).catch((err) => {
13981
- console.error('[DevSkin] Failed to create session:', err);
13982
- });
13983
- // Track page visibility changes
13984
- this.setupVisibilityTracking();
13985
- // Track page unload
13986
- this.setupUnloadTracking();
13987
- this.initialized = true;
14003
+ else {
14004
+ // Fallback for older browsers
14005
+ setTimeout(initHeavyCollectors, 1);
14006
+ }
14007
+ }
14008
+ else {
14009
+ // Node.js environment (SSR)
14010
+ initHeavyCollectors();
14011
+ }
13988
14012
  if (this.config.debug) {
13989
- console.log('[DevSkin] SDK initialized successfully');
14013
+ console.log('[DevSkin] SDK initialization started (heavy collectors loading in background)');
13990
14014
  }
13991
14015
  }
13992
14016
  /**
13993
14017
  * Track a custom event
14018
+ * Works immediately after init() even if heavy collectors are still loading
13994
14019
  */
13995
14020
  track(eventName, properties) {
13996
- var _a, _b;
13997
- if (!this.initialized) {
14021
+ var _a, _b, _c, _d;
14022
+ if (!this.transport) {
13998
14023
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
13999
14024
  return;
14000
14025
  }
@@ -14002,15 +14027,15 @@ class DevSkinSDK {
14002
14027
  eventName: eventName,
14003
14028
  eventType: 'track',
14004
14029
  timestamp: new Date().toISOString(),
14005
- sessionId: this.sessionId,
14006
- userId: this.userId || undefined,
14007
- anonymousId: this.anonymousId || undefined,
14030
+ sessionId: (_a = this.sessionId) !== null && _a !== void 0 ? _a : undefined,
14031
+ userId: (_b = this.userId) !== null && _b !== void 0 ? _b : undefined,
14032
+ anonymousId: (_c = this.anonymousId) !== null && _c !== void 0 ? _c : undefined,
14008
14033
  properties: Object.assign(Object.assign({}, properties), this.getContextData()),
14009
14034
  pageUrl: window.location.href,
14010
14035
  pageTitle: document.title,
14011
14036
  };
14012
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendEvent(eventData);
14013
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14037
+ this.transport.sendEvent(eventData);
14038
+ if ((_d = this.config) === null || _d === void 0 ? void 0 : _d.debug) {
14014
14039
  console.log('[DevSkin] Event tracked:', eventData);
14015
14040
  }
14016
14041
  }
@@ -14039,23 +14064,24 @@ class DevSkinSDK {
14039
14064
  }
14040
14065
  /**
14041
14066
  * Identify a user
14067
+ * Works immediately after init() even if heavy collectors are still loading
14042
14068
  */
14043
14069
  identify(userId, traits) {
14044
- var _a, _b;
14045
- if (!this.initialized) {
14070
+ var _a, _b, _c;
14071
+ if (!this.transport) {
14046
14072
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14047
14073
  return;
14048
14074
  }
14049
14075
  this.userId = userId;
14050
14076
  const userData = {
14051
14077
  userId: userId,
14052
- anonymousId: this.anonymousId || undefined,
14078
+ anonymousId: (_a = this.anonymousId) !== null && _a !== void 0 ? _a : undefined,
14053
14079
  traits: Object.assign(Object.assign({}, traits), this.getContextData()),
14054
- sessionId: this.sessionId,
14080
+ sessionId: (_b = this.sessionId) !== null && _b !== void 0 ? _b : undefined,
14055
14081
  timestamp: new Date().toISOString(),
14056
14082
  };
14057
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.identifyUser(userData);
14058
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14083
+ this.transport.identifyUser(userData);
14084
+ if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14059
14085
  console.log('[DevSkin] User identified:', userData);
14060
14086
  }
14061
14087
  }