import React from 'react'; /** * Identidad + sesión para el tracking de navegación (Fase 1). * Implementa el "Contrato común Web+KMP". * * Modelo: el SDK gestiona el `user_id` (persistente). El `session_id` es propiedad * del BACKEND: llega en la respuesta de `POST /sdk/popups` y se cachea; el backend * cose sesiones por `user_id` + ventana. El SDK NO genera ni expira sesiones. * * Sin dependencias de DOM: recibe el storage y (opcionalmente) el reloj y el * generador de uuid por inyección. El binding a `localStorage` vive en `createDefaultStorage`. */ /** Store clave/valor mínimo (espejo conceptual del `KeyValueStorage` de KMP). */ interface KeyValueStorage { getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; } /** * Device info para el context de analytics (Technology #11–13). * * En Web enviamos `device_type` (heurística fiable) + `user_agent` crudo, y dejamos * que el backend derive OS/modelo/navegador del UA (como hace GA) — el parseo de UA en * cliente es frágil. `app_version` lo provee el cliente (una web no tiene versión de app * nativa). En KMP las APIs nativas dan os_version/device_model reales. */ interface DeviceInfo { device_type: 'mobile' | 'tablet' | 'desktop'; os_version?: string; device_model?: string; app_version?: string; user_agent?: string; } /** * Capa de Analytics (eventos GA-style) — canal SEPARADO del feedback de survey. * * El feedback de survey se envía con `magicfeedback.send()`. La analítica de * navegación/comportamiento (page views, mini-services, tasks/funnels, meaningful * interactions, findability…) va por un ENDPOINT PROPIO, vinculada por `user_id`. * * De momento NO se hace la llamada: el `sink` por defecto hace `console.log` del * payload exacto que se enviaría (dry-run), para poder verificarlo. Cuando el * endpoint exista, se sustituye el sink por un POST. */ interface AnalyticsEvent { name: string; timestamp: number; params?: Record; } interface AnalyticsEnvelope { /** Clave pública del cliente (auth). */ publicKey?: string; /** Id que vincula la analítica al usuario (el backend cose por aquí). */ userId: string | null; /** Session id del backend si ya se conoce (puede ir null). */ sessionId: string | null; context: { platform: string; language?: string; /** Info de dispositivo (Technology #11–13). */ device?: DeviceInfo; /** User attributes del cliente, usados para breakdowns (registration_status, pass_type, …). */ attributes: Record; }; events: AnalyticsEvent[]; } /** * Mapeo del envelope de analytics → body de `POST /sdk/feedback`. * * La analítica se envía como un Feedback del modelo del Surveys SDK, agrupado por una * INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false` y * `finished:false` (nunca se "cierra"); el backend cose por `sessionId` + `user_id`. * * Encoding (decisión 2026-06-22): * - cada evento (dato recabado) → una `metric` {key: nombre_evento, value: JSON(timestamp + params)} * - identidad (user_id) → `profile` como `external-user-id` * - contexto (user_id, session_id, platform, language, device, attributes) → `metadata` * - `answers` vacío * - `text` vacío */ interface AnalyticsKeys { publicKey: string; integration: string; } /** * Configuration options for initializing the DeepdotsPopups SDK */ interface DeepdotsInitParams { /** API key for authentication */ apiKey?: string; /** Node environment: 'development' or 'production' */ nodeEnv?: 'development' | 'production'; /** Enable debug logging */ debug?: boolean; /** Optional user id to send with popup events */ userId?: string; /** Starts tracking enabled (default) or disabled (e.g. until consent is granted). Equivalent to calling `setTrackingEnabled` after init. */ trackingEnabled?: boolean; /** Host app version (for analytics device info / Technology #11). */ appVersion?: string; /** Injectable persistent storage (RN: adapter over AsyncStorage). If missing, uses localStorage/in-memory. */ storage?: KeyValueStorage; /** Platform used in the analytics envelope. Default is 'web'; in RN pass 'android'/'ios'. */ platform?: 'web' | 'android' | 'ios'; /** Injectable device info (RN: from react-native-device-info). If missing, it is derived from the browser. */ device?: DeviceInfo; /** * Analytics channel keys (`POST /sdk/feedback`). If provided, analytics is SENT * to the configured integration; if missing, it stays in dry-run mode (console.log only). */ analytics?: AnalyticsKeys; /** * Internal user attributes known only by the host (language, age, plan, ...), sent * to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling * `setContactAttributes` after init (sends only when values changed from the last sync). */ contactAttributes?: Record; } /** * Options for configuring survey triggers */ interface TriggerConfig { /** Trigger type: 'time' (delay), 'scroll' (scroll percentage), 'exit' (route exit), 'click' (element id), 'event' (host event) */ type: 'time' | 'scroll' | 'exit' | 'click' | 'event'; /** Value for the trigger (milliseconds for time, percentage for scroll, seconds for exit, element id for click, event name for event) */ value?: number | string; /** Survey ID to show when triggered */ surveyId: string; /** Optional popup definition id (used to disambiguate repeated survey ids) */ popupId?: string; } /** * Event types emitted by the SDK */ type DeepdotsEventType = 'popup_shown' | 'popup_clicked' | 'survey_completed'; /** * Event payload structure */ interface DeepdotsEvent { type: DeepdotsEventType; surveyId: string; timestamp: number; data?: Record; } /** * Event listener callback */ type EventListener = (event: DeepdotsEvent) => void; /** Accept action (open survey) */ interface PopupActionAccept { label: string; surveyId: string; } /** Decline action */ interface PopupActionDecline { label: string; cooldownDays?: number; } /** Start Surveys Action */ interface PopupActionStart { label: string; } /** Complete action (accept and auto-complete survey) */ interface PopupActionComplete { label: string; surveyId: string; autoCompleteParams: Record; cooldownDays?: number; } /** Set of actions available in the popup */ interface PopupActions { accept?: PopupActionAccept; decline?: PopupActionDecline; complete?: PopupActionComplete; start?: PopupActionStart; back?: PopupActionDecline; } /** Configurable popup styles */ interface PopupStyle { theme: 'light' | 'dark'; position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center'; /** @deprecated Not used by the SDK; kept only for API compatibility */ imageUrl?: string | null; } interface PopupRenderer { /** Preparar recursos si aplica */ init?(): void; /** Mostrar popup */ show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string): void; /** Ocultar popup */ hide(): void; } /** Valor de un atributo de contact (info interna del usuario que solo conoce el host). */ type ContactAttributeValue = string | number | boolean; type ContactAttributes = Record; /** * Main class for managing survey popups */ declare class DeepdotsPopups { private config; private listeners; private triggers; private initialized; private renderer; private popupContainer; private popupDefinitions; private popupsLoaded; private pendingAutoLaunch; private answeredSurveys; private surveyProgress; private lastShown; private surveyToPopupId; private deferredExitTimers; private baseUrl; private env; /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */ private tracking; /** Capa de analytics (canal separado del feedback). Null hasta init(). */ private analytics; /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */ private navObserver; /** Tiempo activo (engagement time, #8). */ private engagement; /** Atributos internos del usuario identificado → POST /sdk/popups/contact. Null si no hay userId. */ private contact; /** Marca si ya se inició la navegación manual (setScreen) — para RN sin History API. */ private navStarted; /** Initialize the SDK with configuration */ init(config: DeepdotsInitParams): void; /** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */ getUserId(): string | null; /** Session id de navegación actual. Null si tracking off. */ getSessionId(): string | null; /** Activa/desactiva el tracking (identidad + sesión + analytics). Kill-switch del contrato §7bis. */ setTrackingEnabled(enabled: boolean): void; /** Registra un evento de analítica (modelo GA: nombre + params). No-op si tracking off. */ track(name: string, params?: Record): void; /** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */ setUserAttributes(attributes: Record): void; /** * Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el * Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del * init. Solo envía si los atributos cambiaron (diff en storage). No-op si tracking off o sin userId. * @returns `true` si se envió al backend, `false` si no hubo cambios o está deshabilitado. */ setContactAttributes(attributes: ContactAttributes): Promise; /** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */ enterMiniService(name: string, entryPointType?: string): void; /** Cierra el mini-service activo (emite `mini_service_exit` con duración, #27). No-op si tracking off. */ exitMiniService(): void; /** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */ trackSearch(query: string, resultsCount: number, params?: Record): void; /** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */ trackFindabilityFriction(frictionTopic: string, params?: Record): void; /** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */ trackFunnelStep(funnel: string, step: string, taskId: string, params?: Record): void; /** * Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation). * El host la llama en cada cambio de pantalla; emite `page_view` al salir de la anterior. */ setScreen(name: string): void; /** App a foreground (RN: AppState 'active'): reanuda el engagement time. */ onForeground(): void; /** * App a background (RN: AppState 'background'): cierra pantalla actual (page_view), * mini-service (mini_service_exit), emite engagement y hace flush del lote. */ onBackground(): void; /** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */ previewAnalytics(): AnalyticsEnvelope; /** Envía (hoy dry-run → console.log) el lote acumulado de analytics y vacía el buffer. */ flushAnalytics(): void; /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */ private flushEngagement; private analyticsIdentity; /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */ private setupAnalyticsFlush; /** Enable auto-launch functionality with configured triggers */ autoLaunch(): void; /** Inicia los triggers configurados */ private startTriggers; /** Configure triggers for auto-launching popups (manual) */ configureTriggers(triggers: TriggerConfig[]): void; /** Deriva triggers desde las definiciones de popup */ private configureTriggersFromDefinitions; /** Lógica para evaluar condiciones antes de mostrar una encuesta */ triggerSurvey(surveyId: string, popupId?: string): void; /** Trigger popups configured with trigger.type = 'event' and matching trigger.value */ triggerEvent(eventName: string): void; private showDefinition; private shouldShow; private matchesSegmentsLang; private matchesSegmentsPath; private evaluateCooldown; private evaluateLegacyCondition; private hasCooldownElapsed; /** Marcar encuesta como contestada externamente */ markSurveyAnswered(surveyId: string): void; /** Queue an exit popup so it can render after navigation */ queueExitPopup(surveyId: string, delaySeconds: number, sourceUrl?: string, popupId?: string): void; private processDeferredExitQueue; private scheduleDeferredExitPopup; private tryShowDeferredExitPopup; private findPopupDefinition; private removeDeferredExitPopup; private getDeferredExitQueue; private setDeferredExitQueue; /** Fetch al servidor para obtener popups */ private fetchPopupsFromServer; /** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */ private postContact; /** Notifica eventos de popup a la API */ private postPopupEvent; /** Add an event listener */ on(eventType: DeepdotsEventType, listener: EventListener): void; /** Remove an event listener */ off(eventType: DeepdotsEventType, listener: EventListener): void; /** Setup popup container element */ private setupPopupContainer; /** Render the popup UI */ private renderPopup; /** Hide the popup */ private hidePopup; /** Emit an event */ private emitEvent; /** Log debug messages */ private log; /** External debug method for triggers */ debug(...args: unknown[]): void; /** Set a custom renderer */ setRenderer(renderer: PopupRenderer): void; private normalizeUrl; private safeParseUrl; private mapPopupTriggerType; private markSurveyProgress; private isPopupDefinition; private isPopupTrigger; private isPopupTriggerType; private isPopupTriggerCondition; private isLegacyPopupTriggerCondition; private normalizeTriggers; private normalizeCooldown; private normalizeLegacyConditions; private normalizePopupDefinition; private validatePopupDefinitions; } /** * Auto-wiring de React Native: concentra TODA la configuración en el SDK para que el * host solo tenga que envolver su app en un ``. * * No importa React ni librerías nativas: el host (o el Provider) le pasa los módulos * nativos que tenga instalados (MMKV, device-info, AppState). Todo es opcional y degrada * con elegancia (sin MMKV → in-memory; sin device-info → sin device info). */ /** Forma mínima de `react-native-mmkv` (instancia MMKV). */ interface MmkvLike { getString(key: string): string | undefined | null; set(key: string, value: string): void; delete(key: string): void; } /** Forma mínima de `react-native-device-info`. */ interface DeviceInfoLike { isTablet?: () => boolean; getSystemVersion?: () => string; getDeviceId?: () => string; getVersion?: () => string; } /** Forma mínima de `AppState` de react-native. */ interface AppStateLike { addEventListener(type: 'change', listener: (state: string) => void): { remove: () => void; } | void; } interface ReactNativeSetupDeps { mmkv?: MmkvLike | null; deviceInfo?: DeviceInfoLike | null; appState?: AppStateLike | null; platform?: 'web' | 'android' | 'ios'; renderer?: PopupRenderer; } /** * Configura el SDK para React Native en una sola llamada: storage persistente (MMKV), * device info, platform, init y lifecycle (AppState → onForeground/onBackground). * Devuelve una función de limpieza (quita el listener de AppState). */ declare function setupReactNative(sdk: DeepdotsPopups, config: DeepdotsInitParams, deps?: ReactNativeSetupDeps): () => void; type EmitFn = (type: DeepdotsEventType, surveyId: string, data?: Record) => void; /** Payload que recibe el host para montar el WebView del survey. */ interface ReactNativeSurveyPayload { surveyId: string; productId: string; /** HTML autocontenido para ``. */ html: string; } interface ReactNativeRendererOptions { /** Se llama al mostrar un popup: monta el WebView con `payload.html`. */ onShow?: (payload: ReactNativeSurveyPayload) => void; /** Se llama al cerrar el popup: desmonta el WebView. */ onHide?: () => void; } /** * Renderer de React Native: el SDK no puede pintar componentes RN, así que entrega el * HTML del survey al host (que lo monta en `react-native-webview`) y traduce los * mensajes del WebView a eventos de popup del SDK (→ `POST /sdk/popups`, Messaging #18–22). * * Uso: * ```tsx * const renderer = new ReactNativePopupRenderer({ * onShow: (p) => setSurvey({ ...p, visible: true }), * onHide: () => setSurvey((s) => ({ ...s, visible: false })), * }); * sdk.setRenderer(renderer); * // … * renderer.handleMessage(e.nativeEvent.data)} /> * ``` */ declare class ReactNativePopupRenderer implements PopupRenderer { private options; private emitFn; private onCloseFn; private currentSurveyId; private partialEmitted; constructor(options?: ReactNativeRendererOptions); init(): void; show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string): void; hide(): void; /** * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a * eventos del SDK: primera interacción → `popup_clicked` (PARTIAL), completado → * `survey_completed` (COMPLETED) y cierra el popup. */ handleMessage(raw: string): void; } /** * Entry de React Native: `@magicfeedback/popup-sdk/react-native`. * * El cliente solo envuelve su app: * ... * y el SDK hace TODO (storage persistente, device info, platform, lifecycle/engagement, * y render de surveys en WebView). Sin pegar nada en su proyecto. * * Peer deps: react, react-native, react-native-webview. Opcionales (auto-detectados): * react-native-mmkv (persistencia), react-native-device-info (device info). */ /** Acceso al SDK desde cualquier componente: `const dd = useDeepdots()`. */ declare const useDeepdots: () => DeepdotsPopups; interface DeepdotsProviderProps { config: DeepdotsInitParams; children?: React.ReactNode; } declare function DeepdotsProvider({ config, children }: DeepdotsProviderProps): React.JSX.Element; export { DeepdotsProvider, type DeepdotsProviderProps, ReactNativePopupRenderer, setupReactNative, useDeepdots };