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