@magicfeedback/popup-sdk 1.1.0 → 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.
package/dist/index.d.mts CHANGED
@@ -1,29 +1,196 @@
1
+ /**
2
+ * Identidad + sesión para el tracking de navegación (Fase 1).
3
+ * Implementa el "Contrato común Web+KMP".
4
+ *
5
+ * Modelo: el SDK gestiona el `user_id` (persistente). El `session_id` es propiedad
6
+ * del BACKEND: llega en la respuesta de `POST /sdk/popups` y se cachea; el backend
7
+ * cose sesiones por `user_id` + ventana. El SDK NO genera ni expira sesiones.
8
+ *
9
+ * Sin dependencias de DOM: recibe el storage y (opcionalmente) el reloj y el
10
+ * generador de uuid por inyección. El binding a `localStorage` vive en `createDefaultStorage`.
11
+ */
12
+ /** Store clave/valor mínimo (espejo conceptual del `KeyValueStorage` de KMP). */
13
+ interface KeyValueStorage {
14
+ getItem(key: string): string | null;
15
+ setItem(key: string, value: string): void;
16
+ removeItem(key: string): void;
17
+ }
18
+ /** Impl en memoria — usada en tests y como fallback sin `localStorage`. */
19
+ declare class InMemoryStorage implements KeyValueStorage {
20
+ private map;
21
+ getItem(key: string): string | null;
22
+ setItem(key: string, value: string): void;
23
+ removeItem(key: string): void;
24
+ }
25
+ /** Par clave/valor con el formato `NativeAnswer` que espera `@magicfeedback/native`. */
26
+ interface IdentityAnswer {
27
+ key: string;
28
+ value: string[];
29
+ }
30
+
31
+ /**
32
+ * Device info para el context de analytics (Technology #11–13).
33
+ *
34
+ * En Web enviamos `device_type` + `user_agent` crudo y dejamos que el backend
35
+ * derive OS/navegador del UA (como hace GA). Los campos adicionales (timezone,
36
+ * referrer, viewport, etc.) se recogen de las APIs del navegador sin permisos.
37
+ * `country`/`city` se rellenan de forma asíncrona (ver geo-info.ts).
38
+ * En KMP las APIs nativas dan os_version/device_model reales.
39
+ */
40
+ interface DeviceInfo {
41
+ device_type: 'mobile' | 'tablet' | 'desktop';
42
+ os_version?: string;
43
+ device_model?: string;
44
+ app_version?: string;
45
+ user_agent?: string;
46
+ timezone?: string;
47
+ referrer?: string;
48
+ viewport_size?: string;
49
+ screen_resolution?: string;
50
+ pixel_ratio?: number;
51
+ entry_type?: string;
52
+ page_load_ms?: number;
53
+ connection_type?: string;
54
+ country?: string;
55
+ city?: string;
56
+ }
57
+
58
+ /**
59
+ * Capa de Analytics (eventos GA-style) — canal SEPARADO del feedback de survey.
60
+ *
61
+ * El feedback de survey se envía con `magicfeedback.send()`. La analítica de
62
+ * navegación/comportamiento (page views, mini-services, tasks/funnels, meaningful
63
+ * interactions, findability…) va por un ENDPOINT PROPIO, vinculada por `user_id`.
64
+ *
65
+ * De momento NO se hace la llamada: el `sink` por defecto hace `console.log` del
66
+ * payload exacto que se enviaría (dry-run), para poder verificarlo. Cuando el
67
+ * endpoint exista, se sustituye el sink por un POST.
68
+ */
69
+
70
+ interface AnalyticsEvent {
71
+ name: string;
72
+ timestamp: number;
73
+ params?: Record<string, unknown>;
74
+ }
75
+ interface AnalyticsEnvelope {
76
+ /** Clave pública del cliente (auth). */
77
+ publicKey?: string;
78
+ /** Id que vincula la analítica al usuario (el backend cose por aquí). */
79
+ userId: string | null;
80
+ /** Session id del backend si ya se conoce (puede ir null). */
81
+ sessionId: string | null;
82
+ context: {
83
+ platform: string;
84
+ language?: string;
85
+ /** Info de dispositivo (Technology #11–13). */
86
+ device?: DeviceInfo;
87
+ /** User attributes del cliente, usados para breakdowns (registration_status, pass_type, …). */
88
+ attributes: Record<string, string>;
89
+ };
90
+ events: AnalyticsEvent[];
91
+ }
92
+ type AnalyticsSink = (payload: AnalyticsEnvelope) => void;
93
+
94
+ /**
95
+ * Mapeo del envelope de analytics → body de `POST /sdk/feedback`.
96
+ *
97
+ * La analítica se envía como un Feedback del modelo del Surveys SDK, agrupado por una
98
+ * INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false` y
99
+ * `finished:false` (nunca se "cierra"); el backend cose por `sessionId` + `user_id`.
100
+ *
101
+ * Encoding:
102
+ * - todo va en `feedback.metadata`: contexto (user_id, session_id, platform…) + eventos
103
+ * - cada entrada usa `value: string[]` (array de un elemento)
104
+ * - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}
105
+ * - identidad (user_id) → `profile` como `external-user-id`
106
+ * - `answers` vacío, `metrics` no se usa
107
+ * - `text` vacío
108
+ */
109
+
110
+ interface AnalyticsKeys {
111
+ publicKey: string;
112
+ integration: string;
113
+ }
114
+ interface FeedbackKV {
115
+ key: string;
116
+ value: string[];
117
+ }
118
+ interface AnalyticsFeedbackBody {
119
+ feedback: {
120
+ text: string;
121
+ answers: FeedbackKV[];
122
+ metadata: FeedbackKV[];
123
+ profile: FeedbackKV[];
124
+ finished: boolean;
125
+ };
126
+ publicKey: string;
127
+ integration: string;
128
+ completed: boolean;
129
+ finished?: boolean;
130
+ /** sessionId devuelto por el primer POST; permite que el backend agrupe todos los eventos en un solo registro. */
131
+ sessionId?: string;
132
+ }
133
+ declare function buildAnalyticsFeedbackBody(envelope: AnalyticsEnvelope, keys: AnalyticsKeys, feedbackSessionId?: string): AnalyticsFeedbackBody;
134
+ interface FeedbackSinkOptions {
135
+ /** Base URL del backend (api(.|-dev.)deepdots.com) ya resuelta por el entorno. */
136
+ baseUrl: string;
137
+ keys: AnalyticsKeys;
138
+ /** Logger opcional (gated por debug). */
139
+ log?: (...args: unknown[]) => void;
140
+ /** fetch inyectable (tests / RN). */
141
+ fetchImpl?: typeof fetch;
142
+ /** Callback invocado cuando el backend devuelve un nuevo sessionId (primer POST). */
143
+ onSessionId?: (id: string) => void;
144
+ }
145
+ /**
146
+ * Sink real: transforma el envelope y hace `POST {baseUrl}/sdk/feedback`.
147
+ * Fire-and-forget (no bloquea el flush); errores solo se loguean.
148
+ * Stateful: cachea el `sessionId` que devuelve el backend en la primera respuesta
149
+ * y lo envía en los siguientes POSTs para que el backend agrupe todos los eventos
150
+ * en un único registro de feedback.
151
+ */
152
+ declare function createFeedbackSink(options: FeedbackSinkOptions): AnalyticsSink;
153
+
1
154
  /**
2
155
  * Configuration options for initializing the DeepdotsPopups SDK
3
156
  */
