@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.
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ BUSY_JS,
2
3
  InMemoryStorage,
3
4
  LABELS,
4
5
  PRIMARY_COLOR_JS,
@@ -20,7 +21,7 @@ import {
20
21
  sdkWarn,
21
22
  setLogger,
22
23
  style_default
23
- } from "./chunk-XHR3MHJO.mjs";
24
+ } from "./chunk-AV2TTQXB.mjs";
24
25
 
25
26
  // src/types/index.ts
26
27
  var POPUP_TRIGGER_CONDITION_STATUSES = [
@@ -429,6 +430,7 @@ ${customCss}
429
430
  var footer=document.getElementById('dd-footer');
430
431
  var spinner=document.getElementById('dd-spinner');
431
432
  var formHost=document.getElementById('mf');
433
+ var formWrapper=document.getElementById('dd-form-wrapper');
432
434
  var errorHint=document.getElementById('dd-error');
433
435
  var backBtn=document.getElementById('dd-back');
434
436
  var startBtn=document.getElementById('dd-start');
@@ -588,6 +590,7 @@ ${customCss}
588
590
  }
589
591
 
590
592
  ${PRIMARY_COLOR_JS}
593
+ ${BUSY_JS}
591
594
  ${REVEAL_JS}
592
595
  // El popup se ense\xF1a cuando el survey est\xE1 pintado; \`ready\` deja al host abrir su propio
593
596
  // Modal en ese momento en vez de montar un WebView en blanco.
@@ -598,6 +601,11 @@ ${REVEAL_JS}
598
601
 
