@magicfeedback/popup-sdk 1.10.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/README.md +23 -1
- package/dist/index.d.mts +57 -1
- package/dist/index.d.ts +57 -1
- package/dist/index.js +237 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +219 -11
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +52 -1
- package/dist/react-native.d.ts +52 -1
- package/dist/react-native.js +238 -14
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +220 -11
- package/dist/react-native.mjs.map +1 -1
- package/dist/{renderPopup-WLOFOCDR.mjs → renderPopup-JF6IB6HL.mjs} +22 -7
- package/dist/renderPopup-JF6IB6HL.mjs.map +1 -0
- package/dist/{renderPopup-424HR5LF.mjs → renderPopup-KSZJ5AN4.mjs} +22 -7
- package/dist/renderPopup-KSZJ5AN4.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/renderPopup-424HR5LF.mjs.map +0 -1
- package/dist/renderPopup-WLOFOCDR.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -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){
|
|
@@ -637,6 +651,9 @@ ${REVEAL_JS}
|
|
|
637
651
|
// Surveys multi-idioma: native pide a la API este idioma (el del init o el del
|
|
638
652
|
// dispositivo); si el survey no lo tiene, la API sirve el idioma por defecto.
|
|
639
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',
|
|
640
657
|
onLoadedEvent:function(args){
|
|
641
658
|
// Idioma en el que native muestra el survey: manda sobre el del init para que el
|
|
642
659
|
// chrome no se quede en ingl\xE9s delante de un survey traducido. Native < 2.3 no lo
|
|
@@ -696,6 +713,8 @@ ${REVEAL_JS}
|
|
|
696
713
|
updateProgress({ progress:form.progress, total:form.total, completed:false, followup:false });
|
|
697
714
|
emitJSON('loaded');
|
|
698
715
|
setLoading(false);
|
|
716
|
+
// La primera carga es la apertura; las siguientes (tras Start) las enfoca native.
|
|
717
|
+
if(!ddSurveyLoaded){ ddSurveyLoaded=true; ddAutofocusOnOpen(); }
|
|
699
718
|
},
|
|
700
719
|
beforeSubmitEvent:function(){
|
|
701
720
|
setLoading(true);
|
|
@@ -846,6 +865,7 @@ var ReactNativePopupRenderer = class {
|
|
|
846
865
|
this.emitReady(surveyId);
|
|
847
866
|
break;
|
|
848
867
|
case "loaded":
|
|
868
|
+
break;
|
|
849
869
|
case "before_submit":
|
|
850
870
|
case "after_submit":
|
|
851
871
|
case "back":
|
|
@@ -873,7 +893,7 @@ function createReactNativeRenderer() {
|
|
|
873
893
|
}
|
|
874
894
|
|
|
875
895
|
// src/ui/preload.ts
|
|
876
|
-
var defaultLoader = () => import("./renderPopup-
|
|
896
|
+
var defaultLoader = () => import("./renderPopup-KSZJ5AN4.mjs");
|
|
877
897
|
var pending = null;
|
|
878
898
|
function preloadRenderPopup(loader = defaultLoader) {
|
|
879
899
|
if (pending) return pending;
|
|
@@ -946,7 +966,7 @@ var BrowserPopupRenderer = class {
|
|
|
946
966
|
if (!this.container) return;
|
|
947
967
|
this.visible = true;
|
|
948
968
|
const container = this.container;
|
|
949
|
-
void import("./renderPopup-
|
|
969
|
+
void import("./renderPopup-KSZJ5AN4.mjs").then(({ renderPopup }) => {
|
|
950
970
|
renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId, options);
|
|
951
971
|
});
|
|
952
972
|
}
|
|
@@ -1221,7 +1241,7 @@ async function safeBodyText(res) {
|
|
|
1221
1241
|
}
|
|
1222
1242
|
}
|
|
1223
1243
|
function createFeedbackSink(options) {
|
|
1224
|
-
let feedbackSessionId;
|
|
1244
|
+
let feedbackSessionId = options.initialSessionId || void 0;
|
|
1225
1245
|
let firstPostInFlight = null;
|
|
1226
1246
|
let generation = 0;
|
|
1227
1247
|
const url = `${options.baseUrl}/sdk/feedback`;
|
|
@@ -1314,6 +1334,50 @@ function createFeedbackSink(options) {
|
|
|
1314
1334
|
};
|
|
1315
1335
|
}
|
|
1316
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
|
+
|
|
1317
1381
|
// src/tracking/navigation-observer.ts
|
|
1318
1382
|
var UUID_PREFIX = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
|
|
1319
1383
|
function screenPath(href) {
|
|
@@ -1908,8 +1972,10 @@ var ANALYTICS_HEARTBEAT_MS = 5 * 6e4;
|
|
|
1908
1972
|
var ANALYTICS_SESSION_TIMEOUT_MS = 30 * 6e4;
|
|
1909
1973
|
var ANALYTICS_SERVER_IDLE_MS = 60 * 6e4;
|
|
1910
1974
|
var ANALYTICS_SESSION_DISCARD_MS = ANALYTICS_SERVER_IDLE_MS - 10 * 6e4;
|
|
1975
|
+
var ANALYTICS_RESUME_WINDOW_MS = 6e4;
|
|
1911
1976
|
var RESERVED_EVENT_PREFIX = "deepdots_";
|
|
1912
1977
|
var CUSTOM_EVENT_PREFIX = "deepdots_event_";
|
|
1978
|
+
var MAX_PENDING_TRIGGERS = 20;
|
|
1913
1979
|
var DeepdotsPopups = class {
|
|
1914
1980
|
constructor() {
|
|
1915
1981
|
this.config = null;
|
|
@@ -1923,6 +1989,14 @@ var DeepdotsPopups = class {
|
|
|
1923
1989
|
// deprecated: mantenido para compatibilidad interna
|
|
1924
1990
|
this.popupDefinitions = [];
|
|
1925
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 = [];
|
|
1926
2000
|
this.pendingAutoLaunch = false;
|
|
1927
2001
|
// Estado que alimenta las reglas de redisplay. Copia de trabajo en memoria; la fuente
|
|
1928
2002
|
// duradera es el storage (`popup-state-store`), rehidratada en `init()` — sin eso, una
|
|
@@ -1977,6 +2051,13 @@ var DeepdotsPopups = class {
|
|
|
1977
2051
|
this.lastEngagementAt = 0;
|
|
1978
2052
|
/** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */
|
|
1979
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;
|
|
1980
2061
|
/**
|
|
1981
2062
|
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
1982
2063
|
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
@@ -2036,10 +2117,17 @@ var DeepdotsPopups = class {
|
|
|
2036
2117
|
}
|
|
2037
2118
|
}
|
|
2038
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
|
+
}
|
|
2039
2126
|
const analyticsSink = config.analytics ? createFeedbackSink({
|
|
2040
2127
|
baseUrl: this.baseUrl,
|
|
2041
2128
|
keys: config.analytics,
|
|
2042
2129
|
log: (...a) => this.log(...a),
|
|
2130
|
+
initialSessionId: resumable?.sessionId,
|
|
2043
2131
|
onSessionId: (id) => {
|
|
2044
2132
|
this.analyticsFeedbackSessionId = id;
|
|
2045
2133
|
this.notifyFeedbackSession(id, "open");
|
|
@@ -2089,6 +2177,9 @@ var DeepdotsPopups = class {
|
|
|
2089
2177
|
this.engagement = new EngagementTracker();
|
|
2090
2178
|
this.setupAnalyticsFlush();
|
|
2091
2179
|
this.openSession();
|
|
2180
|
+
if (resumable && this.analyticsFeedbackSessionId === resumable.sessionId) {
|
|
2181
|
+
this.notifyFeedbackSession(resumable.sessionId, "open");
|
|
2182
|
+
}
|
|
2092
2183
|
const pendingCrashes = this.crashReporter.drainPendingCrashes();
|
|
2093
2184
|
if (this.tracking?.isTrackingEnabled()) {
|
|
2094
2185
|
for (const rec of pendingCrashes) {
|
|
@@ -2099,8 +2190,10 @@ var DeepdotsPopups = class {
|
|
|
2099
2190
|
this.log("SDK initialized", this.config);
|
|
2100
2191
|
if (this.renderer.init) this.renderer.init();
|
|
2101
2192
|
this.setupPopupContainer();
|
|
2193
|
+
this.awaitingPopups = Boolean(this.config.apiKey && this.baseUrl);
|
|
2102
2194
|
this.fetchPopupsFromServer().then((defs) => {
|
|
2103
|
-
|
|
2195
|
+
if (!defs) this.dropPendingTriggers();
|
|
2196
|
+
this.popupDefinitions = this.validatePopupDefinitions(defs ?? []);
|
|
2104
2197
|
this.popupsLoaded = true;
|
|
2105
2198
|
this.log("Popups loaded from API", this.popupDefinitions);
|
|
2106
2199
|
if (this.popupDefinitions.length) this.renderer.preload?.();
|
|
@@ -2109,6 +2202,7 @@ var DeepdotsPopups = class {
|
|
|
2109
2202
|
if (this.pendingAutoLaunch) {
|
|
2110
2203
|
this.startTriggers();
|
|
2111
2204
|
}
|
|
2205
|
+
this.replayPendingTriggers();
|
|
2112
2206
|
});
|
|
2113
2207
|
}
|
|
2114
2208
|
/** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
|
|
@@ -2337,9 +2431,15 @@ var DeepdotsPopups = class {
|
|
|
2337
2431
|
openSession() {
|
|
2338
2432
|
if (this.sessionOpen) return;
|
|
2339
2433
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2434
|
+
const suspendedMs = this.takeSuspendedSession();
|
|
2340
2435
|
this.sessionOpen = true;
|
|
2341
2436
|
this.lastEngagementAt = Date.now();
|
|
2342
|
-
|
|
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
|
+
}
|
|
2343
2443
|
this.navObserver?.restart();
|
|
2344
2444
|
if (this.isDocumentVisible()) this.engagement?.resume();
|
|
2345
2445
|
}
|
|
@@ -2404,6 +2504,75 @@ var DeepdotsPopups = class {
|
|
|
2404
2504
|
}
|
|
2405
2505
|
this.openSession();
|
|
2406
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
|
+
}
|
|
2407
2576
|
/** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
|
|
2408
2577
|
discardSession() {
|
|
2409
2578
|
this.sessionOpen = false;
|
|
@@ -2452,7 +2621,7 @@ var DeepdotsPopups = class {
|
|
|
2452
2621
|
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
2453
2622
|
window.addEventListener("pagehide", () => {
|
|
2454
2623
|
clearInterval(this.analyticsFlushTimer);
|
|
2455
|
-
this.closeSession("page_hide");
|
|
2624
|
+
if (!this.suspendSession()) this.closeSession("page_hide");
|
|
2456
2625
|
});
|
|
2457
2626
|
window.addEventListener("pageshow", (event) => {
|
|
2458
2627
|
if (!event.persisted) return;
|
|
@@ -2520,6 +2689,7 @@ var DeepdotsPopups = class {
|
|
|
2520
2689
|
}
|
|
2521
2690
|
/** Lógica para evaluar condiciones antes de mostrar una encuesta */
|
|
2522
2691
|
triggerSurvey(surveyId, popupId) {
|
|
2692
|
+
if (this.bufferIfAwaitingPopups({ kind: "survey", surveyId, popupId })) return;
|
|
2523
2693
|
const def = this.findPopupDefinition(surveyId, popupId);
|
|
2524
2694
|
if (!def) {
|
|
2525
2695
|
this.debug("No popup definition for trigger", { surveyId, popupId });
|
|
@@ -2541,6 +2711,10 @@ var DeepdotsPopups = class {
|
|
|
2541
2711
|
this.debug("Ignoring empty event trigger name");
|
|
2542
2712
|
return;
|
|
2543
2713
|
}
|
|
2714
|
+
if (this.bufferIfAwaitingPopups({ kind: "event", name: normalized })) return;
|
|
2715
|
+
this.evaluateEvent(normalized);
|
|
2716
|
+
}
|
|
2717
|
+
evaluateEvent(normalized) {
|
|
2544
2718
|
const candidates = this.popupDefinitions.filter((def) => {
|
|
2545
2719
|
return def.triggers.some((trigger) => {
|
|
2546
2720
|
const triggerValue = String(trigger.value ?? "").trim();
|
|
@@ -2558,6 +2732,40 @@ var DeepdotsPopups = class {
|
|
|
2558
2732
|
}
|
|
2559
2733
|
this.showDefinition(matched);
|
|
2560
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
|
+
}
|
|
2561
2769
|
showDefinition(def) {
|
|
2562
2770
|
this.log("Showing popup (definition)", def);
|
|
2563
2771
|
this.surveyToPopupId.set(def.surveyId, def.id);
|
|
@@ -2828,7 +3036,7 @@ var DeepdotsPopups = class {
|
|
|
2828
3036
|
} catch {
|
|
2829
3037
|
}
|
|
2830
3038
|
}
|
|
2831
|
-
/** Fetch al servidor para obtener popups */
|
|
3039
|
+
/** Fetch al servidor para obtener popups. `null` si la carga falló (red, no-2xx, payload inválido). */
|
|
2832
3040
|
async fetchPopupsFromServer() {
|
|
2833
3041
|
const apiKey = this.config?.apiKey;
|
|
2834
3042
|
const baseUrl = this.baseUrl;
|
|
@@ -2844,19 +3052,19 @@ var DeepdotsPopups = class {
|
|
|
2844
3052
|
const response = await fetch(endpoint);
|
|
2845
3053
|
if (!response.ok) {
|
|
2846
3054
|
this.log("Failed to fetch popups", response.status, response.statusText);
|
|
2847
|
-
return
|
|
3055
|
+
return null;
|
|
2848
3056
|
}
|
|
2849
3057
|
const raw = await response.text();
|
|
2850
3058
|
const parsed = JSON.parse(raw);
|
|
2851
3059
|
if (!Array.isArray(parsed)) {
|
|
2852
3060
|
this.log("Unexpected popups payload", parsed);
|
|
2853
|
-
return
|
|
3061
|
+
return null;
|
|
2854
3062
|
}
|
|
2855
3063
|
this.debug("Fetched popups payload", parsed);
|
|
2856
3064
|
return parsed;
|
|
2857
3065
|
} catch (error) {
|
|
2858
3066
|
this.log("Error fetching popups", error);
|
|
2859
|
-
return
|
|
3067
|
+
return null;
|
|
2860
3068
|
}
|
|
2861
3069
|
}
|
|
2862
3070
|
/** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */
|
|
@@ -2976,7 +3184,7 @@ var DeepdotsPopups = class {
|
|
|
2976
3184
|
}
|
|
2977
3185
|
if (!this.popupContainer) return;
|
|
2978
3186
|
const container = this.popupContainer;
|
|
2979
|
-
void import("./renderPopup-
|
|
3187
|
+
void import("./renderPopup-KSZJ5AN4.mjs").then(({ renderPopup }) => {
|
|
2980
3188
|
renderPopup(
|
|
2981
3189
|
container,
|
|
2982
3190
|
surveyId,
|