157
+
4
158
  interface DeepdotsInitParams {
5
159
  /** API key for authentication */
6
160
  apiKey?: string;
7
161
  /** Node environment: 'development' or 'production' */
8
162
  nodeEnv?: 'development' | 'production';
9
- /** Execution mode: 'server' or 'client' */
10
- mode?: 'server' | 'client';
11
163
  /** Enable debug logging */
12
164
  debug?: boolean;
13
- /** Lista de definiciones de popups precargadas */
14
- popups?: PopupDefinition[];
15
165
  /** Optional user id to send with popup events */
16
166
  userId?: string;
167
+ /** Starts tracking enabled (default) or disabled (e.g. until consent is granted). Equivalent to calling `setTrackingEnabled` after init. */
168
+ trackingEnabled?: boolean;
169
+ /** Host app version (for analytics device info / Technology #11). */
170
+ appVersion?: string;
171
+ /** Injectable persistent storage (RN: adapter over AsyncStorage). If missing, uses localStorage/in-memory. */
172
+ storage?: KeyValueStorage;
173
+ /** Platform used in the analytics envelope. Default is 'web'; in RN pass 'android'/'ios'. */
174
+ platform?: 'web' | 'android' | 'ios';
175
+ /** Injectable device info (RN: from react-native-device-info). If missing, it is derived from the browser. */
176
+ device?: DeviceInfo;
177
+ /**
178
+ * Analytics channel keys (`POST /sdk/feedback`). If provided, analytics is SENT
179
+ * to the configured integration; if missing, it stays in dry-run mode (console.log only).
180
+ */
181
+ analytics?: AnalyticsKeys;
182
+ /**
183
+ * Internal user attributes known only by the host (language, age, plan, ...), sent
184
+ * to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling
185
+ * `setContactAttributes` after init (sends only when values changed from the last sync).
186
+ */
187
+ contactAttributes?: Record<string, string | number | boolean>;
17
188
  }
