@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.
@@ -13880,6 +13880,7 @@
13880
13880
  this.anonymousId = null;
13881
13881
  this.sessionStartTime = 0;
13882
13882
  this.initialized = false;
13883
+ this.initializing = false;
13883
13884
  this.heartbeatInterval = null;
13884
13885
  // Collectors
13885
13886
  this.deviceCollector = null;
@@ -13895,12 +13896,15 @@
13895
13896
  }
13896
13897
  /**
13897
13898
  * Initialize the DevSkin SDK
13899
+ * Uses requestIdleCallback to defer heavy initialization without blocking the page
13898
13900
  */
13899
13901
  init(config) {
13900
- if (this.initialized) {
13901
- console.warn('[DevSkin] SDK already initialized');
13902
+ if (this.initialized || this.initializing) {
13903
+ console.warn('[DevSkin] SDK already initialized or initializing');
13902
13904
  return;
13903
13905
  }
13906
+ // Mark as initializing to prevent duplicate init() calls
13907
+ this.initializing = true;
13904
13908
  this.config = Object.assign({ debug: false, captureWebVitals: true, captureNetworkRequests: true, captureErrors: true, captureUserAgent: true, captureLocation: true, captureDevice: true, heatmapOptions: {
13905
13909
  enabled: true,
13906
13910
  trackClicks: true,
@@ -13910,99 +13914,118 @@
13910
13914
  if (this.config.debug) {
13911
13915
  console.log('[DevSkin] Initializing SDK with config:', this.config);
13912
13916
  }
13913
- // Initialize transport
13917
+ // Initialize lightweight components immediately (needed for session context)
13914
13918
  this.transport = new Transport(this.config);
13915
- // Generate anonymous ID if not exists
13916
13919
  this.anonymousId = this.getOrCreateAnonymousId();
13917
- // Initialize collectors BEFORE starting session (so getContextData works)
13918
13920
  this.deviceCollector = new DeviceCollector(this.config);
13919
13921
  this.locationCollector = new LocationCollector(this.config);
13920
13922
  this.browserCollector = new BrowserCollector(this.config);
13921
- // Start session (will now include device/browser/location data)
13922
- // Wait for session creation to complete before starting collectors
13923
- this.startSession().then(() => {
13924
- // Session created, now safe to start collectors that send data
13925
- var _a, _b;
13926
- if (this.config.captureWebVitals) {
13927
- this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13928
- this.performanceCollector.start();
13929
- }
13930
- if (this.config.captureErrors) {
13931
- this.errorCollector = new ErrorCollector(this.config, this.transport);
13932
- this.errorCollector.start();
13933
- }
13934
- if (this.config.captureNetworkRequests) {
13935
- this.networkCollector = new NetworkCollector(this.config, this.transport);
13936
- this.networkCollector.start();
13937
- }
13938
- // Initialize heatmap collector - SEMPRE habilitado
13939
- // Merge default heatmap config with user config
13940
- const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13941
- this.config.heatmapOptions = heatmapConfig;
13942
- this.heatmapCollector = new HeatmapCollector(this.config, this.transport, this.anonymousId, this.sessionId);
13943
- this.heatmapCollector.start();
13944
- // Initialize screenshot collector and capture page
13945
- this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13946
- this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13947
- if (this.config.debug) {
13948
- console.log('[DevSkin] Heatmap collection enabled (always on)');
13949
- }
13950
- // Initialize session recording with rrweb
13951
- if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13952
- // Use RRWebRecorder for complete DOM recording
13953
- // Pass sessionStartTime to ensure timestamp continuity across page navigations
13954
- this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13955
- enabled: true,
13956
- sampleRate: this.config.sessionRecording.sampling || 0.5,
13957
- blockClass: 'rr-block',
13958
- ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13959
- maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13960
- ? this.config.sessionRecording.maskAllInputs
13961
- : true,
13962
- maskInputOptions: {
13963
- password: true,
13964
- email: true,
13965
- tel: true,
13966
- },
13967
- recordCanvas: this.config.sessionRecording.recordCanvas || false,
13968
- collectFonts: true,
13969
- inlineStylesheet: true,
13970
- checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13971
- checkoutEveryNth: 200, // Every 200 events
13972
- }, (events) => {
13973
- var _a;
13974
- // Send rrweb events to backend
13975
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13976
- }, this.sessionStartTime // Pass session start time for timestamp continuity
13977
- );
13978
- // Start recording immediately (session already created)
13979
- this.rrwebRecorder.start();
13980
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
13981
- 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
+ }
13982
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 });
13983
14008
  }
13984
- // Track initial page view
13985
- this.trackPageView();
13986
- // Start heartbeat to update session duration every 30 seconds
13987
- this.startHeartbeat();
13988
- }).catch((err) => {
13989
- console.error('[DevSkin] Failed to create session:', err);
13990
- });
13991
- // Track page visibility changes
13992
- this.setupVisibilityTracking();
13993
- // Track page unload
13994
- this.setupUnloadTracking();
13995
- 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
+ }
13996
14018
  if (this.config.debug) {
13997
- console.log('[DevSkin] SDK initialized successfully');
14019
+ console.log('[DevSkin] SDK initialization started (heavy collectors loading in background)');
13998
14020
  }
13999
14021
  }
14000
14022
  /**
14001
14023
  * Track a custom event
14024
+ * Works immediately after init() even if heavy collectors are still loading
14002
14025
  */
14003
14026
  track(eventName, properties) {
14004
- var _a, _b;
14005
- if (!this.initialized) {
14027
+ var _a, _b, _c, _d;
14028
+ if (!this.transport) {
14006
14029
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14007
14030
  return;
14008
14031
  }
@@ -14010,15 +14033,15 @@
14010
14033
  eventName: eventName,
14011
14034
  eventType: 'track',
14012
14035
  timestamp: new Date().toISOString(),
14013
- sessionId: this.sessionId,
14014
- userId: this.userId || undefined,
14015
- 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,
14016
14039
  properties: Object.assign(Object.assign({}, properties), this.getContextData()),
14017
14040
  pageUrl: window.location.href,
14018
14041
  pageTitle: document.title,
14019
14042
  };
14020
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendEvent(eventData);
14021
- 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) {
14022
14045
  console.log('[DevSkin] Event tracked:', eventData);
14023
14046
  }
14024
14047
  }
@@ -14047,23 +14070,24 @@
14047
14070
  }
14048
14071
  /**
14049
14072
  * Identify a user
14073
+ * Works immediately after init() even if heavy collectors are still loading
14050
14074
  */
14051
14075
  identify(userId, traits) {
14052
- var _a, _b;
14053
- if (!this.initialized) {
14076
+ var _a, _b, _c;
14077
+ if (!this.transport) {
14054
14078
  console.warn('[DevSkin] SDK not initialized. Call init() first.');
14055
14079
  return;
14056
14080
  }
14057
14081
  this.userId = userId;
14058
14082
  const userData = {
14059
14083
  userId: userId,
14060
- anonymousId: this.anonymousId || undefined,
14084
+ anonymousId: (_a = this.anonymousId) !== null && _a !== void 0 ? _a : undefined,
14061
14085
  traits: Object.assign(Object.assign({}, traits), this.getContextData()),
14062
- sessionId: this.sessionId,
14086
+ sessionId: (_b = this.sessionId) !== null && _b !== void 0 ? _b : undefined,
14063
14087
  timestamp: new Date().toISOString(),
14064
14088
  };
14065
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.identifyUser(userData);
14066
- 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) {
14067
14091
  console.log('[DevSkin] User identified:', userData);
14068
14092
  }
14069
14093
  }