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