@magicfeedback/popup-sdk 1.8.0 → 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.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ BUSY_JS,
2
3
  LABELS,
3
4
  PRIMARY_COLOR_JS,
4
5
  RESOLVE_LOCALE_JS,
@@ -17,7 +18,7 @@ import {
17
18
  sdkWarn,
18
19
  setLogger,
19
20
  style_default
20
- } from "./chunk-322DDMX6.mjs";
21
+ } from "./chunk-EBJZDVC6.mjs";
21
22
 
22
23
  // src/react-native/index.tsx
23
24
  import { createContext, useContext, useEffect, useRef, useState } from "react";
@@ -431,6 +432,7 @@ ${customCss}
431
432
  var footer=document.getElementById('dd-footer');
432
433
  var spinner=document.getElementById('dd-spinner');
433
434
  var formHost=document.getElementById('mf');
435
+ var formWrapper=document.getElementById('dd-form-wrapper');
434
436
  var errorHint=document.getElementById('dd-error');
435
437
  var backBtn=document.getElementById('dd-back');
436
438
  var startBtn=document.getElementById('dd-start');
@@ -590,6 +592,7 @@ ${customCss}
590
592
  }
591
593
 
592
594
  ${PRIMARY_COLOR_JS}
595
+ ${BUSY_JS}
593
596
  ${REVEAL_JS}
594
597
  // El popup se ense\xF1a cuando el survey est\xE1 pintado; \`ready\` deja al host abrir su propio
595
598
  // Modal en ese momento en vez de montar un WebView en blanco.
@@ -600,6 +603,11 @@ ${REVEAL_JS}
600
603
 
