@magicfeedback/popup-sdk 1.11.0 → 1.13.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.
@@ -1449,6 +1449,23 @@ var NavigationObserver = class {
1449
1449
  if (!this.installed || this.currentScreen != null || typeof window === "undefined") return;
1450
1450
  this.begin(window.location.href);
1451
1451
  }
1452
+ /**
1453
+ * La pantalla en curso, sin cerrarla ni emitir nada; null si no hay (antes de `begin`, tras
1454
+ * `stop`/`discard`). Para que el `user_engagement` lleve la pantalla: si el cierre de la
1455
+ * sesión no llega nunca (pestaña dormida y descartada, app matada), la última pantalla no
1456
+ * tiene `page_view` y solo queda esto.
1457
+ */
1458
+ current() {
1459
+ if (this.currentScreen == null) return null;
1460
+ return {
1461
+ screen: this.currentScreen,
1462
+ path: this.currentPath ?? this.currentScreen,
1463
+ seconds: this.secondsSinceEntry(this.now())
1464
+ };
1465
+ }
1466
+ secondsSinceEntry(at) {
1467
+ return Math.max(0, Math.round((at - this.entryAt) / 1e3));
1468
+ }
1452
1469
  closeCurrent() {
1453
1470
  if (this.currentScreen == null) return;
1454
1471
  const exit = this.now();
@@ -1457,7 +1474,7 @@ var NavigationObserver = class {
1457
1474
  path: this.currentPath ?? this.currentScreen,
1458
1475
  entry: this.entryAt,
1459
1476
  exit,
1460
- durationSeconds: Math.max(0, Math.round((exit - this.entryAt) / 1e3))
1477
+ durationSeconds: this.secondsSinceEntry(exit)
1461
1478
  };
1462
1479
  this.listeners.forEach((l) => {
1463
1480
  try {
@@ -2178,7 +2195,7 @@ var DeepdotsPopups = class {
2178
2195
  this.navObserver.install();
2179
2196
  this.engagement = new EngagementTracker();
2180
2197
  this.setupAnalyticsFlush();
2181
- this.openSession();
2198
+ this.openSession("cold_start");
2182
2199
  if (resumable && this.analyticsFeedbackSessionId === resumable.sessionId) {
2183
2200
  this.notifyFeedbackSession(resumable.sessionId, "open");
2184
2201
  }
@@ -2236,7 +2253,7 @@ var DeepdotsPopups = class {
2236
2253
  setTrackingEnabled(enabled) {
2237
2254
  if (!enabled) this.closeSession("tracking_disabled");
2238
2255
  this.tracking?.setTrackingEnabled(enabled);
2239
- if (enabled) this.openSession();
2256
+ if (enabled) this.openSession("tracking_enabled");
2240
2257
  if (enabled) this.resolveGeo();
2241
2258
  this.log("tracking \xB7 setTrackingEnabled:", enabled, "\xB7 session_id:", this.tracking?.getSessionId() ?? null);
2242
2259
  }
@@ -2353,7 +2370,7 @@ var DeepdotsPopups = class {
2353
2370
  /** App a foreground (RN: AppState 'active'): reanuda el engagement time y abre sesión nueva. */
2354
2371
  onForeground() {
2355
2372
  this.engagement?.resume();
2356
- this.openSession();
2373
+ this.openSession("foreground");
2357
2374
  }
2358
2375
  /**
2359
2376
  * App a background (RN: AppState 'background'): FIN DE SESIÓN. Cierra pantalla actual
@@ -2401,7 +2418,7 @@ var DeepdotsPopups = class {
2401
2418
  this.answeredSurveys.clear();
2402
2419
  clearPopupState(storage);
2403
2420
  this.log("tracking \xB7 user_change \xB7 user_id:", this.tracking.getUserId());
2404
- this.openSession();
2421
+ this.openSession("user_change");
2405
2422
  }
2406
2423
  /**
2407
2424
  * Cierra la sesión actual: vuelca todo lo que quedaba abierto y envía el último lote
@@ -2427,17 +2444,24 @@ var DeepdotsPopups = class {
2427
2444
  this.log("tracking \xB7 session_end:", reason);
2428
2445
  }
2429
2446
  /**
2430
- * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente:
2431
- * se llama en init(), al volver a foreground y al conceder el consentimiento.
2447
+ * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente.
2448
+ * El `reason` dice por qué empieza (ver `SessionStartReason`):
2449
+ * - `cold_start`: init() (carga de la página / arranque de la app RN).
2450
+ * - `foreground`: onForeground() tras un cierre (RN).
2451
+ * - `tracking_enabled`: setTrackingEnabled(true).
2452
+ * - `user_change`: setUserId() o init() con otro userId.
2453
+ * - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta.
2454
+ * - `bfcache_restore`: vuelta desde la bfcache fuera de la ventana de reanudación.
2455
+ * Si hay una sesión suspendida a tiempo emite `deepdots_session_resume` en su lugar, sin reason.
2432
2456
  */
2433
- openSession() {
2457
+ openSession(reason) {
2434
2458
  if (this.sessionOpen) return;
2435
2459
  if (!this.tracking?.isTrackingEnabled()) return;
2436
2460
  const suspendedMs = this.takeSuspendedSession();
2437
2461
  this.sessionOpen = true;
2438
2462
  this.lastEngagementAt = Date.now();
2439
2463
  if (suspendedMs === null) {
2440
- this.track("deepdots_session_start", {});
2464
+ this.track("deepdots_session_start", { reason });
2441
2465
  } else {
2442
2466
  this.track("deepdots_session_resume", { suspended_ms: suspendedMs });
2443
2467
  this.log("tracking \xB7 session resumed after", suspendedMs, "ms:", this.analyticsFeedbackSessionId);
@@ -2467,12 +2491,29 @@ var DeepdotsPopups = class {
2467
2491
  if (!this.tracking?.isTrackingEnabled()) return;
2468
2492
  this.analytics?.flush(this.analyticsIdentity(), options);
2469
2493
  }
2470
- /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
2494
+ /**
2495
+ * Emite un evento `user_engagement` con el tiempo activo acumulado (#8) y la pantalla en
2496
+ * curso: `screen` y `path` (los mismos que llevará su `page_view`) y `screen_seconds` (lo
2497
+ * que lleva en ella, medido como el `duration_seconds` del `page_view`).
2498
+ *
2499
+ * El `page_view` sale al SALIR de la pantalla; si el cierre no llega nunca (pestaña dormida
2500
+ * y descartada al volver, pestaña o app matada en segundo plano), la última pantalla de la
2501
+ * sesión se perdía y una visita de una sola pantalla se quedaba sin ninguna. Con la pantalla
2502
+ * en el latido y en el engagement de ocultar la pestaña, el backend la reconstruye.
2503
+ *
2504
+ * En los cierres (`closeSession`, `suspendSession`) el observador ya emitió el `page_view`
2505
+ * y no tiene pantalla, así que ese engagement va sin `screen`: nunca se cuenta dos veces.
2506
+ * Tampoco se emite un `page_view` aquí, por lo mismo.
2507
+ */
2471
2508
  flushEngagement() {
2472
2509
  if (!this.tracking?.isTrackingEnabled()) return;
2473
2510
  const ms = this.engagement?.consume() ?? 0;
2474
2511
  if (ms > 0) {
2475
- this.track("deepdots_user_engagement", { engagement_time_msec: ms });
2512
+ const current = this.navObserver?.current();
2513
+ this.track("deepdots_user_engagement", {
2514
+ engagement_time_msec: ms,
2515
+ ...current ? { screen: current.screen, path: current.path, screen_seconds: current.seconds } : {}
2516
+ });
2476
2517
  this.lastEngagementAt = Date.now();
2477
2518
  }
2478
2519
  }
@@ -2504,7 +2545,7 @@ var DeepdotsPopups = class {
2504
2545
  } else {
2505
2546
  this.discardSession();
2506
2547
  }
2507
- this.openSession();
2548
+ this.openSession("idle_timeout");
2508
2549
  }
2509
2550
  /**
2510
2551
  * `pagehide` en web: SUSPENDE la sesión en vez de cerrarla, porque el navegador lo lanza
@@ -2628,7 +2669,7 @@ var DeepdotsPopups = class {
2628
2669
  window.addEventListener("pageshow", (event) => {
2629
2670
  if (!event.persisted) return;
2630
2671
  this.startAnalyticsFlushTimer();
2631
- this.openSession();
2672
+ this.openSession("bfcache_restore");
2632
2673
  });
2633
2674
  document.addEventListener("visibilitychange", () => {
2634
2675
  if (document.visibilityState === "hidden") {