@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.
@@ -571,8 +571,15 @@ declare class DeepdotsPopups {
571
571
  */
572
572
  private closeSession;
573
573
  /**
574
- * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente:
575
- * se llama en init(), al volver a foreground y al conceder el consentimiento.
574
+ * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente.
575
+ * El `reason` dice por qué empieza (ver `SessionStartReason`):
576
+ * - `cold_start`: init() (carga de la página / arranque de la app RN).
577
+ * - `foreground`: onForeground() tras un cierre (RN).
578
+ * - `tracking_enabled`: setTrackingEnabled(true).
579
+ * - `user_change`: setUserId() o init() con otro userId.
580
+ * - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta.
581
+ * - `bfcache_restore`: vuelta desde la bfcache fuera de la ventana de reanudación.
582
+ * Si hay una sesión suspendida a tiempo emite `deepdots_session_resume` en su lugar, sin reason.
576
583
  */
577
584
  private openSession;
578
585
  /** Web: si la pestaña se ve. Sin `document` (RN) manda AppState vía onForeground/onBackground. */
@@ -588,7 +595,20 @@ declare class DeepdotsPopups {
588
595
  final?: boolean;
589
596
  sessionEnd?: boolean;
590
597
  }): void;
591
- /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
598
+ /**
599
+ * Emite un evento `user_engagement` con el tiempo activo acumulado (#8) y la pantalla en
600
+ * curso: `screen` y `path` (los mismos que llevará su `page_view`) y `screen_seconds` (lo
601
+ * que lleva en ella, medido como el `duration_seconds` del `page_view`).
602
+ *
603
+ * El `page_view` sale al SALIR de la pantalla; si el cierre no llega nunca (pestaña dormida
604
+ * y descartada al volver, pestaña o app matada en segundo plano), la última pantalla de la
605
+ * sesión se perdía y una visita de una sola pantalla se quedaba sin ninguna. Con la pantalla
606
+ * en el latido y en el engagement de ocultar la pestaña, el backend la reconstruye.
607
+ *
608
+ * En los cierres (`closeSession`, `suspendSession`) el observador ya emitió el `page_view`
609
+ * y no tiene pantalla, así que ese engagement va sin `screen`: nunca se cuenta dos veces.
610
+ * Tampoco se emite un `page_view` aquí, por lo mismo.
611
+ */
592
612
  private flushEngagement;
593
613
  /** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
594
614
  private onAnalyticsFlushTick;
@@ -571,8 +571,15 @@ declare class DeepdotsPopups {
571
571
  */
572
572
  private closeSession;
573
573
  /**
574
- * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente:
575
- * se llama en init(), al volver a foreground y al conceder el consentimiento.
574
+ * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente.
575
+ * El `reason` dice por qué empieza (ver `SessionStartReason`):
576
+ * - `cold_start`: init() (carga de la página / arranque de la app RN).
577
+ * - `foreground`: onForeground() tras un cierre (RN).
578
+ * - `tracking_enabled`: setTrackingEnabled(true).
579
+ * - `user_change`: setUserId() o init() con otro userId.
580
+ * - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta.
581
+ * - `bfcache_restore`: vuelta desde la bfcache fuera de la ventana de reanudación.
582
+ * Si hay una sesión suspendida a tiempo emite `deepdots_session_resume` en su lugar, sin reason.
576
583
  */
577
584
  private openSession;
578
585
  /** Web: si la pestaña se ve. Sin `document` (RN) manda AppState vía onForeground/onBackground. */
@@ -588,7 +595,20 @@ declare class DeepdotsPopups {
588
595
  final?: boolean;
589
596
  sessionEnd?: boolean;
590
597
  }): void;
591
- /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
598
+ /**
599
+ * Emite un evento `user_engagement` con el tiempo activo acumulado (#8) y la pantalla en
600
+ * curso: `screen` y `path` (los mismos que llevará su `page_view`) y `screen_seconds` (lo
601
+ * que lleva en ella, medido como el `duration_seconds` del `page_view`).
602
+ *
603
+ * El `page_view` sale al SALIR de la pantalla; si el cierre no llega nunca (pestaña dormida
604
+ * y descartada al volver, pestaña o app matada en segundo plano), la última pantalla de la
605
+ * sesión se perdía y una visita de una sola pantalla se quedaba sin ninguna. Con la pantalla
606
+ * en el latido y en el engagement de ocultar la pestaña, el backend la reconstruye.
607
+ *
608
+ * En los cierres (`closeSession`, `suspendSession`) el observador ya emitió el `page_view`
609
+ * y no tiene pantalla, así que ese engagement va sin `screen`: nunca se cuenta dos veces.
610
+ * Tampoco se emite un `page_view` aquí, por lo mismo.
611
+ */
592
612
  private flushEngagement;
