@magicfeedback/popup-sdk 1.8.2 → 1.8.4
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 +34 -2
- package/dist/index.d.mts +48 -2
- package/dist/index.d.ts +48 -2
- package/dist/index.js +132 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +133 -21
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +39 -1
- package/dist/react-native.d.ts +39 -1
- package/dist/react-native.js +130 -18
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +130 -18
- 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
|
@@ -121,7 +121,13 @@ Config fields:
|
|
|
121
121
|
- `logger?: DeepdotsLogger` — replaces `console` for SDK logs
|
|
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
|
-
delivery. Without it the analytics channel stays in dry-run
|
|
124
|
+
delivery. Without it the analytics channel stays in dry-run: nothing is sent, and the
|
|
125
|
+
payload is only logged when `debug` is on.
|
|
126
|
+
- `geolocation?: boolean` — IP geolocation (country/city) for analytics, default `true`. It
|
|
127
|
+
calls third-party services (`ipapi.co`, then `ipwho.is`, then `ipinfo.io` as fallbacks, 3 s
|
|
128
|
+
timeout each, stopping at the first answer). It only runs when `analytics` is set, tracking
|
|
129
|
+
is enabled and the 30-day cache is missing or expired, at most once per SDK instance. Pass
|
|
130
|
+
`false` to never call those services nor attach country/city.
|
|
125
131
|
- `appVersion?: string` — reported in the analytics device context
|
|
126
132
|
- `storage?: KeyValueStorage` — persistence override (React Native uses MMKV; the browser
|
|
127
133
|
defaults to `localStorage`)
|
|
@@ -268,7 +274,7 @@ session start/end and unhandled errors are automatic. The rest is instrumented b
|
|
|
268
274
|
| `trackFunnelStep(funnel, step, taskId, params?)` | A funnel step, correlated by `taskId` |
|
|
269
275
|
| `trackFindabilityFriction(topic, params?)` | Friction signal |
|
|
270
276
|
| `trackMeaningfulInteraction(interactionType, params?)` | A meaningful interaction, grouped by `interaction_type` |
|
|
271
|
-
| `enterMiniService(name, entryPointType?)` / `exitMiniService(name)` |
|
|
277
|
+
| `enterMiniService(name, entryPointType?)` / `exitMiniService(name)` | A task flow with a start and an end — [not a page](#mini-services--enterminiservicename-entrypointtype--exitminiservicename) |
|
|
272
278
|
| `setUserAttributes(map)` / `setMetric(key, value)` | Breakdown dimensions / measurable values |
|
|
273
279
|
| `setUserId(userId?)` | Login, logout or account switch |
|
|
274
280
|
| `endSession()` | Closes the session explicitly |
|
|
@@ -332,6 +338,32 @@ values (oldest evicted first). A rejected event does not consume state: after a
|
|
|
332
338
|
> restricted permissions, will not fire it — so a client-side `delivered` count sits below the
|
|
333
339
|
> real one. For a reliable denominator, take `delivered` from your sending provider.
|
|
334
340
|
|
|
341
|
+
### Mini-services — `enterMiniService(name, entryPointType?)` / `exitMiniService(name)`
|
|
342
|
+
|
|
343
|
+
A mini-service is a **self-contained task inside the app, with a start and an end**: a booking,
|
|
344
|
+
a checkout, an onboarding, a form the user fills in. Deepdots treats it as a flow. It reports
|
|
345
|
+
completion and drop-off per mini-service, draws it in the session journey as a lane between
|
|
346
|
+
where it was entered and where it was left, and tags every event emitted while it is active
|
|
347
|
+
with `mini_service`.
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
popups.enterMiniService('checkout', 'cart_button'); // the user starts the task
|
|
351
|
+
// ...
|
|
352
|
+
popups.exitMiniService('checkout'); // and finishes it
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- Enter it when the task starts and exit it when the task is done. One still open when the
|
|
356
|
+
session closes is exited by the SDK.
|
|
357
|
+
- `entryPointType` is optional and says where the user started it from (a banner, a menu, a
|
|
358
|
+
deep link). It is sent as `entry_point_type`.
|
|
359
|
+
|
|
360
|
+
**A mini-service is not a page, a section or an area of the app.** Navigation is already
|
|
361
|
+
recorded as `page_view`, with the screen and how long the user stayed on it, and time per area
|
|
362
|
+
comes from grouping those page views by screen. Opening a mini-service for every section turns
|
|
363
|
+
each navigation into an abandoned flow: a move from `/feedback` to `/explorer` reads as
|
|
364
|
+
*"entered the explorer mini-service and never completed it"*, the journey loses the edge
|
|
365
|
+
between the two pages, and every section counts as drop-off.
|
|
366
|
+
|
|
335
367
|
## Supported Trigger Types
|
|
336
368
|
|
|
337
369
|
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
|
|
@@ -251,9 +257,17 @@ interface DeepdotsInitParams {
|
|
|
251
257
|
device?: DeviceInfo;
|
|
252
258
|
/**
|
|
253
259
|
* Analytics channel keys (`POST /sdk/feedback`). If provided, analytics is SENT
|
|
254
|
-
* to the configured integration; if missing, it stays in dry-run mode
|
|
260
|
+
* to the configured integration; if missing, it stays in dry-run mode: nothing is sent, and the payload is only
|
|
261
|
+
* logged when `debug` is true.
|
|
255
262
|
*/
|
|
256
263
|
analytics?: AnalyticsKeys;
|
|
264
|
+
/**
|
|
265
|
+
* IP geolocation (country/city) added to analytics. Default `true`. The lookup calls
|
|
266
|
+
* third-party services (ipapi.co, then ipwho.is, then ipinfo.io as fallbacks, 3 s timeout
|
|
267
|
+
* each) and only runs when `analytics` is configured, tracking is enabled and the 30-day
|
|
268
|
+
* cache is missing or expired. Set `false` to never call them nor attach country/city.
|
|
269
|
+
*/
|
|
270
|
+
geolocation?: boolean;
|
|
257
271
|
/**
|
|
258
272
|
* Internal user attributes known only by the host (language, age, plan, ...), sent
|
|
259
273
|
* to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling
|
|
@@ -330,8 +344,10 @@ type EventListener = (event: DeepdotsEvent) => void;
|
|
|
330
344
|
* - `user_change`: `setUserId()` o un `init()` con otro `userId` (login/logout del host).
|
|
331
345
|
* - `tracking_disabled`: `setTrackingEnabled(false)` (consentimiento revocado).
|
|
332
346
|
* - `manual`: `endSession()` llamado por el host.
|
|
347
|
+
* - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta (web). La sesión
|
|
348
|
+
* anterior se cierra y se abre otra, como hace GA con su timeout de sesión.
|
|
333
349
|
*/
|
|
334
|
-
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual';
|
|
350
|
+
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual' | 'idle_timeout';
|
|
335
351
|
/** Trigger type used by remote popup definitions */
|
|
336
352
|
type PopupTriggerType = 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';
|
|
337
353
|
type PopupTriggerConditionStatus = 'SHOWED' | 'PARTIAL' | 'COMPLETED';
|
|
@@ -704,11 +720,21 @@ declare class DeepdotsPopups {
|
|
|
704
720
|
* nueva al volver a foreground o al conceder el consentimiento más tarde.
|
|
705
721
|
*/
|
|
706
722
|
private sessionOpen;
|
|
723
|
+
/** Último `user_engagement` emitido (ms epoch): marca el latido. */
|
|
724
|
+
private lastEngagementAt;
|
|
725
|
+
/** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */
|
|
726
|
+
private hiddenAt;
|
|
707
727
|
/**
|
|
708
728
|
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
709
729
|
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
710
730
|
*/
|
|
711
731
|
private language;
|
|
732
|
+
/** `geolocation` del init: si es false no se usa ni la caché ni el lookup. */
|
|
733
|
+
private geoEnabled;
|
|
734
|
+
/** Solo se consulta a terceros si el país/ciudad se va a enviar (analytics configurado). */
|
|
735
|
+
private geoWanted;
|
|
736
|
+
/** Evita más de un lookup por instancia (el consentimiento puede ir y venir). */
|
|
737
|
+
private geoLookupStarted;
|
|
712
738
|
/** Initialize the SDK with configuration */
|
|
713
739
|
init(config: DeepdotsInitParams): void;
|
|
714
740
|
/** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
|
|
@@ -820,6 +846,26 @@ declare class DeepdotsPopups {
|
|
|
820
846
|
}): void;
|
|
821
847
|
/** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
|
|
822
848
|
private flushEngagement;
|
|
849
|
+
/** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
|
|
850
|
+
private onAnalyticsFlushTick;
|
|
851
|
+
/**
|
|
852
|
+
* La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión
|
|
853
|
+
* anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,
|
|
854
|
+
* si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.
|
|
855
|
+
* - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se
|
|
856
|
+
* cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).
|
|
857
|
+
* - Más: ya la cerró el backend → se descarta sin enviar nada.
|
|
858
|
+
*/
|
|
859
|
+
private onDocumentVisible;
|
|
860
|
+
/** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
|
|
861
|
+
private discardSession;
|
|
862
|
+
/**
|
|
863
|
+
* Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se
|
|
864
|
+
* aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →
|
|
865
|
+
* ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:
|
|
866
|
+
* analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.
|
|
867
|
+
*/
|
|
868
|
+
private resolveGeo;
|
|
823
869
|
private analyticsIdentity;
|
|
824
870
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
825
871
|
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
|
|
@@ -251,9 +257,17 @@ interface DeepdotsInitParams {
|
|
|
251
257
|
device?: DeviceInfo;
|
|
252
258
|
/**
|
|
253
259
|
* Analytics channel keys (`POST /sdk/feedback`). If provided, analytics is SENT
|
|
254
|
-
* to the configured integration; if missing, it stays in dry-run mode
|
|
260
|
+
* to the configured integration; if missing, it stays in dry-run mode: nothing is sent, and the payload is only
|
|
261
|
+
* logged when `debug` is true.
|
|
255
262
|
*/
|
|
256
263
|
analytics?: AnalyticsKeys;
|
|
264
|
+
/**
|
|
265
|
+
* IP geolocation (country/city) added to analytics. Default `true`. The lookup calls
|
|
266
|
+
* third-party services (ipapi.co, then ipwho.is, then ipinfo.io as fallbacks, 3 s timeout
|
|
267
|
+
* each) and only runs when `analytics` is configured, tracking is enabled and the 30-day
|
|
268
|
+
* cache is missing or expired. Set `false` to never call them nor attach country/city.
|
|
269
|
+
*/
|
|
270
|
+
geolocation?: boolean;
|
|
257
271
|
/**
|
|
258
272
|
* Internal user attributes known only by the host (language, age, plan, ...), sent
|
|
259
273
|
* to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling
|
|
@@ -330,8 +344,10 @@ type EventListener = (event: DeepdotsEvent) => void;
|
|
|
330
344
|
* - `user_change`: `setUserId()` o un `init()` con otro `userId` (login/logout del host).
|
|
331
345
|
* - `tracking_disabled`: `setTrackingEnabled(false)` (consentimiento revocado).
|
|
332
346
|
* - `manual`: `endSession()` llamado por el host.
|
|
347
|
+
* - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta (web). La sesión
|
|
348
|
+
* anterior se cierra y se abre otra, como hace GA con su timeout de sesión.
|
|
333
349
|
*/
|
|
334
|
-
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual';
|
|
350
|
+
type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual' | 'idle_timeout';
|
|
335
351
|
/** Trigger type used by remote popup definitions */
|
|
336
352
|
type PopupTriggerType = 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';
|
|
337
353
|
type PopupTriggerConditionStatus = 'SHOWED' | 'PARTIAL' | 'COMPLETED';
|
|
@@ -704,11 +720,21 @@ declare class DeepdotsPopups {
|
|
|
704
720
|
* nueva al volver a foreground o al conceder el consentimiento más tarde.
|
|
705
721
|
*/
|
|
706
722
|
private sessionOpen;
|
|
723
|
+
/** Último `user_engagement` emitido (ms epoch): marca el latido. */
|
|
724
|
+
private lastEngagementAt;
|
|
725
|
+
/** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */
|
|
726
|
+
private hiddenAt;
|
|
707
727
|
/**
|
|
708
728
|
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
709
729
|
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
710
730
|
*/
|
|
711
731
|
private language;
|
|
732
|
+
/** `geolocation` del init: si es false no se usa ni la caché ni el lookup. */
|
|
733
|
+
private geoEnabled;
|
|
734
|
+
/** Solo se consulta a terceros si el país/ciudad se va a enviar (analytics configurado). */
|
|
735
|
+
private geoWanted;
|
|
736
|
+
/** Evita más de un lookup por instancia (el consentimiento puede ir y venir). */
|
|
737
|
+
private geoLookupStarted;
|
|
712
738
|
/** Initialize the SDK with configuration */
|
|
713
739
|
init(config: DeepdotsInitParams): void;
|
|
714
740
|
/** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
|
|
@@ -820,6 +846,26 @@ declare class DeepdotsPopups {
|
|
|
820
846
|
}): void;
|
|
821
847
|
/** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
|
|
822
848
|
private flushEngagement;
|
|
849
|
+
/** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
|
|
850
|
+
private onAnalyticsFlushTick;
|
|
851
|
+
/**
|
|
852
|
+
* La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión
|
|
853
|
+
* anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,
|
|
854
|
+
* si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.
|
|
855
|
+
* - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se
|
|
856
|
+
* cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).
|
|
857
|
+
* - Más: ya la cerró el backend → se descarta sin enviar nada.
|
|
858
|
+
*/
|
|
859
|
+
private onDocumentVisible;
|
|
860
|
+
/** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
|
|
861
|
+
private discardSession;
|
|
862
|
+
/**
|
|
863
|
+
* Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se
|
|
864
|
+
* aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →
|
|
865
|
+
* ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:
|
|
866
|
+
* analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.
|
|
867
|
+
*/
|
|
868
|
+
private resolveGeo;
|
|
823
869
|
private analyticsIdentity;
|
|
824
870
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
825
871
|
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) {
|
|
@@ -7567,7 +7611,9 @@ var DeepdotsPopups = class {
|
|
|
7567
7611
|
onSessionReset: () => {
|
|
7568
7612
|
this.analyticsFeedbackSessionId = void 0;
|
|
7569
7613
|
}
|
|
7570
|
-
}) : createDryRunSink((...a) =>
|
|
7614
|
+
}) : createDryRunSink((...a) => {
|
|
7615
|
+
if (this.config?.debug) this.logger.log(...a);
|
|
7616
|
+
});
|
|
7571
7617
|
const device = config.device ?? collectDeviceInfo(config.appVersion);
|
|
7572
7618
|
this.language = resolveLanguage({
|
|
7573
7619
|
explicit: config.language,
|
|
@@ -7595,15 +7641,9 @@ var DeepdotsPopups = class {
|
|
|
7595
7641
|
if (this.tracking?.isTrackingEnabled()) {
|
|
7596
7642
|
this.crashReporter.install();
|
|
7597
7643
|
}
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
if (geo) {
|
|
7602
|
-
this.analytics?.updateDevice(geo);
|
|
7603
|
-
writeCachedGeo(storage, geo, Date.now());
|
|
7604
|
-
}
|
|
7605
|
-
}).catch(() => {
|
|
7606
|
-
});
|
|
7644
|
+
this.geoEnabled = config.geolocation ?? true;
|
|
7645
|
+
this.geoWanted = this.geoEnabled && !!config.analytics;
|
|
7646
|
+
this.resolveGeo();
|
|
7607
7647
|
this.navObserver = new NavigationObserver();
|
|
7608
7648
|
this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
7609
7649
|
this.navObserver.install();
|
|
@@ -7645,6 +7685,7 @@ var DeepdotsPopups = class {
|
|
|
7645
7685
|
if (!enabled) this.closeSession("tracking_disabled");
|
|
7646
7686
|
this.tracking?.setTrackingEnabled(enabled);
|
|
7647
7687
|
if (enabled) this.openSession();
|
|
7688
|
+
if (enabled) this.resolveGeo();
|
|
7648
7689
|
this.log("tracking \xB7 setTrackingEnabled:", enabled, "\xB7 session_id:", this.tracking?.getSessionId() ?? null);
|
|
7649
7690
|
}
|
|
7650
7691
|
// ───────── Analytics (canal separado del feedback, vinculado por user_id) ─────────
|
|
@@ -7841,6 +7882,7 @@ var DeepdotsPopups = class {
|
|
|
7841
7882
|
if (this.sessionOpen) return;
|
|
7842
7883
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7843
7884
|
this.sessionOpen = true;
|
|
7885
|
+
this.lastEngagementAt = Date.now();
|
|
7844
7886
|
this.track("deepdots_session_start", {});
|
|
7845
7887
|
this.navObserver?.restart();
|
|
7846
7888
|
if (this.isDocumentVisible()) this.engagement?.resume();
|
|
@@ -7871,7 +7913,76 @@ var DeepdotsPopups = class {
|
|
|
7871
7913
|
flushEngagement() {
|
|
7872
7914
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7873
7915
|
const ms = this.engagement?.consume() ?? 0;
|
|
7874
|
-
if (ms > 0)
|
|
7916
|
+
if (ms > 0) {
|
|
7917
|
+
this.track("deepdots_user_engagement", { engagement_time_msec: ms });
|
|
7918
|
+
this.lastEngagementAt = Date.now();
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
7921
|
+
/** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */
|
|
7922
|
+
onAnalyticsFlushTick() {
|
|
7923
|
+
if (this.sessionOpen && this.isDocumentVisible() && Date.now() - this.lastEngagementAt >= ANALYTICS_HEARTBEAT_MS) {
|
|
7924
|
+
this.flushEngagement();
|
|
7925
|
+
}
|
|
7926
|
+
this.flushAnalytics();
|
|
7927
|
+
}
|
|
7928
|
+
/**
|
|
7929
|
+
* La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión
|
|
7930
|
+
* anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,
|
|
7931
|
+
* si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.
|
|
7932
|
+
* - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se
|
|
7933
|
+
* cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).
|
|
7934
|
+
* - Más: ya la cerró el backend → se descarta sin enviar nada.
|
|
7935
|
+
*/
|
|
7936
|
+
onDocumentVisible() {
|
|
7937
|
+
const hiddenFor = this.hiddenAt === null ? 0 : Date.now() - this.hiddenAt;
|
|
7938
|
+
this.hiddenAt = null;
|
|
7939
|
+
if (!this.sessionOpen) return;
|
|
7940
|
+
if (hiddenFor < ANALYTICS_SESSION_TIMEOUT_MS) {
|
|
7941
|
+
this.engagement?.resume();
|
|
7942
|
+
return;
|
|
7943
|
+
}
|
|
7944
|
+
if (hiddenFor < ANALYTICS_SESSION_DISCARD_MS) {
|
|
7945
|
+
this.closeSession("idle_timeout");
|
|
7946
|
+
} else {
|
|
7947
|
+
this.discardSession();
|
|
7948
|
+
}
|
|
7949
|
+
this.openSession();
|
|
7950
|
+
}
|
|
7951
|
+
/** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */
|
|
7952
|
+
discardSession() {
|
|
7953
|
+
this.sessionOpen = false;
|
|
7954
|
+
this.navObserver?.discard();
|
|
7955
|
+
this.analytics?.discardSession(this.analyticsIdentity());
|
|
7956
|
+
this.engagement?.consume();
|
|
7957
|
+
this.engagement?.pause();
|
|
7958
|
+
this.analyticsFeedbackSessionId = void 0;
|
|
7959
|
+
this.tracking?.setSessionId(null);
|
|
7960
|
+
this.log("tracking \xB7 session discarded after", ANALYTICS_SESSION_DISCARD_MS / 6e4, "min hidden");
|
|
7961
|
+
}
|
|
7962
|
+
/**
|
|
7963
|
+
* Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se
|
|
7964
|
+
* aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →
|
|
7965
|
+
* ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:
|
|
7966
|
+
* analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.
|
|
7967
|
+
*/
|
|
7968
|
+
resolveGeo() {
|
|
7969
|
+
const storage = this.storage;
|
|
7970
|
+
if (!this.geoEnabled || !storage || !this.analytics) return;
|
|
7971
|
+
const cachedGeo = readCachedGeo(storage, Date.now());
|
|
7972
|
+
if (cachedGeo) {
|
|
7973
|
+
this.analytics.updateDevice(cachedGeo);
|
|
7974
|
+
return;
|
|
7975
|
+
}
|
|
7976
|
+
if (!this.geoWanted || this.geoLookupStarted) return;
|
|
7977
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
7978
|
+
this.geoLookupStarted = true;
|
|
7979
|
+
fetchGeo().then((geo) => {
|
|
7980
|
+
if (geo) {
|
|
7981
|
+
this.analytics?.updateDevice(geo);
|
|
7982
|
+
writeCachedGeo(storage, geo, Date.now());
|
|
7983
|
+
}
|
|
7984
|
+
}).catch(() => {
|
|
7985
|
+
});
|
|
7875
7986
|
}
|
|
7876
7987
|
analyticsIdentity() {
|
|
7877
7988
|
return {
|
|
@@ -7881,8 +7992,8 @@ var DeepdotsPopups = class {
|
|
|
7881
7992
|
}
|
|
7882
7993
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
7883
7994
|
setupAnalyticsFlush() {
|
|
7884
|
-
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
7885
7995
|
this.startAnalyticsFlushTimer();
|
|
7996
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
7886
7997
|
window.addEventListener("pagehide", () => {
|
|
7887
7998
|
clearInterval(this.analyticsFlushTimer);
|
|
7888
7999
|
this.closeSession("page_hide");
|
|
@@ -7897,14 +8008,15 @@ var DeepdotsPopups = class {
|
|
|
7897
8008
|
this.flushEngagement();
|
|
7898
8009
|
this.engagement?.pause();
|
|
7899
8010
|
this.flushAnalytics();
|
|
8011
|
+
this.hiddenAt = Date.now();
|
|
7900
8012
|
} else if (document.visibilityState === "visible") {
|
|
7901
|
-
|
|
8013
|
+
this.onDocumentVisible();
|
|
7902
8014
|
}
|
|
7903
8015
|
});
|
|
7904
8016
|
}
|
|
7905
8017
|
startAnalyticsFlushTimer() {
|
|
7906
8018
|
clearInterval(this.analyticsFlushTimer);
|
|
7907
|
-
this.analyticsFlushTimer = setInterval(() => this.
|
|
8019
|
+
this.analyticsFlushTimer = setInterval(() => this.onAnalyticsFlushTick(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
7908
8020
|
}
|
|
7909
8021
|
/** Enable auto-launch functionality with configured triggers */
|
|
7910
8022
|
autoLaunch() {
|