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