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