@devskin/browser-sdk 1.0.30 → 1.0.32

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.
@@ -1,3 +1,35 @@
1
+ /******************************************************************************
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
14
+ ***************************************************************************** */
15
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
16
+
17
+
18
+ function __awaiter$1(thisArg, _arguments, P, generator) {
19
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
+ return new (P || (P = Promise))(function (resolve, reject) {
21
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
25
+ });
26
+ }
27
+
28
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
29
+ var e = new Error(message);
30
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
31
+ };
32
+
1
33
  class DeviceCollector {
2
34
  constructor(config) {
3
35
  this.config = config;
@@ -115,38 +147,6 @@ class DeviceCollector {
115
147
  }
116
148
  }
117
149
 
118
- /******************************************************************************
119
- Copyright (c) Microsoft Corporation.
120
-
121
- Permission to use, copy, modify, and/or distribute this software for any
122
- purpose with or without fee is hereby granted.
123
-
124
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
125
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
126
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
127
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
128
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
129
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
130
- PERFORMANCE OF THIS SOFTWARE.
131
- ***************************************************************************** */
132
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
133
-
134
-
135
- function __awaiter$1(thisArg, _arguments, P, generator) {
136
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
137
- return new (P || (P = Promise))(function (resolve, reject) {
138
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
139
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
140
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
141
- step((generator = generator.apply(thisArg, _arguments || [])).next());
142
- });
143
- }
144
-
145
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
146
- var e = new Error(message);
147
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
148
- };
149
-
150
150
  class LocationCollector {
151
151
  constructor(config) {
152
152
  this.config = config;
@@ -334,12 +334,12 @@ class PerformanceCollector {
334
334
  if (this.config.debug) {
335
335
  console.log(`[DevSkin] Web Vital ${metric.name}:`, metric.value);
336
336
  }
337
- // Send metric to backend
337
+ // Send metric to backend - match backend schema
338
338
  this.transport.sendPerformanceMetric({
339
- name: metric.name,
339
+ metricName: metric.name, // Changed from 'name' to 'metricName'
340
340
  value: metric.value,
341
341
  rating: metric.rating,
342
- delta: metric.delta,
342
+ url: window.location.href,
343
343
  timestamp: new Date().toISOString(),
344
344
  });
345
345
  }
@@ -362,19 +362,9 @@ class PerformanceCollector {
362
362
  });
363
363
  }
364
364
  this.transport.sendPerformanceMetric({
365
- name: 'Navigation',
365
+ metricName: 'Navigation',
366
366
  value: windowLoad,
367
- details: {
368
- domLoad,
369
- windowLoad,
370
- navigationType: navigation.type,
371
- redirectCount: navigation.redirectCount,
372
- dns: timing.domainLookupEnd - timing.domainLookupStart,
373
- tcp: timing.connectEnd - timing.connectStart,
374
- request: timing.responseStart - timing.requestStart,
375
- response: timing.responseEnd - timing.responseStart,
376
- dom: timing.domComplete - timing.domLoading,
377
- },
367
+ url: window.location.href,
378
368
  timestamp: new Date().toISOString(),
379
369
  });
380
370
  }, 0);
@@ -408,9 +398,9 @@ class PerformanceCollector {
408
398
  console.log('[DevSkin] Resource Timings:', resourceStats);
409
399
  }
410
400
  this.transport.sendPerformanceMetric({
411
- name: 'Resources',
401
+ metricName: 'Resources',
412
402
  value: resources.length,
413
- details: resourceStats,
403
+ url: window.location.href,
414
404
  timestamp: new Date().toISOString(),
415
405
  });
416
406
  }, 1000);
@@ -427,13 +417,9 @@ class PerformanceCollector {
427
417
  console.log('[DevSkin] Long Task detected:', entry);
428
418
  }
429
419
  this.transport.sendPerformanceMetric({
430
- name: 'LongTask',
420
+ metricName: 'LongTask',
431
421
  value: entry.duration,
432
- details: {
433
- name: entry.name,
434
- entryType: entry.entryType,
435
- startTime: entry.startTime,
436
- },
422
+ url: window.location.href,
437
423
  timestamp: new Date().toISOString(),
438
424
  });
439
425
  }