18
189
  interface DeepdotsConfig {
19
190
  /** API key for authentication */
20
191
  apiKey?: string;
21
- /** Execution mode: 'server' or 'client' */
22
- mode?: 'server' | 'client';
23
192
  /** Enable debug logging */
24
193
  debug?: boolean;
25
- /** Lista de definiciones de popups precargadas */
26
- popups?: PopupDefinition[];
27
194
  /** Optional user id to send with popup events */
28
195
  userId?: string;
29
196
  }
@@ -40,17 +207,6 @@ interface TriggerConfig {
40
207
  /** Optional popup definition id (used to disambiguate repeated survey ids) */
41
208
  popupId?: string;
42
209
  }
43
- /**
44
- * Options for showing a popup
45
- */
46
- interface ShowOptions {
47
- /** Survey ID to display */
48
- surveyId: string;
49
- /** Product ID associated with the survey */
50
- productId: string;
51
- /** Additional data to pass with the survey */
52
- data?: Record<string, unknown>;
53
- }
54
210
  /**
55
211
  * Event types emitted by the SDK
56
212
  */
@@ -68,26 +224,26 @@ interface DeepdotsEvent {
68
224
  * Event listener callback
69
225
  */
70
226
  type EventListener = (event: DeepdotsEvent) => void;
71
- /** Tipo de trigger específico para definiciones de popup remotas */
227
+ /** Trigger type used by remote popup definitions */
72
228
  type PopupTriggerType = 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';
73
229
  type PopupTriggerConditionStatus = 'SHOWED' | 'PARTIAL' | 'COMPLETED';
74
230
  declare const POPUP_TRIGGER_CONDITION_STATUSES: PopupTriggerConditionStatus[];
75
- /** Condición adicional para la activación del popup */
231
+ /** Extra condition used to activate the popup */
76
232
  interface PopupTriggerCondition {
77
233
  answered: PopupTriggerConditionStatus;
78
234
  cooldownDays: number;
79
235
  }
80
- /** Trigger asociado a la definición del popup */
236
+ /** Trigger attached to the popup definition */
81
237
  interface PopupTrigger {
82
238
  type: PopupTriggerType;
83
239
  value: number | string;
84
240
  }
85
- /** Acción de aceptar (abrir encuesta) */
241
+ /** Accept action (open survey) */
86
242
  interface PopupActionAccept {
87
243
  label: string;
88
244
  surveyId: string;
89
245
  }
90
- /** Acción de declinar */
246
+ /** Decline action */
91
247
  interface PopupActionDecline {
92
248
  label: string;
93
249
  cooldownDays?: number;
@@ -96,14 +252,14 @@ interface PopupActionDecline {
96
252
  interface PopupActionStart {
97
253
  label: string;
98
254
  }
99
- /** Acción de completar (aceptar y completar encuesta automáticamente) */
255
+ /** Complete action (accept and auto-complete survey) */
100
256
  interface PopupActionComplete {
101
257
  label: string;
102
258
  surveyId: string;
103
259
  autoCompleteParams: Record<string, unknown>;
104
260
  cooldownDays?: number;
105
261
  }
106
- /** Conjunto de acciones disponibles en el popup */
262
+ /** Set of actions available in the popup */
107
263
  interface PopupActions {
108
264
  accept?: PopupActionAccept;
109
265
  decline?: PopupActionDecline;
@@ -111,20 +267,20 @@ interface PopupActions {
111
267
  start?: PopupActionStart;
112
268
  back?: PopupActionDecline;
113
269
  }
114
- /** Estilos configurables del popup */
270
+ /** Configurable popup styles */
115
271
  interface PopupStyle {
116
272
  theme: 'light' | 'dark';
117
273
  position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';
118
- /** @deprecated No usado por el SDK — solo se conserva por compatibilidad con la API */
274
+ /** @deprecated Not used by the SDK; kept only for API compatibility */
119
275
  imageUrl?: string | null;
120
276
  }
121
- /** Segmentos o targeting para mostrar el popup */
277
+ /** Segments/targeting rules for showing the popup */
122
278
  interface PopupSegments {
123
279
  lang?: string[];
124
280
  path?: string[];
125
281
  [key: string]: unknown;
126
282
  }
127
- /** Definición completa de un popup */
283
+ /** Full popup definition */
128
284
  interface PopupDefinition {
129
285
  id: string;
130
286
  title: string;
@@ -142,10 +298,59 @@ interface PopupRenderer {
142
298
  /** Preparar recursos si aplica */
143
299
  init?(): void;
144
300
  /** Mostrar popup */
145
- 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): void;
301
+ 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;
146
302
  /** Ocultar popup */
147
303
  hide(): void;
148
304
  }
305
+ /** Renderer que no hace nada (SSR / entornos sin DOM) */
306
+ declare class NoopPopupRenderer implements PopupRenderer {
307
+ show(): void;
308
+ hide(): void;
309
+ }
310
+ /** Renderer para navegadores usando la implementación actual basada en renderPopup */
311
+ declare class BrowserPopupRenderer implements PopupRenderer {
312
+ private container;
313
+ private visible;
314
+ init(): void;
315
+ 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;
316
+ hide(): void;
317
+ }
318
+ /** Factoría para obtener renderer por defecto */
319
+ declare function createDefaultRenderer(): PopupRenderer;
320
+
321
+ /** Valor de un atributo de contact (info interna del usuario que solo conoce el host). */
322
+ type ContactAttributeValue = string | number | boolean;
323
+ type ContactAttributes = Record<string, ContactAttributeValue>;
324
+ /** Body de `POST /sdk/popups/contact`. */
325
+ interface ContactBody {
326
+ publicKey: string;
327
+ userId: string;
328
+ userAttributes: ContactAttributes;
329
+ }
330
+ interface ContactManagerOptions {
331
+ storage: KeyValueStorage;
332
+ publicKey: string;
333
+ userId: string;
334
+ /** Envío real (inyectable para tests): hace el `POST /sdk/popups/contact`. */
335
+ post: (body: ContactBody) => Promise<void>;
336
+ }
337
+ /**
338
+ * Gestiona los atributos de contact del usuario identificado (`userId` del init).
339
+ * Solo envía cuando cambian respecto a lo último persistido en storage, para ahorrar
340
+ * peticiones (el host puede llamar `setAttributes` en cada identificación sin miedo).
341
+ */
342
+ declare class ContactManager {
343
+ private readonly storage;
344
+ private readonly publicKey;
345
+ private readonly userId;
346
+ private readonly post;
347
+ constructor(options: ContactManagerOptions);
348
+ /**
349
+ * Envía los atributos si cambiaron respecto a lo último enviado.
350
+ * @returns `true` si se envió, `false` si no hubo cambios.
351
+ */
352
+ setAttributes(attributes: ContactAttributes): Promise<boolean>;
353
+ }
149
354
 
150
355
  /**
151
356
  * Main class for managing survey popups
@@ -167,20 +372,76 @@ declare class DeepdotsPopups {
167
372
  private deferredExitTimers;
168
373
  private baseUrl;
169
374
  private env;
375
+ /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */
376
+ private tracking;
377
+ /** Capa de analytics (canal separado del feedback). Null hasta init(). */
378
+ private analytics;
379
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
380
+ private analyticsFeedbackSessionId;
381
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
382
+ private analyticsFlushTimer;
383
+ /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
384
+ private navObserver;
385
+ /** Tiempo activo (engagement time, #8). */
386
+ private engagement;
387
+ /** Atributos internos del usuario identificado → POST /sdk/popups/contact. Null si no hay userId. */
388
+ private contact;
389
+ /** Marca si ya se inició la navegación manual (setScreen) — para RN sin History API. */
390
+ private navStarted;
170
391
  /** Initialize the SDK with configuration */
171
392
  init(config: DeepdotsInitParams): void;
393
+ /** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
394
+ getUserId(): string | null;
395
+ /** Session id de navegación actual. Null si tracking off. */
396
+ getSessionId(): string | null;
397
+ /** Activa/desactiva el tracking (identidad + sesión + analytics). Kill-switch del contrato §7bis. */
398
+ setTrackingEnabled(enabled: boolean): void;
399
+ /** Registra un evento de analítica (modelo GA: nombre + params). No-op si tracking off. */
400
+ track(name: string, params?: Record<string, unknown>): void;
401
+ /** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
402
+ setUserAttributes(attributes: Record<string, string | number | boolean>): void;
403
+ /**
404
+ * Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
405
+ * Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
406
+ * init. Solo envía si los atributos cambiaron (diff en storage). No-op si tracking off o sin userId.
407
+ * @returns `true` si se envió al backend, `false` si no hubo cambios o está deshabilitado.
408
+ */
409
+ setContactAttributes(attributes: ContactAttributes): Promise<boolean>;
410
+ /** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */
411
+ enterMiniService(name: string, entryPointType?: string): void;
412
+ /** Cierra el mini-service activo (emite `mini_service_exit` con duración, #27). No-op si tracking off. */
413
+ exitMiniService(): void;
414
+ /** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
415
+ trackSearch(query: string, resultsCount: number, params?: Record<string, unknown>): void;
416
+ /** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
417
+ trackFindabilityFriction(frictionTopic: string, params?: Record<string, unknown>): void;
418
+ /** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */
419
+ trackFunnelStep(funnel: string, step: string, taskId: string, params?: Record<string, unknown>): void;
420
+ /**
421
+ * Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).
422
+ * El host la llama en cada cambio de pantalla; emite `page_view` al salir de la anterior.
423
+ */
424
+ setScreen(name: string): void;
425
+ /** App a foreground (RN: AppState 'active'): reanuda el engagement time. */
426
+ onForeground(): void;
427
+ /**
428
+ * App a background (RN: AppState 'background'): cierra pantalla actual (page_view),
429
+ * mini-service (mini_service_exit), emite engagement y hace flush del lote.
430
+ */
431
+ onBackground(): void;
432
+ /** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */
433
+ previewAnalytics(): AnalyticsEnvelope;
434
+ /** Envía (hoy dry-run → console.log) el lote acumulado de analytics y vacía el buffer. */
435
+ flushAnalytics(): void;
436
+ /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
437
+ private flushEngagement;
438
+ private analyticsIdentity;
439
+ /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
440
+ private setupAnalyticsFlush;
172
441
  /** Enable auto-launch functionality with configured triggers */
173
442
  autoLaunch(): void;
174
443
  /** Inicia los triggers configurados */
175
444
  private startTriggers;
176
- /** Show a popup immediately (supports legacy ShowOptions and new PopupDefinition) */
177
- show(options: PopupDefinition | {
178
- surveyId: string;
179
- productId: string;
180
- data?: Record<string, unknown>;
181
- }): void;
182
- /** Mostrar popup por id de definición (usa surveyId interno) */
183
- showByPopupId(popupId: string): void;
184
445
  /** Configure triggers for auto-launching popups (manual) */
185
446
  configureTriggers(triggers: TriggerConfig[]): void;
186
447
  /** Deriva triggers desde las definiciones de popup */
@@ -209,6 +470,8 @@ declare class DeepdotsPopups {
209
470
  private setDeferredExitQueue;
210
471
  /** Fetch al servidor para obtener popups */
211
472
  private fetchPopupsFromServer;
473
+ /** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */
474
+ private postContact;
212
475
  /** Notifica eventos de popup a la API */
213
476
  private postPopupEvent;
214
477
  /** Add an event listener */
@@ -245,4 +508,118 @@ declare class DeepdotsPopups {
245
508
  private validatePopupDefinitions;
246
509
  }
247
510
 
248
- export { type DeepdotsConfig, type DeepdotsEvent, type DeepdotsEventType, DeepdotsPopups, type EventListener, POPUP_TRIGGER_CONDITION_STATUSES, type PopupDefinition, type PopupTrigger, type PopupTriggerCondition, type PopupTriggerConditionStatus, type ShowOptions, type TriggerConfig };
511
+ type EmitFn = (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void;
512
+ /** Payload que recibe el host para montar el WebView del survey. */
513
+ interface ReactNativeSurveyPayload {
514
+ surveyId: string;
515
+ productId: string;
516
+ /** HTML autocontenido para `<WebView source={{ html }}>`. */
517
+ html: string;
518
+ }
519
+ interface ReactNativeRendererOptions {
520
+ /** Se llama al mostrar un popup: monta el WebView con `payload.html`. */
521
+ onShow?: (payload: ReactNativeSurveyPayload) => void;
522
+ /** Se llama al cerrar el popup: desmonta el WebView. */
523
+ onHide?: () => void;
524
+ }
525
+ /**
526
+ * Renderer de React Native: el SDK no puede pintar componentes RN, así que entrega el
527
+ * HTML del survey al host (que lo monta en `react-native-webview`) y traduce los
528
+ * mensajes del WebView a eventos de popup del SDK (→ `POST /sdk/popups`, Messaging #18–22).
529
+ *
530
+ * Uso:
531
+ * ```tsx
532
+ * const renderer = new ReactNativePopupRenderer({
533
+ * onShow: (p) => setSurvey({ ...p, visible: true }),
534
+ * onHide: () => setSurvey((s) => ({ ...s, visible: false })),
535
+ * });
536
+ * sdk.setRenderer(renderer);
537
+ * // …
538
+ * <WebView source={{ html: survey.html }}
539
+ * onMessage={(e) => renderer.handleMessage(e.nativeEvent.data)} />
540
+ * ```
541
+ */
542
+ declare class ReactNativePopupRenderer implements PopupRenderer {
543
+ private options;
544
+ private emitFn;
545
+ private onCloseFn;
546
+ private currentSurveyId;
547
+ private partialEmitted;
548
+ constructor(options?: ReactNativeRendererOptions);
549
+ init(): void;
550
+ 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;
551
+ hide(): void;
552
+ /**
553
+ * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
554
+ * eventos del SDK: primera interacción → `popup_clicked` (PARTIAL), completado →
555
+ * `survey_completed` (COMPLETED) y cierra el popup.
556
+ */
557
+ handleMessage(raw: string): void;
558
+ }
559
+
560
+ /**
561
+ * Construye un HTML autocontenido que renderiza el survey de `@magicfeedback/native`
562
+ * dentro de un WebView (React Native). Carga el bundle desde CDN, crea el form con la
563
+ * identidad inyectada (`profile`/`metadata`) y reenvía los eventos del survey al host
564
+ * vía el puente de react-native-webview: `window.ReactNativeWebView.postMessage`.
565
+ *
566
+ * Mensajes emitidos (JSON `{name, payload}`): `loaded`, `before_submit`, `after_submit`,
567
+ * `survey_completed`, `back`, `popup_close`.
568
+ */
569
+ interface BuildSurveyHtmlOptions {
570
+ surveyId: string;
571
+ productId: string;
572
+ env?: string;
573
+ profile?: IdentityAnswer[];
574
+ metadata?: IdentityAnswer[];
575
+ version?: string;
576
+ }
577
+ declare function buildSurveyHtml(opts: BuildSurveyHtmlOptions): string;
578
+
579
+ /**
580
+ * Auto-wiring de React Native: concentra TODA la configuración en el SDK para que el
581
+ * host solo tenga que envolver su app en un `<DeepdotsProvider config={...}>`.
582
+ *
583
+ * No importa React ni librerías nativas: el host (o el Provider) le pasa los módulos
584
+ * nativos que tenga instalados (MMKV, device-info, AppState). Todo es opcional y degrada
585
+ * con elegancia (sin MMKV → in-memory; sin device-info → sin device info).
586
+ */
587
+
588
+ /** Forma mínima de `react-native-mmkv` (instancia MMKV). */
589
+ interface MmkvLike {
590
+ getString(key: string): string | undefined | null;
591
+ set(key: string, value: string): void;
592
+ delete(key: string): void;
593
+ }
594
+ /** Forma mínima de `react-native-device-info`. */
595
+ interface DeviceInfoLike {
596
+ isTablet?: () => boolean;
597
+ getSystemVersion?: () => string;
598
+ getDeviceId?: () => string;
599
+ getVersion?: () => string;
600
+ }
601
+ /** Forma mínima de `AppState` de react-native. */
602
+ interface AppStateLike {
603
+ addEventListener(type: 'change', listener: (state: string) => void): {
604
+ remove: () => void;
605
+ } | void;
606
+ }
607
+ interface ReactNativeSetupDeps {
608
+ mmkv?: MmkvLike | null;
609
+ deviceInfo?: DeviceInfoLike | null;
610
+ appState?: AppStateLike | null;
611
+ platform?: 'web' | 'android' | 'ios';
612
+ renderer?: PopupRenderer;
613
+ }
614
+ /** Adaptador KeyValueStorage (síncrono) sobre una instancia de MMKV. */
615
+ declare function mmkvStorage(mmkv: MmkvLike): KeyValueStorage;
616
+ /** Recoge device info desde react-native-device-info (Technology #11–13). */
617
+ declare function collectRnDevice(d: DeviceInfoLike): DeviceInfo;
618
+ /**
619
+ * Configura el SDK para React Native en una sola llamada: storage persistente (MMKV),
620
+ * device info, platform, init y lifecycle (AppState → onForeground/onBackground).
621
+ * Devuelve una función de limpieza (quita el listener de AppState).
622
+ */
623
+ declare function setupReactNative(sdk: DeepdotsPopups, config: DeepdotsInitParams, deps?: ReactNativeSetupDeps): () => void;
624
+
625
+ export { type AnalyticsFeedbackBody, type AnalyticsKeys, type AppStateLike, BrowserPopupRenderer, type ContactAttributeValue, type ContactAttributes, type ContactBody, ContactManager, type DeepdotsConfig, type DeepdotsEvent, type DeepdotsEventType, DeepdotsPopups, type DeviceInfo, type DeviceInfoLike, type EventListener, type FeedbackKV, InMemoryStorage, type KeyValueStorage, type MmkvLike, NoopPopupRenderer, POPUP_TRIGGER_CONDITION_STATUSES, type PopupDefinition, type PopupRenderer, type PopupTrigger, type PopupTriggerCondition, type PopupTriggerConditionStatus, ReactNativePopupRenderer, type ReactNativeRendererOptions, type ReactNativeSetupDeps, type ReactNativeSurveyPayload, type TriggerConfig, buildAnalyticsFeedbackBody, buildSurveyHtml, collectRnDevice, createDefaultRenderer, createFeedbackSink, mmkvStorage, setupReactNative };