601
604
  function setLoading(isLoading){
602
605
  spinner.style.display=isLoading?'flex':'none';
606
+ // El spinner no tapa el survey (28px en posicion absoluta), asi que sin esto el usuario
607
+ // puede seguir cambiando de opcion mientras se envia la pagina, y ese cambio ya no viaja
608
+ // con ella. Se bloquea el wrapper, que es el elemento estable: el div de dentro lo
609
+ // sustituye @magicfeedback/native por su propio contenedor al generar el survey.
610
+ ddSetSurveyBusy(formWrapper, isLoading);
603
611
  if(!isLoading){
604
612
  formHost.style.visibility='visible';
605
613
  // Fin de la carga, sea por survey cargado o por error. Entre p\xE1ginas es no-op: la
@@ -862,7 +870,7 @@ function createReactNativeRenderer() {
862
870
  }
863
871
 
864
872
  // src/ui/preload.ts
865
- var defaultLoader = () => import("./renderPopup-SNDPX5RX.mjs");
873
+ var defaultLoader = () => import("./renderPopup-E2OLIXIQ.mjs");
866
874
  var pending = null;
867
875
  function preloadRenderPopup(loader = defaultLoader) {
868
876
  if (pending) return pending;
@@ -935,7 +943,7 @@ var BrowserPopupRenderer = class {
935
943
  if (!this.container) return;
936
944
  this.visible = true;
937
945
  const container = this.container;
938
- void import("./renderPopup-SNDPX5RX.mjs").then(({ renderPopup }) => {
946
+ void import("./renderPopup-E2OLIXIQ.mjs").then(({ renderPopup }) => {
939
947
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId, options);
940
948
  });
941
949
  }
@@ -1200,14 +1208,7 @@ function createFeedbackSink(options) {
1200
1208
  const post = async (body, final, sessionEnd = false) => {
1201
1209
  const json = JSON.stringify(body);
1202
1210
  const small = json.length <= KEEPALIVE_MAX_BYTES;
1203
- if (final && options.sendBeaconImpl && small) {
1204
- const payload = typeof Blob !== "undefined" ? new Blob([json], { type: "application/json" }) : json;
1205
- if (options.sendBeaconImpl(url, payload)) {
1206
- options.log?.("[DeepdotsAnalytics] final flush via sendBeacon");
1207
- return;
1208
- }
1209
- options.log?.("[DeepdotsAnalytics] sendBeacon rejected the batch; falling back to fetch");
1210
- }
1211
+ if (final) options.log?.("[DeepdotsAnalytics] final flush via fetch keepalive");
1211
1212
  const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
1212
1213
  if (!f) {
1213
1214
  options.log?.("[DeepdotsAnalytics] no fetch available; payload not sent", body);
@@ -1217,6 +1218,9 @@ function createFeedbackSink(options) {
1217
1218
  method: "POST",
1218
1219
  headers: { "Content-Type": "application/json" },
1219
1220
  body: json,
1221
+ // La API autentica por `publicKey` en el body y responde `Allow-Origin: *`: una
1222
+ // petición con credenciales fallaría el CORS. Explícito para no depender del default.
1223
+ credentials: "omit",
1220
1224
  // keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.
1221
1225
  ...small ? { keepalive: true } : {}
1222
1226
  });
@@ -1309,6 +1313,15 @@ var NavigationObserver = class {
1309
1313
  this.closeCurrent();
1310
1314
  this.currentScreen = null;
1311
1315
  }
1316
+ /**
1317
+ * Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión
1318
+ * nueva sin navegar (cambio de usuario, vuelta de la bfcache). No-op si nunca se instaló
1319
+ * (navegación manual por `setScreen`, RN) o si ya hay pantalla en curso.
1320
+ */
1321
+ restart() {
1322
+ if (!this.installed || this.currentScreen != null || typeof window === "undefined") return;
1323
+ this.begin(window.location.href);
1324
+ }
1312
1325
  closeCurrent() {
1313
1326
  if (this.currentScreen == null) return;
1314
1327
  const exit = this.now();
@@ -1948,8 +1961,6 @@ var DeepdotsPopups = class {
1948
1961
  baseUrl: this.baseUrl,
1949
1962
  keys: config.analytics,
1950
1963
  log: (...a) => this.log(...a),
1951
- // sendBeacon: único transporte que sobrevive al cierre de la página.
1952
- sendBeaconImpl: typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? (url, body) => navigator.sendBeacon(url, body) : void 0,
1953
1964
  onSessionId: (id) => {
1954
1965
  this.analyticsFeedbackSessionId = id;
1955
1966
  },
@@ -1997,7 +2008,6 @@ var DeepdotsPopups = class {
1997
2008
  this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
1998
2009
  this.navObserver.install();
1999
2010
  this.engagement = new EngagementTracker();
2000
- this.engagement.resume();
2001
2011
  this.setupAnalyticsFlush();
2002
2012
  this.openSession();
2003
2013
  const pendingCrashes = this.crashReporter.drainPendingCrashes();
@@ -2216,6 +2226,7 @@ var DeepdotsPopups = class {
2216
2226
  this.navStarted = false;
2217
2227
  this.analytics?.exitAllMiniServices();
2218
2228
  this.flushEngagement();
2229
+ this.engagement?.pause();
2219
2230
  this.track("deepdots_session_end", { reason });
2220
2231
  this.flushAnalytics({ final: reason === "page_hide", sessionEnd: true });
2221
2232
  this.analyticsFeedbackSessionId = void 0;
@@ -2231,6 +2242,12 @@ var DeepdotsPopups = class {
2231
2242
  if (!this.tracking?.isTrackingEnabled()) return;
2232
2243
  this.sessionOpen = true;
2233
2244
  this.track("deepdots_session_start", {});
2245
+ this.navObserver?.restart();
2246
+ if (this.isDocumentVisible()) this.engagement?.resume();
2247
+ }
2248
+ /** Web: si la pestaña se ve. Sin `document` (RN) manda AppState vía onForeground/onBackground. */
2249
+ isDocumentVisible() {
2250
+ return typeof document === "undefined" || document.visibilityState !== "hidden";
2234
2251
  }
2235
2252
  /** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */
2236
2253
  previewAnalytics() {
@@ -2243,8 +2260,8 @@ var DeepdotsPopups = class {
2243
2260
  }
2244
2261
  /**
2245
2262
  * Envía el lote acumulado de analytics y vacía el buffer.
2246
- * `final: true` (cierre de página/app) cambia el transporte a `sendBeacon`, que sobrevive
2247
- * al unload; un lote que falle por red o 5xx se re-encola para el siguiente flush.
2263
+ * `final: true` (cierre de página/app): el lote no espera al primer POST en vuelo, porque
2264
+ * el documento se muere; un lote que falle por red o 5xx se re-encola para el siguiente flush.
2248
2265
  */
2249
2266
  flushAnalytics(options) {
2250
2267
  if (!this.tracking?.isTrackingEnabled()) return;
@@ -2265,21 +2282,30 @@ var DeepdotsPopups = class {
2265
2282
  /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
2266
2283
  setupAnalyticsFlush() {
2267
2284
  if (typeof document === "undefined" || typeof window === "undefined") return;
2268
- this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
2285
+ this.startAnalyticsFlushTimer();
2269
2286
  window.addEventListener("pagehide", () => {
2270
2287
  clearInterval(this.analyticsFlushTimer);
2271
2288
  this.closeSession("page_hide");
2272
2289
  });
2290
+ window.addEventListener("pageshow", (event) => {
2291
+ if (!event.persisted) return;
2292
+ this.startAnalyticsFlushTimer();
2293
+ this.openSession();
2294
+ });
2273
2295
  document.addEventListener("visibilitychange", () => {
2274
2296
  if (document.visibilityState === "hidden") {
2275
2297
  this.flushEngagement();
2276
2298
  this.engagement?.pause();
2277
2299
  this.flushAnalytics();
2278
2300
  } else if (document.visibilityState === "visible") {
2279
- this.engagement?.resume();
2301
+ if (this.sessionOpen) this.engagement?.resume();
2280
2302
  }
2281
2303
  });
2282
2304
  }
2305
+ startAnalyticsFlushTimer() {
2306
+ clearInterval(this.analyticsFlushTimer);
2307
+ this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
2308
+ }
2283
2309
  /** Enable auto-launch functionality with configured triggers */
2284
2310
  autoLaunch() {
2285
2311
  if (!this.initialized) {
@@ -2373,6 +2399,7 @@ var DeepdotsPopups = class {
2373
2399
  this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
2374
2400
  }
2375
2401
  shouldShow(def, pathUrl, skipPathCheck = false) {
2402
+ if (this.storage) this.loadPopupState(this.storage);
2376
2403
  if (this.isPathExcluded(def, pathUrl)) {
2377
2404
  return false;
2378
2405
  }
@@ -2781,7 +2808,7 @@ var DeepdotsPopups = class {
2781
2808
  }
2782
2809
  if (!this.popupContainer) return;
2783
2810
  const container = this.popupContainer;
2784
- void import("./renderPopup-SNDPX5RX.mjs").then(({ renderPopup }) => {
2811
+ void import("./renderPopup-E2OLIXIQ.mjs").then(({ renderPopup }) => {
2785
2812
  renderPopup(
2786
2813
  container,
2787
2814
  surveyId,
@@ -2899,22 +2926,32 @@ var DeepdotsPopups = class {
2899
2926
  this.persistPopupState();
2900
2927
  }
2901
2928
  /**
2902
- * Rehidrata el estado de redisplay desde el storage. `answeredSurveys` se reconstruye
2903
- * desde las entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.
2929
+ * Fusiona el estado de redisplay del storage con el de memoria: por cada popup/survey gana
2930
+ * la entrada MÁS RECIENTE. Fusionar y no sustituir es lo que hace convivir a varias
2931
+ * pestañas sobre el mismo `localStorage`: se llama en el `init()`, antes de cada decisión
2932
+ * de mostrar y antes de cada escritura. `answeredSurveys` se reconstruye desde las
2933
+ * entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.
2904
2934
  */
2905
2935
  loadPopupState(storage) {
2906
2936
  const state = readPopupState(storage);
2907
2937
  Object.entries(state.lastShown).forEach(([popupId, timestamp]) => {
2908
- this.lastShown.set(popupId, timestamp);
2938
+ if (timestamp > (this.lastShown.get(popupId) ?? -Infinity)) this.lastShown.set(popupId, timestamp);
2909
2939
  });
2910
2940
  Object.entries(state.progress).forEach(([surveyId, entry]) => {
2941
+ const current = this.surveyProgress.get(surveyId);
2942
+ if (current && current.timestamp >= entry.timestamp) return;
2911
2943
  this.surveyProgress.set(surveyId, { status: entry.status, timestamp: entry.timestamp });
2912
2944
  if (entry.status === "COMPLETED") this.answeredSurveys.add(surveyId);
2913
2945
  });
2914
2946
  }
2915
- /** Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria. */
2947
+ /**
2948
+ * Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria.
2949
+ * Fusiona antes de escribir: escribir solo la foto en memoria borraba lo que otra pestaña
2950
+ * hubiese guardado desde nuestro `init()`.
2951
+ */
2916
2952
  persistPopupState() {
2917
2953
  if (!this.storage) return;
2954
+ this.loadPopupState(this.storage);
2918
2955
  writePopupState(this.storage, {
2919
2956
  lastShown: Object.fromEntries(this.lastShown),
2920
2957
  progress: Object.fromEntries(this.surveyProgress)