@magicfeedback/popup-sdk 1.8.4 → 1.9.0

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.
package/dist/index.js CHANGED
@@ -583,7 +583,9 @@ var init_style = __esm({
583
583
  * repintar\xEDa su web.
584
584
  * 4. El grupo .magicfeedback-radio se cualifica como div.magicfeedback-radio (la clase acaba
585
585
  * tambi\xE9n en el <input> del boolean).
586
- * 5. Al final, un bloque de reglas propias del popup (fuente en los controles y padding del
586
+ * 5. Todo :hover va dentro de @media (hover: hover): en t\xE1ctil WebKit deja el hover pegado
587
+ * en el \xFAltimo punto tocado y la opci\xF3n de la p\xE1gina siguiente parec\xEDa seleccionada.
588
+ * 6. Al final, un bloque de reglas propias del popup (fuente en los controles y padding del
587
589
  * radio nativo).
588
590
  *
589
591
  * Las variables --mf-* se dejan en :root a prop\xF3sito: el modal del priority-list se portea a
@@ -821,9 +823,11 @@ var init_style = __esm({
821
823
  text-underline-offset: 2px;
822
824
  }
823
825
 
824
- .magicfeedback-info-message a:hover,
825
- .magicfeedback-start-message a:hover {
826
- color: var(--mf-primary-hover);
826
+ @media (hover: hover) {
827
+ .magicfeedback-info-message a:hover,
828
+ .magicfeedback-start-message a:hover {
829
+ color: var(--mf-primary-hover);
830
+ }
827
831
  }
828
832
 
829
833
  .magicfeedback-info-message ul,
@@ -1049,10 +1053,12 @@ div.magicfeedback-radio {
1049
1053
  transition: var(--mf-transition-fast);
1050
1054
  }
1051
1055
 
1052
- .magicfeedback-checkbox-container:hover,
1053
- .magicfeedback-radio-container:hover {
1054
- background-color: var(--mf-bg-hover);
1055
- border-color: var(--mf-primary);
1056
+ @media (hover: hover) {
1057
+ .magicfeedback-checkbox-container:hover,
1058
+ .magicfeedback-radio-container:hover {
1059
+ background-color: var(--mf-bg-hover);
1060
+ border-color: var(--mf-primary);
1061
+ }
1056
1062
  }
1057
1063
 
1058
1064
  .magicfeedback-checkbox-container:has(input:checked),
@@ -1119,9 +1125,11 @@ div.magicfeedback-radio {
1119
1125
  height: inherit;
1120
1126
  }
1121
1127
 
1122
- .magicfeedback-boolean-option label:hover {
1123
- background-color: var(--mf-bg-hover);
1124
- border-color: var(--mf-primary);
1128
+ @media (hover: hover) {
1129
+ .magicfeedback-boolean-option label:hover {
1130
+ background-color: var(--mf-bg-hover);
1131
+ border-color: var(--mf-primary);
1132
+ }
1125
1133
  }
1126
1134
 
1127
1135
  .magicfeedback-boolean-option input[type="radio"]:checked + label {
@@ -1192,9 +1200,11 @@ div.magicfeedback-radio {
1192
1200
  border: 2px solid transparent;
1193
1201
  }
1194
1202
 
1195
- .magicfeedback-rating-option-label-container img:hover {
1196
- transform: scale(1.1);
1197
- border-color: var(--mf-primary);
1203
+ @media (hover: hover) {
1204
+ .magicfeedback-rating-option-label-container img:hover {
1205
+ transform: scale(1.1);
1206
+ border-color: var(--mf-primary);
1207
+ }
1198
1208
  }
1199
1209
 
1200
1210
  .magicfeedback-rating-option-label-container input[type="radio"]:checked + img {
@@ -1346,9 +1356,11 @@ div.magicfeedback-radio {
1346
1356
  font-size: 0.8rem;
1347
1357
  }
1348
1358
 
1349
- .magicfeedback-rating-number-option-row .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,
1350
- .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {
1351
- background: var(--mf-primary-light);
1359
+ @media (hover: hover) {
1360
+ .magicfeedback-rating-number-option-row .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,
1361
+ .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {
1362
+ background: var(--mf-primary-light);
1363
+ }
1352
1364
  }
1353
1365
 
1354
1366
  .magicfeedback-rating-number-option-row .magicfeedback-rating-number-option-label-container input[type="radio"]:checked + .magicfeedback-rating-number-value,
@@ -1385,8 +1397,10 @@ div.magicfeedback-radio {
1385
1397
  font-size: var(--mf-font-size-sm);
1386
1398
  }
1387
1399
 
1388
- .magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {
1389
- background: var(--mf-primary-light);
1400
+ @media (hover: hover) {
1401
+ .magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {
1402
+ background: var(--mf-primary-light);
1403
+ }
1390
1404
  }
1391
1405
 
1392
1406
  .magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container input[type="radio"]:checked + .magicfeedback-rating-number-value {
@@ -1419,9 +1433,11 @@ div.magicfeedback-radio {
1419
1433
  color: var(--mf-primary);
1420
1434
  }
1421
1435
 
1422
- .magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,
1423
- .magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {
1424
- background: var(--mf-primary-light);
1436
+ @media (hover: hover) {
1437
+ .magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,
1438
+ .magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {
1439
+ background: var(--mf-primary-light);
1440
+ }
1425
1441
  }
1426
1442
 
1427
1443
  .magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container input[type="radio"]:checked + .magicfeedback-rating-number-value,
@@ -1475,10 +1491,12 @@ div.magicfeedback-radio {
1475
1491
  transition: var(--mf-transition-fast);
1476
1492
  }
1477
1493
 
1478
- .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover,
1479
- .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover ~ span {
1480
- color: #fbbf24;
1481
- transform: scale(1.1);
1494
+ @media (hover: hover) {
1495
+ .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover,
1496
+ .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover ~ span {
1497
+ color: #fbbf24;
1498
+ transform: scale(1.1);
1499
+ }
1482
1500
  }
1483
1501
 
1484
1502
  .magicfeedback-rating-star-selected {
@@ -1519,9 +1537,11 @@ div.magicfeedback-radio {
1519
1537
  transition: var(--mf-transition-fast);
1520
1538
  }
1521
1539
 
1522
- .magicfeedback-image-option-label-container:hover img {
1523
- border-color: var(--mf-primary);
1524
- box-shadow: var(--mf-shadow-md);
1540
+ @media (hover: hover) {
1541
+ .magicfeedback-image-option-label-container:hover img {
1542
+ border-color: var(--mf-primary);
1543
+ box-shadow: var(--mf-shadow-md);
1544
+ }
1525
1545
  }
1526
1546
 
1527
1547
  .magicfeedback-image-option-label-container input:checked + img {
@@ -1562,9 +1582,11 @@ div.magicfeedback-radio {
1562
1582
  background-color: var(--mf-bg-secondary);
1563
1583
  }
1564
1584
 
1565
- .magicfeedback-multi-question-matrix-row-tr:hover {
1566
- background-color: var(--mf-bg-hover);
1567
- box-shadow: var(--mf-shadow-sm);
1585
+ @media (hover: hover) {
1586
+ .magicfeedback-multi-question-matrix-row-tr:hover {
1587
+ background-color: var(--mf-bg-hover);
1588
+ box-shadow: var(--mf-shadow-sm);
1589
+ }
1568
1590
  }
1569
1591
 
1570
1592
  .magicfeedback-multi-question-matrix-table td {
@@ -1600,9 +1622,11 @@ div.magicfeedback-radio {
1600
1622
  transition: var(--mf-transition-fast);
1601
1623
  }
1602
1624
 
1603
- .magicfeedback-priority-list-item:hover {
1604
- box-shadow: var(--mf-shadow-sm);
1605
- border-color: var(--mf-primary);
1625
+ @media (hover: hover) {
1626
+ .magicfeedback-priority-list-item:hover {
1627
+ box-shadow: var(--mf-shadow-sm);
1628
+ border-color: var(--mf-primary);
1629
+ }
1606
1630
  }
1607
1631
 
1608
1632
  .magicfeedback-priority-list-item-label {
@@ -1623,10 +1647,12 @@ div.magicfeedback-radio {
1623
1647
  user-select: none;
1624
1648
  }
1625
1649
 
1626
- .magicfeedback-priority-list-arrow-up:hover,
1627
- .magicfeedback-priority-list-arrow-down:hover {
1628
- color: var(--mf-primary);
1629
- transform: scale(1.2);
1650
+ @media (hover: hover) {
1651
+ .magicfeedback-priority-list-arrow-up:hover,
1652
+ .magicfeedback-priority-list-arrow-down:hover {
1653
+ color: var(--mf-primary);
1654
+ transform: scale(1.2);
1655
+ }
1630
1656
  }
1631
1657
 
1632
1658
  /* ========================================
@@ -1733,10 +1759,12 @@ div.magicfeedback-radio {
1733
1759
  box-shadow: var(--mf-shadow-md);
1734
1760
  }
1735
1761
 
1736
- .magicfeedback-submit:hover {
1737
- background-color: var(--mf-primary-hover);
1738
- box-shadow: var(--mf-shadow-lg);
1739
- transform: translateY(-2px);
1762
+ @media (hover: hover) {
1763
+ .magicfeedback-submit:hover {
1764
+ background-color: var(--mf-primary-hover);
1765
+ box-shadow: var(--mf-shadow-lg);
1766
+ transform: translateY(-2px);
1767
+ }
1740
1768
  }
1741
1769
 
1742
1770
  .magicfeedback-submit:active {
@@ -1762,9 +1790,11 @@ div.magicfeedback-radio {
1762
1790
  transition: var(--mf-transition-fast);
1763
1791
  }
1764
1792
 
1765
- .magicfeedback-back:hover {
1766
- border-color: var(--mf-primary);
1767
- background-color: var(--mf-bg-hover);
1793
+ @media (hover: hover) {
1794
+ .magicfeedback-back:hover {
1795
+ border-color: var(--mf-primary);
1796
+ background-color: var(--mf-bg-hover);
1797
+ }
1768
1798
  }
1769
1799
 
1770
1800
  /* ========================================
@@ -1784,8 +1814,10 @@ div.magicfeedback-radio {
1784
1814
  transition: var(--mf-transition-fast);
1785
1815
  }
1786
1816
 
1787
- .magicfeedback-skip-container:hover {
1788
- color: var(--mf-primary);
1817
+ @media (hover: hover) {
1818
+ .magicfeedback-skip-container:hover {
1819
+ color: var(--mf-primary);
1820
+ }
1789
1821
  }
1790
1822
 
1791
1823
  .magicfeedback-skip-container input[type="checkbox"] {
@@ -1926,9 +1958,11 @@ div.magicfeedback-radio {
1926
1958
  box-shadow: var(--mf-shadow-sm);
1927
1959
  }
1928
1960
 
1929
- .magicfeedback-button:hover {
1930
- background: var(--mf-bg-hover);
1931
- border-color: var(--mf-primary);
1961
+ @media (hover: hover) {
1962
+ .magicfeedback-button:hover {
1963
+ background: var(--mf-bg-hover);
1964
+ border-color: var(--mf-primary);
1965
+ }
1932
1966
  }
1933
1967
 
1934
1968
  .magicfeedback-button:active {
@@ -1942,8 +1976,10 @@ div.magicfeedback-radio {
1942
1976
  box-shadow: var(--mf-shadow-md);
1943
1977
  }
1944
1978
 
1945
- .magicfeedback-button-primary:hover {
1946
- background: var(--mf-primary-hover);
1979
+ @media (hover: hover) {
1980
+ .magicfeedback-button-primary:hover {
1981
+ background: var(--mf-primary-hover);
1982
+ }
1947
1983
  }
1948
1984
 
1949
1985
  .magicfeedback-button-primary:active {
@@ -1969,8 +2005,10 @@ div.magicfeedback-radio {
1969
2005
  box-shadow: var(--mf-shadow-md);
1970
2006
  }
1971
2007
 
1972
- .magicfeedback-start-message-button:hover {
1973
- background: var(--mf-primary-hover);
2008
+ @media (hover: hover) {
2009
+ .magicfeedback-start-message-button:hover {
2010
+ background: var(--mf-primary-hover);
2011
+ }
1974
2012
  }
1975
2013
 
1976
2014
  /* ========================================
@@ -2106,12 +2144,18 @@ div.magicfeedback-radio {
2106
2144
  color: #666;
2107
2145
  }
2108
2146
 
2109
- .magicfeedback-modal-close:hover,
2110
2147
  .magicfeedback-modal-close:focus {
2111
2148
  color: #000;
2112
2149
  outline: none;
2113
2150
  }
2114
2151
 
2152
+ @media (hover: hover) {
2153
+ .magicfeedback-modal-close:hover {
2154
+ color: #000;
2155
+ outline: none;
2156
+ }
2157
+ }
2158
+
2115
2159
  /* ========================================
2116
2160
  FILE / IMAGE UPLOAD
2117
2161
  ======================================== */
@@ -2155,9 +2199,11 @@ div.magicfeedback-radio {
2155
2199
  transition: var(--mf-transition);
2156
2200
  }
2157
2201
 
2158
- .magicfeedback-upload-dropzone:hover {
2159
- border-color: var(--mf-primary);
2160
- background-color: var(--mf-bg-hover);
2202
+ @media (hover: hover) {
2203
+ .magicfeedback-upload-dropzone:hover {
2204
+ border-color: var(--mf-primary);
2205
+ background-color: var(--mf-bg-hover);
2206
+ }
2161
2207
  }
2162
2208
 
2163
2209
  .magicfeedback-upload-dropzone:focus-within {
@@ -2295,13 +2341,20 @@ div.magicfeedback-radio {
2295
2341
  transition: var(--mf-transition-fast);
2296
2342
  }
2297
2343
 
2298
- .magicfeedback-upload-remove:hover,
2299
2344
  .magicfeedback-upload-remove:focus-visible {
2300
2345
  color: var(--mf-error);
2301
2346
  background-color: var(--mf-primary-light);
2302
2347
  outline: none;
2303
2348
  }
2304
2349
 
2350
+ @media (hover: hover) {
2351
+ .magicfeedback-upload-remove:hover {
2352
+ color: var(--mf-error);
2353
+ background-color: var(--mf-primary-light);
2354
+ outline: none;
2355
+ }
2356
+ }
2357
+
2305
2358
  .magicfeedback-upload-remove svg {
2306
2359
  width: 16px;
2307
2360
  height: 16px;
@@ -6816,8 +6869,10 @@ async function safeBodyText(res) {
6816
6869
  function createFeedbackSink(options) {
6817
6870
  let feedbackSessionId;
6818
6871
  let firstPostInFlight = null;
6872
+ let generation = 0;
6819
6873
  const url = `${options.baseUrl}/sdk/feedback`;
6820
6874
  const post = async (body, final, sessionEnd = false) => {
6875
+ const postGeneration = generation;
6821
6876
  const json = JSON.stringify(body);
6822
6877
  const small = json.length <= KEEPALIVE_MAX_BYTES;
6823
6878
  if (final) options.log?.("[DeepdotsAnalytics] final flush via fetch keepalive");
@@ -6847,9 +6902,25 @@ function createFeedbackSink(options) {
6847
6902
  );
6848
6903
  return;
6849
6904
  }
6850
- if (sessionEnd) return;
6905
+ if (sessionEnd) {
6906
+ let closedId = body.sessionId;
6907
+ try {
6908
+ const data = await res.json();
6909
+ closedId = data?.sessionId ?? closedId;
6910
+ } catch {
6911
+ }
6912
+ try {
6913
+ if (closedId) options.onSessionClosed?.(closedId);
6914
+ } catch {
6915
+ }
6916
+ return;
6917
+ }
6851
6918
  try {
6852
6919
  const data = await res.json();
6920
+ if (postGeneration !== generation) {
6921
+ options.log?.("[DeepdotsAnalytics] sessionId of a closed session ignored:", data?.sessionId);
6922
+ return;
6923
+ }
6853
6924
  if (data?.sessionId && data.sessionId !== feedbackSessionId) {
6854
6925
  feedbackSessionId = data.sessionId;
6855
6926
  options.log?.("[DeepdotsAnalytics] feedbackSessionId cached:", feedbackSessionId);
@@ -6862,6 +6933,7 @@ function createFeedbackSink(options) {
6862
6933
  if (meta?.resetSession) {
6863
6934
  feedbackSessionId = void 0;
6864
6935
  firstPostInFlight = null;
6936
+ generation++;
6865
6937
  options.onSessionReset?.();
6866
6938
  return;
6867
6939
  }
@@ -6876,6 +6948,7 @@ function createFeedbackSink(options) {
6876
6948
  if (sessionEnd) {
6877
6949
  feedbackSessionId = void 0;
6878
6950
  firstPostInFlight = null;
6951
+ generation++;
6879
6952
  options.onSessionReset?.();
6880
6953
  } else if (!feedbackSessionId) {
6881
6954
  firstPostInFlight = sent.then(
@@ -6889,7 +6962,7 @@ function createFeedbackSink(options) {
6889
6962
 
6890
6963
  // src/tracking/navigation-observer.ts
6891
6964
  var UUID_PREFIX = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
6892
- function normalizeScreen(href) {
6965
+ function screenPath(href) {
6893
6966
  let pathAndHash;
6894
6967
  try {
6895
6968
  const u = new URL(href, "http://deepdots.local");
@@ -6897,7 +6970,10 @@ function normalizeScreen(href) {
6897
6970
  } catch {
6898
6971
  pathAndHash = href;
6899
6972
  }
6900
- pathAndHash = pathAndHash.replace(/\?[^#]*/g, "");
6973
+ return pathAndHash.replace(/\?[^#]*/g, "") || "/";
6974
+ }
6975
+ function normalizeScreen(href) {
6976
+ const pathAndHash = screenPath(href);
6901
6977
  const collapsed = pathAndHash.split("/").map((seg) => /^[0-9]+$/.test(seg) || UUID_PREFIX.test(seg) ? ":id" : seg).join("/");
6902
6978
  return collapsed || "/";
6903
6979
  }
@@ -6905,6 +6981,7 @@ var NavigationObserver = class {
6905
6981
  constructor(options = {}) {
6906
6982
  this.listeners = /* @__PURE__ */ new Set();
6907
6983
  this.currentScreen = null;
6984
+ this.currentPath = null;
6908
6985
  this.entryAt = 0;
6909
6986
  this.installed = false;
6910
6987
  this.domHandlers = [];
@@ -6916,20 +6993,23 @@ var NavigationObserver = class {
6916
6993
  /** Fija la pantalla inicial (sin emitir visita; su duración se cierra al salir). */
6917
6994
  begin(href) {
6918
6995
  this.currentScreen = normalizeScreen(href);
6996
+ this.currentPath = screenPath(href);
6919
6997
  this.entryAt = this.now();
6920
6998
  }
6921
6999
  /** Navega a una pantalla nueva: cierra la anterior (emite visita) y abre la nueva. */
6922
7000
  visit(href) {
6923
- const next = normalizeScreen(href);
6924
- if (next === this.currentScreen) return;
7001
+ const nextPath = screenPath(href);
7002
+ if (this.currentScreen != null && nextPath === this.currentPath) return;
6925
7003
  this.closeCurrent();
6926
- this.currentScreen = next;
7004
+ this.currentScreen = normalizeScreen(href);
7005
+ this.currentPath = nextPath;
6927
7006
  this.entryAt = this.now();
6928
7007
  }
6929
7008
  /** Cierra la pantalla actual emitiendo su visita (p. ej. al cerrar la app/pestaña). */
6930
7009
  stop() {
6931
7010
  this.closeCurrent();
6932
7011
  this.currentScreen = null;
7012
+ this.currentPath = null;
6933
7013
  }
6934
7014
  /**
6935
7015
  * Olvida la pantalla actual SIN emitir su visita. Para una sesión que el backend ya dio
@@ -6938,6 +7018,7 @@ var NavigationObserver = class {
6938
7018
  */
6939
7019
  discard() {
6940
7020
  this.currentScreen = null;
7021
+ this.currentPath = null;
6941
7022
  }
6942
7023
  /**
6943
7024
  * Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión
@@ -6953,6 +7034,7 @@ var NavigationObserver = class {
6953
7034
  const exit = this.now();
6954
7035
  const visit = {
6955
7036
  screen: this.currentScreen,
7037
+ path: this.currentPath ?? this.currentScreen,
6956
7038
  entry: this.entryAt,
6957
7039
  exit,
6958
7040
  durationSeconds: Math.max(0, Math.round((exit - this.entryAt) / 1e3))
@@ -7517,6 +7599,8 @@ var DeepdotsPopups = class {
7517
7599
  this.analytics = null;
7518
7600
  /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
7519
7601
  this.analyticsFeedbackSessionId = void 0;
7602
+ /** `init({ onFeedbackSession })`: avisa al host al abrir y cerrar cada registro. */
7603
+ this.onFeedbackSession = void 0;
7520
7604
  /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
7521
7605
  this.analyticsFlushTimer = void 0;
7522
7606
  /** Crash & error reporting (#14–17). Null hasta init(). */
@@ -7574,6 +7658,7 @@ var DeepdotsPopups = class {
7574
7658
  this.renderChrome = config.renderChrome ?? true;
7575
7659
  this.showProgressBar = config.showProgressBar;
7576
7660
  this.surveyCss = config.surveyCss;
7661
+ this.onFeedbackSession = config.onFeedbackSession;
7577
7662
  this.config = {
7578
7663
  apiKey: config.apiKey || void 0,
7579
7664
  debug: config.debug || false,
@@ -7607,7 +7692,9 @@ var DeepdotsPopups = class {
7607
7692
  log: (...a) => this.log(...a),
7608
7693
  onSessionId: (id) => {
7609
7694
  this.analyticsFeedbackSessionId = id;
7695
+ this.notifyFeedbackSession(id, "open");
7610
7696
  },
7697
+ onSessionClosed: (id) => this.notifyFeedbackSession(id, "closed"),
7611
7698
  onSessionReset: () => {
7612
7699
  this.analyticsFeedbackSessionId = void 0;
7613
7700
  }
@@ -7645,7 +7732,9 @@ var DeepdotsPopups = class {
7645
7732
  this.geoWanted = this.geoEnabled && !!config.analytics;
7646
7733
  this.resolveGeo();
7647
7734
  this.navObserver = new NavigationObserver();
7648
- this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
7735
+ this.navObserver.onVisit(
7736
+ (v) => this.track("deepdots_page_view", { screen: v.screen, path: v.path, duration_seconds: v.durationSeconds })
7737
+ );
7649
7738
  this.navObserver.install();
7650
7739
  this.engagement = new EngagementTracker();
7651
7740
  this.setupAnalyticsFlush();
@@ -7680,6 +7769,23 @@ var DeepdotsPopups = class {
7680
7769
  getSessionId() {
7681
7770
  return this.tracking?.getSessionId() ?? null;
7682
7771
  }
7772
+ /**
7773
+ * Id del registro de analytics abierto: el `sessionId` que devolvió `POST /sdk/feedback`,
7774
+ * que la API guarda como `sdkSessionId` en el Feedback en que se convierte la sesión. No es
7775
+ * `getSessionId()`. Null hasta que el backend acepta el primer lote y de nuevo al cerrar la
7776
+ * sesión: para saber también qué registro se cerró, usa `init({ onFeedbackSession })`.
7777
+ */
7778
+ getFeedbackSessionId() {
7779
+ return this.analyticsFeedbackSessionId ?? null;
7780
+ }
7781
+ /** Avisa al host del registro abierto o cerrado; un fallo del host no corta el envío. */
7782
+ notifyFeedbackSession(sessionId, status) {
7783
+ try {
7784
+ this.onFeedbackSession?.({ sessionId, status });
7785
+ } catch (error) {
7786
+ this.log("analytics \xB7 onFeedbackSession threw:", error);
7787
+ }
7788
+ }
7683
7789
  /** Activa/desactiva el tracking (identidad + sesión + analytics). Kill-switch del contrato §7bis. */
7684
7790
  setTrackingEnabled(enabled) {
7685
7791
  if (!enabled) this.closeSession("tracking_disabled");