@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.
@@ -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;
@@ -340,12 +340,12 @@
340
340
  if (this.config.debug) {
341
341
  console.log(`[DevSkin] Web Vital ${metric.name}:`, metric.value);
342
342
  }
343
- // Send metric to backend
343
+ // Send metric to backend - match backend schema
344
344
  this.transport.sendPerformanceMetric({
345
- name: metric.name,
345
+ metricName: metric.name, // Changed from 'name' to 'metricName'
346
346
  value: metric.value,
347
347
  rating: metric.rating,
348
- delta: metric.delta,
348
+ url: window.location.href,
349
349
  timestamp: new Date().toISOString(),
350
350
  });
351
351
  }
@@ -368,19 +368,9 @@
368
368
  });
369
369
  }
370
370
  this.transport.sendPerformanceMetric({
371
- name: 'Navigation',
371
+ metricName: 'Navigation',
372
372
  value: windowLoad,
373
- details: {
374
- domLoad,
375
- windowLoad,
376
- navigationType: navigation.type,
377
- redirectCount: navigation.redirectCount,
378
- dns: timing.domainLookupEnd - timing.domainLookupStart,
379
- tcp: timing.connectEnd - timing.connectStart,
380
- request: timing.responseStart - timing.requestStart,
381
- response: timing.responseEnd - timing.responseStart,
382
- dom: timing.domComplete - timing.domLoading,
383
- },
373
+ url: window.location.href,
384
374
  timestamp: new Date().toISOString(),
385
375
  });
386
376
  }, 0);
@@ -414,9 +404,9 @@
414
404
  console.log('[DevSkin] Resource Timings:', resourceStats);
415
405
  }
416
406
  this.transport.sendPerformanceMetric({
417
- name: 'Resources',
407
+ metricName: 'Resources',
418
408
  value: resources.length,
419
- details: resourceStats,
409
+ url: window.location.href,
420
410
  timestamp: new Date().toISOString(),
421
411
  });
422
412
  }, 1000);
@@ -433,13 +423,9 @@
433
423
  console.log('[DevSkin] Long Task detected:', entry);
434
424
  }
435
425
  this.transport.sendPerformanceMetric({
436
- name: 'LongTask',
426
+ metricName: 'LongTask',
437
427
  value: entry.duration,
438
- details: {
439
- name: entry.name,
440
- entryType: entry.entryType,
441
- startTime: entry.startTime,
442
- },
428
+ url: window.location.href,
443
429
  timestamp: new Date().toISOString(),
444
430
  });
445
431
  }
@@ -679,7 +665,7 @@
679
665
  return;
680
666
  const originalFetch = window.fetch;
