@magicfeedback/popup-sdk 1.1.1 → 1.1.2

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.
@@ -21,10 +21,11 @@ interface KeyValueStorage {
21
21
  /**
22
22
  * Device info para el context de analytics (Technology #11–13).
23
23
  *
24
- * En Web enviamos `device_type` (heurística fiable) + `user_agent` crudo, y dejamos
25
- * que el backend derive OS/modelo/navegador del UA (como hace GA) — el parseo de UA en
26
- * cliente es frágil. `app_version` lo provee el cliente (una web no tiene versión de app
27
- * nativa). En KMP las APIs nativas dan os_version/device_model reales.
24
+ * En Web enviamos `device_type` + `user_agent` crudo y dejamos que el backend
25
+ * derive OS/navegador del UA (como hace GA). Los campos adicionales (timezone,
26
+ * referrer, viewport, etc.) se recogen de las APIs del navegador sin permisos.
27
+ * `country`/`city` se rellenan de forma asíncrona (ver geo-info.ts).
28
+ * En KMP las APIs nativas dan os_version/device_model reales.
28
29
  */
29
30
  interface DeviceInfo {
30
31
  device_type: 'mobile' | 'tablet' | 'desktop';
@@ -32,6 +33,16 @@ interface DeviceInfo {
32
33
  device_model?: string;
33
34
  app_version?: string;
34
35
  user_agent?: string;
36
+ timezone?: string;
37
+ referrer?: string;
38
+ viewport_size?: string;
39
+ screen_resolution?: string;
40
+ pixel_ratio?: number;
41
+ entry_type?: string;
42
+ page_load_ms?: number;
43
+ connection_type?: string;
44
+ country?: string;
45
+ city?: string;
35
46
  }
36
47
 
37
48
  /**
@@ -76,11 +87,12 @@ interface AnalyticsEnvelope {
76
87
  * INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false` y
77
88
  * `finished:false` (nunca se "cierra"); el backend cose por `sessionId` + `user_id`.
78
89
  *
79
- * Encoding (decisión 2026-06-22):
80
- * - cada evento (dato recabado) → una `metric` {key: nombre_evento, value: JSON(timestamp + params)}
90
+ * Encoding:
91
+ * - todo va en `feedback.metadata`: contexto (user_id, session_id, platform…) + eventos
92
+ * - cada entrada usa `value: string[]` (array de un elemento)
93
+ * - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}
81
94
  * - identidad (user_id) → `profile` como `external-user-id`
82
- * - contexto (user_id, session_id, platform, language, device, attributes) → `metadata`
83
- * - `answers` vacío
95
+ * - `answers` vacío, `metrics` no se usa
84
96
  * - `text` vacío
85
97
  */
86
98
 
@@ -195,7 +207,7 @@ interface PopupRenderer {
195
207
  /** Preparar recursos si aplica */
196
208
  init?(): void;
197
209
  /** Mostrar popup */
198
- show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string): void;
210
+ show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
199
211
  /** Ocultar popup */
200
212
  hide(): void;
201
213
  }
@@ -228,6 +240,10 @@ declare class DeepdotsPopups {
228
240
  private tracking;
229
241
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
230
242
  private analytics;
243
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
244
+ private analyticsFeedbackSessionId;
245
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
246
+ private analyticsFlushTimer;
231
247
  /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
232
248
  private navObserver;
233
249
  /** Tiempo activo (engagement time, #8). */
@@ -437,7 +453,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
437
453
  private partialEmitted;
438
454
  constructor(options?: ReactNativeRendererOptions);
439
455
  init(): void;
440
- show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string): void;
456
+ show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
441
457
  hide(): void;
442
458
  /**
443
459
  * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
@@ -21,10 +21,11 @@ interface KeyValueStorage {
21
21
  /**
22
22
  * Device info para el context de analytics (Technology #11–13).
23
23
  *
24
- * En Web enviamos `device_type` (heurística fiable) + `user_agent` crudo, y dejamos
25
- * que el backend derive OS/modelo/navegador del UA (como hace GA) — el parseo de UA en
26
- * cliente es frágil. `app_version` lo provee el cliente (una web no tiene versión de app
27
- * nativa). En KMP las APIs nativas dan os_version/device_model reales.
24
+ * En Web enviamos `device_type` + `user_agent` crudo y dejamos que el backend
25
+ * derive OS/navegador del UA (como hace GA). Los campos adicionales (timezone,
26
+ * referrer, viewport, etc.) se recogen de las APIs del navegador sin permisos.
27
+ * `country`/`city` se rellenan de forma asíncrona (ver geo-info.ts).
28
+ * En KMP las APIs nativas dan os_version/device_model reales.
28
29
  */
29
30
  interface DeviceInfo {
30
31
  device_type: 'mobile' | 'tablet' | 'desktop';
@@ -32,6 +33,16 @@ interface DeviceInfo {
32
33
  device_model?: string;
33
34
  app_version?: string;
34
35
  user_agent?: string;
36
+ timezone?: string;
37
+ referrer?: string;
38
+ viewport_size?: string;
39
+ screen_resolution?: string;
40
+ pixel_ratio?: number;
41
+ entry_type?: string;
42
+ page_load_ms?: number;
43
+ connection_type?: string;
44
+ country?: string;
45
+ city?: string;
35
46
  }
36
47
 
37
48
  /**
@@ -76,11 +87,12 @@ interface AnalyticsEnvelope {
76
87
  * INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false` y
77
88
  * `finished:false` (nunca se "cierra"); el backend cose por `sessionId` + `user_id`.
78
89
  *
79
- * Encoding (decisión 2026-06-22):
80
- * - cada evento (dato recabado) → una `metric` {key: nombre_evento, value: JSON(timestamp + params)}
90
+ * Encoding:
91
+ * - todo va en `feedback.metadata`: contexto (user_id, session_id, platform…) + eventos
92
+ * - cada entrada usa `value: string[]` (array de un elemento)
93
+ * - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}
81
94
  * - identidad (user_id) → `profile` como `external-user-id`
82
- * - contexto (user_id, session_id, platform, language, device, attributes) → `metadata`
83
- * - `answers` vacío
95
+ * - `answers` vacío, `metrics` no se usa
84
96
  * - `text` vacío
85
97
  */
86
98
 
@@ -195,7 +207,7 @@ interface PopupRenderer {
195
207
  /** Preparar recursos si aplica */
196
208
  init?(): void;
197
209
  /** Mostrar popup */
198
- show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string): void;
210
+ show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
199
211
  /** Ocultar popup */
200
212
  hide(): void;
201
213
  }
@@ -228,6 +240,10 @@ declare class DeepdotsPopups {
228
240
  private tracking;
229
241
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
230
242
  private analytics;
243
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
244
+ private analyticsFeedbackSessionId;
245
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
246
+ private analyticsFlushTimer;
231
247
  /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
232
248
  private navObserver;
233
249
  /** Tiempo activo (engagement time, #8). */
@@ -437,7 +453,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
437
453
  private partialEmitted;
438
454
  constructor(options?: ReactNativeRendererOptions);
439
455
  init(): void;
440
- show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string): void;
456
+ show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
441
457
  hide(): void;
442
458
  /**
443
459
  * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
@@ -48,12 +48,13 @@ function createDefaultStorage() {
48
48
  }
49
49
  return new InMemoryStorage();
50
50
  }
51
- function buildSurveyIdentity(userId, sessionId, miniService = null) {
51
+ function buildSurveyIdentity(userId, sessionId, miniService = null, analyticsFeedbackSessionId = null) {
52
52
  const profile = userId ? [{ key: "external-user-id", value: [userId] }] : [];
53
53
  const metadata = [];
54
54
  if (sessionId) metadata.push({ key: "session_id", value: [sessionId] });
55
55
  if (userId) metadata.push({ key: "user_id", value: [userId] });
56
56
  if (miniService) metadata.push({ key: "mini_service", value: [miniService] });
57
+ if (analyticsFeedbackSessionId) metadata.push({ key: "deepdots_analytics_feedback_session_id", value: [analyticsFeedbackSessionId] });
57
58
  return { profile, metadata };
58
59
  }
59
60
  function defaultUuid() {
@@ -1063,7 +1064,7 @@ function ensureResponsiveStyles(_popup) {
1063
1064
  `;
1064
1065
  document.head.appendChild(style);
1065
1066
  }
1066
- async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
1067
+ async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
1067
1068
  let surveyCompletedEmitted = false;
1068
1069
  let stylesInjected = false;
1069
1070
  const isDark = style?.theme === "dark";
@@ -1374,7 +1375,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
1374
1375
  env: env === "production" ? "prod" : "dev"
1375
1376
  }
1376
1377
  );
1377
- const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
1378
+ const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
1378
1379
  formInstance = import_native.default.form(surveyId, productId, profile, metadata);
1379
1380
  const generateOptions = {
1380
1381
  addButton: false,
@@ -1787,12 +1788,12 @@ var ReactNativePopupRenderer = class {
1787
1788
  }
1788
1789
  init() {
1789
1790
  }
1790
- show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
1791
+ show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
1791
1792
  this.emitFn = emit;
1792
1793
  this.onCloseFn = onClose;
1793
1794
  this.currentSurveyId = surveyId;
1794
1795
  this.partialEmitted = false;
1795
- const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
1796
+ const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
1796
1797
  const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
1797
1798
  if (this.options.onShow) {
1798
1799
  this.options.onShow({ surveyId, productId, html });
@@ -1883,14 +1884,14 @@ var BrowserPopupRenderer = class {
1883
1884
  document.body.appendChild(this.container);
1884
1885
  }
1885
1886
  }
1886
- show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
1887
+ show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
1887
1888
  if (this.visible) return;
1888
1889
  if (!this.container || !document.body.contains(this.container)) this.init();
1889
1890
  if (!this.container) return;
1890
1891
  this.visible = true;
1891
1892
  const container = this.container;
1892
1893
  void Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports)).then(({ renderPopup: renderPopup2 }) => {
1893
- renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
1894
+ renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
1894
1895
  });
1895
1896
  }
1896
1897
  hide() {
@@ -1932,6 +1933,12 @@ var AnalyticsManager = class {
1932
1933
  this.language = options.language;
1933
1934
  this.device = options.device;
1934
1935
  this.platform = options.platform ?? "web";
1936
+ this.maxBatchSize = options.maxBatchSize ?? 20;
1937
+ this.onFlushNeeded = options.onFlushNeeded;
1938
+ }
1939
+ /** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
1940
+ updateDevice(partial) {
1941
+ this.device = { ...this.device, ...partial };
1935
1942
  }
1936
1943
  /** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
1937
1944
  setUserAttributes(attrs) {
@@ -1965,6 +1972,9 @@ var AnalyticsManager = class {
1965
1972
  timestamp: this.now(),
1966
1973
  params: Object.keys(merged).length ? merged : void 0
1967
1974
  });
1975
+ if (this.events.length >= this.maxBatchSize) {
1976
+ this.onFlushNeeded?.();
1977
+ }
1968
1978
  }
1969
1979
  /** Mini-service activo (para inyectarlo en la metadata del survey, #33). */
1970
1980
  getMiniService() {
@@ -2002,44 +2012,55 @@ var AnalyticsManager = class {
2002
2012
  // src/analytics/feedback-payload.ts
2003
2013
  function pushKV(list, key, value) {
2004
2014
  if (value === void 0 || value === null || value === "") return;
2005
- list.push({ key, value: String(value) });
2015
+ list.push({ key, value: [String(value)] });
2006
2016
  }
2007
2017
  function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
2008
2018
  const { context } = envelope;
2009
- const metrics = envelope.events.map((e) => ({
2010
- key: e.name,
2011
- value: JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })
2012
- }));
2013
2019
  const profile = [];
2014
2020
  pushKV(profile, "external-user-id", envelope.userId);
2015
2021
  const metadata = [];
2016
- pushKV(metadata, "user_id", envelope.userId);
2017
- pushKV(metadata, "session_id", envelope.sessionId);
2018
- pushKV(metadata, "platform", context.platform);
2019
- pushKV(metadata, "language", context.language);
2022
+ pushKV(metadata, "deepdots_user_id", envelope.userId);
2023
+ pushKV(metadata, "deepdots_session_id", envelope.sessionId);
2024
+ pushKV(metadata, "deepdots_platform", context.platform);
2025
+ pushKV(metadata, "deepdots_language", context.language);
2020
2026
  if (context.device) {
2021
- pushKV(metadata, "device_type", context.device.device_type);
2022
- pushKV(metadata, "os_version", context.device.os_version);
2023
- pushKV(metadata, "device_model", context.device.device_model);
2024
- pushKV(metadata, "app_version", context.device.app_version);
2025
- pushKV(metadata, "user_agent", context.device.user_agent);
2027
+ const d = context.device;
2028
+ pushKV(metadata, "deepdots_device_type", d.device_type);
2029
+ pushKV(metadata, "deepdots_os_version", d.os_version);
2030
+ pushKV(metadata, "deepdots_device_model", d.device_model);
2031
+ pushKV(metadata, "deepdots_app_version", d.app_version);
2032
+ pushKV(metadata, "deepdots_user_agent", d.user_agent);
2033
+ pushKV(metadata, "deepdots_timezone", d.timezone);
2034
+ pushKV(metadata, "deepdots_referrer", d.referrer);
2035
+ pushKV(metadata, "deepdots_viewport_size", d.viewport_size);
2036
+ pushKV(metadata, "deepdots_screen_resolution", d.screen_resolution);
2037
+ pushKV(metadata, "deepdots_pixel_ratio", d.pixel_ratio);
2038
+ pushKV(metadata, "deepdots_entry_type", d.entry_type);
2039
+ pushKV(metadata, "deepdots_page_load_ms", d.page_load_ms);
2040
+ pushKV(metadata, "deepdots_connection_type", d.connection_type);
2041
+ pushKV(metadata, "deepdots_country", d.country);
2042
+ pushKV(metadata, "deepdots_city", d.city);
2026
2043
  }
2027
2044
  for (const [k, v] of Object.entries(context.attributes ?? {})) {
2028
2045
  pushKV(metadata, k, v);
2029
2046
  }
2047
+ for (const e of envelope.events) {
2048
+ metadata.push({
2049
+ key: e.name,
2050
+ value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
2051
+ });
2052
+ }
2030
2053
  return {
2031
2054
  feedback: {
2032
2055
  text: "",
2033
2056
  answers: [],
2034
2057
  metadata,
2035
- metrics,
2036
2058
  profile,
2037
2059
  finished: false
2038
2060
  },
2039
2061
  publicKey: keys.publicKey,
2040
2062
  integration: keys.integration,
2041
2063
  completed: false,
2042
- // finished: false,
2043
2064
  ...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
2044
2065
  };
2045
2066
  }
