@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.
@@ -18,7 +18,7 @@ import {
18
18
  sdkWarn,
19
19
  setLogger,
20
20
  style_default
21
- } from "./chunk-EBJZDVC6.mjs";
21
+ } from "./chunk-AKVMUQXO.mjs";
22
22
 
23
23
  // src/react-native/index.tsx
24
24
  import { createContext, useContext, useEffect, useRef, useState } from "react";
@@ -870,7 +870,7 @@ function createReactNativeRenderer() {
870
870
  }
871
871
 
872
872
  // src/ui/preload.ts
873
- var defaultLoader = () => import("./renderPopup-E2OLIXIQ.mjs");
873
+ var defaultLoader = () => import("./renderPopup-LEM2MLWT.mjs");
874
874
  var pending = null;
875
875
  function preloadRenderPopup(loader = defaultLoader) {
876
876
  if (pending) return pending;
@@ -943,7 +943,7 @@ var BrowserPopupRenderer = class {
943
943
  if (!this.container) return;
944
944
  this.visible = true;
945
945
  const container = this.container;
946
- void import("./renderPopup-E2OLIXIQ.mjs").then(({ renderPopup }) => {
946
+ void import("./renderPopup-LEM2MLWT.mjs").then(({ renderPopup }) => {
947
947
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId, options);
948
948
  });
949
949
  }
@@ -1220,8 +1220,10 @@ async function safeBodyText(res) {
1220
1220
  function createFeedbackSink(options) {
1221
1221
  let feedbackSessionId;
1222
1222
  let firstPostInFlight = null;
1223
+ let generation = 0;
1223
1224
  const url = `${options.baseUrl}/sdk/feedback`;
1224
1225
  const post = async (body, final, sessionEnd = false) => {
1226
+ const postGeneration = generation;
1225
1227
  const json = JSON.stringify(body);
1226
1228
  const small = json.length <= KEEPALIVE_MAX_BYTES;
1227
1229
  if (final) options.log?.("[DeepdotsAnalytics] final flush via fetch keepalive");
@@ -1251,9 +1253,25 @@ function createFeedbackSink(options) {
1251
1253
  );
1252
1254
  return;
1253
1255
  }
1254
- if (sessionEnd) return;
1256
+ if (sessionEnd) {
1257
+ let closedId = body.sessionId;
1258
+ try {
1259
+ const data = await res.json();
1260
+ closedId = data?.sessionId ?? closedId;
1261
+ } catch {
1262
+ }
1263
+ try {
1264
+ if (closedId) options.onSessionClosed?.(closedId);
1265
+ } catch {
1266
+ }
1267
+ return;
1268
+ }
1255
1269
  try {
1256
1270
  const data = await res.json();
1271
+ if (postGeneration !== generation) {
1272
+ options.log?.("[DeepdotsAnalytics] sessionId of a closed session ignored:", data?.sessionId);
1273
+ return;
1274
+ }
1257
1275
  if (data?.sessionId && data.sessionId !== feedbackSessionId) {
1258
1276
  feedbackSessionId = data.sessionId;
1259
1277
  options.log?.("[DeepdotsAnalytics] feedbackSessionId cached:", feedbackSessionId);
@@ -1266,6 +1284,7 @@ function createFeedbackSink(options) {
1266
1284
  if (meta?.resetSession) {
1267
1285
  feedbackSessionId = void 0;
1268
1286
  firstPostInFlight = null;
1287
+ generation++;
1269
1288
  options.onSessionReset?.();
1270
1289
  return;
1271
1290
  }
@@ -1280,6 +1299,7 @@ function createFeedbackSink(options) {
1280
1299
  if (sessionEnd) {
1281
1300
  feedbackSessionId = void 0;
1282
1301
  firstPostInFlight = null;
1302
+ generation++;
1283
1303
  options.onSessionReset?.();
1284
1304
  } else if (!feedbackSessionId) {
1285
1305
  firstPostInFlight = sent.then(
@@ -1293,7 +1313,7 @@ function createFeedbackSink(options) {
1293
1313
 
1294
1314
  // src/tracking/navigation-observer.ts
1295
1315
  var UUID_PREFIX = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
1296
- function normalizeScreen(href) {
1316
+ function screenPath(href) {
1297
1317
  let pathAndHash;
1298
1318
  try {
1299
1319
  const u = new URL(href, "http://deepdots.local");
@@ -1301,7 +1321,10 @@ function normalizeScreen(href) {
1301
1321
  } catch {
1302
1322
  pathAndHash = href;
1303
1323
  }
1304
- pathAndHash = pathAndHash.replace(/\?[^#]*/g, "");
1324
+ return pathAndHash.replace(/\?[^#]*/g, "") || "/";
1325
+ }
1326
+ function normalizeScreen(href) {
1327
+ const pathAndHash = screenPath(href);
1305
1328
  const collapsed = pathAndHash.split("/").map((seg) => /^[0-9]+$/.test(seg) || UUID_PREFIX.test(seg) ? ":id" : seg).join("/");
1306
1329
  return collapsed || "/";
1307
1330
  }
@@ -1309,6 +1332,7 @@ var NavigationObserver = class {
1309
1332
  constructor(options = {}) {
1310
1333
  this.listeners = /* @__PURE__ */ new Set();
1311
1334
  this.currentScreen = null;
1335
+ this.currentPath = null;
1312
1336
  this.entryAt = 0;
1313
1337
  this.installed = false;
1314
1338
  this.domHandlers = [];
@@ -1320,20 +1344,23 @@ var NavigationObserver = class {
1320
1344
  /** Fija la pantalla inicial (sin emitir visita; su duración se cierra al salir). */
1321
1345
  begin(href) {
1322
1346
  this.currentScreen = normalizeScreen(href);
1347
+ this.currentPath = screenPath(href);
1323
1348
  this.entryAt = this.now();
1324
1349
  }
1325
1350
  /** Navega a una pantalla nueva: cierra la anterior (emite visita) y abre la nueva. */
1326
1351
  visit(href) {
1327
- const next = normalizeScreen(href);
1328
- if (next === this.currentScreen) return;
1352
+ const nextPath = screenPath(href);
1353
+ if (this.currentScreen != null && nextPath === this.currentPath) return;
1329
1354
  this.closeCurrent();
1330
- this.currentScreen = next;
1355
+ this.currentScreen = normalizeScreen(href);
1356
+ this.currentPath = nextPath;
1331
1357
  this.entryAt = this.now();
1332
1358
  }
1333
1359
  /** Cierra la pantalla actual emitiendo su visita (p. ej. al cerrar la app/pestaña). */
1334
1360
  stop() {
1335
1361
  this.closeCurrent();
1336
1362
  this.currentScreen = null;
1363
+ this.currentPath = null;
1337
1364
  }
1338
1365
  /**
1339
1366
  * Olvida la pantalla actual SIN emitir su visita. Para una sesión que el backend ya dio
@@ -1342,6 +1369,7 @@ var NavigationObserver = class {
1342
1369
  */
1343
1370
  discard() {
1344
1371
  this.currentScreen = null;
1372
+ this.currentPath = null;
1345
1373
  }
1346
1374
  /**
1347
1375
  * Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión
@@ -1357,6 +1385,7 @@ var NavigationObserver = class {
1357
1385
  const exit = this.now();
1358
1386
  const visit = {
1359
1387
  screen: this.currentScreen,
1388
+ path: this.currentPath ?? this.currentScreen,
1360
1389
  entry: this.entryAt,
1361
1390
  exit,
1362
1391
  durationSeconds: Math.max(0, Math.round((exit - this.entryAt) / 1e3))
@@ -1917,6 +1946,8 @@ var DeepdotsPopups = class {
1917
1946
  this.analytics = null;
1918
1947
  /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
1919
1948
  this.analyticsFeedbackSessionId = void 0;
1949
+ /** `init({ onFeedbackSession })`: avisa al host al abrir y cerrar cada registro. */
1950
+ this.onFeedbackSession = void 0;
1920
1951
  /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
1921
1952
  this.analyticsFlushTimer = void 0;
1922
1953
  /** Crash & error reporting (#14–17). Null hasta init(). */
@@ -1974,6 +2005,7 @@ var DeepdotsPopups = class {
1974
2005
  this.renderChrome = config.renderChrome ?? true;
1975
2006
  this.showProgressBar = config.showProgressBar;
1976
2007
  this.surveyCss = config.surveyCss;
2008
+ this.onFeedbackSession = config.onFeedbackSession;
1977
2009
  this.config = {
1978
2010
  apiKey: config.apiKey || void 0,
1979
2011
  debug: config.debug || false,
@@ -2007,7 +2039,9 @@ var DeepdotsPopups = class {
2007
2039
  log: (...a) => this.log(...a),
2008
2040
  onSessionId: (id) => {
2009
2041
  this.analyticsFeedbackSessionId = id;
2042
+ this.notifyFeedbackSession(id, "open");
2010
2043
  },
2044
+ onSessionClosed: (id) => this.notifyFeedbackSession(id, "closed"),
2011
2045
  onSessionReset: () => {
2012
2046
  this.analyticsFeedbackSessionId = void 0;
2013
2047
  }
@@ -2045,7 +2079,9 @@ var DeepdotsPopups = class {
2045
2079
  this.geoWanted = this.geoEnabled && !!config.analytics;
2046
2080
  this.resolveGeo();
2047
2081
  this.navObserver = new NavigationObserver();
2048
- this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
2082
+ this.navObserver.onVisit(
2083
+ (v) => this.track("deepdots_page_view", { screen: v.screen, path: v.path, duration_seconds: v.durationSeconds })
2084
+ );
2049
2085
  this.navObserver.install();
2050
2086
  this.engagement = new EngagementTracker();
2051
2087
  this.setupAnalyticsFlush();
@@ -2080,6 +2116,23 @@ var DeepdotsPopups = class {
2080
2116
  getSessionId() {
2081
2117
  return this.tracking?.getSessionId() ?? null;
2082
2118
  }
2119
+ /**
2120
+ * Id del registro de analytics abierto: el `sessionId` que devolvió `POST /sdk/feedback`,
2121
+ * que la API guarda como `sdkSessionId` en el Feedback en que se convierte la sesión. No es
2122
+ * `getSessionId()`. Null hasta que el backend acepta el primer lote y de nuevo al cerrar la
2123
+ * sesión: para saber también qué registro se cerró, usa `init({ onFeedbackSession })`.
2124
+ */
2125
+ getFeedbackSessionId() {
2126
+ return this.analyticsFeedbackSessionId ?? null;
2127
+ }
2128
+ /** Avisa al host del registro abierto o cerrado; un fallo del host no corta el envío. */
2129
+ notifyFeedbackSession(sessionId, status) {
2130
+ try {
2131
+ this.onFeedbackSession?.({ sessionId, status });
2132
+ } catch (error) {
2133
+ this.log("analytics \xB7 onFeedbackSession threw:", error);
2134
+ }
2135
+ }
2083
2136
  /** Activa/desactiva el tracking (identidad + sesión + analytics). Kill-switch del contrato §7bis. */
2084
2137
  setTrackingEnabled(enabled) {
2085
2138
  if (!enabled) this.closeSession("tracking_disabled");
@@ -2920,7 +2973,7 @@ var DeepdotsPopups = class {
2920
2973
  }
2921
2974
  if (!this.popupContainer) return;
2922
2975
  const container = this.popupContainer;
2923
- void import("./renderPopup-E2OLIXIQ.mjs").then(({ renderPopup }) => {
2976
+ void import("./renderPopup-LEM2MLWT.mjs").then(({ renderPopup }) => {
2924
2977
  renderPopup(
2925
2978
  container,
2926
2979
  surveyId,