@magicfeedback/popup-sdk 1.8.2 → 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 +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +129 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +130 -20
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +37 -0
- package/dist/react-native.d.ts +37 -0
- package/dist/react-native.js +127 -17
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +127 -17
- package/dist/react-native.mjs.map +1 -1
- package/dist/{renderPopup-ERUKQE4U.mjs → renderPopup-3CZ6WTXF.mjs} +3 -3
- package/dist/renderPopup-3CZ6WTXF.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/renderPopup-ERUKQE4U.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -122,6 +122,11 @@ Config fields:
|
|
|
122
122
|
- `trackingEnabled?: boolean` — starts analytics on (default) or off, for consent flows
|
|
123
123
|
- `analytics?: { publicKey: string; integration: string }` — enables the real analytics
|
|
124
124
|
delivery. Without it the analytics channel stays in dry-run and only logs the payload.
|
|
125
|
+
- `geolocation?: boolean` — IP geolocation (country/city) for analytics, default `true`. It
|
|
126
|
+
calls third-party services (`ipapi.co`, then `ipwho.is`, then `ipinfo.io` as fallbacks, 3 s
|
|
127
|
+
timeout each, stopping at the first answer). It only runs when `analytics` is set, tracking
|
|
128
|
+
is enabled and the 30-day cache is missing or expired, at most once per SDK instance. Pass
|
|
129
|
+
`false` to never call those services nor attach country/city.
|
|
125
130
|
- `appVersion?: string` — reported in the analytics device context
|
|
126
131
|
- `storage?: KeyValueStorage` — persistence override (React Native uses MMKV; the browser
|
|
127
132
|
defaults to `localStorage`)
|
|
@@ -268,7 +273,7 @@ session start/end and unhandled errors are automatic. The rest is instrumented b
|
|
|
268
273
|
| `trackFunnelStep(funnel, step, taskId, params?)` | A funnel step, correlated by `taskId` |
|
|
269
274
|
| `trackFindabilityFriction(topic, params?)` | Friction signal |
|
|
270
275
|
| `trackMeaningfulInteraction(interactionType, params?)` | A meaningful interaction, grouped by `interaction_type` |
|
|
271
|
-
| `enterMiniService(name, entryPointType?)` / `exitMiniService(name)` |
|
|
276
|
+
| `enterMiniService(name, entryPointType?)` / `exitMiniService(name)` | A task flow with a start and an end — [not a page](#mini-services--enterminiservicename-entrypointtype--exitminiservicename) |
|
|
272
277
|
| `setUserAttributes(map)` / `setMetric(key, value)` | Breakdown dimensions / measurable values |
|
|
273
278
|
| `setUserId(userId?)` | Login, logout or account switch |
|
|
274
279
|
| `endSession()` | Closes the session explicitly |
|
|
@@ -332,6 +337,32 @@ values (oldest evicted first). A rejected event does not consume state: after a
|
|
|
332
337
|
> restricted permissions, will not fire it — so a client-side `delivered` count sits below the
|
|
333
338
|
> real one. For a reliable denominator, take `delivered` from your sending provider.
|
|
334
339
|
|
|
340
|
+
### Mini-services — `enterMiniService(name, entryPointType?)` / `exitMiniService(name)`
|
|
341
|
+
|
|
342
|
+
A mini-service is a **self-contained task inside the app, with a start and an end**: a booking,
|
|
343
|
+
a checkout, an onboarding, a form the user fills in. Deepdots treats it as a flow. It reports
|
|
344
|
+
completion and drop-off per mini-service, draws it in the session journey as a lane between
|
|
345
|
+
where it was entered and where it was left, and tags every event emitted while it is active
|
|
346
|
+
with `mini_service`.
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
popups.enterMiniService('checkout', 'cart_button'); // the user starts the task
|
|
350
|
+
// ...
|
|
351
|
+
popups.exitMiniService('checkout'); // and finishes it
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
- Enter it when the task starts and exit it when the task is done. One still open when the
|
|
355
|
+
session closes is exited by the SDK.
|
|
356
|
+
- `entryPointType` is optional and says where the user started it from (a banner, a menu, a
|
|
357
|
+
deep link). It is sent as `entry_point_type`.
|
|
358
|
+
|
|
359
|
+
**A mini-service is not a page, a section or an area of the app.** Navigation is already
|
|
360
|
+
recorded as `page_view`, with the screen and how long the user stayed on it, and time per area
|
|
361
|
+
comes from grouping those page views by screen. Opening a mini-service for every section turns
|
|
362
|
+
each navigation into an abandoned flow: a move from `/feedback` to `/explorer` reads as
|
|
363
|
+
*"entered the explorer mini-service and never completed it"*, the journey loses the edge
|
|
364
|
+
between the two pages, and every section counts as drop-off.
|
|
365
|
+
|
|
335
366
|
## Supported Trigger Types
|
|
336
367
|
|
|
337
368
|
Popup definitions use these trigger types:
|
package/dist/index.d.mts
CHANGED
|
@@ -105,6 +105,12 @@ interface AnalyticsFlushMeta {
|
|
|
105
105
|
* el `sessionId`, para que el lote siguiente abra un registro nuevo.
|
|
106
106
|
*/
|
|
107
107
|
sessionEnd?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* `true` para olvidar la sesión SIN enviar nada (el payload llega vacío): el backend ya la
|
|
110
|
+
* cerró por inactividad. El sink descarta su `sessionId` para que el lote siguiente abra un
|
|
111
|
+
* registro nuevo, igual que tras `sessionEnd`, pero sin POST.
|
|
112
|
+
*/
|
|
113
|
+
resetSession?: boolean;
|
|
108
114
|
}
|
|
109
115
|
/**
|
|
110
116
|
* Envío del lote. Si devuelve una promesa que **rechaza**, el manager re-encola los
|
|
@@ -254,6 +260,13 @@ interface DeepdotsInitParams {
|
|
|
254
260
|
* to the configured integration; if missing, it stays in dry-run mode (console.log only).
|
|
255
261
|
*/
|
|
256
262
|
analytics?: AnalyticsKeys;
|
|
263
|
+
/**
|
|
264
|
+
* IP geolocation (country/city) added to analytics. Default `true`. The lookup calls
|
|
265
|
+
* third-party services (ipapi.co, then ipwho.is, then ipinfo.io as fallbacks, 3 s timeout
|
|
266
|
+
* each) and only runs when `analytics` is configured, tracking is enabled and the 30-day
|
|
267
|
+
* cache is missing or expired. Set `false` to never call them nor attach country/city.
|
|
268
|
+
*/
|
|
269
|
+
geolocation?: boolean;
|
|
257
270
|
/**
|
|
258
271
|
* Internal user attributes known only by the host (language, age, plan, ...), sent
|
|
259
272
|
* to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling
|
|
@@ -330,8 +343,10 @@ type EventListener = (event: DeepdotsEvent) => void;
|
|
|
330
343
|
* - `user_change`: `setUserId()` o un `init()` con otro `userId` (login/logout del host).
|
|
331
344
|
* - `tracking_disabled`: `setTrackingEnabled(false)` (consentimiento revocado).
|
|
332
345
|
* - `manual`: `endSession()` llamado por el host.
|
|
346
|
+
* - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta (web). La sesión
|
|
347
|
+
* anterior se cierra y se abre otra, como hace GA con su timeout de sesión.
|
|
333
348
|
*/
|
|
334
|
-
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual';
|
|
349
|
+
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual' | 'idle_timeout';
|
|
335
350
|
/** Trigger type used by remote popup definitions */
|
|
336
351
|
type PopupTriggerType = 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';
|
|
337
352
|
type PopupTriggerConditionStatus = 'SHOWED' | 'PARTIAL' | 'COMPLETED';
|
|
@@ -704,11 +719,21 @@ declare class DeepdotsPopups {
|
|
|
704
719
|
* nueva al volver a foreground o al conceder el consentimiento más tarde.
|
|
705
720
|
*/
|
|
706
721
|
private sessionOpen;
|
|
722
|
+
/** Último `user_engagement` emitido (ms epoch): marca el latido. */
|
|
723
|
+
private lastEngagementAt;
|
|
724
|
+
/** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */
|
|
725
|
+
private hiddenAt;
|
|
707
726
|
/**
|
|
708
727
|
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
709
728
|
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
710
729
|
*/
|
|
711
730
|
private language;
|
|
731
|
+
/** `geolocation` del init: si es false no se usa ni la caché ni el lookup. */
|
|
732
|
+
private geoEnabled;
|
|
733
|
+
/** Solo se consulta a terceros si el país/ciudad se va a enviar (analytics configurado). */
|
|
734
|
+
private geoWanted;
|
|
735
|
+
/** Evita más de un lookup por instancia (el consentimiento puede ir y venir). */
|
|
736
|
+
private geoLookupStarted;
|
|
712
737
|
/** Initialize the SDK with configuration */
|
|
713
738
|
init(config: DeepdotsInitParams): void;
|
|
714
739
|
/** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
|
|
@@ -820,6 +845,26 @@ declare class DeepdotsPopups {
|
|
|
820
845
|
}): void;
|
|
821
846
|
/** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
|
|
822
847
|
private flushEngagement;
|
|
848
|
+
/** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
|
|
849
|
+
private onAnalyticsFlushTick;
|
|
850
|
+
/**
|
|
851
|
+
* La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión
|
|
852
|
+
* anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,
|
|
853
|
+
* si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.
|
|
854
|
+
* - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se
|
|
855
|
+
* cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).
|
|
856
|
+
* - Más: ya la cerró el backend → se descarta sin enviar nada.
|
|
857
|
+
*/
|
|
858
|
+
private onDocumentVisible;
|
|
859
|
+
/** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
|
|
860
|
+
private discardSession;
|
|
861
|
+
/**
|
|
862
|
+
* Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se
|
|
863
|
+
* aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →
|
|
864
|
+
* ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:
|
|
865
|
+
* analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.
|
|
866
|
+
*/
|
|
867
|
+
private resolveGeo;
|
|
823
868
|
private analyticsIdentity;
|
|
824
869
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
825
870
|
private setupAnalyticsFlush;
|
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,12 @@ interface AnalyticsFlushMeta {
|
|
|
105
105
|
* el `sessionId`, para que el lote siguiente abra un registro nuevo.
|
|
106
106
|
*/
|
|
107
107
|
sessionEnd?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* `true` para olvidar la sesión SIN enviar nada (el payload llega vacío): el backend ya la
|
|
110
|
+
* cerró por inactividad. El sink descarta su `sessionId` para que el lote siguiente abra un
|
|
111
|
+
* registro nuevo, igual que tras `sessionEnd`, pero sin POST.
|
|
112
|
+
*/
|
|
113
|
+
resetSession?: boolean;
|
|
108
114
|
}
|
|
109
115
|
/**
|
|
110
116
|
* Envío del lote. Si devuelve una promesa que **rechaza**, el manager re-encola los
|
|
@@ -254,6 +260,13 @@ interface DeepdotsInitParams {
|
|
|
254
260
|
* to the configured integration; if missing, it stays in dry-run mode (console.log only).
|
|
255
261
|
*/
|
|
256
262
|
analytics?: AnalyticsKeys;
|
|
263
|
+
/**
|
|
264
|
+
* IP geolocation (country/city) added to analytics. Default `true`. The lookup calls
|
|
265
|
+
* third-party services (ipapi.co, then ipwho.is, then ipinfo.io as fallbacks, 3 s timeout
|
|
266
|
+
* each) and only runs when `analytics` is configured, tracking is enabled and the 30-day
|
|
267
|
+
* cache is missing or expired. Set `false` to never call them nor attach country/city.
|
|
268
|
+
*/
|
|
269
|
+
geolocation?: boolean;
|
|
257
270
|
/**
|
|
258
271
|
* Internal user attributes known only by the host (language, age, plan, ...), sent
|
|
259
272
|
* to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling
|
|
@@ -330,8 +343,10 @@ type EventListener = (event: DeepdotsEvent) => void;
|
|
|
330
343
|
* - `user_change`: `setUserId()` o un `init()` con otro `userId` (login/logout del host).
|
|
331
344
|
* - `tracking_disabled`: `setTrackingEnabled(false)` (consentimiento revocado).
|
|
332
345
|
* - `manual`: `endSession()` llamado por el host.
|
|
346
|
+
* - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta (web). La sesión
|
|
347
|
+
* anterior se cierra y se abre otra, como hace GA con su timeout de sesión.
|
|
333
348
|
*/
|
|
334
|
-
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual';
|
|
349
|
+
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual' | 'idle_timeout';
|
|
335
350
|
/** Trigger type used by remote popup definitions */
|
|
336
351
|
type PopupTriggerType = 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';
|
|
337
352
|
type PopupTriggerConditionStatus = 'SHOWED' | 'PARTIAL' | 'COMPLETED';
|
|
@@ -704,11 +719,21 @@ declare class DeepdotsPopups {
|
|
|
704
719
|
* nueva al volver a foreground o al conceder el consentimiento más tarde.
|
|
705
720
|
*/
|
|
706
721
|
private sessionOpen;
|
|
722
|
+
/** Último `user_engagement` emitido (ms epoch): marca el latido. */
|
|
723
|
+
private lastEngagementAt;
|
|
724
|
+
/** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */
|
|
725
|
+
private hiddenAt;
|
|
707
726
|
/**
|
|
708
727
|
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
709
728
|
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
710
729
|
*/
|
|
711
730
|
private language;
|
|
731
|
+
/** `geolocation` del init: si es false no se usa ni la caché ni el lookup. */
|
|
732
|
+
private geoEnabled;
|
|
733
|
+
/** Solo se consulta a terceros si el país/ciudad se va a enviar (analytics configurado). */
|
|
734
|
+
private geoWanted;
|
|
735
|
+
/** Evita más de un lookup por instancia (el consentimiento puede ir y venir). */
|
|
736
|
+
private geoLookupStarted;
|
|
712
737
|
/** Initialize the SDK with configuration */
|
|
713
738
|
init(config: DeepdotsInitParams): void;
|
|
714
739
|
/** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
|
|
@@ -820,6 +845,26 @@ declare class DeepdotsPopups {
|
|
|
820
845
|
}): void;
|
|
821
846
|
/** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
|
|
822
847
|
private flushEngagement;
|
|
848
|
+
/** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
|
|
849
|
+
private onAnalyticsFlushTick;
|
|
850
|
+
/**
|
|
851
|
+
* La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión
|
|
852
|
+
* anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,
|
|
853
|
+
* si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.
|
|
854
|
+
* - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se
|
|
855
|
+
* cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).
|
|
856
|
+
* - Más: ya la cerró el backend → se descarta sin enviar nada.
|
|
857
|
+
*/
|
|
858
|
+
private onDocumentVisible;
|
|
859
|
+
/** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
|
|
860
|
+
private discardSession;
|
|
861
|
+
/**
|
|
862
|
+
* Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se
|
|
863
|
+
* aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →
|
|
864
|
+
* ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:
|
|
865
|
+
* analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.
|
|
866
|
+
*/
|
|
867
|
+
private resolveGeo;
|
|
823
868
|
private analyticsIdentity;
|
|
824
869
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
825
870
|
private setupAnalyticsFlush;
|
package/dist/index.js
CHANGED
|
@@ -2454,9 +2454,9 @@ var init_logo = __esm({
|
|
|
2454
2454
|
}
|
|
2455
2455
|
});
|
|
2456
2456
|
|
|
2457
|
-
//
|
|
2457
|
+
// node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js
|
|
2458
2458
|
var require_magicfeedback_sdk_browser = __commonJS({
|
|
2459
|
-
"
|
|
2459
|
+
"node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js"(exports, module2) {
|
|
2460
2460
|
"use strict";
|
|
2461
2461
|
!(function(e, t) {
|
|
2462
2462
|
"object" == typeof exports && "object" == typeof module2 ? module2.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.magicfeedback = t() : e.magicfeedback = t();
|
|
@@ -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
|
|
@@ -6843,6 +6859,12 @@ function createFeedbackSink(options) {
|
|
|
6843
6859
|
}
|
|
6844
6860
|
};
|
|
6845
6861
|
return async (envelope, meta) => {
|
|
6862
|
+
if (meta?.resetSession) {
|
|
6863
|
+
feedbackSessionId = void 0;
|
|
6864
|
+
firstPostInFlight = null;
|
|
6865
|
+
options.onSessionReset?.();
|
|
6866
|
+
return;
|
|
6867
|
+
}
|
|
6846
6868
|
const final = meta?.final === true;
|
|
6847
6869
|
const sessionEnd = meta?.sessionEnd === true;
|
|
6848
6870
|
if (!feedbackSessionId && firstPostInFlight && !final) {
|
|
@@ -6909,6 +6931,14 @@ var NavigationObserver = class {
|
|
|
6909
6931
|
this.closeCurrent();
|
|
6910
6932
|
this.currentScreen = null;
|
|
6911
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
|
+
}
|
|
6912
6942
|
/**
|
|
6913
6943
|
* Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión
|
|
6914
6944
|
* nueva sin navegar (cambio de usuario, vuelta de la bfcache). No-op si nunca se instaló
|
|
@@ -7442,6 +7472,10 @@ var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
|
7442
7472
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
7443
7473
|
var ANALYTICS_MAX_BATCH_SIZE = 20;
|
|
7444
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;
|
|
7445
7479
|
var RESERVED_EVENT_PREFIX = "deepdots_";
|
|
7446
7480
|
var CUSTOM_EVENT_PREFIX = "deepdots_event_";
|
|
7447
7481
|
var DeepdotsPopups = class {
|
|
@@ -7505,11 +7539,21 @@ var DeepdotsPopups = class {
|
|
|
7505
7539
|
* nueva al volver a foreground o al conceder el consentimiento más tarde.
|
|
7506
7540
|
*/
|
|
7507
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;
|
|
7508
7546
|
/**
|
|
7509
7547
|
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
7510
7548
|
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
7511
7549
|
*/
|
|
7512
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;
|
|
7513
7557
|
}
|
|
7514
7558
|
/** Initialize the SDK with configuration */
|
|
7515
7559
|
init(config) {
|
|
@@ -7595,15 +7639,9 @@ var DeepdotsPopups = class {
|
|
|
7595
7639
|
if (this.tracking?.isTrackingEnabled()) {
|
|
7596
7640
|
this.crashReporter.install();
|
|
7597
7641
|
}
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
if (geo) {
|
|
7602
|
-
this.analytics?.updateDevice(geo);
|
|
7603
|
-
writeCachedGeo(storage, geo, Date.now());
|
|
7604
|
-
}
|
|
7605
|
-
}).catch(() => {
|
|
7606
|
-
});
|
|
7642
|
+
this.geoEnabled = config.geolocation ?? true;
|
|
7643
|
+
this.geoWanted = this.geoEnabled && !!config.analytics;
|
|
7644
|
+
this.resolveGeo();
|
|
7607
7645
|
this.navObserver = new NavigationObserver();
|
|
7608
7646
|
this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
7609
7647
|
this.navObserver.install();
|
|
@@ -7645,6 +7683,7 @@ var DeepdotsPopups = class {
|
|
|
7645
7683
|
if (!enabled) this.closeSession("tracking_disabled");
|
|
7646
7684
|
this.tracking?.setTrackingEnabled(enabled);
|
|
7647
7685
|
if (enabled) this.openSession();
|
|
7686
|
+
if (enabled) this.resolveGeo();
|
|
7648
7687
|
this.log("tracking \xB7 setTrackingEnabled:", enabled, "\xB7 session_id:", this.tracking?.getSessionId() ?? null);
|
|
7649
7688
|
}
|
|
7650
7689
|
// ───────── Analytics (canal separado del feedback, vinculado por user_id) ─────────
|
|
@@ -7841,6 +7880,7 @@ var DeepdotsPopups = class {
|
|
|
7841
7880
|
if (this.sessionOpen) return;
|
|
7842
7881
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7843
7882
|
this.sessionOpen = true;
|
|
7883
|
+
this.lastEngagementAt = Date.now();
|
|
7844
7884
|
this.track("deepdots_session_start", {});
|
|
7845
7885
|
this.navObserver?.restart();
|
|
7846
7886
|
if (this.isDocumentVisible()) this.engagement?.resume();
|
|
@@ -7871,7 +7911,76 @@ var DeepdotsPopups = class {
|
|
|
7871
7911
|
flushEngagement() {
|
|
7872
7912
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7873
7913
|
const ms = this.engagement?.consume() ?? 0;
|
|
7874
|
-
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
|
+
});
|
|
7875
7984
|
}
|
|
7876
7985
|
analyticsIdentity() {
|
|
7877
7986
|
return {
|
|
@@ -7881,8 +7990,8 @@ var DeepdotsPopups = class {
|
|
|
7881
7990
|
}
|
|
7882
7991
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
7883
7992
|
setupAnalyticsFlush() {
|
|
7884
|
-
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
7885
7993
|
this.startAnalyticsFlushTimer();
|
|
7994
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
7886
7995
|
window.addEventListener("pagehide", () => {
|
|
7887
7996
|
clearInterval(this.analyticsFlushTimer);
|
|
7888
7997
|
this.closeSession("page_hide");
|
|
@@ -7897,14 +8006,15 @@ var DeepdotsPopups = class {
|
|
|
7897
8006
|
this.flushEngagement();
|
|
7898
8007
|
this.engagement?.pause();
|
|
7899
8008
|
this.flushAnalytics();
|
|
8009
|
+
this.hiddenAt = Date.now();
|
|
7900
8010
|
} else if (document.visibilityState === "visible") {
|
|
7901
|
-
|
|
8011
|
+
this.onDocumentVisible();
|
|
7902
8012
|
}
|
|
7903
8013
|
});
|
|
7904
8014
|
}
|
|
7905
8015
|
startAnalyticsFlushTimer() {
|
|
7906
8016
|
clearInterval(this.analyticsFlushTimer);
|
|
7907
|
-
this.analyticsFlushTimer = setInterval(() => this.
|
|
8017
|
+
this.analyticsFlushTimer = setInterval(() => this.onAnalyticsFlushTick(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
7908
8018
|
}
|
|
7909
8019
|
/** Enable auto-launch functionality with configured triggers */
|
|
7910
8020
|
autoLaunch() {
|