@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/README.md +23 -1
- package/dist/assets/assets/style.css +303 -1
- package/dist/{chunk-MTV7T4OW.mjs → chunk-GXEPYT33.mjs} +304 -2
- package/dist/{chunk-MTV7T4OW.mjs.map → chunk-GXEPYT33.mjs.map} +1 -1
- package/dist/{chunk-AKVMUQXO.mjs → chunk-TSORXOQG.mjs} +304 -2
- package/dist/{chunk-AKVMUQXO.mjs.map → chunk-TSORXOQG.mjs.map} +1 -1
- package/dist/index.d.mts +57 -1
- package/dist/index.d.ts +57 -1
- package/dist/index.js +1752 -898
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +229 -16
- 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 +556 -21
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +230 -16
- package/dist/react-native.mjs.map +1 -1
- package/dist/{renderPopup-LEM2MLWT.mjs → renderPopup-JF6IB6HL.mjs} +29 -10
- package/dist/renderPopup-JF6IB6HL.mjs.map +1 -0
- package/dist/{renderPopup-LEGTSBLD.mjs → renderPopup-KSZJ5AN4.mjs} +1231 -892
- package/dist/renderPopup-KSZJ5AN4.mjs.map +1 -0
- package/package.json +5 -3
- package/dist/renderPopup-LEGTSBLD.mjs.map +0 -1
- package/dist/renderPopup-LEM2MLWT.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
sdkWarn,
|
|
22
22
|
setLogger,
|
|
23
23
|
style_default
|
|
24
|
-
} from "./chunk-
|
|
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.
|
|
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
|
|
639
|
-
// ingl\xE9s delante de un survey traducido.
|
|
640
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
3187
|
+
void import("./renderPopup-KSZJ5AN4.mjs").then(({ renderPopup }) => {
|
|
2975
3188
|
renderPopup(
|
|
2976
3189
|
container,
|
|
2977
3190
|
surveyId,
|