593
613
  /** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
594
614
  private onAnalyticsFlushTick;
@@ -4979,6 +4979,23 @@ var NavigationObserver = class {
4979
4979
  if (!this.installed || this.currentScreen != null || typeof window === "undefined") return;
4980
4980
  this.begin(window.location.href);
4981
4981
  }
4982
+ /**
4983
+ * La pantalla en curso, sin cerrarla ni emitir nada; null si no hay (antes de `begin`, tras
4984
+ * `stop`/`discard`). Para que el `user_engagement` lleve la pantalla: si el cierre de la
4985
+ * sesión no llega nunca (pestaña dormida y descartada, app matada), la última pantalla no
4986
+ * tiene `page_view` y solo queda esto.
4987
+ */
4988
+ current() {
4989
+ if (this.currentScreen == null) return null;
4990
+ return {
4991
+ screen: this.currentScreen,
4992
+ path: this.currentPath ?? this.currentScreen,
4993
+ seconds: this.secondsSinceEntry(this.now())
4994
+ };
4995
+ }
4996
+ secondsSinceEntry(at) {
4997
+ return Math.max(0, Math.round((at - this.entryAt) / 1e3));
4998
+ }
4982
4999
  closeCurrent() {
4983
5000
  if (this.currentScreen == null) return;
4984
5001
  const exit = this.now();
@@ -4987,7 +5004,7 @@ var NavigationObserver = class {
4987
5004
  path: this.currentPath ?? this.currentScreen,
4988
5005
  entry: this.entryAt,
4989
5006
  exit,
4990
- durationSeconds: Math.max(0, Math.round((exit - this.entryAt) / 1e3))
5007
+ durationSeconds: this.secondsSinceEntry(exit)
4991
5008
  };
4992
5009
  this.listeners.forEach((l) => {
4993
5010
  try {
@@ -5712,7 +5729,7 @@ var DeepdotsPopups = class {
5712
5729
  this.navObserver.install();
5713
5730
  this.engagement = new EngagementTracker();
5714
5731
  this.setupAnalyticsFlush();
5715
- this.openSession();
5732
+ this.openSession("cold_start");
5716
5733
  if (resumable && this.analyticsFeedbackSessionId === resumable.sessionId) {
5717
5734
  this.notifyFeedbackSession(resumable.sessionId, "open");
5718
5735
  }
@@ -5770,7 +5787,7 @@ var DeepdotsPopups = class {
5770
5787
  setTrackingEnabled(enabled) {
5771
5788
  if (!enabled) this.closeSession("tracking_disabled");
5772
5789
  this.tracking?.setTrackingEnabled(enabled);
5773
- if (enabled) this.openSession();
5790
+ if (enabled) this.openSession("tracking_enabled");
5774
5791
  if (enabled) this.resolveGeo();
5775
5792
  this.log("tracking \xB7 setTrackingEnabled:", enabled, "\xB7 session_id:", this.tracking?.getSessionId() ?? null);
5776
5793
  }
@@ -5887,7 +5904,7 @@ var DeepdotsPopups = class {
5887
5904
  /** App a foreground (RN: AppState 'active'): reanuda el engagement time y abre sesión nueva. */
5888
5905
  onForeground() {
5889
5906
  this.engagement?.resume();
5890
- this.openSession();
5907
+ this.openSession("foreground");
5891
5908
  }
5892
5909
  /**
5893
5910
  * App a background (RN: AppState 'background'): FIN DE SESIÓN. Cierra pantalla actual
@@ -5935,7 +5952,7 @@ var DeepdotsPopups = class {
5935
5952
  this.answeredSurveys.clear();
5936
5953
  clearPopupState(storage);
5937
5954
  this.log("tracking \xB7 user_change \xB7 user_id:", this.tracking.getUserId());
5938
- this.openSession();
5955
+ this.openSession("user_change");
5939
5956
  }
5940
5957
  /**
5941
5958
  * Cierra la sesión actual: vuelca todo lo que quedaba abierto y envía el último lote
@@ -5961,17 +5978,24 @@ var DeepdotsPopups = class {
5961
5978
  this.log("tracking \xB7 session_end:", reason);
5962
5979
  }
5963
5980
  /**
5964
- * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente:
5965
- * se llama en init(), al volver a foreground y al conceder el consentimiento.
5981
+ * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente.
5982
+ * El `reason` dice por qué empieza (ver `SessionStartReason`):
5983
+ * - `cold_start`: init() (carga de la página / arranque de la app RN).
5984
+ * - `foreground`: onForeground() tras un cierre (RN).
5985
+ * - `tracking_enabled`: setTrackingEnabled(true).
5986
+ * - `user_change`: setUserId() o init() con otro userId.
5987
+ * - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta.
5988
+ * - `bfcache_restore`: vuelta desde la bfcache fuera de la ventana de reanudación.
5989
+ * Si hay una sesión suspendida a tiempo emite `deepdots_session_resume` en su lugar, sin reason.
5966
5990
  */
5967
- openSession() {
5991
+ openSession(reason) {
5968
5992
  if (this.sessionOpen) return;
5969
5993
  if (!this.tracking?.isTrackingEnabled()) return;
5970
5994
  const suspendedMs = this.takeSuspendedSession();
5971
5995
  this.sessionOpen = true;
5972
5996
  this.lastEngagementAt = Date.now();
5973
5997
  if (suspendedMs === null) {
5974
- this.track("deepdots_session_start", {});
5998
+ this.track("deepdots_session_start", { reason });
5975
5999
  } else {
5976
6000
  this.track("deepdots_session_resume", { suspended_ms: suspendedMs });
5977
6001
  this.log("tracking \xB7 session resumed after", suspendedMs, "ms:", this.analyticsFeedbackSessionId);
@@ -6001,12 +6025,29 @@ var DeepdotsPopups = class {
6001
6025
  if (!this.tracking?.isTrackingEnabled()) return;
6002
6026
  this.analytics?.flush(this.analyticsIdentity(), options);
6003
6027
  }
6004
- /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
6028
+ /**
6029
+ * Emite un evento `user_engagement` con el tiempo activo acumulado (#8) y la pantalla en
6030
+ * curso: `screen` y `path` (los mismos que llevará su `page_view`) y `screen_seconds` (lo
6031
+ * que lleva en ella, medido como el `duration_seconds` del `page_view`).
6032
+ *
6033
+ * El `page_view` sale al SALIR de la pantalla; si el cierre no llega nunca (pestaña dormida
6034
+ * y descartada al volver, pestaña o app matada en segundo plano), la última pantalla de la
6035
+ * sesión se perdía y una visita de una sola pantalla se quedaba sin ninguna. Con la pantalla
6036
+ * en el latido y en el engagement de ocultar la pestaña, el backend la reconstruye.
6037
+ *
6038
+ * En los cierres (`closeSession`, `suspendSession`) el observador ya emitió el `page_view`
6039
+ * y no tiene pantalla, así que ese engagement va sin `screen`: nunca se cuenta dos veces.
6040
+ * Tampoco se emite un `page_view` aquí, por lo mismo.
6041
+ */
6005
6042
  flushEngagement() {
6006
6043
  if (!this.tracking?.isTrackingEnabled()) return;
6007
6044
  const ms = this.engagement?.consume() ?? 0;
6008
6045
  if (ms > 0) {
6009
- this.track("deepdots_user_engagement", { engagement_time_msec: ms });
6046
+ const current = this.navObserver?.current();
6047
+ this.track("deepdots_user_engagement", {
6048
+ engagement_time_msec: ms,
6049
+ ...current ? { screen: current.screen, path: current.path, screen_seconds: current.seconds } : {}
6050
+ });
6010
6051
  this.lastEngagementAt = Date.now();
6011
6052
  }
6012
6053
  }
@@ -6038,7 +6079,7 @@ var DeepdotsPopups = class {
6038
6079
  } else {
6039
6080
  this.discardSession();
6040
6081
  }
6041
- this.openSession();
6082
+ this.openSession("idle_timeout");
6042
6083
  }
6043
6084
  /**
6044
6085
  * `pagehide` en web: SUSPENDE la sesión en vez de cerrarla, porque el navegador lo lanza
@@ -6162,7 +6203,7 @@ var DeepdotsPopups = class {
6162
6203
  window.addEventListener("pageshow", (event) => {
6163
6204
  if (!event.persisted) return;
6164
6205
  this.startAnalyticsFlushTimer();
6165
- this.openSession();
6206
+ this.openSession("bfcache_restore");
6166
6207
  });
6167
6208
  document.addEventListener("visibilitychange", () => {
6168
6209
  if (document.visibilityState === "hidden") {