@magicfeedback/popup-sdk 1.9.0 → 1.11.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
@@ -21,7 +21,7 @@ import {
21
21
  sdkWarn,
22
22
  setLogger,
23
23
  style_default
24
- } from "./chunk-MTV7T4OW.mjs";
24
+ } from "./chunk-GXEPYT33.mjs";
25
25
 
26
26
  // src/types/index.ts
27
27
  var POPUP_TRIGGER_CONDITION_STATUSES = [
@@ -254,7 +254,7 @@ function trimmedOrNull(value) {
254
254
  }
255
255
  function buildSurveyHtml(opts) {
256
256
  const env = opts.env === "development" ? "dev" : "prod";
257
- const version = opts.version ?? "2.2.22";
257
+ const version = opts.version ?? "2.2.31";
258
258
  const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;
259
259
  const sid = jsonForScript(opts.surveyId);
260
260
  const pid = jsonForScript(opts.productId);
@@ -592,11 +592,25 @@ ${customCss}
592
592
  ${PRIMARY_COLOR_JS}
593
593
  ${BUSY_JS}
594
594
  ${REVEAL_JS}
595
+ // Foco en la primera pregunta si es de escribir. Al navegar lo pone @magicfeedback/native
596
+ // (autofocus:'navigation'); la apertura la decide el popup, que es quien sabe cu\xE1ndo se revela.
597
+ // Necesita el popup revelado y el survey cargado, que llegan en cualquier orden, y solo con
598
+ // rat\xF3n o trackpad: en t\xE1ctil sacar\xEDa el teclado sin que el usuario haya tocado nada.
599
+ var ddRevealed=false, ddSurveyLoaded=false, ddOpenFocusDone=false;
600
+ function ddAutofocusOnOpen(){
601
+ if(ddOpenFocusDone || !ddRevealed || !ddSurveyLoaded){ return; }
602
+ ddOpenFocusDone=true;
603
+ var fine=typeof window.matchMedia==='function' && window.matchMedia('(pointer: fine)').matches;
604
+ var f=window.DeepdotsForm;
605
+ if(fine && f && typeof f.focusFirstQuestion==='function'){ f.focusFirstQuestion(); }
606
+ }
595
607
  // El popup se ense\xF1a cuando el survey est\xE1 pintado; \`ready\` deja al host abrir su propio
596
608
  // Modal en ese momento en vez de montar un WebView en blanco.
597
609
  var ddReveal=ddCreateReveal(popup, function(){
598
610
  document.body.classList.add('dd-ready');
599
611
  emitJSON('ready');
612
+ ddRevealed=true;
613
+ ddAutofocusOnOpen();
600
614
  }, ${REVEAL_TIMEOUT_MS});
601
615
 
602
616
  function setLoading(isLoading){
@@ -634,10 +648,18 @@ ${REVEAL_JS}
634
648
  // textContent, as\xED que el mensaje de la plataforma (HTML con imagen) no se ver\xEDa, y su
635
649
  // fallback es un literal gen\xE9rico que ignora style.successMessage.
636
650
  addSuccessScreen:false,
651
+ // Surveys multi-idioma: native pide a la API este idioma (el del init o el del
652
+ // dispositivo); si el survey no lo tiene, la API sirve el idioma por defecto.
653
+ lang:${initLangJson}||undefined,
654
+ // Foco en la primera pregunta de escribir tras Start, Siguiente o Atr\xE1s. Las versiones
655
+ // de native sin esta opci\xF3n la ignoran.
656
+ autofocus:'navigation',
637
657
  onLoadedEvent:function(args){
638
- // Idioma del survey: manda sobre el del init para que el chrome no se quede en
639
- // ingl\xE9s delante de un survey traducido.
640
- var langs=args && args.formData ? args.formData.lang : null;
658
+ // Idioma en el que native muestra el survey: manda sobre el del init para que el
659
+ // chrome no se quede en ingl\xE9s delante de un survey traducido. Native < 2.3 no lo
660
+ // env\xEDa: entonces el primero de formData.lang (el idioma por defecto).
661
+ var shown=args && typeof args.lang==='string' && args.lang.trim() ? args.lang : null;
662
+ var langs=shown ? [shown] : (args && args.formData ? args.formData.lang : null);
641
663
  if(langs && langs.length){
642
664
  for(var li=0;li<langs.length;li++){
643
665
  if(typeof langs[li]==='string' && langs[li].trim()){ ddApplyLabels(langs[li]); break; }
@@ -691,6 +713,8 @@ ${REVEAL_JS}
691
713
  updateProgress({ progress:form.progress, total:form.total, completed:false, followup:false });
692
714
  emitJSON('loaded');
693
715
  setLoading(false);
716
+ // La primera carga es la apertura; las siguientes (tras Start) las enfoca native.
717
+ if(!ddSurveyLoaded){ ddSurveyLoaded=true; ddAutofocusOnOpen(); }
694
718
  },
695
719
  beforeSubmitEvent:function(){
696
720
  setLoading(true);
@@ -841,6 +865,7 @@ var ReactNativePopupRenderer = class {
841
865
  this.emitReady(surveyId);
842
866
  break;
843
867
  case "loaded":
868
+ break;
844
869
  case "before_submit":
845
870
  case "after_submit":
846
871
  case "back":
@@ -868,7 +893,7 @@ function createReactNativeRenderer() {
868
893
  }
869
894
 
870
895
  // src/ui/preload.ts
871
- var defaultLoader = () => import("./renderPopup-LEGTSBLD.mjs");
896
+ var defaultLoader = () => import("./renderPopup-KSZJ5AN4.mjs");
872
897
  var pending = null;
873
898
  function preloadRenderPopup(loader = defaultLoader) {
874
899
  if (pending) return pending;
@@ -941,7 +966,7 @@ var BrowserPopupRenderer = class {
941
966
  if (!this.container) return;
942
967
  this.visible = true;
943
968
  const container = this.container;
944
- void import("./renderPopup-LEGTSBLD.mjs").then(({ renderPopup }) => {
969
+ void import("./renderPopup-KSZJ5AN4.mjs").then(({ renderPopup }) => {
945
970
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId, options);
946
971
  });
947
972
  }
@@ -1216,7 +1241,7 @@ async function safeBodyText(res) {
1216
1241
  }
1217
1242
  }
1218
1243
  function createFeedbackSink(options) {
1219
- let feedbackSessionId;
1244
+ let feedbackSessionId = options.initialSessionId || void 0;
1220
1245
  let firstPostInFlight = null;
1221
1246
  let generation = 0;
1222
1247
  const url = `${options.baseUrl}/sdk/feedback`;
@@ -1309,6 +1334,50 @@ function createFeedbackSink(options) {
1309
1334
  };
1310
1335
  }
1311
1336
 
1337
+ // src/analytics/suspended-session.ts
1338
+ var SUSPENDED_SESSION_KEY = "deepdots.analytics.suspended";
1339
+ function getSessionStorage() {
1340
+ try {
1341
+ return typeof window !== "undefined" && window.sessionStorage ? window.sessionStorage : null;
1342
+ } catch {
1343
+ return null;
1344
+ }
1345
+ }
1346
+ function writeSuspendedSession(storage, session) {
1347
+ try {
1348
+ storage.setItem(SUSPENDED_SESSION_KEY, JSON.stringify(session));
1349
+ return true;
1350
+ } catch {
1351
+ return false;
1352
+ }
1353
+ }
1354
+ function clearSuspendedSession(storage) {
1355
+ try {
1356
+ storage.removeItem(SUSPENDED_SESSION_KEY);
1357
+ } catch {
1358
+ }
1359
+ }
1360
+ function takeSuspendedSession(storage, match, now, windowMs) {
1361
+ let raw = null;
1362
+ try {
1363
+ raw = storage.getItem(SUSPENDED_SESSION_KEY);
1364
+ } catch {
1365
+ return null;
1366
+ }
1367
+ if (!raw) return null;
1368
+ clearSuspendedSession(storage);
1369
+ try {
1370
+ const s = JSON.parse(raw);
1371
+ if (typeof s.sessionId !== "string" || !s.sessionId) return null;
1372
+ if (typeof s.at !== "number" || now - s.at < 0 || now - s.at > windowMs) return null;
1373
+ if (s.integration !== match.integration) return null;
1374
+ if ((s.userId ?? null) !== match.userId) return null;
1375
+ return { sessionId: s.sessionId, integration: s.integration, userId: s.userId ?? null, at: s.at };
1376
+ } catch {
1377
+ return null;
1378
+ }
1379
+ }
1380
+
1312
1381
  // src/tracking/navigation-observer.ts
1313
1382
  var UUID_PREFIX = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
1314
1383
  function screenPath(href) {
@@ -1903,8 +1972,10 @@ var ANALYTICS_HEARTBEAT_MS = 5 * 6e4;
1903
1972
  var ANALYTICS_SESSION_TIMEOUT_MS = 30 * 6e4;
1904
1973
  var ANALYTICS_SERVER_IDLE_MS = 60 * 6e4;
1905
1974
  var ANALYTICS_SESSION_DISCARD_MS = ANALYTICS_SERVER_IDLE_MS - 10 * 6e4;
1975
+ var ANALYTICS_RESUME_WINDOW_MS = 6e4;
1906
1976
  var RESERVED_EVENT_PREFIX = "deepdots_";
1907
1977
  var CUSTOM_EVENT_PREFIX = "deepdots_event_";
1978
+ var MAX_PENDING_TRIGGERS = 20;
1908
1979
  var DeepdotsPopups = class {
1909
1980
  constructor() {
1910
1981
  this.config = null;
@@ -1918,6 +1989,14 @@ var DeepdotsPopups = class {
1918
1989
  // deprecated: mantenido para compatibilidad interna
1919
1990
  this.popupDefinitions = [];
1920
1991
  this.popupsLoaded = false;
1992
+ /**
1993
+ * `true` mientras el `GET /sdk/{apiKey}/popups` de `init()` está en vuelo. En esa ventana los
1994
+ * disparos del host (`triggerEvent`, `triggerSurvey`) no tienen contra qué evaluarse, así que
1995
+ * se guardan en `pendingTriggers` y se reproducen al cargar. Click, scroll, time y exit no
1996
+ * hacen falta: son listeners que `autoLaunch` instala ya con las definiciones cargadas.
1997
+ */
1998
+ this.awaitingPopups = false;
1999
+ this.pendingTriggers = [];
1921
2000
  this.pendingAutoLaunch = false;
1922
2001
  // Estado que alimenta las reglas de redisplay. Copia de trabajo en memoria; la fuente
1923
2002
  // duradera es el storage (`popup-state-store`), rehidratada en `init()` — sin eso, una
@@ -1972,6 +2051,13 @@ var DeepdotsPopups = class {
1972
2051
  this.lastEngagementAt = 0;
1973
2052
  /** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */
1974
2053
  this.hiddenAt = null;
2054
+ /**
2055
+ * Sesión suspendida por un `pagehide` (ms epoch y `deepdots_user_id`); null si no hay.
2056
+ * `openSession` la reanuda si sigue a tiempo (ver ANALYTICS_RESUME_WINDOW_MS).
2057
+ */
2058
+ this.suspended = null;
2059
+ /** Integración de analytics del init: la sesión suspendida solo la hereda la misma. */
2060
+ this.analyticsIntegration = void 0;
1975
2061
  /**
1976
2062
  * Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
1977
2063
  * verdad para el context de analytics Y para la segmentación por idioma de los popups.
@@ -2031,10 +2117,17 @@ var DeepdotsPopups = class {
2031
2117
  }
2032
2118
  }
2033
2119
  this.log("tracking \xB7 user_id:", this.tracking.getUserId(), "\xB7 new_user:", this.tracking.isNewUser(), "\xB7 enabled:", this.tracking.isTrackingEnabled());
2120
+ this.analyticsIntegration = config.analytics?.integration;
2121
+ const resumable = this.readResumableSession();
2122
+ if (resumable) {
2123
+ this.analyticsFeedbackSessionId = resumable.sessionId;
2124
+ this.suspended = { at: resumable.at, userId: resumable.userId };
2125
+ }
2034
2126
  const analyticsSink = config.analytics ? createFeedbackSink({
2035
2127
  baseUrl: this.baseUrl,
2036
2128
  keys: config.analytics,
2037
2129
  log: (...a) => this.log(...a),
2130
+ initialSessionId: resumable?.sessionId,
2038
2131
  onSessionId: (id) => {
2039
2132
  this.analyticsFeedbackSessionId = id;
2040
2133
  this.notifyFeedbackSession(id, "open");
@@ -2084,6 +2177,9 @@ var DeepdotsPopups = class {
2084
2177
  this.engagement = new EngagementTracker();
2085
2178
  this.setupAnalyticsFlush();
2086
2179
  this.openSession();
2180
+ if (resumable && this.analyticsFeedbackSessionId === resumable.sessionId) {
2181
+ this.notifyFeedbackSession(resumable.sessionId, "open");
2182
+ }
2087
2183
  const pendingCrashes = this.crashReporter.drainPendingCrashes();
2088
2184
  if (this.tracking?.isTrackingEnabled()) {
2089
2185
  for (const rec of pendingCrashes) {
@@ -2094,8 +2190,10 @@ var DeepdotsPopups = class {
2094
2190
  this.log("SDK initialized", this.config);
2095
2191
  if (this.renderer.init) this.renderer.init();
2096
2192
  this.setupPopupContainer();
2193
+ this.awaitingPopups = Boolean(this.config.apiKey && this.baseUrl);
2097
2194
  this.fetchPopupsFromServer().then((defs) => {
2098
- this.popupDefinitions = this.validatePopupDefinitions(defs);
2195
+ if (!defs) this.dropPendingTriggers();
2196
+ this.popupDefinitions = this.validatePopupDefinitions(defs ?? []);
2099
2197
  this.popupsLoaded = true;
2100
2198
  this.log("Popups loaded from API", this.popupDefinitions);
2101
2199
  if (this.popupDefinitions.length) this.renderer.preload?.();
@@ -2104,6 +2202,7 @@ var DeepdotsPopups = class {
2104
2202
  if (this.pendingAutoLaunch) {
2105
2203
  this.startTriggers();
2106
2204
  }
2205
+ this.replayPendingTriggers();
2107
2206
  });
2108
2207
  }
2109
2208
  /** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
@@ -2332,9 +2431,15 @@ var DeepdotsPopups = class {
2332
2431
  openSession() {
2333
2432
  if (this.sessionOpen) return;
2334
2433
  if (!this.tracking?.isTrackingEnabled()) return;
2434
+ const suspendedMs = this.takeSuspendedSession();
2335
2435
  this.sessionOpen = true;
2336
2436
  this.lastEngagementAt = Date.now();
2337
- this.track("deepdots_session_start", {});
2437
+ if (suspendedMs === null) {
2438
+ this.track("deepdots_session_start", {});
2439
+ } else {
2440
+ this.track("deepdots_session_resume", { suspended_ms: suspendedMs });
2441
+ this.log("tracking \xB7 session resumed after", suspendedMs, "ms:", this.analyticsFeedbackSessionId);
2442
+ }
2338
2443
  this.navObserver?.restart();
2339
2444
  if (this.isDocumentVisible()) this.engagement?.resume();
2340
2445
  }
@@ -2399,6 +2504,75 @@ var DeepdotsPopups = class {
2399
2504
  }
2400
2505
  this.openSession();
2401
2506
  }
2507
+ /**
2508
+ * `pagehide` en web: SUSPENDE la sesión en vez de cerrarla, porque el navegador lo lanza
2509
+ * igual al recargar que al cerrar la pestaña. Vuelca lo pendiente (page_view de la
2510
+ * pantalla, mini-services, engagement) con un `deepdots_session_suspend` y SIN
2511
+ * `completed`, y deja el registro en `sessionStorage` (ver `suspended-session.ts`):
2512
+ * - si la página vuelve a cargar en esta pestaña, `init()` sigue en el mismo registro;
2513
+ * - si la pestaña se cerró, el backend lo cierra a los pocos minutos sin otro lote.
2514
+ *
2515
+ * Devuelve false cuando no se puede suspender — aún no se conoce el registro (el primer
2516
+ * POST sigue en vuelo) o no hay `sessionStorage` — y entonces el caller cierra como antes.
2517
+ */
2518
+ suspendSession() {
2519
+ if (!this.sessionOpen || !this.tracking?.isTrackingEnabled()) return false;
2520
+ const sessionId = this.analyticsFeedbackSessionId;
2521
+ const integration = this.analyticsIntegration;
2522
+ const storage = getSessionStorage();
2523
+ if (!sessionId || !integration || !storage) return false;
2524
+ const at = Date.now();
2525
+ const userId = this.tracking.getUserId();
2526
+ if (!writeSuspendedSession(storage, { sessionId, integration, userId, at })) return false;
2527
+ this.sessionOpen = false;
2528
+ this.suspended = { at, userId };
2529
+ this.navObserver?.stop();
2530
+ this.navStarted = false;
2531
+ this.analytics?.exitAllMiniServices();
2532
+ this.flushEngagement();
2533
+ this.engagement?.pause();
2534
+ this.track("deepdots_session_suspend", { reason: "page_hide" });
2535
+ this.flushAnalytics({ final: true });
2536
+ this.log("tracking \xB7 session suspended:", sessionId);
2537
+ return true;
2538
+ }
2539
+ /**
2540
+ * Recoge la sesión que dejó suspendida la carga anterior de esta pestaña (ver
2541
+ * `suspendSession`). Solo con analytics configurado y el tracking activo: sin ellos no hay
2542
+ * registro en el que seguir.
2543
+ */
2544
+ readResumableSession() {
2545
+ const integration = this.analyticsIntegration;
2546
+ const storage = getSessionStorage();
2547
+ if (!integration || !storage || !this.tracking?.isTrackingEnabled()) return null;
2548
+ return takeSuspendedSession(
2549
+ storage,
2550
+ { integration, userId: this.tracking.getUserId() },
2551
+ Date.now(),
2552
+ ANALYTICS_RESUME_WINDOW_MS
2553
+ );
2554
+ }
2555
+ /**
2556
+ * Consume la sesión suspendida al abrir: los ms que estuvo suspendida si se puede seguir en
2557
+ * ella (mismo usuario, dentro de ANALYTICS_RESUME_WINDOW_MS); null si no había o ya no toca.
2558
+ * En ese caso se olvida su registro sin enviar nada: lo cierra el backend, y la sesión que
2559
+ * se abre ahora va a uno nuevo.
2560
+ */
2561
+ takeSuspendedSession() {
2562
+ const suspended = this.suspended;
2563
+ if (!suspended) return null;
2564
+ this.suspended = null;
2565
+ const storage = getSessionStorage();
2566
+ if (storage) clearSuspendedSession(storage);
2567
+ const suspendedMs = Math.max(0, Date.now() - suspended.at);
2568
+ if (this.analyticsFeedbackSessionId && suspendedMs <= ANALYTICS_RESUME_WINDOW_MS && suspended.userId === (this.tracking?.getUserId() ?? null)) {
2569
+ return suspendedMs;
2570
+ }
2571
+ this.analytics?.discardSession(this.analyticsIdentity());
2572
+ this.analyticsFeedbackSessionId = void 0;
2573
+ this.tracking?.setSessionId(null);
2574
+ return null;
2575
+ }
2402
2576
  /** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
2403
2577
  discardSession() {
2404
2578
  this.sessionOpen = false;
@@ -2447,7 +2621,7 @@ var DeepdotsPopups = class {
2447
2621
  if (typeof document === "undefined" || typeof window === "undefined") return;
2448
2622
  window.addEventListener("pagehide", () => {
2449
2623
  clearInterval(this.analyticsFlushTimer);
2450
- this.closeSession("page_hide");
2624
+ if (!this.suspendSession()) this.closeSession("page_hide");
2451
2625
  });
2452
2626
  window.addEventListener("pageshow", (event) => {
2453
2627
  if (!event.persisted) return;
@@ -2515,6 +2689,7 @@ var DeepdotsPopups = class {
2515
2689
  }
2516
2690
  /** Lógica para evaluar condiciones antes de mostrar una encuesta */
2517
2691
  triggerSurvey(surveyId, popupId) {
2692
+ if (this.bufferIfAwaitingPopups({ kind: "survey", surveyId, popupId })) return;
2518
2693
  const def = this.findPopupDefinition(surveyId, popupId);
2519
2694
  if (!def) {
2520
2695
  this.debug("No popup definition for trigger", { surveyId, popupId });
@@ -2536,6 +2711,10 @@ var DeepdotsPopups = class {
2536
2711
  this.debug("Ignoring empty event trigger name");
2537
2712
  return;
2538
2713
  }
2714
+ if (this.bufferIfAwaitingPopups({ kind: "event", name: normalized })) return;
2715
+ this.evaluateEvent(normalized);
2716
+ }
2717
+ evaluateEvent(normalized) {
2539
2718
  const candidates = this.popupDefinitions.filter((def) => {
2540
2719
  return def.triggers.some((trigger) => {
2541
2720
  const triggerValue = String(trigger.value ?? "").trim();
@@ -2553,6 +2732,40 @@ var DeepdotsPopups = class {
2553
2732
  }
2554
2733
  this.showDefinition(matched);
2555
2734
  }
2735
+ /**
2736
+ * Guarda `trigger` si las definiciones aún no han llegado. Devuelve `true` si lo guardó (el
2737
+ * llamador no debe evaluarlo ahora) y `false` si no hay carga en vuelo (se evalúa ya, como
2738
+ * siempre: tras cargar, tras un fallo o sin apiKey).
2739
+ */
2740
+ bufferIfAwaitingPopups(trigger) {
2741
+ if (!this.awaitingPopups) return false;
2742
+ if (this.pendingTriggers.length >= MAX_PENDING_TRIGGERS) {
2743
+ const dropped = this.pendingTriggers.shift();
2744
+ this.debug("Pending trigger buffer full; dropped oldest", dropped);
2745
+ }
2746
+ this.pendingTriggers.push(trigger);
2747
+ this.debug("Popups not loaded yet; trigger buffered", trigger);
2748
+ return true;
2749
+ }
2750
+ /** La carga falló: sin definiciones no hay nada que reproducir, se descarta todo. */
2751
+ dropPendingTriggers() {
2752
+ const dropped = this.pendingTriggers.length;
2753
+ this.awaitingPopups = false;
2754
+ this.pendingTriggers = [];
2755
+ if (dropped > 0) this.debug("Popups failed to load; dropped pending triggers", dropped);
2756
+ }
2757
+ /** Reproduce en orden por la evaluación normal (segmentos, cooldowns y condiciones de AHORA). */
2758
+ replayPendingTriggers() {
2759
+ this.awaitingPopups = false;
2760
+ const replay = this.pendingTriggers;
2761
+ this.pendingTriggers = [];
2762
+ if (!replay.length) return;
2763
+ this.debug("Replaying triggers received before popups loaded", replay.length);
2764
+ replay.forEach((trigger) => {
2765
+ if (trigger.kind === "event") this.evaluateEvent(trigger.name);
2766
+ else this.triggerSurvey(trigger.surveyId, trigger.popupId);
2767
+ });
2768
+ }
2556
2769
  showDefinition(def) {
2557
2770
  this.log("Showing popup (definition)", def);
2558
2771
  this.surveyToPopupId.set(def.surveyId, def.id);
@@ -2823,7 +3036,7 @@ var DeepdotsPopups = class {
2823
3036
  } catch {
2824
3037
  }
2825
3038
  }
2826
- /** Fetch al servidor para obtener popups */
3039
+ /** Fetch al servidor para obtener popups. `null` si la carga falló (red, no-2xx, payload inválido). */
2827
3040
  async fetchPopupsFromServer() {
2828
3041
  const apiKey = this.config?.apiKey;
2829
3042
  const baseUrl = this.baseUrl;
@@ -2839,19 +3052,19 @@ var DeepdotsPopups = class {
2839
3052
  const response = await fetch(endpoint);
2840
3053
  if (!response.ok) {
2841
3054
  this.log("Failed to fetch popups", response.status, response.statusText);
2842
- return [];
3055
+ return null;
2843
3056
  }
2844
3057
  const raw = await response.text();
2845
3058
  const parsed = JSON.parse(raw);
2846
3059
  if (!Array.isArray(parsed)) {
2847
3060
  this.log("Unexpected popups payload", parsed);
2848
- return [];
3061
+ return null;
2849
3062
  }
2850
3063
  this.debug("Fetched popups payload", parsed);
2851
3064
  return parsed;
2852
3065
  } catch (error) {
2853
3066
  this.log("Error fetching popups", error);
2854
- return [];
3067
+ return null;
2855
3068
  }
2856
3069
  }
2857
3070
  /** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */
@@ -2971,7 +3184,7 @@ var DeepdotsPopups = class {
2971
3184
  }
2972
3185
  if (!this.popupContainer) return;
2973
3186
  const container = this.popupContainer;
2974
- void import("./renderPopup-LEGTSBLD.mjs").then(({ renderPopup }) => {
3187
+ void import("./renderPopup-KSZJ5AN4.mjs").then(({ renderPopup }) => {
2975
3188
  renderPopup(
2976
3189
  container,
2977
3190
  surveyId,