681
667
  window.fetch = (...args) => __awaiter$1(this, void 0, void 0, function* () {
682
- var _a, _b, _c;
668
+ var _a, _b;
683
669
  const [resource, config] = args;
684
670
  const url = typeof resource === 'string'
685
671
  ? resource
@@ -705,26 +691,18 @@
705
691
  const networkRequest = {
706
692
  url,
707
693
  method,
708
- status: response.status,
709
- duration,
710
- size: yield this.getResponseSize(clonedResponse),
711
- type: 'fetch',
694
+ statusCode: response.status,
695
+ durationMs: duration,
696
+ responseSize: yield this.getResponseSize(clonedResponse),
712
697
  timestamp: new Date().toISOString(),
713
- failed: !response.ok,
714
698
  };
715
699
  // Capture headers if enabled
716
700
  if ((_b = this.config.networkRequestOptions) === null || _b === void 0 ? void 0 : _b.captureHeaders) {
717
- networkRequest.headers = this.headersToObject(response.headers);
701
+ networkRequest.responseHeaders = this.headersToObject(response.headers);
718
702
  }
719
- // Capture body if enabled (only for failed requests to avoid performance issues)
720
- if (((_c = this.config.networkRequestOptions) === null || _c === void 0 ? void 0 : _c.captureBody) && !response.ok) {
721
- try {
722
- const text = yield clonedResponse.text();
723
- networkRequest.body = text.substring(0, 10000); // Limit to 10KB
724
- }
725
- catch (error) {
726
- // Body might not be readable
727
- }
703
+ // Capture error message for failed requests
704
+ if (!response.ok) {
705
+ networkRequest.errorMessage = `HTTP ${response.status} ${response.statusText}`;
728
706
  }
729
707
  if (this.config.debug) {
730
708
  console.log('[DevSkin] Network request tracked:', networkRequest);
@@ -738,10 +716,9 @@
738
716
  const networkRequest = {
739
717
  url,
740
718
  method,
741
- duration,
742
- type: 'fetch',
719
+ durationMs: duration,
743
720
  timestamp: new Date().toISOString(),
744
- failed: true,
721
+ errorMessage: error instanceof Error ? error.message : 'Network request failed',
745
722
  };
746
723
  if (this.config.debug) {
747
724
  console.log('[DevSkin] Network request failed:', networkRequest);
@@ -780,7 +757,7 @@
780
757
  const collector = window.__devskinNetworkCollector;
781
758
  // Track when request completes
782
759
  const handleLoad = () => {
783
- var _a, _b, _c, _d;
760
+ var _a, _b;
784
761
  const duration = Date.now() - devskin.startTime;
785
762
  // Check if should be ignored
786
763
  if (collector === null || collector === void 0 ? void 0 : collector.shouldIgnoreUrl(devskin.url)) {
@@ -795,25 +772,17 @@
795
772
  const networkRequest = {
796
773
  url: devskin.url,
797
774
  method: devskin.method,
798
- status: xhr.status,
799
- duration,
800
- type: 'xhr',
775
+ statusCode: xhr.status,
776
+ durationMs: duration,
801
777
  timestamp: new Date().toISOString(),
802
- failed: xhr.status === 0 || xhr.status >= 400,
803
778
  };
804
779
  // Capture headers if enabled
805
780
  if ((_b = collector === null || collector === void 0 ? void 0 : collector.config.networkRequestOptions) === null || _b === void 0 ? void 0 : _b.captureHeaders) {
806
- networkRequest.headers = collector.parseResponseHeaders(xhr.getAllResponseHeaders());
781
+ networkRequest.responseHeaders = collector.parseResponseHeaders(xhr.getAllResponseHeaders());
807
782
  }
808
- // Capture body if enabled (only for failed requests)
809
- if (((_c = collector === null || collector === void 0 ? void 0 : collector.config.networkRequestOptions) === null || _c === void 0 ? void 0 : _c.captureBody) &&
810
- networkRequest.failed) {
811
- try {
812
- networkRequest.body = (_d = xhr.responseText) === null || _d === void 0 ? void 0 : _d.substring(0, 10000);
813
- }
814
- catch (error) {
815
- // Response might not be readable
816
- }
783
+ // Capture error message for failed requests
784
+ if (xhr.status === 0 || xhr.status >= 400) {
785
+ networkRequest.errorMessage = `HTTP ${xhr.status} ${xhr.statusText}`;
817
786
  }
818
787
  if (collector === null || collector === void 0 ? void 0 : collector.config.debug) {
819
788
  console.log('[DevSkin] XHR request tracked:', networkRequest);
@@ -826,10 +795,9 @@
826
795
  const networkRequest = {
827
796
  url: devskin.url,
828
797
  method: devskin.method,
829
- duration,
830
- type: 'xhr',
798
+ durationMs: duration,
831
799
  timestamp: new Date().toISOString(),
832
- failed: true,
800
+ errorMessage: 'XHR request failed',
833
801
  };
834
802
  if (collector === null || collector === void 0 ? void 0 : collector.config.debug) {
835
803
  console.log('[DevSkin] XHR request failed:', networkRequest);
@@ -13592,6 +13560,7 @@
13592
13560
  this.flushInterval = null;
13593
13561
  this.maxQueueSize = 20; // Reduced from 50
13594
13562
  this.flushIntervalMs = 2000; // 2 seconds (reduced from 5s)
13563
+ this.sessionId = null;
13595
13564
  this.apiUrl = config.apiUrl || 'https://api.devskin.com';
13596
13565
  // Start periodic flush
13597
13566
  this.startPeriodicFlush();
@@ -13608,6 +13577,9 @@
13608
13577
  });
13609
13578
  }
13610
13579
  }
13580
+ setSessionId(sessionId) {
13581
+ this.sessionId = sessionId;
13582
+ }
13611
13583
  sendEvent(event) {
13612
13584
  this.enqueue('event', event);
13613
13585
  }
@@ -13616,8 +13588,11 @@
13616
13588
  this.sendToBackend('/v1/analytics/identify', user);
13617
13589
  }
13618
13590
  startSession(session) {
13619
- // Send session start immediately to RUM endpoint
13620
- this.sendToBackend('/v1/rum/sessions', session);
13591
+ return __awaiter$1(this, void 0, void 0, function* () {
13592
+ // Send session start immediately to RUM endpoint
13593
+ // MUST await to ensure session is created before other requests
13594
+ yield this.sendToBackend('/v1/rum/sessions', session);
13595
+ });
13621
13596
  }
13622
13597
  sendError(error) {
13623
13598
  this.enqueue('error', error);
@@ -13729,9 +13704,16 @@
13729
13704
  }
13730
13705
  }
13731
13706
  enqueue(type, data) {
13732
- // Add applicationId to RUM events (event, error, network, performance)
13707
+ // Add applicationId and sessionId to RUM events (event, error, network, performance)
13733
13708
  // Heatmap uses apiKey/appId in payload root instead
13734
- const enrichedData = type !== 'heatmap' ? Object.assign(Object.assign({}, data), { applicationId: this.config.appId }) : data;
13709
+ let enrichedData = data;
13710
+ if (type !== 'heatmap') {
13711
+ enrichedData = Object.assign(Object.assign({}, data), { applicationId: this.config.appId });
13712
+ // Add sessionId to network and performance requests (required by backend)
13713
+ if ((type === 'network' || type === 'performance') && this.sessionId) {
13714
+ enrichedData.sessionId = this.sessionId;
13715
+ }
13716
+ }
13735
13717
  this.queue.push({
13736
13718
  type,
13737
13719
  data: enrichedData,
@@ -13895,7 +13877,6 @@
13895
13877
  * Initialize the DevSkin SDK
13896
13878
  */
13897
13879
  init(config) {
13898
- var _a;
13899
13880
  if (this.initialized) {
13900
13881
  console.warn('[DevSkin] SDK already initialized');
13901
13882
  return;
@@ -13918,69 +13899,71 @@
13918
13899
  this.locationCollector = new LocationCollector(this.config);
13919
13900
  this.browserCollector = new BrowserCollector(this.config);
13920
13901
  // Start session (will now include device/browser/location data)
13921
- this.startSession();
13922
- if (this.config.captureWebVitals) {
13923
- this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13924
- this.performanceCollector.start();
13925
- }
13926
- if (this.config.captureErrors) {
13927
- this.errorCollector = new ErrorCollector(this.config, this.transport);
13928
- this.errorCollector.start();
13929
- }
13930
- if (this.config.captureNetworkRequests) {
13931
- this.networkCollector = new NetworkCollector(this.config, this.transport);
13932
- this.networkCollector.start();
13933
- }
13934
- // Initialize heatmap collector - SEMPRE habilitado
13935
- // Merge default heatmap config with user config
13936
- const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13937
- this.config.heatmapOptions = heatmapConfig;
13938
- this.heatmapCollector = new HeatmapCollector(this.config, this.transport);
13939
- this.heatmapCollector.start();
13940
- // Initialize screenshot collector and capture page
13941
- this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13942
- this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13943
- if (this.config.debug) {
13944
- console.log('[DevSkin] Heatmap collection enabled (always on)');
13945
- }
13946
- // Initialize session recording with rrweb
13947
- if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13948
- // Use RRWebRecorder for complete DOM recording
13949
- this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13950
- enabled: true,
13951
- sampleRate: this.config.sessionRecording.sampling || 0.5,
13952
- blockClass: 'rr-block',
13953
- ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13954
- maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13955
- ? this.config.sessionRecording.maskAllInputs
13956
- : true,
13957
- maskInputOptions: {
13958
- password: true,
13959
- email: true,
13960
- tel: true,
13961
- },
13962
- recordCanvas: this.config.sessionRecording.recordCanvas || false,
13963
- collectFonts: true,
13964
- inlineStylesheet: true,
13965
- checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13966
- checkoutEveryNth: 200, // Every 200 events
13967
- }, (events) => {
13968
- var _a;
13969
- // Send rrweb events to backend
13970
- (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13971
- });
13972
- // Wait 500ms before starting recording to ensure session is created in backend
13973
- // This prevents race condition where FullSnapshot is sent before session exists
13974
- setTimeout(() => {
13975
- var _a, _b;
13976
- (_a = this.rrwebRecorder) === null || _a === void 0 ? void 0 : _a.start();
13902
+ // Wait for session creation to complete before starting collectors
13903
+ this.startSession().then(() => {
13904
+ // Session created, now safe to start collectors that send data
13905
+ var _a, _b;
13906
+ if (this.config.captureWebVitals) {
13907
+ this.performanceCollector = new PerformanceCollector(this.config, this.transport);
13908
+ this.performanceCollector.start();
13909
+ }
13910
+ if (this.config.captureErrors) {
13911
+ this.errorCollector = new ErrorCollector(this.config, this.transport);
13912
+ this.errorCollector.start();
13913
+ }
13914
+ if (this.config.captureNetworkRequests) {
13915
+ this.networkCollector = new NetworkCollector(this.config, this.transport);
13916
+ this.networkCollector.start();
13917
+ }
13918
+ // Initialize heatmap collector - SEMPRE habilitado
13919
+ // Merge default heatmap config with user config
13920
+ const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
13921
+ this.config.heatmapOptions = heatmapConfig;
13922
+ this.heatmapCollector = new HeatmapCollector(this.config, this.transport);
13923
+ this.heatmapCollector.start();
13924
+ // Initialize screenshot collector and capture page
13925
+ this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
13926
+ this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
13927
+ if (this.config.debug) {
13928
+ console.log('[DevSkin] Heatmap collection enabled (always on)');
13929
+ }
13930
+ // Initialize session recording with rrweb
13931
+ if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
13932
+ // Use RRWebRecorder for complete DOM recording
13933
+ this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
13934
+ enabled: true,
13935
+ sampleRate: this.config.sessionRecording.sampling || 0.5,
13936
+ blockClass: 'rr-block',
13937
+ ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
13938
+ maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
13939
+ ? this.config.sessionRecording.maskAllInputs
13940
+ : true,
13941
+ maskInputOptions: {
13942
+ password: true,
13943
+ email: true,
13944
+ tel: true,
13945
+ },
13946
+ recordCanvas: this.config.sessionRecording.recordCanvas || false,
13947
+ collectFonts: true,
13948
+ inlineStylesheet: true,
13949
+ checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
13950
+ checkoutEveryNth: 200, // Every 200 events
13951
+ }, (events) => {
13952
+ var _a;
13953
+ // Send rrweb events to backend
13954
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
13955
+ });
13956
+ // Start recording immediately (session already created)
13957
+ this.rrwebRecorder.start();
13977
13958
  if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
13978
13959
  console.log('[DevSkin] RRWeb recording started for session:', this.sessionId);
13979
13960
  }
13980
- }, 500);
13981
- }
13982
- // Track initial page view
13983
- this.trackPageView();
13961
+ }
13962
+ // Track initial page view
13963
+ this.trackPageView();
13964
+ }).catch((err) => {
13965
+ console.error('[DevSkin] Failed to create session:', err);
13966
+ });
13984
13967
  // Track page visibility changes
13985
13968
  this.setupVisibilityTracking();
13986
13969
  // Track page unload
@@ -14105,32 +14088,39 @@
14105
14088
  * Private methods
14106
14089
  */
14107
14090
  startSession() {
14108
- var _a, _b, _c;
14109
- // Check if there's an active session (stored in sessionStorage to persist across page navigations)
14110
- const existingSessionId = sessionStorage.getItem('devskin_session_id');
14111
- const existingSessionStart = sessionStorage.getItem('devskin_session_start');
14112
- if (existingSessionId && existingSessionStart) {
14113
- // Resume existing session
14114
- this.sessionId = existingSessionId;
14115
- this.sessionStartTime = parseInt(existingSessionStart, 10);
14116
- if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.debug) {
14117
- console.log('[DevSkin] Resuming existing session:', this.sessionId);
14091
+ return __awaiter$1(this, void 0, void 0, function* () {
14092
+ var _a, _b, _c, _d, _e;
14093
+ // Check if there's an active session (stored in sessionStorage to persist across page navigations)
14094
+ const existingSessionId = sessionStorage.getItem('devskin_session_id');
14095
+ const existingSessionStart = sessionStorage.getItem('devskin_session_start');
14096
+ if (existingSessionId && existingSessionStart) {
14097
+ // Resume existing session
14098
+ this.sessionId = existingSessionId;
14099
+ this.sessionStartTime = parseInt(existingSessionStart, 10);
14100
+ // Set sessionId in transport so it can be added to network/performance requests
14101
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.setSessionId(this.sessionId);
14102
+ if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
14103
+ console.log('[DevSkin] Resuming existing session:', this.sessionId);
14104
+ }
14105
+ // Send page view but DON'T create a new session
14106
+ // The session is already created, just continue it
14107
+ return;
14118
14108
  }
14119
- // Send page view but DON'T create a new session
14120
- // The session is already created, just continue it
14121
- return;
14122
- }
14123
- // Create new session
14124
- this.sessionId = this.generateId();
14125
- this.sessionStartTime = Date.now();
14126
- // Store in sessionStorage (persists across page navigations in same tab)
14127
- sessionStorage.setItem('devskin_session_id', this.sessionId);
14128
- sessionStorage.setItem('devskin_session_start', this.sessionStartTime.toString());
14129
- const sessionData = Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, startedAt: new Date().toISOString(), platform: 'web' }, this.getContextData());
14130
- (_b = this.transport) === null || _b === void 0 ? void 0 : _b.startSession(sessionData);
14131
- if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
14132
- console.log('[DevSkin] New session created:', this.sessionId);
14133
- }
14109
+ // Create new session
14110
+ this.sessionId = this.generateId();
14111
+ this.sessionStartTime = Date.now();
14112
+ // Store in sessionStorage (persists across page navigations in same tab)
14113
+ sessionStorage.setItem('devskin_session_id', this.sessionId);
14114
+ sessionStorage.setItem('devskin_session_start', this.sessionStartTime.toString());
14115
+ // Set sessionId in transport so it can be added to network/performance requests
14116
+ (_c = this.transport) === null || _c === void 0 ? void 0 : _c.setSessionId(this.sessionId);
14117
+ const sessionData = Object.assign({ sessionId: this.sessionId, userId: this.userId || undefined, anonymousId: this.anonymousId, startedAt: new Date().toISOString(), platform: 'web' }, this.getContextData());
14118
+ // CRITICAL: Await session creation to ensure it exists before sending metrics/requests
14119
+ yield ((_d = this.transport) === null || _d === void 0 ? void 0 : _d.startSession(sessionData));
14120
+ if ((_e = this.config) === null || _e === void 0 ? void 0 : _e.debug) {
14121
+ console.log('[DevSkin] New session created:', this.sessionId);
14122
+ }
14123
+ });
14134
14124
  }
14135
14125
  getContextData() {
14136
14126
  var _a, _b, _c, _d;