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