@devskin/browser-sdk 1.0.33 → 1.0.35

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.
@@ -13856,6 +13856,7 @@ class DevSkinSDK {
13856
13856
  this.anonymousId = null;
13857
13857
  this.sessionStartTime = 0;
13858
13858
  this.initialized = false;
13859
+ this.heartbeatInterval = null;
13859
13860
  // Collectors
13860
13861
  this.deviceCollector = null;
13861
13862
  this.locationCollector = null;
@@ -13956,6 +13957,8 @@ class DevSkinSDK {
13956
13957
  }
13957
13958
  // Track initial page view
13958
13959
  this.trackPageView();
13960
+ // Start heartbeat to update session duration every 30 seconds
13961
+ this.startHeartbeat();
13959
13962
  }).catch((err) => {
13960
13963
  console.error('[DevSkin] Failed to create session:', err);
13961
13964
  });
@@ -14171,47 +14174,66 @@ class DevSkinSDK {
14171
14174
  }
14172
14175
  setupUnloadTracking() {
14173
14176
  // CRITICAL: Flush data BEFORE page unloads to avoid losing final events
14177
+ // IMPORTANT: NEVER clear sessionStorage - it expires naturally when tab closes
14174
14178
  // 1. visibilitychange - fires when tab is hidden (most reliable)
14175
14179
  document.addEventListener('visibilitychange', () => {
14176
14180
  var _a, _b;
14177
14181
  if (document.hidden) {
14178
- // User switched tabs or minimized - flush immediately
14182
+ // User switched tabs or minimized - update duration and flush
14183
+ this.updateSessionDuration();
14179
14184
  (_a = this.rrwebRecorder) === null || _a === void 0 ? void 0 : _a.stop(); // Stop recording and flush
14180
14185
  (_b = this.transport) === null || _b === void 0 ? void 0 : _b.flush(true); // Use beacon
14181
14186
  }
14182
14187
  });
14183
14188
  // 2. pagehide - fires when page is being unloaded
14184
- window.addEventListener('pagehide', (event) => {
14185
- var _a, _b, _c;
14186
- const isActualClose = !event.persisted;
14187
- if (isActualClose) {
14188
- // Tab is closing - end session
14189
- this.track('page_unload');
14190
- if (this.sessionId && this.sessionStartTime) {
14191
- const endedAt = new Date();
14192
- const durationMs = Date.now() - this.sessionStartTime;
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
14195
- // Clear session storage since session is ending
14196
- sessionStorage.removeItem('devskin_session_id');
14197
- sessionStorage.removeItem('devskin_session_start');
14198
- }
14199
- }
14200
- else {
14201
- // Navigation - track page change
14202
- this.track('page_navigation');
14203
- }
14204
- // ALWAYS flush (whether navigation or close)
14205
- (_b = this.rrwebRecorder) === null || _b === void 0 ? void 0 : _b.stop(); // Stop recording and flush remaining events
14206
- (_c = this.transport) === null || _c === void 0 ? void 0 : _c.flush(true); // Use beacon for reliability
14189
+ window.addEventListener('pagehide', () => {
14190
+ var _a, _b;
14191
+ // Track navigation (we can't distinguish between page navigation and tab close reliably)
14192
+ this.track('page_navigation');
14193
+ // Update duration but DON'T mark as ending (let heartbeat timeout handle session expiry)
14194
+ this.updateSessionDuration(false);
14195
+ // NEVER clear sessionStorage - it persists across navigations in same tab
14196
+ // and expires automatically when tab actually closes
14197
+ // Flush data before page unloads
14198
+ (_a = this.rrwebRecorder) === null || _a === void 0 ? void 0 : _a.stop(); // Stop recording and flush remaining events
14199
+ (_b = this.transport) === null || _b === void 0 ? void 0 : _b.flush(true); // Use beacon for reliability
14207
14200
  });
14208
14201
  // 3. beforeunload - backup for older browsers
14209
14202
  window.addEventListener('beforeunload', () => {
14210
14203
  var _a, _b;
14204
+ // Update duration but DON'T mark as ending
14205
+ this.updateSessionDuration(false);
14211
14206
  (_a = this.rrwebRecorder) === null || _a === void 0 ? void 0 : _a.stop();
14212
14207
  (_b = this.transport) === null || _b === void 0 ? void 0 : _b.flush(true);
14213
14208
  });
14214
14209
  }
14210
+ /**
14211
+ * Start heartbeat to update session duration periodically
14212
+ */
14213
+ startHeartbeat() {
14214
+ // Update duration every 30 seconds
14215
+ this.heartbeatInterval = setInterval(() => {
14216
+ this.updateSessionDuration();
14217
+ }, 30000); // 30 seconds
14218
+ }
14219
+ /**
14220
+ * Update session duration
14221
+ */
14222
+ updateSessionDuration(isEnding = false) {
14223
+ var _a, _b;
14224
+ if (!this.sessionId || !this.sessionStartTime)
14225
+ return;
14226
+ const durationMs = Date.now() - this.sessionStartTime;
14227
+ const payload = Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, durationMs: durationMs, platform: 'web' }, this.getContextData());
14228
+ if (isEnding) {
14229
+ payload.endedAt = new Date().toISOString();
14230
+ }
14231
+ // Use beacon if ending, otherwise regular request
14232
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.startSession(payload, isEnding);
14233
+ if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14234
+ console.log('[DevSkin] Session duration updated:', durationMs, 'ms', isEnding ? '(ending)' : '');
14235
+ }
14236
+ }
14215
14237
  }
14216
14238
  // Create singleton instance
14217
14239
  const DevSkin = new DevSkinSDK();