@magicfeedback/popup-sdk 1.1.1 → 1.1.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.
@@ -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
  }
@@ -204,6 +216,30 @@ interface PopupRenderer {
204
216
  type ContactAttributeValue = string | number | boolean;
205
217
  type ContactAttributes = Record<string, ContactAttributeValue>;
206
218
 
219
+ /**
220
+ * Crash & error reporting (Stability #14–17) — captura de errores GESTIONADOS.
221
+ *
222
+ * - `reportError()`: API pública para el host (app viva) → emite `deepdots_app_crash` ya.
223
+ * - handlers de `window` (errores no capturados): persisten a disco y se reenvían
224
+ * en el siguiente arranque (el proceso puede morir antes del flush normal).
225
+ *
226
+ * El contexto de device/sesión se captura EN EL MOMENTO del crash y se guarda en el
227
+ * record — no se lee del envelope en el replay (el replay puede ocurrir en otra
228
+ * app_version/OS). Las dims OS/device en Web suelen ir vacías (se derivan del UA en backend).
229
+ */
230
+
231
+ type CrashSeverity = 'fatal' | 'error' | 'warning';
232
+ interface ReportErrorOptions {
233
+ severity?: CrashSeverity;
234
+ handled?: boolean;
235
+ context?: Record<string, unknown>;
236
+ }
237
+ /** Forma mínima de `global.ErrorUtils` de React Native. */
238
+ interface ReactNativeErrorUtils {
239
+ getGlobalHandler?(): ((error: unknown, isFatal?: boolean) => void) | undefined;
240
+ setGlobalHandler(handler: (error: unknown, isFatal?: boolean) => void): void;
241
+ }
242
+
207
243
  /**
208
244
  * Main class for managing survey popups
209
245
  */
@@ -228,6 +264,12 @@ declare class DeepdotsPopups {
228
264
  private tracking;
229
265
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
230
266
  private analytics;
267
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
268
+ private analyticsFeedbackSessionId;
269
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
270
+ private analyticsFlushTimer;
271
+ /** Crash & error reporting (#14–17). Null hasta init(). */
272
+ private crashReporter;
231
273
  /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
232
274
  private navObserver;
233
275
  /** Tiempo activo (engagement time, #8). */
@@ -248,6 +290,10 @@ declare class DeepdotsPopups {
248
290
  track(name: string, params?: Record<string, unknown>): void;
249
291
  /** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
250
292
  setUserAttributes(attributes: Record<string, string | number | boolean>): void;
293
+ /** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
294
+ reportError(error: unknown, options?: ReportErrorOptions): void;
295
+ /** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
296
+ installReactNativeCrashHandler(errorUtils: ReactNativeErrorUtils): void;
251
297
  /**
252
298
  * Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
253
299
  * Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
@@ -257,8 +303,8 @@ declare class DeepdotsPopups {
257
303
  setContactAttributes(attributes: ContactAttributes): Promise<boolean>;
258
304
  /** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */
259
305
  enterMiniService(name: string, entryPointType?: string): void;
260
- /** Cierra el mini-service activo (emite `mini_service_exit` con duración, #27). No-op si tracking off. */
261
- exitMiniService(): void;
306
+ /** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */
307
+ exitMiniService(name: string): void;
262
308
  /** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
263
309
  trackSearch(query: string, resultsCount: number, params?: Record<string, unknown>): void;
264
310
  /** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
@@ -390,6 +436,8 @@ interface ReactNativeSetupDeps {
390
436
  appState?: AppStateLike | null;
391
437
  platform?: 'web' | 'android' | 'ios';
392
438
  renderer?: PopupRenderer;
439
+ /** `global.ErrorUtils` de RN (para capturar errores JS no manejados). Default: `globalThis.ErrorUtils`. */
440
+ errorUtils?: ReactNativeErrorUtils | null;
393
441
  }
394
442
  /**
395
443
  * Configura el SDK para React Native en una sola llamada: storage persistente (MMKV),
@@ -437,7 +485,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
437
485
  private partialEmitted;
438
486
  constructor(options?: ReactNativeRendererOptions);
439
487
  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;
488
+ 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
489
  hide(): void;
442
490
  /**
443
491
  * 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
  }
@@ -204,6 +216,30 @@ interface PopupRenderer {
204
216
  type ContactAttributeValue = string | number | boolean;
205
217
  type ContactAttributes = Record<string, ContactAttributeValue>;
206
218
 
219
+ /**
220
+ * Crash & error reporting (Stability #14–17) — captura de errores GESTIONADOS.
221
+ *
222
+ * - `reportError()`: API pública para el host (app viva) → emite `deepdots_app_crash` ya.
223
+ * - handlers de `window` (errores no capturados): persisten a disco y se reenvían
224
+ * en el siguiente arranque (el proceso puede morir antes del flush normal).
225
+ *
226
+ * El contexto de device/sesión se captura EN EL MOMENTO del crash y se guarda en el
227
+ * record — no se lee del envelope en el replay (el replay puede ocurrir en otra
228
+ * app_version/OS). Las dims OS/device en Web suelen ir vacías (se derivan del UA en backend).
229
+ */
230
+
231
+ type CrashSeverity = 'fatal' | 'error' | 'warning';
232
+ interface ReportErrorOptions {
233
+ severity?: CrashSeverity;
234
+ handled?: boolean;
235
+ context?: Record<string, unknown>;
236
+ }
237
+ /** Forma mínima de `global.ErrorUtils` de React Native. */
238
+ interface ReactNativeErrorUtils {
239
+ getGlobalHandler?(): ((error: unknown, isFatal?: boolean) => void) | undefined;
240
+ setGlobalHandler(handler: (error: unknown, isFatal?: boolean) => void): void;
241
+ }
242
+
207
243
  /**
208
244
  * Main class for managing survey popups
209
245
  */
@@ -228,6 +264,12 @@ declare class DeepdotsPopups {
228
264
  private tracking;
229
265
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
230
266
  private analytics;
267
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
268
+ private analyticsFeedbackSessionId;
269
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
270
+ private analyticsFlushTimer;
271
+ /** Crash & error reporting (#14–17). Null hasta init(). */
272
+ private crashReporter;
231
273
  /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
232
274
  private navObserver;
233
275
  /** Tiempo activo (engagement time, #8). */
@@ -248,6 +290,10 @@ declare class DeepdotsPopups {
248
290
  track(name: string, params?: Record<string, unknown>): void;
249
291
  /** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
250
292
  setUserAttributes(attributes: Record<string, string | number | boolean>): void;
293
+ /** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
294
+ reportError(error: unknown, options?: ReportErrorOptions): void;
295
+ /** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
296
+ installReactNativeCrashHandler(errorUtils: ReactNativeErrorUtils): void;
251
297
  /**
252
298
  * Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
253
299
  * Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
@@ -257,8 +303,8 @@ declare class DeepdotsPopups {
257
303
  setContactAttributes(attributes: ContactAttributes): Promise<boolean>;
258
304
  /** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */
259
305
  enterMiniService(name: string, entryPointType?: string): void;
260
- /** Cierra el mini-service activo (emite `mini_service_exit` con duración, #27). No-op si tracking off. */
261
- exitMiniService(): void;
306
+ /** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */
307
+ exitMiniService(name: string): void;
262
308
  /** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
263
309
  trackSearch(query: string, resultsCount: number, params?: Record<string, unknown>): void;
264
310
  /** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
@@ -390,6 +436,8 @@ interface ReactNativeSetupDeps {
390
436
  appState?: AppStateLike | null;
391
437
  platform?: 'web' | 'android' | 'ios';
392
438
  renderer?: PopupRenderer;
439
+ /** `global.ErrorUtils` de RN (para capturar errores JS no manejados). Default: `globalThis.ErrorUtils`. */
440
+ errorUtils?: ReactNativeErrorUtils | null;
393
441
  }
394
442
  /**
395
443
  * Configura el SDK para React Native en una sola llamada: storage persistente (MMKV),
@@ -437,7 +485,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
437
485
  private partialEmitted;
438
486
  constructor(options?: ReactNativeRendererOptions);
439
487
  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;
488
+ 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
489
  hide(): void;
442
490
  /**
443
491
  * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a