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