npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ocularsolution/react-native

v0.2.0

Published

Adaptador React Native para el SDK de Ocular Solution.

Readme

@ocularsolution/react-native

SDK oficial de Ocular Solution para integrar videollamadas en aplicaciones React Native / Expo.

Con solo envolver la app en <OcularProvider>, el SDK renderiza un botón flotante nativo (equivalente a la burbuja del widget web) y un drawer con la videollamada embebida al 100%. La llamada sobrevive a la navegación y al minimizado: la WebView interna nunca se desmonta. Los permisos de cámara/micrófono los gestiona el SDK.

Compatibilidad

| Componente | Versión | |---|---| | React Native | ≥ 0.73 (validado con 0.81) | | React | ≥ 18 (validado con 19.1) | | Expo SDK | 52+ (validado con 54) | | react-native-webview | ≥ 13 | | react-native-gesture-handler / reanimated | ≥ 2 / ≥ 3.16 — drag del drawer en el hilo de UI | | TypeScript (opcional) | ≥ 5.0 — tipos .d.ts incluidos |

1. Antes de empezar

Solicite a Ocular Solution sus datos de aprovisionamiento:

  1. widgetCode — código de organización de su widget. Configúrelo por variable de entorno; no lo incruste en el repositorio.
  2. Autorización de su appOrigin — el origin https desde el que su app usará el widget debe estar dado de alta por Ocular Solution. Sin este registro el widget no se inicializa (verá OCULAR_E_WIDGET_TIMEOUT).

La URL del script del widget viene fija en el SDK (producción); solo se sobreescribe con widgetUrl si Ocular Solution le indica un entorno especial.

2. Instalación

npx expo install @ocularsolution/react-native react-native-webview react-native-gesture-handler react-native-reanimated react-native-safe-area-context expo-audio

Un solo comando: instala el SDK y alinea las dependencias nativas con la versión de tu Expo SDK. La mayoría de plantillas de Expo ya incluyen varias de ellas — en ese caso expo install solo verifica versiones. expo-audio se usa para una línea obligatoria en iOS (ver §4).

pnpm add @ocularsolution/react-native react-native-webview react-native-gesture-handler react-native-reanimated react-native-safe-area-context
npx pod-install

Los gestores modernos (npm ≥7, pnpm ≥8) instalan las peers automáticamente; verifica que las versiones nativas correspondan a tu versión de React Native. La sesión de audio de iOS (§4) puede configurarse con cualquier mecanismo que ponga la AVAudioSession en modo grabación.

3. Permisos (app.json)

{
  "expo": {
    "android": {
      "permissions": ["CAMERA", "RECORD_AUDIO", "MODIFY_AUDIO_SETTINGS"]
    },
    "ios": {
      "infoPlist": {
        "NSCameraUsageDescription": "La cámara se usa para la videollamada.",
        "NSMicrophoneUsageDescription": "El micrófono se usa para la videollamada."
      }
    }
  }
}

En runtime no necesita pedirlos: el SDK los solicita solo (Android) o deja que iOS muestre sus diálogos al capturar. Si quedan denegados, el drawer muestra un panel nativo con botón de conceder y acceso a Ajustes.

4. Uso — integración mínima

// app/_layout.tsx (ejemplo con Expo Router)
import { useEffect } from 'react';
import { Stack } from 'expo-router';
import { setAudioModeAsync } from 'expo-audio';
import { OcularProvider } from '@ocularsolution/react-native';

export default function RootLayout() {
  // iOS: habilitar grabación en la sesión de audio (imprescindible para el micrófono).
  useEffect(() => {
    setAudioModeAsync({ allowsRecording: true, playsInSilentMode: true }).catch(() => {});
  }, []);

  return (
    <OcularProvider
      widgetCode={process.env.EXPO_PUBLIC_OCULAR_WIDGET_CODE!}
      appOrigin={process.env.EXPO_PUBLIC_OCULAR_APP_ORIGIN}
    >
      <Stack />
    </OcularProvider>
  );
}

Eso es todo: cuando el widget termina de inicializarse aparece el botón flotante; al tocarlo sube el drawer con la videollamada. El asa (tap o arrastre que sigue al dedo), el fondo y el botón back de Android minimizan sin cortar la llamada.

5. Flujos de negocio

El comportamiento por defecto (botón flotante siempre disponible) es solo uno de los flujos posibles.

CTA propio, sin botón flotante