@@ -2064,6 +2085,7 @@ function createFeedbackSink(options) {
2064
2085
  if (data?.sessionId && data.sessionId !== feedbackSessionId) {
2065
2086
  feedbackSessionId = data.sessionId;
2066
2087
  options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
2088
+ options.onSessionId?.(feedbackSessionId);
2067
2089
  }
2068
2090
  } catch {
2069
2091
  }
@@ -2177,15 +2199,57 @@ function parseDeviceType(ua) {
2177
2199
  }
2178
2200
  return "desktop";
2179
2201
  }
2202
+ function getEntryType() {
2203
+ if (typeof performance === "undefined") return void 0;
2204
+ const entries = performance.getEntriesByType("navigation");
2205
+ if (!entries.length) return void 0;
2206
+ return entries[0].type;
2207
+ }
2208
+ function getPageLoadMs() {
2209
+ if (typeof performance === "undefined") return void 0;
2210
+ const entries = performance.getEntriesByType("navigation");
2211
+ if (!entries.length) return void 0;
2212
+ const load = entries[0].loadEventEnd;
2213
+ return load > 0 ? Math.round(load) : void 0;
2214
+ }
2215
+ function getConnectionType() {
2216
+ if (typeof navigator === "undefined") return void 0;
2217
+ return navigator.connection?.effectiveType ?? void 0;
2218
+ }
2180
2219
  function collectDeviceInfo(appVersion) {
2181
2220
  const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
2182
2221
  return {
2183
2222
  device_type: parseDeviceType(ua),
2184
2223
  user_agent: ua || void 0,
2185
- app_version: appVersion
2224
+ app_version: appVersion,
2225
+ timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0,
2226
+ referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
2227
+ viewport_size: typeof window !== "undefined" ? `${window.innerWidth}x${window.innerHeight}` : void 0,
2228
+ screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
2229
+ pixel_ratio: typeof window !== "undefined" ? window.devicePixelRatio : void 0,
2230
+ entry_type: getEntryType(),
2231
+ page_load_ms: getPageLoadMs(),
2232
+ connection_type: getConnectionType()
2186
2233
  };
2187
2234
  }
