@magicfeedback/popup-sdk 1.8.1 → 1.8.2

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
@@ -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-ERUKQE4U.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-ERUKQE4U.mjs").then(({ renderPopup }) => {
945
945
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId, options);
946
946
  });
947
947
  }
@@ -1206,14 +1206,7 @@ function createFeedbackSink(options) {
1206
1206
  const post = async (body, final, sessionEnd = false) => {
1207
1207
  const json = JSON.stringify(body);
1208
1208
  const small = json.length <= KEEPALIVE_MAX_BYTES;
1209
- if (final && options.sendBeaconImpl && small) {
1210
- const payload = typeof Blob !== "undefined" ? new Blob([json], { type: "application/json" }) : json;
1211
- if (options.sendBeaconImpl(url, payload)) {
1212
- options.log?.("[DeepdotsAnalytics] final flush via sendBeacon");
1213
- return;
1214
- }
1215
- options.log?.("[DeepdotsAnalytics] sendBeacon rejected the batch; falling back to fetch");
1216
- }
1209
+ if (final) options.log?.("[DeepdotsAnalytics] final flush via fetch keepalive");
1217
1210
  const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
1218
1211
  if (!f) {
1219
1212
  options.log?.("[DeepdotsAnalytics] no fetch available; payload not sent", body);
@@ -1223,6 +1216,9 @@ function createFeedbackSink(options) {
1223
1216
  method: "POST",
1224
1217
  headers: { "Content-Type": "application/json" },
1225
1218
  body: json,
1219
+ // La API autentica por `publicKey` en el body y responde `Allow-Origin: *`: una
1220
+ // petición con credenciales fallaría el CORS. Explícito para no depender del default.
1221
+ credentials: "omit",
1226
1222
  // keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.
1227
1223
  ...small ? { keepalive: true } : {}
1228
1224
  });
@@ -1315,6 +1311,15 @@ var NavigationObserver = class {
1315
1311
  this.closeCurrent();
1316
1312
  this.currentScreen = null;
1317
1313
  }
1314
+ /**
1315
+ * Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión
1316
+ * nueva sin navegar (cambio de usuario, vuelta de la bfcache). No-op si nunca se instaló
1317
+ * (navegación manual por `setScreen`, RN) o si ya hay pantalla en curso.
1318
+ */
1319
+ restart() {
1320
+ if (!this.installed || this.currentScreen != null || typeof window === "undefined") return;
1321
+ this.begin(window.location.href);
1322
+ }
1318
1323
  closeCurrent() {
1319
1324
  if (this.currentScreen == null) return;
1320
1325
  const exit = this.now();
@@ -1954,8 +1959,6 @@ var DeepdotsPopups = class {
1954
1959
  baseUrl: this.baseUrl,
1955
1960
  keys: config.analytics,
1956
1961
  log: (...a) => this.log(...a),
1957
- // sendBeacon: único transporte que sobrevive al cierre de la página.
1958
- sendBeaconImpl: typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? (url, body) => navigator.sendBeacon(url, body) : void 0,
1959
1962
  onSessionId: (id) => {
1960
1963
  this.analyticsFeedbackSessionId = id;
1961
1964
  },
@@ -2003,7 +2006,6 @@ var DeepdotsPopups = class {
2003
2006
  this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
2004
2007
  this.navObserver.install();
2005
2008
  this.engagement = new EngagementTracker();
2006
- this.engagement.resume();
2007
2009
  this.setupAnalyticsFlush();
2008
2010
  this.openSession();
2009
2011
  const pendingCrashes = this.crashReporter.drainPendingCrashes();
@@ -2222,6 +2224,7 @@ var DeepdotsPopups = class {
2222
2224
  this.navStarted = false;
2223
2225
  this.analytics?.exitAllMiniServices();
2224
2226
  this.flushEngagement();
2227
+ this.engagement?.pause();
2225
2228
  this.track("deepdots_session_end", { reason });
2226
2229
  this.flushAnalytics({ final: reason === "page_hide", sessionEnd: true });
2227
2230
  this.analyticsFeedbackSessionId = void 0;
@@ -2237,6 +2240,12 @@ var DeepdotsPopups = class {
2237
2240
  if (!this.tracking?.isTrackingEnabled()) return;
2238
2241
  this.sessionOpen = true;
2239
2242
  this.track("deepdots_session_start", {});
2243
+ this.navObserver?.restart();
2244
+ if (this.isDocumentVisible()) this.engagement?.resume();
2245
+ }
2246
+ /** Web: si la pestaña se ve. Sin `document` (RN) manda AppState vía onForeground/onBackground. */
2247
+ isDocumentVisible() {
2248
+ return typeof document === "undefined" || document.visibilityState !== "hidden";
2240
2249
  }
2241
2250
  /** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */
2242
2251
  previewAnalytics() {
@@ -2249,8 +2258,8 @@ var DeepdotsPopups = class {
2249
2258
  }
2250
2259
  /**
2251
2260
  * Envía el lote acumulado de analytics y vacía el buffer.
2252
- * `final: true` (cierre de página/app) cambia el transporte a `sendBeacon`, que sobrevive
2253
- * al unload; un lote que falle por red o 5xx se re-encola para el siguiente flush.
2261
+ * `final: true` (cierre de página/app): el lote no espera al primer POST en vuelo, porque
2262
+ * el documento se muere; un lote que falle por red o 5xx se re-encola para el siguiente flush.
2254
2263
  */
2255
2264
  flushAnalytics(options) {
2256
2265
  if (!this.tracking?.isTrackingEnabled()) return;
@@ -2271,21 +2280,30 @@ var DeepdotsPopups = class {
2271
2280
  /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
2272
2281
  setupAnalyticsFlush() {
2273
2282
  if (typeof document === "undefined" || typeof window === "undefined") return;
2274
- this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
2283
+ this.startAnalyticsFlushTimer();
2275
2284
  window.addEventListener("pagehide", () => {
2276
2285
  clearInterval(this.analyticsFlushTimer);
2277
2286
  this.closeSession("page_hide");
2278
2287
  });
2288
+ window.addEventListener("pageshow", (event) => {
2289
+ if (!event.persisted) return;
2290
+ this.startAnalyticsFlushTimer();
2291
+ this.openSession();
2292
+ });
2279
2293
  document.addEventListener("visibilitychange", () => {
2280
2294
  if (document.visibilityState === "hidden") {
2281
2295
  this.flushEngagement();
2282
2296
  this.engagement?.pause();
2283
2297
  this.flushAnalytics();
2284
2298
  } else if (document.visibilityState === "visible") {
2285
- this.engagement?.resume();
2299
+ if (this.sessionOpen) this.engagement?.resume();
2286
2300
  }
2287
2301
  });
2288
2302
  }
2303
+ startAnalyticsFlushTimer() {
2304
+ clearInterval(this.analyticsFlushTimer);
2305
+ this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
2306
+ }
2289
2307
  /** Enable auto-launch functionality with configured triggers */
2290
2308
  autoLaunch() {
2291
2309
  if (!this.initialized) {
@@ -2379,6 +2397,7 @@ var DeepdotsPopups = class {
2379
2397
  this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
2380
2398
  }
2381
2399
  shouldShow(def, pathUrl, skipPathCheck = false) {
2400
+ if (this.storage) this.loadPopupState(this.storage);
2382
2401
  if (this.isPathExcluded(def, pathUrl)) {
2383
2402
  return false;
2384
2403
  }
@@ -2787,7 +2806,7 @@ var DeepdotsPopups = class {
2787
2806
  }
2788
2807
  if (!this.popupContainer) return;
2789
2808
  const container = this.popupContainer;
2790
- void import("./renderPopup-3CZ6WTXF.mjs").then(({ renderPopup }) => {
2809
+ void import("./renderPopup-ERUKQE4U.mjs").then(({ renderPopup }) => {
2791
2810
  renderPopup(
2792
2811
  container,
2793
2812
  surveyId,
@@ -2905,22 +2924,32 @@ var DeepdotsPopups = class {
2905
2924
  this.persistPopupState();
2906
2925
  }
2907
2926
  /**
2908
- * Rehidrata el estado de redisplay desde el storage. `answeredSurveys` se reconstruye
2909
- * desde las entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.
2927
+ * Fusiona el estado de redisplay del storage con el de memoria: por cada popup/survey gana
2928
+ * la entrada MÁS RECIENTE. Fusionar y no sustituir es lo que hace convivir a varias
2929
+ * pestañas sobre el mismo `localStorage`: se llama en el `init()`, antes de cada decisión
2930
+ * de mostrar y antes de cada escritura. `answeredSurveys` se reconstruye desde las
2931
+ * entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.
2910
2932
  */
2911
2933
  loadPopupState(storage) {
2912
2934
  const state = readPopupState(storage);
2913
2935
  Object.entries(state.lastShown).forEach(([popupId, timestamp]) => {
2914
- this.lastShown.set(popupId, timestamp);
2936
+ if (timestamp > (this.lastShown.get(popupId) ?? -Infinity)) this.lastShown.set(popupId, timestamp);
2915
2937
  });
2916
2938
  Object.entries(state.progress).forEach(([surveyId, entry]) => {
2939
+ const current = this.surveyProgress.get(surveyId);
2940
+ if (current && current.timestamp >= entry.timestamp) return;
2917
2941
  this.surveyProgress.set(surveyId, { status: entry.status, timestamp: entry.timestamp });
2918
2942
  if (entry.status === "COMPLETED") this.answeredSurveys.add(surveyId);
2919
2943
  });
2920
2944
  }
2921
- /** Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria. */
2945
+ /**
2946
+ * Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria.
2947
+ * Fusiona antes de escribir: escribir solo la foto en memoria borraba lo que otra pestaña
2948
+ * hubiese guardado desde nuestro `init()`.
2949
+ */
2922
2950
  persistPopupState() {
2923
2951
  if (!this.storage) return;
2952
+ this.loadPopupState(this.storage);
2924
2953
  writePopupState(this.storage, {
2925
2954
  lastShown: Object.fromEntries(this.lastShown),
2926
2955
  progress: Object.fromEntries(this.surveyProgress)