<OcularProvider {...config} floatingProps={{ showButton: false }}>
// En cualquier pantalla: su propio botón/banner abre la videollamada
const { openDrawer } = useOcularUI();
<Button title="Hablar con un asesor" onPress={() => openDrawer()} />

Botón solo en ciertas pantallas

const { setButtonVisible } = useOcularUI();
useEffect(() => {
  setButtonVisible(false);          // ocultar en esta pantalla
  return () => setButtonVisible(null); // restaurar el automático al salir
}, [setButtonVisible]);

Apertura programática (deep link, post-checkout, evento de negocio)

const { openDrawer, closeDrawer, toggleDrawer, drawerOpen, widgetReady } = useOcularUI();

Personalización del botón y el drawer

<OcularProvider
  {...config}
  floatingProps={{
    buttonColor: '#156B35',            // color de la burbuja (default)
    buttonIconSource: { uri: '...' },  // o require('./icono.png'); default: ícono oficial embebido
    position: 'left',                  // lado del botón
    drawerHeight: '90%',
    bottomInset: 34,                   // espacio bajo la llamada (home indicator)
  }}
>

Control total (sin UI del SDK)

<OcularProvider {...config} floating={false}>

Con floating={false} el SDK no renderiza UI; monte <OcularCall style={...} /> donde quiera (pantalla dedicada, Modal propio). No combine floating con <OcularCall>: comparten el transport.

6. API

<OcularProvider>

| Prop | Tipo | Req. | Descripción | |---|---|---|---| | widgetCode | string | Sí | Código de organización | | appOrigin | string | No* | Origin https autorizado (identidad ante el backend). *Requerido en la práctica | | widgetUrl | string | No | Override de la URL del script del widget. Default: la oficial de producción | | floating | boolean | No | UI automática (botón + drawer). Default true | | floatingProps | OcularFloatingWidgetProps | No | Personalización del botón/drawer (ver §5) | | locale | 'en'\|'es'\|'pt'\|'tr' | No | Idioma inicial del widget | | debug | boolean | No | Diagnóstico: emite eventos boot/fetch/xhr como widget:message. Solo desarrollo | | widgetAppOrigin | string | No | Override del filtro de mensajes del iframe (por defecto se resuelve en runtime) | | widgetModule | string | No | data-modules del loader. Default 'call' | | dataset | Record<string,string> | No | Atributos data-* extra del loader | | autoLoad | boolean | No | Inicia load() al montar (con floating la carga ya es automática) | | readyTimeoutMs | number | No | Timeout de inicialización (default 20000) | | logger | Logger | No | Logger opcional (debug/info/warn/error) |

floatingProps — diccionario completo

| Prop | Tipo | Default | Descripción | |---|---|---|---| | position | 'left'\|'right' | 'right' | Lado del botón flotante | | bottomOffset | number | 24 | Separación inferior del botón, adicional al safe area | | sideOffset | number | 16 | Separación lateral del botón | | buttonSize | number | 72 | Diámetro del botón (como la burbuja web) | | buttonColor | string | '#156B35' | Color del botón, del halo y de los paneles | | buttonIconSource | ImageSourcePropType | ícono oficial embebido | Imagen del ícono ({uri} o require(...)) | | buttonIcon | ReactNode | — | Ícono totalmente personalizado; prioridad sobre buttonIconSource | | showButton | boolean | true | false = flujo "CTA propio" (sin burbuja) | | highlightInCall | boolean | true | Halo pulsante + badge durante la llamada activa | | drawerHeight | number\|'N%' | '85%' | Alto del drawer | | bottomInset | number | 12 | Espacio bajo la llamada, adicional al safe area | | zoom | number | 1 | Escala visual de la UI de la llamada (0.5–2), sin desbordes | | ensurePermissions | () => Promise<boolean> | gestor interno | Override de permisos: pídelos y resuelve true si quedaron concedidos; con false el drawer muestra el panel de permisos. Úsalo para integrar expo-camera o lógica propia |

useOcularUI() — control de la UI nativa

const {
  widgetReady,      // el widget terminó de inicializarse
  drawerOpen,       // drawer visible
  inCall,           // llamada activa (entre call-started y call-ended)
  openDrawer, closeDrawer, toggleDrawer,
  buttonVisible,    // null = automático · false = oculto
  setButtonVisible, // control dinámico del botón flotante
} = useOcularUI();

Durante una llamada activa el botón flotante se destaca solo (halo pulsante + badge); se desactiva con floatingProps={{ highlightInCall: false }}. inCall permite además CTAs propios ("Volver a la llamada") o bloquear flujos de negocio mientras hay llamada.

