@devskin/browser-sdk 1.0.40 → 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.
@@ -13874,6 +13874,7 @@ class DevSkinSDK {
13874
13874
  this.anonymousId = null;
13875
13875
  this.sessionStartTime = 0;
13876
13876
  this.initialized = false;
13877
+ this.initializing = false;
13877
13878
  this.heartbeatInterval = null;
13878
13879
  // Collectors
13879
13880
  this.deviceCollector = null;
@@ -13889,12 +13890,15 @@ class DevSkinSDK {
13889
13890
  }
13890
13891
  /**
13891
13892
  * Initialize the DevSkin SDK
13893
+ * Uses requestIdleCallback to defer heavy initialization without blocking the page
13892
13894
  */
13893
13895
  init(config) {
13894
- if (this.initialized) {
13895
- console.warn('[DevSkin] SDK already initialized');
13896
+ if (this.initialized || this.initializing) {
13897
+ console.warn('[DevSkin] SDK already initialized or initializing');
13896
13898
  return;
13897
13899
  }
13900
+ // Mark as initializing to prevent duplicate init() calls
13901
+ this.initializing = true;
13898
13902
  this.config = Object.assign({ debug: false, captureWebVitals: true, captureNetworkRequests: true, captureErrors: true, captureUserAgent: true, captureLocation: true, captureDevice: true, heatmapOptions: {
13899
13903
  enabled: true,
13900
13904
  trackClicks: true,
@@ -13904,99 +13908,118 @@ class DevSkinSDK {
13904
13908
  if (this.config.debug) {
13905
13909
  console.log('[DevSkin] Initializing SDK with config:', this.config);
13906
13910
  }
13907
- // Initialize transport
13911
+ // Initialize lightweight components immediately (needed for session context)
13908
13912
  this.transport = new Transport(this.config);
13909
- // Generate anonymous ID if not exists
13910
13913
  this.anonymousId = this.getOrCreateAnonymousId();
13911
- // Initialize collectors BEFORE starting session (so getContextData works)
13912
13914
  this.deviceCollector = new DeviceCollector(this.config);
13913
13915
  this.locationCollector = new LocationCollector(this.config);
13914
13916
  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);
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
+ }
13976
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 });
13977
14002
  }
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;
14003
+ else {
14004
+ // Fallback for older browsers
14005
+ setTimeout(initHeavyCollectors, 1);
14006
+ }
14007
+ }
14008
+ else {
14009
+ // Node.js environment (SSR)
14010
+ initHeavyCollectors();
14011
+ }
13990
14012
  if (this.config.debug) {
13991
- console.log('[DevSkin] SDK initialized successfully');
14013
+ console.log('[DevSkin] SDK initialization started (heavy collectors loading in background)');
13992
14014
  }
13993
14015
  }
13994
14016
  /**
13995
14017
  * Track a custom event
14018
+ * Works immediately after init() even if heavy collectors are still loading
13996
14019
  */
13997
14020
  track(eventName, properties) {
13998
- var _a, _b;
13999
- if (!this.initialized) {
14021
+ var _a, _b, _c, _d;
14022
+ if (!this.transport) {
14000
14023
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14001
14024
  return;
14002
14025
  }
@@ -14004,15 +14027,15 @@ class DevSkinSDK {
14004
14027
  eventName: eventName,
14005
14028
  eventType: 'track',
14006
14029
  timestamp: new Date().toISOString(),
14007
- sessionId: this.sessionId,
14008
- userId: this.userId || undefined,
14009
- 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,
14010
14033
  properties: Object.assign(Object.assign({}, properties), this.getContextData()),
14011
14034
  pageUrl: window.location.href,
14012
14035
  pageTitle: document.title,
14013
14036
  };
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) {
14037
+ this.transport.sendEvent(eventData);
14038
+ if ((_d = this.config) === null || _d === void 0 ? void 0 : _d.debug) {
14016
14039
  console.log('[DevSkin] Event tracked:', eventData);
14017
14040
  }
14018
14041
  }
@@ -14041,23 +14064,24 @@ class DevSkinSDK {
14041
14064
  }
14042
14065
  /**
14043
14066
  * Identify a user
14067
+ * Works immediately after init() even if heavy collectors are still loading
14044
14068
  */
14045
14069
  identify(userId, traits) {
14046
- var _a, _b;
14047
- if (!this.initialized) {
14070
+ var _a, _b, _c;
14071
+ if (!this.transport) {
14048
14072
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14049
14073
  return;
14050
14074
  }
14051
14075
  this.userId = userId;
14052
14076
  const userData = {
14053
14077
  userId: userId,
14054
- anonymousId: this.anonymousId || undefined,
14078
+ anonymousId: (_a = this.anonymousId) !== null && _a !== void 0 ? _a : undefined,
14055
14079
  traits: Object.assign(Object.assign({}, traits), this.getContextData()),
14056
- sessionId: this.sessionId,
14080
+ sessionId: (_b = this.sessionId) !== null && _b !== void 0 ? _b : undefined,
14057
14081
  timestamp: new Date().toISOString(),
14058
14082
  };
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) {
14083
+ this.transport.identifyUser(userData);
14084
+ if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14061
14085
  console.log('[DevSkin] User identified:', userData);
14062
14086
  }
14063
14087
  }