@devskin/browser-sdk 1.0.31 → 1.0.33

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.
@@ -1,3 +1,35 @@
1
+ /******************************************************************************
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
14
+ ***************************************************************************** */
15
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
16
+
17
+
18
+ function __awaiter$1(thisArg, _arguments, P, generator) {
19
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
+ return new (P || (P = Promise))(function (resolve, reject) {
21
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
25
+ });
26
+ }
27
+
28
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
29
+ var e = new Error(message);
30
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
31
+ };
32
+
1
33
  class DeviceCollector {
2
34
  constructor(config) {
3
35
  this.config = config;
@@ -115,38 +147,6 @@ class DeviceCollector {
115
147
  }
116
148
  }
117
149
 
118
- /******************************************************************************
119
- Copyright (c) Microsoft Corporation.
120
-
121
- Permission to use, copy, modify, and/or distribute this software for any
122
- purpose with or without fee is hereby granted.
123
-
124
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
125
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
126
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
127
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
128
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
129
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
130
- PERFORMANCE OF THIS SOFTWARE.
131
- ***************************************************************************** */
132
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
133
-
134
-
135
- function __awaiter$1(thisArg, _arguments, P, generator) {
136
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
137
- return new (P || (P = Promise))(function (resolve, reject) {
138
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
139
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
140
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
141
- step((generator = generator.apply(thisArg, _arguments || [])).next());
142
- });
143
- }
144
-
145
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
146
- var e = new Error(message);
147
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
148
- };
149
-
150
150
  class LocationCollector {
151
151
  constructor(config) {
152
152
  this.config = config;
@@ -13581,9 +13581,13 @@ class Transport {
13581
13581
  // Send user identification immediately (don't queue)
13582
13582
  this.sendToBackend('/v1/analytics/identify', user);
13583
13583
  }
13584
- startSession(session) {
13585
- // Send session start immediately to RUM endpoint
13586
- this.sendToBackend('/v1/rum/sessions', session);
13584
+ startSession(session_1) {
13585
+ return __awaiter$1(this, arguments, void 0, function* (session, useBeacon = false) {
13586
+ // Send session start immediately to RUM endpoint
13587
+ // MUST await to ensure session is created before other requests
13588
+ // Use beacon for page unload events (more reliable)
13589
+ yield this.sendToBackend('/v1/rum/sessions', session, useBeacon);
13590
+ });
13587
13591
  }
13588
13592
  sendError(error) {
13589
13593
  this.enqueue('error', error);
@@ -13868,7 +13872,6 @@ class DevSkinSDK {
13868
13872
  * Initialize the DevSkin SDK
13869
13873
  */
13870
13874
  init(config) {
13871
- var _a;
13872
13875
  if (this.initialized) {
13873
13876
  console.warn('[DevSkin] SDK already initialized');
13874
13877
  return;
@@ -13891,69 +13894,71 @@ class DevSkinSDK {
13891
13894
  this.locationCollector = new LocationCollector(this.config);
13892
13895
  this.browserCollector = new BrowserCollector(this.config);
13893
13896
  // Start session (will now include device/browser/location data)
13894
- this.startSession();
13895
- if (this.config.captureWebVitals) {
13896
- this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13897
- this.performanceCollector.start();
13898
- }
13899
- if (this.config.captureErrors) {
13900
- this.errorCollector = new ErrorCollector(this.config, this.transport);
13901
- this.errorCollector.start();
13902
- }
13903
- if (this.config.captureNetworkRequests) {
13904
- this.networkCollector = new NetworkCollector(this.config, this.transport);
13905
- this.networkCollector.start();
13906
- }
13907
- // Initialize heatmap collector - SEMPRE habilitado
13908
- // Merge default heatmap config with user config
13909
- const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13910
- this.config.heatmapOptions = heatmapConfig;
13911
- this.heatmapCollector = new HeatmapCollector(this.config, this.transport);
13912
- this.heatmapCollector.start();
13913
- // Initialize screenshot collector and capture page
13914
- this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13915
- this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13916
- if (this.config.debug) {
13917
- console.log('[DevSkin] Heatmap collection enabled (always on)');
13918
- }
13919
- // Initialize session recording with rrweb
13920
- if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13921
- // Use RRWebRecorder for complete DOM recording
13922
- this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13923
- enabled: true,
13924
- sampleRate: this.config.sessionRecording.sampling || 0.5,
13925
- blockClass: 'rr-block',
13926
- ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13927
- maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13928
- ? this.config.sessionRecording.maskAllInputs
13929
- : true,
13930
- maskInputOptions: {
13931
- password: true,
13932
- email: true,
13933
- tel: true,
13934
- },
13935
- recordCanvas: this.config.sessionRecording.recordCanvas || false,
13936
- collectFonts: true,
13937
- inlineStylesheet: true,
13938
- checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13939
- checkoutEveryNth: 200, // Every 200 events
13940
- }, (events) => {
13941
- var _a;
13942
- // Send rrweb events to backend
13943
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13944
- });
13945
- // Wait 500ms before starting recording to ensure session is created in backend
13946
- // This prevents race condition where FullSnapshot is sent before session exists
13947
- setTimeout(() => {
13948
- var _a, _b;
13949
- (_a = this.rrwebRecorder) === null || _a === void 0 ? void 0 : _a.start();
13897
+ // Wait for session creation to complete before starting collectors
13898
+ this.startSession().then(() => {
13899
+ // Session created, now safe to start collectors that send data
13900
+ var _a, _b;
13901
+ if (this.config.captureWebVitals) {
13902
+ this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13903
+ this.performanceCollector.start();
13904
+ }
13905
+ if (this.config.captureErrors) {
13906
+ this.errorCollector = new ErrorCollector(this.config, this.transport);
13907
+ this.errorCollector.start();
13908
+ }
13909
+ if (this.config.captureNetworkRequests) {
13910
+ this.networkCollector = new NetworkCollector(this.config, this.transport);
13911
+ this.networkCollector.start();
13912
+ }
13913
+ // Initialize heatmap collector - SEMPRE habilitado
13914
+ // Merge default heatmap config with user config
13915
+ const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13916
+ this.config.heatmapOptions = heatmapConfig;
13917
+ this.heatmapCollector = new HeatmapCollector(this.config, this.transport);
13918
+ this.heatmapCollector.start();
13919
+ // Initialize screenshot collector and capture page
13920
+ this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13921
+ this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13922
+ if (this.config.debug) {
13923
+ console.log('[DevSkin] Heatmap collection enabled (always on)');
13924
+ }
13925
+ // Initialize session recording with rrweb
13926
+ if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13927
+ // Use RRWebRecorder for complete DOM recording
13928
+ this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13929
+ enabled: true,
13930
+ sampleRate: this.config.sessionRecording.sampling || 0.5,
13931
+ blockClass: 'rr-block',
13932
+ ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13933
+ maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13934
+ ? this.config.sessionRecording.maskAllInputs
13935
+ : true,
13936
+ maskInputOptions: {
13937
+ password: true,
13938
+ email: true,
13939
+ tel: true,
13940
+ },
13941
+ recordCanvas: this.config.sessionRecording.recordCanvas || false,
13942
+ collectFonts: true,
13943
+ inlineStylesheet: true,
13944
+ checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13945
+ checkoutEveryNth: 200, // Every 200 events
13946
+ }, (events) => {
13947
+ var _a;
13948
+ // Send rrweb events to backend
13949
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13950
+ });
13951
+ // Start recording immediately (session already created)
13952
+ this.rrwebRecorder.start();
13950
13953
  if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
13951
13954
  console.log('[DevSkin] RRWeb recording started for session:', this.sessionId);
13952
13955
  }
13953
- }, 500);
13954
- }
13955
- // Track initial page view
13956
- this.trackPageView();
13956
+ }
13957
+ // Track initial page view
13958
+ this.trackPageView();
13959
+ }).catch((err) => {
13960
+ console.error('[DevSkin] Failed to create session:', err);
13961
+ });
13957
13962
  // Track page visibility changes
13958
13963
  this.setupVisibilityTracking();
13959
13964
  // Track page unload
@@ -14078,36 +14083,39 @@ class DevSkinSDK {
14078
14083
  * Private methods
14079
14084
  */
14080
14085
  startSession() {
14081
- var _a, _b, _c, _d, _e;
14082
- // Check if there's an active session (stored in sessionStorage to persist across page navigations)
14083
- const existingSessionId = sessionStorage.getItem('devskin_session_id');
14084
- const existingSessionStart = sessionStorage.getItem('devskin_session_start');
14085
- if (existingSessionId && existingSessionStart) {
14086
- // Resume existing session
14087
- this.sessionId = existingSessionId;
14088
- this.sessionStartTime = parseInt(existingSessionStart, 10);
14086
+ return __awaiter$1(this, void 0, void 0, function* () {
14087
+ var _a, _b, _c, _d, _e;
14088
+ // Check if there's an active session (stored in sessionStorage to persist across page navigations)
14089
+ const existingSessionId = sessionStorage.getItem('devskin_session_id');
14090
+ const existingSessionStart = sessionStorage.getItem('devskin_session_start');
14091
+ if (existingSessionId && existingSessionStart) {
14092
+ // Resume existing session
14093
+ this.sessionId = existingSessionId;
14094
+ this.sessionStartTime = parseInt(existingSessionStart, 10);
14095
+ // Set sessionId in transport so it can be added to network/performance requests
14096
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.setSessionId(this.sessionId);
14097
+ if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14098
+ console.log('[DevSkin] Resuming existing session:', this.sessionId);
14099
+ }
14100
+ // Send page view but DON'T create a new session
14101
+ // The session is already created, just continue it
14102
+ return;
14103
+ }
14104
+ // Create new session
14105
+ this.sessionId = this.generateId();
14106
+ this.sessionStartTime = Date.now();
14107
+ // Store in sessionStorage (persists across page navigations in same tab)
14108
+ sessionStorage.setItem('devskin_session_id', this.sessionId);
14109
+ sessionStorage.setItem('devskin_session_start', this.sessionStartTime.toString());
14089
14110
  // Set sessionId in transport so it can be added to network/performance requests
14090
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.setSessionId(this.sessionId);
14091
- if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14092
- console.log('[DevSkin] Resuming existing session:', this.sessionId);
14111
+ (_c = this.transport) === null || _c === void 0 ? void 0 : _c.setSessionId(this.sessionId);
14112
+ const sessionData = Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, startedAt: new Date().toISOString(), platform: 'web' }, this.getContextData());
14113
+ // CRITICAL: Await session creation to ensure it exists before sending metrics/requests
14114
+ yield ((_d = this.transport) === null || _d === void 0 ? void 0 : _d.startSession(sessionData));
14115
+ if ((_e = this.config) === null || _e === void 0 ? void 0 : _e.debug) {
14116
+ console.log('[DevSkin] New session created:', this.sessionId);
14093
14117
  }
14094
- // Send page view but DON'T create a new session
14095
- // The session is already created, just continue it
14096
- return;
14097
- }
14098
- // Create new session
14099
- this.sessionId = this.generateId();
14100
- this.sessionStartTime = Date.now();
14101
- // Store in sessionStorage (persists across page navigations in same tab)
14102
- sessionStorage.setItem('devskin_session_id', this.sessionId);
14103
- sessionStorage.setItem('devskin_session_start', this.sessionStartTime.toString());
14104
- // Set sessionId in transport so it can be added to network/performance requests
14105
- (_c = this.transport) === null || _c === void 0 ? void 0 : _c.setSessionId(this.sessionId);
14106
- const sessionData = Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, startedAt: new Date().toISOString(), platform: 'web' }, this.getContextData());
14107
- (_d = this.transport) === null || _d === void 0 ? void 0 : _d.startSession(sessionData);
14108
- if ((_e = this.config) === null || _e === void 0 ? void 0 : _e.debug) {
14109
- console.log('[DevSkin] New session created:', this.sessionId);
14110
- }
14118
+ });
14111
14119
  }
14112
14120
  getContextData() {
14113
14121
  var _a, _b, _c, _d;
@@ -14182,7 +14190,8 @@ class DevSkinSDK {
14182
14190
  if (this.sessionId && this.sessionStartTime) {
14183
14191
  const endedAt = new Date();
14184
14192
  const durationMs = Date.now() - this.sessionStartTime;
14185
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.startSession(Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, endedAt: endedAt.toISOString(), durationMs: durationMs, platform: 'web' }, this.getContextData()));
14193
+ // Use beacon for reliable delivery during page unload
14194
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.startSession(Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, endedAt: endedAt.toISOString(), durationMs: durationMs, platform: 'web' }, this.getContextData()), true); // true = use sendBeacon
14186
14195
  // Clear session storage since session is ending
14187
14196
  sessionStorage.removeItem('devskin_session_id');
14188
14197
  sessionStorage.removeItem('devskin_session_start');