useOcularCall() — estado y comandos del widget

const { state, load, show, hide, toggle, setLocale, setCustomer, showPopUp, align } = useOcularCall();
// state: 'idle' | 'loading' | 'ready' | 'visible' | 'hidden' | 'error'

show()/hide()/toggle() equivalen a abrir/cerrar el drawer (misma máquina de estados que useOcularUI). setLocale() y setCustomer() hablan con el widget; showPopUp() y align() solo aplican al modo floating={false} con <OcularCall> (en modo embebido el widget no tiene burbuja propia y son no-ops). Los comandos emitidos antes de que el widget esté listo se encolan y ejecutan en orden.

useOcular() — cliente y eventos

Devuelve el OcularClient de @ocularsolution/core:

const client = useOcular();
useEffect(() => client.on('widget:message', ({ data }) => {
  if (data?.type === 'ocular-widget-event' && data.msg === 'call-started') { /* ... */ }
}), [client]);

Diccionario de eventos del cliente:

| Evento | Payload | Cuándo | |---|---|---| | state:changed | { from, to } | Cada transición de la máquina de estados | | widget:ready | — | El widget terminó de inicializarse | | widget:message | { data } | Mensajes del widget/iframe y señales de diagnóstico | | call:joined / call:left | { callId, ... } | Al abrir/cerrar el drawer (show/hide) | | error | { code, message, cause } | Fallo de carga/transport |

Eventos de ciclo de vida del widget — llegan como widget:message con data.type === 'ocular-widget-event' y data.msgis-ready · button-displayed · opened · minimized · hidden · closed · call-started · call-ended · scheduled-success. (call-ended se captura también del callback oficial ocularCallEnded() del widget y se normaliza al mismo formato.)

useOcularDebug()

requestSnapshot() pide a la página embebida una radiografía de su estado real (flags de ready, scripts, iframes, últimas peticiones si debug). Llega como widget:message con data.ocularDebug === 'snapshot'. No requiere que el widget haya señalado ready.

Exports de bajo nivel (integraciones avanzadas)

| Export | Uso | |---|---| | <OcularFloatingWidget {...props} /> | Montar el botón + drawer manualmente (con floating={false} en el provider); una sola instancia | | <OcularCall style /> | WebView del widget en modo flotante clásico, para pantalla/Modal propios | | buildEmbeddedHtml(options) | Documento HTML del modo embebido (#ocular-service-container al 100%) | | buildEmbedHtml(options) | Documento HTML del modo flotante clásico | | WebViewTransport | Transport RN del OcularClient (attach/detach/receiveFromWebView, cola pre-ready) |

7. Errores

Todos los errores llevan code; llegan por el evento error del cliente o como widget:message de tipo ocular:error.

| Código | Causa | Solución | |---|---|---| | OCULAR_E_WIDGET_TIMEOUT | Origin no autorizado, widgetUrl/widgetCode incorrectos o sin red | Verifique el aprovisionamiento con Ocular Solution | | OCULAR_E_INVALID_CONFIG | widgetUrl no https, widgetCode/zoom/origins con formato inválido | Corrija la configuración (el mensaje indica el campo) | | OCULAR_E_SCRIPT_LOAD | La WebView no pudo descargar el script del widget | Verifique la URL y la conectividad | | OCULAR_E_NETWORK | Fallo de red/DNS al cargar la página (el drawer muestra el panel "Sin conexión" con reintento) | Verifique la conexión del dispositivo | | OCULAR_E_TRANSPORT | load() falló (envuelve la causa, p. ej. el timeout) | Revise cause.code | | OCULAR_E_INTERNAL | Error dentro de la página embebida (método no permitido/no disponible, excepción JS) | Revise el mensaje; use debug y Snapshot | | Micrófono no inicia (iOS) | Falta setAudioModeAsync({ allowsRecording: true }) | Aplique el snippet del §4 | | Cámara/micrófono denegados | Permisos revocados por el usuario | El drawer muestra el panel de permisos con acceso a Ajustes |

Para depurar la WebView: Android chrome://inspect · iOS Safari → menú Desarrollo (el SDK habilita la inspección en desarrollo).

Protección de datos

setCustomer() permite asociar datos del usuario a la sesión. No envíe datos personales sin base legal y consentimiento. Las videollamadas pueden ser grabadas según la configuración de su organización: informe al usuario conforme a la normativa aplicable.


© Ocular Solution. Soporte: a través de su canal de cuenta. Incidentes de seguridad: [email protected]