599
602
  function setLoading(isLoading){
600
603
  spinner.style.display=isLoading?'flex':'none';
604
+ // El spinner no tapa el survey (28px en posicion absoluta), asi que sin esto el usuario
605
+ // puede seguir cambiando de opcion mientras se envia la pagina, y ese cambio ya no viaja
606
+ // con ella. Se bloquea el wrapper, que es el elemento estable: el div de dentro lo
607
+ // sustituye @magicfeedback/native por su propio contenedor al generar el survey.
608
+ ddSetSurveyBusy(formWrapper, isLoading);
601
609
  if(!isLoading){
602
610
  formHost.style.visibility='visible';
603
611
  // Fin de la carga, sea por survey cargado o por error. Entre p\xE1ginas es no-op: la
@@ -860,7 +868,7 @@ function createReactNativeRenderer() {
860
868
  }
861
869
 
862
870
  // src/ui/preload.ts
863
- var defaultLoader = () => import("./renderPopup-HTNVACFV.mjs");
871
+ var defaultLoader = () => import("./renderPopup-ERUKQE4U.mjs");
864
872
  var pending = null;
865
873
  function preloadRenderPopup(loader = defaultLoader) {
866
874
  if (pending) return pending;
@@ -933,7 +941,7 @@ var BrowserPopupRenderer = class {
933
941
  if (!this.container) return;
934
942
  this.visible = true;
935
943
  const container = this.container;
936
- void import("./renderPopup-HTNVACFV.mjs").then(({ renderPopup }) => {
944
+ void import("./renderPopup-ERUKQE4U.mjs").then(({ renderPopup }) => {
937
945
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId, options);
938
946
  });
939
947
  }
@@ -1198,14 +1206,7 @@ function createFeedbackSink(options) {
1198
1206
  const post = async (body, final, sessionEnd = false) => {
1199
1207
  const json = JSON.stringify(body);
1200
1208
  const small = json.length <= KEEPALIVE_MAX_BYTES;
1201
- if (final && options.sendBeaconImpl && small) {
1202
- const payload = typeof Blob !== "undefined" ? new Blob([json], { type: "application/json" }) : json;
1203
- if (options.sendBeaconImpl(url, payload)) {
1204
- options.log?.("[DeepdotsAnalytics] final flush via sendBeacon");
1205
- return;
1206
- }
1207
- options.log?.("[DeepdotsAnalytics] sendBeacon rejected the batch; falling back to fetch");
1208
- }
1209
+ if (final) options.log?.("[DeepdotsAnalytics] final flush via fetch keepalive");
1209
1210
  const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
1210
1211
  if (!f) {
1211
1212
  options.log?.("[DeepdotsAnalytics] no fetch available; payload not sent", body);
@@ -1215,6 +1216,9 @@ function createFeedbackSink(options) {
1215
1216
  method: "POST",
1216
1217
  headers: { "Content-Type": "application/json" },
1217
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",
1218
1222
  // keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.
1219
1223
  ...small ? { keepalive: true } : {}
1220
1224
  });
@@ -1307,6 +1311,15 @@ var NavigationObserver = class {
1307
1311
  this.closeCurrent();
1308
1312
  this.currentScreen = null;
1309
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
+ }
1310
1323
  closeCurrent() {
1311
1324
  if (this.currentScreen == null) return;
1312
1325
  const exit = this.now();
@@ -1946,8 +1959,6 @@ var DeepdotsPopups = class {
1946
1959
  baseUrl: this.baseUrl,
1947
1960
  keys: config.analytics,
1948
1961
  log: (...a) => this.log(...a),
1949
- // sendBeacon: único transporte que sobrevive al cierre de la página.
1950
- sendBeaconImpl: typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? (url, body) => navigator.sendBeacon(url, body) : void 0,
1951
1962
  onSessionId: (id) => {
1952
1963
  this.analyticsFeedbackSessionId = id;
1953
1964
  },
@@ -1995,7 +2006,6 @@ var DeepdotsPopups = class {
1995
2006
  this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
1996
2007
  this.navObserver.install();
1997
2008
  this.engagement = new EngagementTracker();
1998
- this.engagement.resume();
1999
2009
  this.setupAnalyticsFlush();
2000
2010
  this.openSession();
2001
2011
  const pendingCrashes = this.crashReporter.drainPendingCrashes();
@@ -2214,6 +2224,7 @@ var DeepdotsPopups = class {
2214
2224
  this.navStarted = false;
2215
2225
  this.analytics?.exitAllMiniServices();
2216
2226
  this.flushEngagement();
2227
+ this.engagement?.pause();
2217
2228
  this.track("deepdots_session_end", { reason });
2218
2229
  this.flushAnalytics({ final: reason === "page_hide", sessionEnd: true });
2219
2230
  this.analyticsFeedbackSessionId = void 0;
@@ -2229,6 +2240,12 @@ var DeepdotsPopups = class {
2229
2240
  if (!this.tracking?.isTrackingEnabled()) return;
2230
2241
  this.sessionOpen = true;
2231
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";
2232
2249
  }
2233
2250
  /** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */
2234
2251
  previewAnalytics() {
@@ -2241,8 +2258,8 @@ var DeepdotsPopups = class {
2241
2258
  }
2242
2259
  /**
2243
2260
  * Envía el lote acumulado de analytics y vacía el buffer.
2244
- * `final: true` (cierre de página/app) cambia el transporte a `sendBeacon`, que sobrevive
2245
- * 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.
2246
2263
  */
2247
2264
  flushAnalytics(options) {
2248
2265
  if (!this.tracking?.isTrackingEnabled()) return;
@@ -2263,21 +2280,30 @@ var DeepdotsPopups = class {
2263
2280
  /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
2264
2281
  setupAnalyticsFlush() {
2265
2282
  if (typeof document === "undefined" || typeof window === "undefined") return;
2266
- this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
2283
+ this.startAnalyticsFlushTimer();
2267
2284
  window.addEventListener("pagehide", () => {
2268
2285
  clearInterval(this.analyticsFlushTimer);
2269
2286
  this.closeSession("page_hide");
2270
2287
  });
2288
+ window.addEventListener("pageshow", (event) => {
2289
+ if (!event.persisted) return;
2290
+ this.startAnalyticsFlushTimer();
2291
+ this.openSession();
2292
+ });
2271
2293
  document.addEventListener("visibilitychange", () => {
2272
2294
  if (document.visibilityState === "hidden") {
2273
2295
  this.flushEngagement();
2274
2296
  this.engagement?.pause();
2275
2297
  this.flushAnalytics();
2276
2298
  } else if (document.visibilityState === "visible") {
2277
- this.engagement?.resume();
2299
+ if (this.sessionOpen) this.engagement?.resume();
2278
2300
  }
2279
2301
  });
2280
2302
  }
2303
+ startAnalyticsFlushTimer() {
2304
+ clearInterval(this.analyticsFlushTimer);
2305
+ this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
2306
+ }
2281
2307
  /** Enable auto-launch functionality with configured triggers */
2282
2308
  autoLaunch() {
2283
2309
  if (!this.initialized) {
@@ -2371,6 +2397,7 @@ var DeepdotsPopups = class {
2371
2397
  this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
2372
2398
  }
2373
2399
  shouldShow(def, pathUrl, skipPathCheck = false) {
2400
+ if (this.storage) this.loadPopupState(this.storage);
2374
2401
  if (this.isPathExcluded(def, pathUrl)) {
2375
2402
  return false;
2376
2403
  }
@@ -2779,7 +2806,7 @@ var DeepdotsPopups = class {
2779
2806
  }
2780
2807
  if (!this.popupContainer) return;
2781
2808
  const container = this.popupContainer;
2782
- void import("./renderPopup-HTNVACFV.mjs").then(({ renderPopup }) => {
2809
+ void import("./renderPopup-ERUKQE4U.mjs").then(({ renderPopup }) => {
2783
2810
  renderPopup(
2784
2811
  container,
2785
2812
  surveyId,
@@ -2897,22 +2924,32 @@ var DeepdotsPopups = class {
2897
2924
  this.persistPopupState();
2898
2925
  }
2899
2926
  /**
2900
- * Rehidrata el estado de redisplay desde el storage. `answeredSurveys` se reconstruye
2901
- * 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`.
2902
2932
  */
2903
2933
  loadPopupState(storage) {
2904
2934
  const state = readPopupState(storage);
2905
2935
  Object.entries(state.lastShown).forEach(([popupId, timestamp]) => {
2906
- this.lastShown.set(popupId, timestamp);
2936
+ if (timestamp > (this.lastShown.get(popupId) ?? -Infinity)) this.lastShown.set(popupId, timestamp);
2907
2937
  });
2908
2938
  Object.entries(state.progress).forEach(([surveyId, entry]) => {
2939
+ const current = this.surveyProgress.get(surveyId);
2940
+ if (current && current.timestamp >= entry.timestamp) return;
2909
2941
  this.surveyProgress.set(surveyId, { status: entry.status, timestamp: entry.timestamp });
2910
2942
  if (entry.status === "COMPLETED") this.answeredSurveys.add(surveyId);
2911
2943
  });
2912
2944
  }
2913
- /** 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
+ */
2914
2950
  persistPopupState() {
2915
2951
  if (!this.storage) return;
2952
+ this.loadPopupState(this.storage);
2916
2953
  writePopupState(this.storage, {
2917
2954
  lastShown: Object.fromEntries(this.lastShown),
2918
2955
  progress: Object.fromEntries(this.surveyProgress)