@magicfeedback/popup-sdk 1.8.1 → 1.8.3
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 +32 -1
- package/dist/index.d.mts +71 -12
- package/dist/index.d.ts +71 -12
- package/dist/index.js +172 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +172 -33
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +52 -5
- package/dist/react-native.d.ts +52 -5
- package/dist/react-native.js +172 -33
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +172 -33
- package/dist/react-native.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6564,10 +6564,13 @@ init_tracking_manager();
|
|
|
6564
6564
|
|
|
6565
6565
|
// src/analytics/analytics-manager.ts
|
|
6566
6566
|
function createDryRunSink(log = console.log) {
|
|
6567
|
-
return (payload) =>
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6567
|
+
return (payload, meta) => {
|
|
6568
|
+
if (meta?.resetSession) return;
|
|
6569
|
+
log(
|
|
6570
|
+
"[DeepdotsAnalytics] (dry-run \xB7 NOT sent \xB7 no init.analytics) POST /sdk/feedback \u2192",
|
|
6571
|
+
JSON.stringify(payload, null, 2)
|
|
6572
|
+
);
|
|
6573
|
+
};
|
|
6571
6574
|
}
|
|
6572
6575
|
var dryRunSink = createDryRunSink();
|
|
6573
6576
|
var AnalyticsManager = class {
|
|
@@ -6634,6 +6637,19 @@ var AnalyticsManager = class {
|
|
|
6634
6637
|
this.exitMiniService(name);
|
|
6635
6638
|
}
|
|
6636
6639
|
}
|
|
6640
|
+
/**
|
|
6641
|
+
* Olvida la sesión en curso SIN emitir nada: vacía el buffer y los mini-services activos
|
|
6642
|
+
* (sin `mini_service_exit`). Para una sesión que el backend ya cerró por inactividad: lo
|
|
6643
|
+
* que quedara pendiente pertenecía a ella y no debe llegar con la sesión nueva.
|
|
6644
|
+
*/
|
|
6645
|
+
discardSession(identity) {
|
|
6646
|
+
this.events = [];
|
|
6647
|
+
this.activeMiniServices.clear();
|
|
6648
|
+
try {
|
|
6649
|
+
void this.sink({ ...this.buildPayload(identity), events: [] }, { resetSession: true });
|
|
6650
|
+
} catch {
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6637
6653
|
/**
|
|
6638
6654
|
* Olvida lo que pertenecía al usuario anterior (user attributes + métricas) al cambiar de
|
|
6639
6655
|
* usuario. NO toca el buffer de eventos: el cierre de sesión ya lo vació con la identidad
|
|
@@ -6804,14 +6820,7 @@ function createFeedbackSink(options) {
|
|
|
6804
6820
|
const post = async (body, final, sessionEnd = false) => {
|
|
6805
6821
|
const json = JSON.stringify(body);
|
|
6806
6822
|
const small = json.length <= KEEPALIVE_MAX_BYTES;
|
|
6807
|
-
if (final
|
|
6808
|
-
const payload = typeof Blob !== "undefined" ? new Blob([json], { type: "application/json" }) : json;
|
|
6809
|
-
if (options.sendBeaconImpl(url, payload)) {
|
|
6810
|
-
options.log?.("[DeepdotsAnalytics] final flush via sendBeacon");
|
|
6811
|
-
return;
|
|
6812
|
-
}
|
|
6813
|
-
options.log?.("[DeepdotsAnalytics] sendBeacon rejected the batch; falling back to fetch");
|
|
6814
|
-
}
|
|
6823
|
+
if (final) options.log?.("[DeepdotsAnalytics] final flush via fetch keepalive");
|
|
6815
6824
|
const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
6816
6825
|
if (!f) {
|
|
6817
6826
|
options.log?.("[DeepdotsAnalytics] no fetch available; payload not sent", body);
|
|
@@ -6821,6 +6830,9 @@ function createFeedbackSink(options) {
|
|
|
6821
6830
|
method: "POST",
|
|
6822
6831
|
headers: { "Content-Type": "application/json" },
|
|
6823
6832
|
body: json,
|
|
6833
|
+
// La API autentica por `publicKey` en el body y responde `Allow-Origin: *`: una
|
|
6834
|
+
// petición con credenciales fallaría el CORS. Explícito para no depender del default.
|
|
6835
|
+
credentials: "omit",
|
|
6824
6836
|
// keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.
|
|
6825
6837
|
...small ? { keepalive: true } : {}
|
|
6826
6838
|
});
|
|
@@ -6847,6 +6859,12 @@ function createFeedbackSink(options) {
|
|
|
6847
6859
|
}
|
|
6848
6860
|
};
|
|
6849
6861
|
return async (envelope, meta) => {
|
|
6862
|
+
if (meta?.resetSession) {
|
|
6863
|
+
feedbackSessionId = void 0;
|
|
6864
|
+
firstPostInFlight = null;
|
|
6865
|
+
options.onSessionReset?.();
|
|
6866
|
+
return;
|
|
6867
|
+
}
|
|
6850
6868
|
const final = meta?.final === true;
|
|
6851
6869
|
const sessionEnd = meta?.sessionEnd === true;
|
|
6852
6870
|
if (!feedbackSessionId && firstPostInFlight && !final) {
|
|
@@ -6913,6 +6931,23 @@ var NavigationObserver = class {
|
|
|
6913
6931
|
this.closeCurrent();
|
|
6914
6932
|
this.currentScreen = null;
|
|
6915
6933
|
}
|
|
6934
|
+
/**
|
|
6935
|
+
* Olvida la pantalla actual SIN emitir su visita. Para una sesión que el backend ya dio
|
|
6936
|
+
* por cerrada: su visita no tiene dónde ir, y mandarla con la sesión nueva la contaría
|
|
6937
|
+
* donde no ocurrió. Después, `restart()` vuelve a empezar en la pantalla actual.
|
|
6938
|
+
*/
|
|
6939
|
+
discard() {
|
|
6940
|
+
this.currentScreen = null;
|
|
6941
|
+
}
|
|
6942
|
+
/**
|
|
6943
|
+
* Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión
|
|
6944
|
+
* nueva sin navegar (cambio de usuario, vuelta de la bfcache). No-op si nunca se instaló
|
|
6945
|
+
* (navegación manual por `setScreen`, RN) o si ya hay pantalla en curso.
|
|
6946
|
+
*/
|
|
6947
|
+
restart() {
|
|
6948
|
+
if (!this.installed || this.currentScreen != null || typeof window === "undefined") return;
|
|
6949
|
+
this.begin(window.location.href);
|
|
6950
|
+
}
|
|
6916
6951
|
closeCurrent() {
|
|
6917
6952
|
if (this.currentScreen == null) return;
|
|
6918
6953
|
const exit = this.now();
|
|
@@ -7437,6 +7472,10 @@ var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
|
7437
7472
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
7438
7473
|
var ANALYTICS_MAX_BATCH_SIZE = 20;
|
|
7439
7474
|
var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
|
|
7475
|
+
var ANALYTICS_HEARTBEAT_MS = 5 * 6e4;
|
|
7476
|
+
var ANALYTICS_SESSION_TIMEOUT_MS = 30 * 6e4;
|
|
7477
|
+
var ANALYTICS_SERVER_IDLE_MS = 60 * 6e4;
|
|
7478
|
+
var ANALYTICS_SESSION_DISCARD_MS = ANALYTICS_SERVER_IDLE_MS - 10 * 6e4;
|
|
7440
7479
|
var RESERVED_EVENT_PREFIX = "deepdots_";
|
|
7441
7480
|
var CUSTOM_EVENT_PREFIX = "deepdots_event_";
|
|
7442
7481
|
var DeepdotsPopups = class {
|
|
@@ -7500,11 +7539,21 @@ var DeepdotsPopups = class {
|
|
|
7500
7539
|
* nueva al volver a foreground o al conceder el consentimiento más tarde.
|
|
7501
7540
|
*/
|
|
7502
7541
|
this.sessionOpen = false;
|
|
7542
|
+
/** Último `user_engagement` emitido (ms epoch): marca el latido. */
|
|
7543
|
+
this.lastEngagementAt = 0;
|
|
7544
|
+
/** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */
|
|
7545
|
+
this.hiddenAt = null;
|
|
7503
7546
|
/**
|
|
7504
7547
|
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
7505
7548
|
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
7506
7549
|
*/
|
|
7507
7550
|
this.language = void 0;
|
|
7551
|
+
/** `geolocation` del init: si es false no se usa ni la caché ni el lookup. */
|
|
7552
|
+
this.geoEnabled = true;
|
|
7553
|
+
/** Solo se consulta a terceros si el país/ciudad se va a enviar (analytics configurado). */
|
|
7554
|
+
this.geoWanted = false;
|
|
7555
|
+
/** Evita más de un lookup por instancia (el consentimiento puede ir y venir). */
|
|
7556
|
+
this.geoLookupStarted = false;
|
|
7508
7557
|
}
|
|
7509
7558
|
/** Initialize the SDK with configuration */
|
|
7510
7559
|
init(config) {
|
|
@@ -7556,8 +7605,6 @@ var DeepdotsPopups = class {
|
|
|
7556
7605
|
baseUrl: this.baseUrl,
|
|
7557
7606
|
keys: config.analytics,
|
|
7558
7607
|
log: (...a) => this.log(...a),
|
|
7559
|
-
// sendBeacon: único transporte que sobrevive al cierre de la página.
|
|
7560
|
-
sendBeaconImpl: typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? (url, body) => navigator.sendBeacon(url, body) : void 0,
|
|
7561
7608
|
onSessionId: (id) => {
|
|
7562
7609
|
this.analyticsFeedbackSessionId = id;
|
|
7563
7610
|
},
|
|
@@ -7592,20 +7639,13 @@ var DeepdotsPopups = class {
|
|
|
7592
7639
|
if (this.tracking?.isTrackingEnabled()) {
|
|
7593
7640
|
this.crashReporter.install();
|
|
7594
7641
|
}
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
if (geo) {
|
|
7599
|
-
this.analytics?.updateDevice(geo);
|
|
7600
|
-
writeCachedGeo(storage, geo, Date.now());
|
|
7601
|
-
}
|
|
7602
|
-
}).catch(() => {
|
|
7603
|
-
});
|
|
7642
|
+
this.geoEnabled = config.geolocation ?? true;
|
|
7643
|
+
this.geoWanted = this.geoEnabled && !!config.analytics;
|
|
7644
|
+
this.resolveGeo();
|
|
7604
7645
|
this.navObserver = new NavigationObserver();
|
|
7605
7646
|
this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
7606
7647
|
this.navObserver.install();
|
|
7607
7648
|
this.engagement = new EngagementTracker();
|
|
7608
|
-
this.engagement.resume();
|
|
7609
7649
|
this.setupAnalyticsFlush();
|
|
7610
7650
|
this.openSession();
|
|
7611
7651
|
const pendingCrashes = this.crashReporter.drainPendingCrashes();
|
|
@@ -7643,6 +7683,7 @@ var DeepdotsPopups = class {
|
|
|
7643
7683
|
if (!enabled) this.closeSession("tracking_disabled");
|
|
7644
7684
|
this.tracking?.setTrackingEnabled(enabled);
|
|
7645
7685
|
if (enabled) this.openSession();
|
|
7686
|
+
if (enabled) this.resolveGeo();
|
|
7646
7687
|
this.log("tracking \xB7 setTrackingEnabled:", enabled, "\xB7 session_id:", this.tracking?.getSessionId() ?? null);
|
|
7647
7688
|
}
|
|
7648
7689
|
// ───────── Analytics (canal separado del feedback, vinculado por user_id) ─────────
|
|
@@ -7824,6 +7865,7 @@ var DeepdotsPopups = class {
|
|
|
7824
7865
|
this.navStarted = false;
|
|
7825
7866
|
this.analytics?.exitAllMiniServices();
|
|
7826
7867
|
this.flushEngagement();
|
|
7868
|
+
this.engagement?.pause();
|
|
7827
7869
|
this.track("deepdots_session_end", { reason });
|
|
7828
7870
|
this.flushAnalytics({ final: reason === "page_hide", sessionEnd: true });
|
|
7829
7871
|
this.analyticsFeedbackSessionId = void 0;
|
|
@@ -7838,7 +7880,14 @@ var DeepdotsPopups = class {
|
|
|
7838
7880
|
if (this.sessionOpen) return;
|
|
7839
7881
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7840
7882
|
this.sessionOpen = true;
|
|
7883
|
+
this.lastEngagementAt = Date.now();
|
|
7841
7884
|
this.track("deepdots_session_start", {});
|
|
7885
|
+
this.navObserver?.restart();
|
|
7886
|
+
if (this.isDocumentVisible()) this.engagement?.resume();
|
|
7887
|
+
}
|
|
7888
|
+
/** Web: si la pestaña se ve. Sin `document` (RN) manda AppState vía onForeground/onBackground. */
|
|
7889
|
+
isDocumentVisible() {
|
|
7890
|
+
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
7842
7891
|
}
|
|
7843
7892
|
/** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */
|
|
7844
7893
|
previewAnalytics() {
|
|
@@ -7851,8 +7900,8 @@ var DeepdotsPopups = class {
|
|
|
7851
7900
|
}
|
|
7852
7901
|
/**
|
|
7853
7902
|
* Envía el lote acumulado de analytics y vacía el buffer.
|
|
7854
|
-
* `final: true` (cierre de página/app)
|
|
7855
|
-
*
|
|
7903
|
+
* `final: true` (cierre de página/app): el lote no espera al primer POST en vuelo, porque
|
|
7904
|
+
* el documento se muere; un lote que falle por red o 5xx se re-encola para el siguiente flush.
|
|
7856
7905
|
*/
|
|
7857
7906
|
flushAnalytics(options) {
|
|
7858
7907
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
@@ -7862,7 +7911,76 @@ var DeepdotsPopups = class {
|
|
|
7862
7911
|
flushEngagement() {
|
|
7863
7912
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7864
7913
|
const ms = this.engagement?.consume() ?? 0;
|
|
7865
|
-
if (ms > 0)
|
|
7914
|
+
if (ms > 0) {
|
|
7915
|
+
this.track("deepdots_user_engagement", { engagement_time_msec: ms });
|
|
7916
|
+
this.lastEngagementAt = Date.now();
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
/** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
|
|
7920
|
+
onAnalyticsFlushTick() {
|
|
7921
|
+
if (this.sessionOpen && this.isDocumentVisible() && Date.now() - this.lastEngagementAt >= ANALYTICS_HEARTBEAT_MS) {
|
|
7922
|
+
this.flushEngagement();
|
|
7923
|
+
}
|
|
7924
|
+
this.flushAnalytics();
|
|
7925
|
+
}
|
|
7926
|
+
/**
|
|
7927
|
+
* La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión
|
|
7928
|
+
* anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,
|
|
7929
|
+
* si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.
|
|
7930
|
+
* - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se
|
|
7931
|
+
* cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).
|
|
7932
|
+
* - Más: ya la cerró el backend → se descarta sin enviar nada.
|
|
7933
|
+
*/
|
|
7934
|
+
onDocumentVisible() {
|
|
7935
|
+
const hiddenFor = this.hiddenAt === null ? 0 : Date.now() - this.hiddenAt;
|
|
7936
|
+
this.hiddenAt = null;
|
|
7937
|
+
if (!this.sessionOpen) return;
|
|
7938
|
+
if (hiddenFor < ANALYTICS_SESSION_TIMEOUT_MS) {
|
|
7939
|
+
this.engagement?.resume();
|
|
7940
|
+
return;
|
|
7941
|
+
}
|
|
7942
|
+
if (hiddenFor < ANALYTICS_SESSION_DISCARD_MS) {
|
|
7943
|
+
this.closeSession("idle_timeout");
|
|
7944
|
+
} else {
|
|
7945
|
+
this.discardSession();
|
|
7946
|
+
}
|
|
7947
|
+
this.openSession();
|
|
7948
|
+
}
|
|
7949
|
+
/** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
|
|
7950
|
+
discardSession() {
|
|
7951
|
+
this.sessionOpen = false;
|
|
7952
|
+
this.navObserver?.discard();
|
|
7953
|
+
this.analytics?.discardSession(this.analyticsIdentity());
|
|
7954
|
+
this.engagement?.consume();
|
|
7955
|
+
this.engagement?.pause();
|
|
7956
|
+
this.analyticsFeedbackSessionId = void 0;
|
|
7957
|
+
this.tracking?.setSessionId(null);
|
|
7958
|
+
this.log("tracking \xB7 session discarded after", ANALYTICS_SESSION_DISCARD_MS / 6e4, "min hidden");
|
|
7959
|
+
}
|
|
7960
|
+
/**
|
|
7961
|
+
* Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se
|
|
7962
|
+
* aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →
|
|
7963
|
+
* ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:
|
|
7964
|
+
* analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.
|
|
7965
|
+
*/
|
|
7966
|
+
resolveGeo() {
|
|
7967
|
+
const storage = this.storage;
|
|
7968
|
+
if (!this.geoEnabled || !storage || !this.analytics) return;
|
|
7969
|
+
const cachedGeo = readCachedGeo(storage, Date.now());
|
|
7970
|
+
if (cachedGeo) {
|
|
7971
|
+
this.analytics.updateDevice(cachedGeo);
|
|
7972
|
+
return;
|
|
7973
|
+
}
|
|
7974
|
+
if (!this.geoWanted || this.geoLookupStarted) return;
|
|
7975
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7976
|
+
this.geoLookupStarted = true;
|
|
7977
|
+
fetchGeo().then((geo) => {
|
|
7978
|
+
if (geo) {
|
|
7979
|
+
this.analytics?.updateDevice(geo);
|
|
7980
|
+
writeCachedGeo(storage, geo, Date.now());
|
|
7981
|
+
}
|
|
7982
|
+
}).catch(() => {
|
|
7983
|
+
});
|
|
7866
7984
|
}
|
|
7867
7985
|
analyticsIdentity() {
|
|
7868
7986
|
return {
|
|
@@ -7872,22 +7990,32 @@ var DeepdotsPopups = class {
|
|
|
7872
7990
|
}
|
|
7873
7991
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
7874
7992
|
setupAnalyticsFlush() {
|
|
7993
|
+
this.startAnalyticsFlushTimer();
|
|
7875
7994
|
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
7876
|
-
this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
7877
7995
|
window.addEventListener("pagehide", () => {
|
|
7878
7996
|
clearInterval(this.analyticsFlushTimer);
|
|
7879
7997
|
this.closeSession("page_hide");
|
|
7880
7998
|
});
|
|
7999
|
+
window.addEventListener("pageshow", (event) => {
|
|
8000
|
+
if (!event.persisted) return;
|
|
8001
|
+
this.startAnalyticsFlushTimer();
|
|
8002
|
+
this.openSession();
|
|
8003
|
+
});
|
|
7881
8004
|
document.addEventListener("visibilitychange", () => {
|
|
7882
8005
|
if (document.visibilityState === "hidden") {
|
|
7883
8006
|
this.flushEngagement();
|
|
7884
8007
|
this.engagement?.pause();
|
|
7885
8008
|
this.flushAnalytics();
|
|
8009
|
+
this.hiddenAt = Date.now();
|
|
7886
8010
|
} else if (document.visibilityState === "visible") {
|
|
7887
|
-
this.
|
|
8011
|
+
this.onDocumentVisible();
|
|
7888
8012
|
}
|
|
7889
8013
|
});
|
|
7890
8014
|
}
|
|
8015
|
+
startAnalyticsFlushTimer() {
|
|
8016
|
+
clearInterval(this.analyticsFlushTimer);
|
|
8017
|
+
this.analyticsFlushTimer = setInterval(() => this.onAnalyticsFlushTick(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
8018
|
+
}
|
|
7891
8019
|
/** Enable auto-launch functionality with configured triggers */
|
|
7892
8020
|
autoLaunch() {
|
|
7893
8021
|
if (!this.initialized) {
|
|
@@ -7981,6 +8109,7 @@ var DeepdotsPopups = class {
|
|
|
7981
8109
|
this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
|
|
7982
8110
|
}
|
|
7983
8111
|
shouldShow(def, pathUrl, skipPathCheck = false) {
|
|
8112
|
+
if (this.storage) this.loadPopupState(this.storage);
|
|
7984
8113
|
if (this.isPathExcluded(def, pathUrl)) {
|
|
7985
8114
|
return false;
|
|
7986
8115
|
}
|
|
@@ -8507,22 +8636,32 @@ var DeepdotsPopups = class {
|
|
|
8507
8636
|
this.persistPopupState();
|
|
8508
8637
|
}
|
|
8509
8638
|
/**
|
|
8510
|
-
*
|
|
8511
|
-
*
|
|
8639
|
+
* Fusiona el estado de redisplay del storage con el de memoria: por cada popup/survey gana
|
|
8640
|
+
* la entrada MÁS RECIENTE. Fusionar y no sustituir es lo que hace convivir a varias
|
|
8641
|
+
* pestañas sobre el mismo `localStorage`: se llama en el `init()`, antes de cada decisión
|
|
8642
|
+
* de mostrar y antes de cada escritura. `answeredSurveys` se reconstruye desde las
|
|
8643
|
+
* entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.
|
|
8512
8644
|
*/
|
|
8513
8645
|
loadPopupState(storage) {
|
|
8514
8646
|
const state = readPopupState(storage);
|
|
8515
8647
|
Object.entries(state.lastShown).forEach(([popupId, timestamp]) => {
|
|
8516
|
-
this.lastShown.set(popupId, timestamp);
|
|
8648
|
+
if (timestamp > (this.lastShown.get(popupId) ?? -Infinity)) this.lastShown.set(popupId, timestamp);
|
|
8517
8649
|
});
|
|
8518
8650
|
Object.entries(state.progress).forEach(([surveyId, entry]) => {
|
|
8651
|
+
const current = this.surveyProgress.get(surveyId);
|
|
8652
|
+
if (current && current.timestamp >= entry.timestamp) return;
|
|
8519
8653
|
this.surveyProgress.set(surveyId, { status: entry.status, timestamp: entry.timestamp });
|
|
8520
8654
|
if (entry.status === "COMPLETED") this.answeredSurveys.add(surveyId);
|
|
8521
8655
|
});
|
|
8522
8656
|
}
|
|
8523
|
-
/**
|
|
8657
|
+
/**
|
|
8658
|
+
* Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria.
|
|
8659
|
+
* Fusiona antes de escribir: escribir solo la foto en memoria borraba lo que otra pestaña
|
|
8660
|
+
* hubiese guardado desde nuestro `init()`.
|
|
8661
|
+
*/
|
|
8524
8662
|
persistPopupState() {
|
|
8525
8663
|
if (!this.storage) return;
|
|
8664
|
+
this.loadPopupState(this.storage);
|
|
8526
8665
|
writePopupState(this.storage, {
|
|
8527
8666
|
lastShown: Object.fromEntries(this.lastShown),
|
|
8528
8667
|
progress: Object.fromEntries(this.surveyProgress)
|