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