@@ -673,7 +659,7 @@ class NetworkCollector {
673
659
  return;
674
660
  const originalFetch = window.fetch;
675
661
  window.fetch = (...args) => __awaiter$1(this, void 0, void 0, function* () {
676
- var _a, _b, _c;
662
+ var _a, _b;
677
663
  const [resource, config] = args;
678
664
  const url = typeof resource === 'string'
679
665
  ? resource
@@ -699,26 +685,18 @@ class NetworkCollector {
699
685
  const networkRequest = {
700
686
  url,
701
687
  method,
702
- status: response.status,
703
- duration,
704
- size: yield this.getResponseSize(clonedResponse),
705
- type: 'fetch',
688
+ statusCode: response.status,
689
+ durationMs: duration,
690
+ responseSize: yield this.getResponseSize(clonedResponse),
706
691
  timestamp: new Date().toISOString(),
707
- failed: !response.ok,
708
692
  };
709
693
  // Capture headers if enabled
710
694
  if ((_b = this.config.networkRequestOptions) === null || _b === void 0 ? void 0 : _b.captureHeaders) {
711
- networkRequest.headers = this.headersToObject(response.headers);
695
+ networkRequest.responseHeaders = this.headersToObject(response.headers);
712
696
  }
713
- // Capture body if enabled (only for failed requests to avoid performance issues)
714
- if (((_c = this.config.networkRequestOptions) === null || _c === void 0 ? void 0 : _c.captureBody) && !response.ok) {
715
- try {
716
- const text = yield clonedResponse.text();
717
- networkRequest.body = text.substring(0, 10000); // Limit to 10KB
718
- }
719
- catch (error) {
720
- // Body might not be readable
721
- }
697
+ // Capture error message for failed requests
698
+ if (!response.ok) {
699
+ networkRequest.errorMessage = `HTTP ${response.status} ${response.statusText}`;
722
700
  }
723
701
  if (this.config.debug) {
724
702
  console.log('[DevSkin] Network request tracked:', networkRequest);
@@ -732,10 +710,9 @@ class NetworkCollector {
732
710
  const networkRequest = {
733
711
  url,
734
712
  method,
735
- duration,
736
- type: 'fetch',
713
+ durationMs: duration,
737
714
  timestamp: new Date().toISOString(),
738
- failed: true,
715
+ errorMessage: error instanceof Error ? error.message : 'Network request failed',
739
716
  };
740
717
  if (this.config.debug) {
741
718
  console.log('[DevSkin] Network request failed:', networkRequest);
@@ -774,7 +751,7 @@ class NetworkCollector {
774
751
  const collector = window.__devskinNetworkCollector;
775
752
  // Track when request completes
776
753
  const handleLoad = () => {
777
- var _a, _b, _c, _d;
754
+ var _a, _b;
778
755
  const duration = Date.now() - devskin.startTime;
779
756
  // Check if should be ignored
780
757
  if (collector === null || collector === void 0 ? void 0 : collector.shouldIgnoreUrl(devskin.url)) {
@@ -789,25 +766,17 @@ class NetworkCollector {
789
766
  const networkRequest = {
790
767
  url: devskin.url,
791
768
  method: devskin.method,
792
- status: xhr.status,
793
- duration,
794
- type: 'xhr',
769
+ statusCode: xhr.status,
770
+ durationMs: duration,
795
771
  timestamp: new Date().toISOString(),
796
- failed: xhr.status === 0 || xhr.status >= 400,
797
772
  };
798
773
  // Capture headers if enabled
799
774
  if ((_b = collector === null || collector === void 0 ? void 0 : collector.config.networkRequestOptions) === null || _b === void 0 ? void 0 : _b.captureHeaders) {
800
- networkRequest.headers = collector.parseResponseHeaders(xhr.getAllResponseHeaders());
775
+ networkRequest.responseHeaders = collector.parseResponseHeaders(xhr.getAllResponseHeaders());
801
776
  }
802
- // Capture body if enabled (only for failed requests)
803
- if (((_c = collector === null || collector === void 0 ? void 0 : collector.config.networkRequestOptions) === null || _c === void 0 ? void 0 : _c.captureBody) &&
804
- networkRequest.failed) {
805
- try {
806
- networkRequest.body = (_d = xhr.responseText) === null || _d === void 0 ? void 0 : _d.substring(0, 10000);
807
- }
808
- catch (error) {
809
- // Response might not be readable
810
- }
777
+ // Capture error message for failed requests
778
+ if (xhr.status === 0 || xhr.status >= 400) {
779
+ networkRequest.errorMessage = `HTTP ${xhr.status} ${xhr.statusText}`;
811
780
  }
812
781
  if (collector === null || collector === void 0 ? void 0 : collector.config.debug) {
813
782
  console.log('[DevSkin] XHR request tracked:', networkRequest);
@@ -820,10 +789,9 @@ class NetworkCollector {
820
789
  const networkRequest = {
821
790
  url: devskin.url,
822
791
  method: devskin.method,
823
- duration,
824
- type: 'xhr',
792
+ durationMs: duration,
825
793
  timestamp: new Date().toISOString(),
826
- failed: true,
794
+ errorMessage: 'XHR request failed',
827
795
  };
828
796
  if (collector === null || collector === void 0 ? void 0 : collector.config.debug) {
829
797
  console.log('[DevSkin] XHR request failed:', networkRequest);
@@ -13586,6 +13554,7 @@ class Transport {
13586
13554
  this.flushInterval = null;
13587
13555
  this.maxQueueSize = 20; // Reduced from 50
13588
13556
  this.flushIntervalMs = 2000; // 2 seconds (reduced from 5s)
13557
+ this.sessionId = null;
13589
13558
  this.apiUrl = config.apiUrl || 'https://api.devskin.com';
13590
13559
  // Start periodic flush
13591
13560
  this.startPeriodicFlush();
@@ -13602,6 +13571,9 @@ class Transport {
13602
13571
  });
13603
13572
  }
13604
13573
  }
13574
+ setSessionId(sessionId) {
13575
+ this.sessionId = sessionId;
13576
+ }
13605
13577
  sendEvent(event) {
13606
13578
  this.enqueue('event', event);
13607
13579
  }
@@ -13610,8 +13582,11 @@ class Transport {
13610
13582
  this.sendToBackend('/v1/analytics/identify', user);
13611
13583
  }
13612
13584
  startSession(session) {
13613
- // Send session start immediately to RUM endpoint
13614
- this.sendToBackend('/v1/rum/sessions', session);
13585
+ return __awaiter$1(this, void 0, void 0, function* () {
13586
+ // Send session start immediately to RUM endpoint
13587
+ // MUST await to ensure session is created before other requests
13588
+ yield this.sendToBackend('/v1/rum/sessions', session);
13589
+ });
13615
13590
  }
13616
13591
  sendError(error) {
13617
13592
  this.enqueue('error', error);
@@ -13723,9 +13698,16 @@ class Transport {
13723
13698
  }
13724
13699
  }
13725
13700
  enqueue(type, data) {
13726
- // Add applicationId to RUM events (event, error, network, performance)
13701
+ // Add applicationId and sessionId to RUM events (event, error, network, performance)
13727
13702
  // Heatmap uses apiKey/appId in payload root instead
13728
- const enrichedData = type !== 'heatmap' ? Object.assign(Object.assign({}, data), { applicationId: this.config.appId }) : data;
13703
+ let enrichedData = data;
13704
+ if (type !== 'heatmap') {
13705
+ enrichedData = Object.assign(Object.assign({}, data), { applicationId: this.config.appId });
13706
+ // Add sessionId to network and performance requests (required by backend)
13707
+ if ((type === 'network' || type === 'performance') && this.sessionId) {
13708
+ enrichedData.sessionId = this.sessionId;
13709
+ }
13710
+ }
13729
13711
  this.queue.push({
13730
13712
  type,
13731
13713
  data: enrichedData,
@@ -13889,7 +13871,6 @@ class DevSkinSDK {
13889
13871
  * Initialize the DevSkin SDK
13890
13872
  */
13891
13873
  init(config) {
13892
- var _a;
13893
13874
  if (this.initialized) {
13894
13875
  console.warn('[DevSkin] SDK already initialized');
13895
13876
  return;
@@ -13912,69 +13893,71 @@ class DevSkinSDK {
13912
13893
  this.locationCollector = new LocationCollector(this.config);
13913
13894
  this.browserCollector = new BrowserCollector(this.config);
13914
13895
  // Start session (will now include device/browser/location data)
13915
- this.startSession();
13916
- if (this.config.captureWebVitals) {
13917
- this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13918
- this.performanceCollector.start();
13919
- }
13920
- if (this.config.captureErrors) {
13921
- this.errorCollector = new ErrorCollector(this.config, this.transport);
13922
- this.errorCollector.start();
13923
- }
13924
- if (this.config.captureNetworkRequests) {
13925
- this.networkCollector = new NetworkCollector(this.config, this.transport);
13926
- this.networkCollector.start();
13927
- }
13928
- // Initialize heatmap collector - SEMPRE habilitado
13929
- // Merge default heatmap config with user config
13930
- const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13931
- this.config.heatmapOptions = heatmapConfig;
13932
- this.heatmapCollector = new HeatmapCollector(this.config, this.transport);
13933
- this.heatmapCollector.start();
13934
- // Initialize screenshot collector and capture page
13935
- this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13936
- this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13937
- if (this.config.debug) {
13938
- console.log('[DevSkin] Heatmap collection enabled (always on)');
13939
- }
13940
- // Initialize session recording with rrweb
13941
- if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13942
- // Use RRWebRecorder for complete DOM recording
13943
- this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13944
- enabled: true,
13945
- sampleRate: this.config.sessionRecording.sampling || 0.5,
13946
- blockClass: 'rr-block',
13947
- ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13948
- maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13949
- ? this.config.sessionRecording.maskAllInputs
13950
- : true,
13951
- maskInputOptions: {
13952
- password: true,
13953
- email: true,
13954
- tel: true,
13955
- },
13956
- recordCanvas: this.config.sessionRecording.recordCanvas || false,
13957
- collectFonts: true,
13958
- inlineStylesheet: true,
13959
- checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13960
- checkoutEveryNth: 200, // Every 200 events
13961
- }, (events) => {
13962
- var _a;
13963
- // Send rrweb events to backend
13964
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13965
- });
13966
- // Wait 500ms before starting recording to ensure session is created in backend
13967
- // This prevents race condition where FullSnapshot is sent before session exists
13968
- setTimeout(() => {
13969
- var _a, _b;
13970
- (_a = this.rrwebRecorder) === null || _a === void 0 ? void 0 : _a.start();
13896
+ // Wait for session creation to complete before starting collectors
13897
+ this.startSession().then(() => {
13898
+ // Session created, now safe to start collectors that send data
13899
+ var _a, _b;
13900
+ if (this.config.captureWebVitals) {
13901
+ this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13902
+ this.performanceCollector.start();
13903
+ }
13904
+ if (this.config.captureErrors) {
13905
+ this.errorCollector = new ErrorCollector(this.config, this.transport);
13906
+ this.errorCollector.start();
13907
+ }
13908
+ if (this.config.captureNetworkRequests) {
13909
+ this.networkCollector = new NetworkCollector(this.config, this.transport);
13910
+ this.networkCollector.start();
13911
+ }
13912
+ // Initialize heatmap collector - SEMPRE habilitado
13913
+ // Merge default heatmap config with user config
13914
+ const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13915
+ this.config.heatmapOptions = heatmapConfig;
13916
+ this.heatmapCollector = new HeatmapCollector(this.config, this.transport);
13917
+ this.heatmapCollector.start();
13918
+ // Initialize screenshot collector and capture page
13919
+ this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13920
+ this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13921
+ if (this.config.debug) {
13922
+ console.log('[DevSkin] Heatmap collection enabled (always on)');
13923
+ }
13924
+ // Initialize session recording with rrweb
13925
+ if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13926
+ // Use RRWebRecorder for complete DOM recording
13927
+ this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13928
+ enabled: true,
13929
+ sampleRate: this.config.sessionRecording.sampling || 0.5,
13930
+ blockClass: 'rr-block',
13931
+ ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13932
+ maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13933
+ ? this.config.sessionRecording.maskAllInputs
13934
+ : true,
13935
+ maskInputOptions: {
13936
+ password: true,
13937
+ email: true,
13938
+ tel: true,
13939
+ },
13940
+ recordCanvas: this.config.sessionRecording.recordCanvas || false,
13941
+ collectFonts: true,
13942
+ inlineStylesheet: true,
13943
+ checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13944
+ checkoutEveryNth: 200, // Every 200 events
13945
+ }, (events) => {
13946
+ var _a;
13947
+ // Send rrweb events to backend
13948
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13949
+ });
13950
+ // Start recording immediately (session already created)
13951
+ this.rrwebRecorder.start();
13971
13952
  if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
13972
13953
  console.log('[DevSkin] RRWeb recording started for session:', this.sessionId);
13973
13954
  }
13974
- }, 500);
13975
- }
13976
- // Track initial page view
13977
- this.trackPageView();
13955
+ }
13956
+ // Track initial page view
13957
+ this.trackPageView();
13958
+ }).catch((err) => {
13959
+ console.error('[DevSkin] Failed to create session:', err);
13960
+ });
13978
13961
  // Track page visibility changes
13979
13962
  this.setupVisibilityTracking();
13980
13963
  // Track page unload
@@ -14099,32 +14082,39 @@ class DevSkinSDK {
14099
14082
  * Private methods
14100
14083
  */
14101
14084
  startSession() {
14102
- var _a, _b, _c;
14103
- // Check if there's an active session (stored in sessionStorage to persist across page navigations)
14104
- const existingSessionId = sessionStorage.getItem('devskin_session_id');
14105
- const existingSessionStart = sessionStorage.getItem('devskin_session_start');
14106
- if (existingSessionId && existingSessionStart) {
14107
- // Resume existing session
14108
- this.sessionId = existingSessionId;
14109
- this.sessionStartTime = parseInt(existingSessionStart, 10);
14110
- if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.debug) {
14111
- console.log('[DevSkin] Resuming existing session:', this.sessionId);
14085
+ return __awaiter$1(this, void 0, void 0, function* () {
14086
+ var _a, _b, _c, _d, _e;
14087
+ // Check if there's an active session (stored in sessionStorage to persist across page navigations)
14088
+ const existingSessionId = sessionStorage.getItem('devskin_session_id');
14089
+ const existingSessionStart = sessionStorage.getItem('devskin_session_start');
14090
+ if (existingSessionId && existingSessionStart) {
14091
+ // Resume existing session
14092
+ this.sessionId = existingSessionId;
14093
+ this.sessionStartTime = parseInt(existingSessionStart, 10);
14094
+ // Set sessionId in transport so it can be added to network/performance requests
14095
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.setSessionId(this.sessionId);
14096
+ if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14097
+ console.log('[DevSkin] Resuming existing session:', this.sessionId);
14098
+ }
14099
+ // Send page view but DON'T create a new session
14100
+ // The session is already created, just continue it
14101
+ return;
14112
14102
  }
14113
- // Send page view but DON'T create a new session
14114
- // The session is already created, just continue it
14115
- return;
14116
- }
14117
- // Create new session
14118
- this.sessionId = this.generateId();
14119
- this.sessionStartTime = Date.now();
14120
- // Store in sessionStorage (persists across page navigations in same tab)
14121
- sessionStorage.setItem('devskin_session_id', this.sessionId);
14122
- sessionStorage.setItem('devskin_session_start', this.sessionStartTime.toString());
14123
- const sessionData = Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, startedAt: new Date().toISOString(), platform: 'web' }, this.getContextData());
14124
- (_b = this.transport) === null || _b === void 0 ? void 0 : _b.startSession(sessionData);
14125
- if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14126
- console.log('[DevSkin] New session created:', this.sessionId);
14127
- }
14103
+ // Create new session
14104
+ this.sessionId = this.generateId();
14105
+ this.sessionStartTime = Date.now();
14106
+ // Store in sessionStorage (persists across page navigations in same tab)
14107
+ sessionStorage.setItem('devskin_session_id', this.sessionId);
14108
+ sessionStorage.setItem('devskin_session_start', this.sessionStartTime.toString());
14109
+ // Set sessionId in transport so it can be added to network/performance requests
14110
+ (_c = this.transport) === null || _c === void 0 ? void 0 : _c.setSessionId(this.sessionId);
14111
+ const sessionData = Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, startedAt: new Date().toISOString(), platform: 'web' }, this.getContextData());
14112
+ // CRITICAL: Await session creation to ensure it exists before sending metrics/requests
14113
+ yield ((_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
+ }
14117
+ });
14128
14118
  }
14129
14119
  getContextData() {
14130
14120
  var _a, _b, _c, _d;