@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.
@@ -863,9 +863,11 @@
863
863
  }
864
864
 
865
865
  class HeatmapCollector {
866
- constructor(config, transport) {
866
+ constructor(config, transport, anonymousId, sessionId) {
867
867
  this.config = config;
868
868
  this.transport = transport;
869
+ this.anonymousId = anonymousId;
870
+ this.sessionId = sessionId;
869
871
  this.clickData = [];
870
872
  this.scrollData = [];
871
873
  this.mouseMoveData = [];
@@ -1009,21 +1011,21 @@
1009
1011
  // Send clicks individually (backend expects one click event per item)
1010
1012
  if (this.clickData.length > 0) {
1011
1013
  this.clickData.forEach(click => {
1012
- this.transport.sendHeatmapData(Object.assign({ type: 'click' }, click));
1014
+ this.transport.sendHeatmapData(Object.assign({ type: 'click', anonymousId: this.anonymousId, sessionId: this.sessionId }, click));
1013
1015
  });
1014
1016
  this.clickData = [];
1015
1017
  }
1016
1018
  // Send scroll data individually
1017
1019
  if (this.scrollData.length > 0) {
1018
1020
  this.scrollData.forEach(scroll => {
1019
- this.transport.sendHeatmapData(Object.assign({ type: 'scroll' }, scroll));
1021
+ this.transport.sendHeatmapData(Object.assign({ type: 'scroll', anonymousId: this.anonymousId, sessionId: this.sessionId }, scroll));
1020
1022
  });
1021
1023
  this.scrollData = [];
1022
1024
  }
1023
1025
  // Send mouse moves individually
1024
1026
  if (this.mouseMoveData.length > 0) {
1025
1027
  this.mouseMoveData.forEach(move => {
1026
- this.transport.sendHeatmapData(Object.assign({ type: 'mousemove' }, move));
1028
+ this.transport.sendHeatmapData(Object.assign({ type: 'mousemove', anonymousId: this.anonymousId, sessionId: this.sessionId }, move));
1027
1029
  });
1028
1030
  this.mouseMoveData = [];
1029
1031
  }
@@ -13878,6 +13880,7 @@
13878
13880
  this.anonymousId = null;
13879
13881
  this.sessionStartTime = 0;
13880
13882
  this.initialized = false;
13883
+ this.initializing = false;
13881
13884
  this.heartbeatInterval = null;
13882
13885
  // Collectors
13883
13886
  this.deviceCollector = null;
@@ -13893,12 +13896,15 @@
13893
13896
  }
13894
13897
  /**
13895
13898
  * Initialize the DevSkin SDK
13899
+ * Uses requestIdleCallback to defer heavy initialization without blocking the page
13896
13900
  */
13897
13901
  init(config) {
13898
- if (this.initialized) {
13899
- console.warn('[DevSkin] SDK already initialized');
13902
+ if (this.initialized || this.initializing) {
13903
+ console.warn('[DevSkin] SDK already initialized or initializing');
13900
13904
  return;
13901
13905
  }
13906
+ // Mark as initializing to prevent duplicate init() calls
13907
+ this.initializing = true;
13902
13908
  this.config = Object.assign({ debug: false, captureWebVitals: true, captureNetworkRequests: true, captureErrors: true, captureUserAgent: true, captureLocation: true, captureDevice: true, heatmapOptions: {
13903
13909
  enabled: true,
13904
13910
  trackClicks: true,
@@ -13908,99 +13914,118 @@
13908
13914
  if (this.config.debug) {
13909
13915
  console.log('[DevSkin] Initializing SDK with config:', this.config);
13910
13916
  }
13911
- // Initialize transport
13917
+ // Initialize lightweight components immediately (needed for session context)
13912
13918
  this.transport = new Transport(this.config);
13913
- // Generate anonymous ID if not exists
13914
13919
  this.anonymousId = this.getOrCreateAnonymousId();
13915
- // Initialize collectors BEFORE starting session (so getContextData works)
13916
13920
  this.deviceCollector = new DeviceCollector(this.config);
13917
13921
  this.locationCollector = new LocationCollector(this.config);
13918
13922
  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);
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);
13923
+ // Defer heavy initialization to avoid blocking page load/rendering
13924
+ const initHeavyCollectors = () => {
13925
+ // Start session (will now include device/browser/location data)
13926
+ // Wait for session creation to complete before starting collectors
13927
+ this.startSession().then(() => {
13928
+ // Session created, now safe to start collectors that send data
13929
+ var _a, _b;
13930
+ if (this.config.captureWebVitals) {
13931
+ this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13932
+ this.performanceCollector.start();
13933
+ }
13934
+ if (this.config.captureErrors) {
13935
+ this.errorCollector = new ErrorCollector(this.config, this.transport);
13936
+ this.errorCollector.start();
13937
+ }
13938
+ if (this.config.captureNetworkRequests) {
13939
+ this.networkCollector = new NetworkCollector(this.config, this.transport);
13940
+ this.networkCollector.start();
13941
+ }
13942
+ // Initialize heatmap collector - SEMPRE habilitado
13943
+ // Merge default heatmap config with user config
13944
+ const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13945
+ this.config.heatmapOptions = heatmapConfig;
13946
+ this.heatmapCollector = new HeatmapCollector(this.config, this.transport, this.anonymousId, this.sessionId);
13947
+ this.heatmapCollector.start();
13948
+ // Initialize screenshot collector and capture page
13949
+ this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13950
+ this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13951
+ if (this.config.debug) {
13952
+ console.log('[DevSkin] Heatmap collection enabled (always on)');
13953
+ }
13954
+ // Initialize session recording with rrweb
13955
+ 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);
13986
+ }
13980
13987
  }
13988
+ // Track initial page view
13989
+ this.trackPageView();
13990
+ // Start heartbeat to update session duration every 30 seconds
13991
+ this.startHeartbeat();
13992
+ }).catch((err) => {
13993
+ console.error('[DevSkin] Failed to create session:', err);
13994
+ });
13995
+ // Track page visibility changes
13996
+ this.setupVisibilityTracking();
13997
+ // Track page unload
13998
+ this.setupUnloadTracking();
13999
+ // Mark as fully initialized only after everything is loaded
14000
+ this.initialized = true;
14001
+ this.initializing = false;
14002
+ };
14003
+ // Use requestIdleCallback to defer heavy initialization (non-blocking)
14004
+ // Falls back to setTimeout for browsers that don't support it
14005
+ if (typeof window !== 'undefined') {
14006
+ if ('requestIdleCallback' in window) {
14007
+ window.requestIdleCallback(initHeavyCollectors, { timeout: 2000 });
13981
14008
  }
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;
14009
+ else {
14010
+ // Fallback for older browsers
14011
+ setTimeout(initHeavyCollectors, 1);
14012
+ }
14013
+ }
14014
+ else {
14015
+ // Node.js environment (SSR)
14016
+ initHeavyCollectors();
14017
+ }
13994
14018
  if (this.config.debug) {
13995
- console.log('[DevSkin] SDK initialized successfully');
14019
+ console.log('[DevSkin] SDK initialization started (heavy collectors loading in background)');
13996
14020
  }
13997
14021
  }
13998
14022
  /**
13999
14023
  * Track a custom event
14024
+ * Works immediately after init() even if heavy collectors are still loading
14000
14025
  */
14001
14026
  track(eventName, properties) {
14002
- var _a, _b;
14003
- if (!this.initialized) {
14027
+ var _a, _b, _c, _d;
14028
+ if (!this.transport) {
14004
14029
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14005
14030
  return;
14006
14031
  }
@@ -14008,15 +14033,15 @@
14008
14033
  eventName: eventName,
14009
14034
  eventType: 'track',
14010
14035
  timestamp: new Date().toISOString(),
14011
- sessionId: this.sessionId,
14012
- userId: this.userId || undefined,
14013
- anonymousId: this.anonymousId || undefined,
14036
+ sessionId: (_a = this.sessionId) !== null && _a !== void 0 ? _a : undefined,
14037
+ userId: (_b = this.userId) !== null && _b !== void 0 ? _b : undefined,
14038
+ anonymousId: (_c = this.anonymousId) !== null && _c !== void 0 ? _c : undefined,
14014
14039
  properties: Object.assign(Object.assign({}, properties), this.getContextData()),
14015
14040
  pageUrl: window.location.href,
14016
14041
  pageTitle: document.title,
14017
14042
  };
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) {
14043
+ this.transport.sendEvent(eventData);
14044
+ if ((_d = this.config) === null || _d === void 0 ? void 0 : _d.debug) {
14020
14045
  console.log('[DevSkin] Event tracked:', eventData);
14021
14046
  }
14022
14047
  }
@@ -14045,23 +14070,24 @@
14045
14070
  }
14046
14071
  /**
14047
14072
  * Identify a user
14073
+ * Works immediately after init() even if heavy collectors are still loading
14048
14074
  */
14049
14075
  identify(userId, traits) {
14050
- var _a, _b;
14051
- if (!this.initialized) {
14076
+ var _a, _b, _c;
14077
+ if (!this.transport) {
14052
14078
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14053
14079
  return;
14054
14080
  }
14055
14081
  this.userId = userId;
14056
14082
  const userData = {
14057
14083
  userId: userId,
14058
- anonymousId: this.anonymousId || undefined,
14084
+ anonymousId: (_a = this.anonymousId) !== null && _a !== void 0 ? _a : undefined,
14059
14085
  traits: Object.assign(Object.assign({}, traits), this.getContextData()),
14060
- sessionId: this.sessionId,
14086
+ sessionId: (_b = this.sessionId) !== null && _b !== void 0 ? _b : undefined,
14061
14087
  timestamp: new Date().toISOString(),
14062
14088
  };
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) {
14089
+ this.transport.identifyUser(userData);
14090
+ if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14065
14091
  console.log('[DevSkin] User identified:', userData);
14066
14092
  }
14067
14093
  }