2188
2235
 
2236
+ // src/analytics/geo-info.ts
2237
+ async function collectGeoInfo(fetchImpl) {
2238
+ const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
2239
+ if (!f) return null;
2240
+ try {
2241
+ const res = await f("https://ipapi.co/json/");
2242
+ if (!res.ok) return null;
2243
+ const data = await res.json();
2244
+ const country = data.country_code || void 0;
2245
+ const city = data.city || void 0;
2246
+ if (!country && !city) return null;
2247
+ return { country, city };
2248
+ } catch {
2249
+ return null;
2250
+ }
2251
+ }
2252
+
2189
2253
  // src/analytics/engagement-tracker.ts
2190
2254
  var EngagementTracker = class {
2191
2255
  constructor(now = () => Date.now()) {
@@ -2257,6 +2321,8 @@ var ContactManager = class {
2257
2321
  // src/core/deepdots-popups.ts
2258
2322
  var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
2259
2323
  var DAY_IN_MS = 24 * 60 * 60 * 1e3;
2324
+ var ANALYTICS_MAX_BATCH_SIZE = 20;
2325
+ var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
2260
2326
  var DeepdotsPopups = class {
2261
2327
  constructor() {
2262
2328
  this.config = null;
@@ -2282,6 +2348,10 @@ var DeepdotsPopups = class {
2282
2348
  this.tracking = null;
2283
2349
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
2284
2350
  this.analytics = null;
2351
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
2352
+ this.analyticsFeedbackSessionId = void 0;
2353
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
2354
+ this.analyticsFlushTimer = void 0;
2285
2355
  /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
2286
2356
  this.navObserver = null;
2287
2357
  /** Tiempo activo (engagement time, #8). */
@@ -2328,14 +2398,23 @@ var DeepdotsPopups = class {
2328
2398
  const analyticsSink = config.analytics ? createFeedbackSink({
2329
2399
  baseUrl: this.baseUrl,
2330
2400
  keys: config.analytics,
2331
- log: (...a) => this.log(...a)
2401
+ log: (...a) => this.log(...a),
2402
+ onSessionId: (id) => {
2403
+ this.analyticsFeedbackSessionId = id;
2404
+ }
2332
2405
  }) : void 0;
2333
2406
  this.analytics = new AnalyticsManager({
2334
2407
  sink: analyticsSink,
2335
2408
  publicKey: config.analytics?.publicKey ?? this.config.apiKey,
2336
2409
  language: typeof navigator !== "undefined" ? navigator.language : void 0,
2337
2410
  platform: config.platform ?? "web",
2338
- device: config.device ?? collectDeviceInfo(config.appVersion)
2411
+ device: config.device ?? collectDeviceInfo(config.appVersion),
2412
+ maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
2413
+ onFlushNeeded: () => this.flushAnalytics()
2414
+ });
2415
+ collectGeoInfo().then((geo) => {
2416
+ if (geo) this.analytics?.updateDevice(geo);
2417
+ }).catch(() => {
2339
2418
  });
2340
2419
  this.navObserver = new NavigationObserver();
2341
2420
  this.navObserver.onVisit((v) => this.track("page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
@@ -2476,7 +2555,9 @@ var DeepdotsPopups = class {
2476
2555
  /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
2477
2556
  setupAnalyticsFlush() {
2478
2557
  if (typeof document === "undefined" || typeof window === "undefined") return;
2558
+ this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
2479
2559
  window.addEventListener("pagehide", () => {
2560
+ clearInterval(this.analyticsFlushTimer);
2480
2561
  this.navObserver?.stop();
2481
2562
  this.exitMiniService();
2482
2563
  this.flushEngagement();
@@ -2920,7 +3001,8 @@ var DeepdotsPopups = class {
2920
3001
  const userId = this.tracking?.getUserId() ?? this.config?.userId;
2921
3002
  const sessionId = this.tracking?.getSessionId() ?? void 0;
2922
3003
  const miniService = this.analytics?.getMiniService() ?? void 0;
2923
- this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null });
3004
+ const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
3005
+ this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
2924
3006
  if (this.renderer) {
2925
3007
  this.renderer.show(
2926
3008
  surveyId,
@@ -2932,7 +3014,8 @@ var DeepdotsPopups = class {
2932
3014
  userId,
2933
3015
  style,
2934
3016
  sessionId,
2935
- miniService
3017
+ miniService,
3018
+ analyticsFeedbackSessionId
2936
3019
  );
2937
3020
  return;
2938
3021
  }
@@ -2950,7 +3033,8 @@ var DeepdotsPopups = class {
2950
3033
  userId,
2951
3034
  style,
2952
3035
  sessionId,
2953
- miniService
3036
+ miniService,
3037
+ analyticsFeedbackSessionId
2954
3038
  );
2955
3039
  });
2956
3040
  }