react-native-qalink
v0.7.0
Published
Real-time error capture SDK for React Native — helps QA teams identify if bugs are frontend or backend
Maintainers
Readme
react-native-qalink 🔍
SDK de captura de errores en tiempo real para React Native. Captura errores, logs, eventos custom, requests HTTP y screenshots — permitiendo al equipo de QA ver en tiempo real qué está pasando en la app, sin necesidad de Android Studio ni logs técnicos.
La conexión WebSocket apunta siempre a wss://ws.airbites.org.
📦 Instalación
npm install react-native-qalink
# o
yarn add react-native-qalinkDependencia opcional para capturas de pantalla:
npm install react-native-view-shot
cd ios && pod install🚀 Inicio rápido
import QALink from 'react-native-qalink';
// 1. Inicializar el SDK
QALink.init({
apiKey: 'qlk_your_api_key_here',
appVersion: '1.0.0',
environment: 'dev',
debug: true,
});
// 2. Configurar contexto de usuario
QALink.setUserContext({
userId: '12345',
email: '[email protected]',
plan: 'premium'
});
// 3. Configurar contexto custom
QALink.setCustomContext({
experimentVariant: 'new_checkout_flow',
featureFlags: { darkMode: true }
});
// 4. Usar los nuevos métodos de logging
QALink.debug('User opened settings');
QALink.info('Payment initiated', { amount: 99.99 });
QALink.warn('Slow API response', { duration: 3500 });
QALink.error('Payment failed', { code: 'CARD_DECLINED' });
QALink.critical('App about to crash');
// 5. Eventos custom
QALink.logEvent('checkout_completed', {
items: 3,
totalAmount: 299.99,
paymentMethod: 'credit_card'
});
// 6. Captura de pantalla (requiere react-native-view-shot)
await QALink.captureScreen('before_payment');🔧 Configuración HTTP avanzada
El SDK captura automáticamente todas las peticiones HTTP (fetch y axios). Puedes configurar qué capturar:
QALink.init({
apiKey: 'qlk_xxx',
appVersion: '1.0.0',
httpConfig: {
// Captura requests y responses
captureRequests: true,
captureResponses: true,
// Filtrar por URL
includeUrls: ['/api/payment', '/api/checkout'],
excludeUrls: ['/api/analytics', '/health'],
// Filtrar por método HTTP
methods: ['POST', 'PUT', 'DELETE'], // ignorar GETs
// Sanitización de datos sensibles
sanitizeHeaders: ['Authorization', 'X-API-Key'],
sanitizeRequestBody: ['password', 'creditCard', 'cvv'],
sanitizeResponseBody: ['token', 'secret'],
// Capturar solo errores HTTP (4xx, 5xx)
captureOnlyErrors: true,
minStatusCode: 400,
// Límite de tamaño de body
maxBodySize: 5000, // bytes
// Performance monitoring
captureTimings: true,
slowRequestThreshold: 2000, // marcar requests > 2s como lentos
}
});Actualizar configuración HTTP en runtime:
QALink.configureHTTP({
captureOnlyErrors: true,
slowRequestThreshold: 1000
});📝 API de Logging
Todos los métodos de logging hacen console.log nativo (visible en Metro) Y envían al servidor QALink:
// Acepta múltiples argumentos como console.log
QALink.debug('User action', { screen: 'Settings' }, 'Extra data');
QALink.info('API call started', {
endpoint: '/api/users',
method: 'GET'
});
QALink.warn('Memory warning', {
available: '50MB',
threshold: '100MB'
});
QALink.error('Validation failed', {
field: 'email',
reason: 'Invalid format'
});
QALink.critical('Unrecoverable error', {
code: 'FATAL_001',
message: 'Cannot connect to database'
});Niveles de log:
debug→ desarrollo y debugginginfo→ información generalwarn→ advertencias y problemas no críticoserror→ errores recuperablescritical→ errores fatales que pueden causar crash
🎯 Eventos Custom
Registra eventos de negocio importantes:
// Evento simple
QALink.logEvent('user_login');
// Evento con datos
QALink.logEvent('purchase_completed', {
productId: 'SKU-123',
price: 29.99,
currency: 'USD',
quantity: 2
});
QALink.logEvent('feature_used', {
featureName: 'dark_mode',
enabled: true,
timestamp: Date.now()
});Los eventos incluyen automáticamente:
- User context (userId, email, etc.)
- Custom context (feature flags, experiments)
- Screen actual
- Session ID
- Device ID
- Timestamp
👤 Context API
User Context
Información del usuario que se adjunta a todos los eventos:
QALink.setUserContext({
userId: '12345',
email: '[email protected]',
username: 'john_doe',
plan: 'premium',
region: 'LATAM'
});
// Actualizar parcialmente
QALink.setUserContext({ plan: 'enterprise' });
// Limpiar
QALink.clearContext();Custom Context
Datos custom que se adjuntan a todos los eventos:
QALink.setCustomContext({
experimentVariant: 'new_ui_v2',
featureFlags: {
darkMode: true,
newCheckout: false
},
build: 'staging-142'
});📸 Captura de Pantalla
Requisito: react-native-view-shot
// Capturar pantalla completa
await QALink.captureScreen('before_payment');
await QALink.captureScreen('error_state');
// Capturar componente específico
import { useRef } from 'react';
const MyComponent = () => {
const viewRef = useRef(null);
const handleCapture = async () => {
await QALink.captureRef(viewRef, 'form_error');
};
return (
<View ref={viewRef}>
{/* contenido */}
</View>
);
};Configuración:
QALink.init({
apiKey: 'qlk_xxx',
appVersion: '1.0.0',
enableScreenCapture: true, // ← activar
});🔌 Interceptores HTTP
Fetch (automático)
El SDK intercepta todos los fetch() automáticamente. No necesitas hacer nada.
Axios
Si usas Axios, puedes interceptar instancias específicas:
import axios from 'axios';
import QALink from 'react-native-qalink';
const apiClient = axios.create({
baseURL: 'https://api.example.com'
});
// Interceptar esta instancia de Axios
QALink.interceptAxios(apiClient);🛠️ Configuración completa
import QALink from 'react-native-qalink';
QALink.init({
// ── Requerido ──────────────────────────────────
apiKey: 'qlk_your_64_char_hex_key',
appVersion: '1.2.3',
// ── Básico ─────────────────────────────────────
enabled: true, // habilitar/deshabilitar SDK
environment: 'dev', // 'dev' | 'prod'
debug: true, // logs de debug en consola
// ── HTTP Interceptors ──────────────────────────
httpConfig: {
captureRequests: true,
captureResponses: true,
includeUrls: ['/api/'],
excludeUrls: ['/health', '/metrics'],
methods: ['POST', 'PUT', 'DELETE'],
sanitizeHeaders: ['Authorization'],
sanitizeRequestBody: ['password', 'token', 'creditCard'],
sanitizeResponseBody: ['secret', 'apiKey'],
captureOnlyErrors: false,
minStatusCode: 200,
maxBodySize: 5000,
captureTimings: true,
slowRequestThreshold: 2000,
},
// ── Legacy Network (compatibilidad) ────────────
logNetworkBodies: false,
sensitiveHeaders: ['Authorization'],
sensitiveUrlPatterns: ['/auth/', '/login'],
sensitiveBodyFields: ['password', 'token'],
// ── Console ─────────────────────────────────────
console: {
captureLogs: true,
captureWarnings: true,
captureErrors: true,
ignorePatterns: ['Ignore this'],
includePatterns: [],
},
// ── Errors ──────────────────────────────────────
captureRuntimeErrors: true, // red screen / yellow box
captureMetroErrors: true, // bundler errors
// ── Screen Capture ──────────────────────────────
enableScreenCapture: true, // requiere react-native-view-shot
// ── Callbacks ───────────────────────────────────
onEvent: (event) => {
console.log('Event sent:', event.type);
}
});🧠 Advanced QA Features (v0.7.0)
Rage Click Detection
Detects when a user taps the same element multiple times rapidly — a strong frustration signal.
// 1. Enable (once, after init)
QALink.enableRageClickDetection({ threshold: 3, windowMs: 600 });
// 2. Call from any onPress handler
<Button
onPress={() => {
QALink.trackUIInteraction('checkout-submit-btn');
handleSubmit();
}}
/>A rage_click event fires in the dashboard when the threshold is hit.
Navigation Loop Detection
Detects when the user bounces back and forth between the same two screens (A→B→A→B…). Signals broken back navigation or an unresolved error state.
// Enable (once, after init)
QALink.enableNavigationLoopDetection(3); // fires after 3 alternations
// Automatically triggered by setScreen() and startPackage()
QALink.setScreen('CheckoutScreen');A navigation_loop event fires in the dashboard with the full navigation pattern.
Silent Failure Detection
The hardest QA bug: API returns 200 OK but the data is null/empty. No error, no crash — the app just silently breaks.
const orders = await fetchOrders(userId); // 200 OK
QALink.trackDataState({
context: 'orders_list',
expected: Array.isArray(orders) && orders.length > 0,
actual: orders,
statusCode: 200,
url: '/api/orders',
});
// If orders is [] → fires a silent_failure event in the dashboardReturns true if the check passes, false if a failure was detected.
Middleware (Event Processing Pipeline)
Intercept, enrich, filter, or transform any event before it's sent.
// Drop noisy console_log events in production
QALink.use((event, next) => {
if (event.type === 'console_log') return; // drop
next(event);
});
// Add a custom tag to every event
QALink.use((event, next) => {
next({ ...event, _release: '2.1.0' } as typeof event);
});
// Log every event locally for debugging
QALink.use((event, next) => {
console.log('[QALink middleware]', event.type);
next(event);
});Middlewares run in insertion order. Calling next is optional — skipping it drops the event.
Offline-First Queue
Events are buffered automatically when the connection drops. Auto-flush triggers:
QALink.init({
apiKey: 'qlk_...',
appVersion: '1.0.0',
offlineQueue: {
maxSize: 500, // max buffered events (drops oldest when full)
flushOnCount: 20, // auto-flush when 20 events are buffered
flushIntervalMs: 30_000, // auto-flush every 30s
},
});
// Manual flush (e.g. before app goes to background)
QALink.flush();If @react-native-async-storage/async-storage is installed, events also persist across app restarts.
Sequence Numbers
Every envelope sent to the dashboard now includes a sequenceNumber — a monotonically increasing counter per session. The dashboard can use it to detect out-of-order or missing events.
📦 Event Packages (v0.6.0)
Los Event Packages agrupan todos los eventos de una pantalla en un solo payload que se envía cuando el usuario sale de ella. Incluyen métricas pre-calculadas y el historial completo de eventos.
Integración en 1 línea — useTrackedScreen
import { useTrackedScreen } from 'react-native-qalink';
function CheckoutScreen() {
useTrackedScreen('CheckoutScreen');
// ↑ inicia el package al montar, lo cierra y envía al desmontar
return <View>...</View>;
}Eso es todo. Todos los eventos generados mientras el usuario está en esa pantalla (API calls, logs, errores, breadcrumbs) se agrupan y se envían juntos al salir.
API manual — startPackage / endPackage
// En React Navigation (App.tsx)
<NavigationContainer
onStateChange={() => {
const screen = navigationRef.current?.getCurrentRoute()?.name ?? '';
QALink.endPackage(); // cierra el package de la pantalla anterior
QALink.startPackage(screen); // inicia uno nuevo para la pantalla actual
}}
>Payload que recibe el dashboard
{
"type": "event_package",
"packageId": "...",
"context": {
"screenName": "CheckoutScreen",
"sessionId": "...",
"startTime": 1710445670000,
"endTime": 1710445680000,
"durationMs": 10000
},
"events": [ ...todos los eventos capturados en esa pantalla... ],
"metrics": {
"totalApiCalls": 3,
"totalErrors": 1,
"totalLogs": 5,
"totalBreadcrumbs": 2,
"totalEvents": 11
}
}Backward compatibility
Si no llamas startPackage ni useTrackedScreen, cada evento se sigue enviando inmediatamente como antes. Sin cambios de comportamiento.
📊 Tipos de eventos capturados
El SDK captura automáticamente:
| Tipo | Descripción |
|------|-------------|
| session_start | Inicio de sesión |
| event_package | Batch de eventos de una pantalla (v0.6.0) |
| rage_click | Usuario tapeó el mismo elemento N veces (v0.7.0) |
| navigation_loop | Usuario rebotando entre mismas pantallas (v0.7.0) |
| silent_failure | API 200 OK con datos inválidos/vacíos (v0.7.0) |
| http_request | Request HTTP (fetch/axios) |
| user_log | Logs con debug/info/warn/error/critical |
| custom_event | Eventos custom con logEvent() |
| console_log | console.log |
| console_warn | console.warn |
| console_error | console.error |
| runtime_error | Red screen / Yellow box |
| metro_error | Errores de Metro Bundler |
| js_error | Errores de JavaScript |
| breadcrumb | Navegación y acciones |
| screen_capture | Screenshots |
🔐 Compatibilidad con fetch-blob
El interceptor de fetch es 100% compatible con fetch-blob:
import { fetch, Blob } from 'react-native-fetch-blob';
// QALink detecta automáticamente Blobs y no intenta serializarlos
const blob = new Blob([...], { type: 'image/jpeg' });
await fetch('https://api.example.com/upload', {
method: 'POST',
body: blob
});
// En el dashboard verás:
// body: { _type: 'Blob', size: 12345, type: 'image/jpeg' }🎯 Casos de uso
1. Tracking de flujo de checkout
QALink.setUserContext({ userId: user.id, email: user.email });
QALink.logEvent('checkout_started', { cartTotal: 299.99 });
QALink.info('Loading payment methods');
// ... usuario selecciona método de pago ...
QALink.logEvent('payment_method_selected', { method: 'credit_card' });
try {
const response = await processPayment();
QALink.logEvent('payment_success', {
orderId: response.orderId,
amount: 299.99
});
} catch (error) {
QALink.error('Payment failed', {
reason: error.message,
code: error.code
});
await QALink.captureScreen('payment_error');
}2. Debugging de API lenta
QALink.configureHTTP({
captureTimings: true,
slowRequestThreshold: 1500, // 1.5s
});
// Los requests > 1.5s aparecerán marcados en el dashboard3. Tracking de experimentos A/B
const variant = getExperimentVariant('new_ui');
QALink.setCustomContext({
experiment: 'new_ui',
variant: variant, // 'A' or 'B'
});
QALink.logEvent('experiment_viewed', {
experiment: 'new_ui',
variant
});4. Event Package con React Navigation
// App.tsx
import { useTrackedScreen } from 'react-native-qalink';
// En cada pantalla — 1 sola línea:
function PaymentScreen() {
useTrackedScreen('PaymentScreen');
// ...
}
function ProfileScreen() {
useTrackedScreen('ProfileScreen');
// ...
}O con NavigationContainer para cobertura automática de todas las pantallas:
// App.tsx
import { NavigationContainer } from '@react-navigation/native';
import { QALink } from 'react-native-qalink';
<NavigationContainer
onStateChange={() => {
const screen = navigationRef.current?.getCurrentRoute()?.name ?? 'unknown';
QALink.endPackage(); // cierra el package de la pantalla anterior
QALink.startPackage(screen); // inicia uno nuevo
}}
>📋 Referencia completa de la API
| Método | Descripción |
|--------|-------------|
| init(config) | Inicializa el SDK. Requerido antes de todo. |
| interceptAxios(instance) | Registra una instancia de Axios. |
| use(middleware) | Agrega un middleware al pipeline de eventos. |
| setUserContext(ctx) | Adjunta datos del usuario a todos los eventos. |
| setCustomContext(ctx) | Adjunta datos custom a todos los eventos. |
| clearContext() | Limpia usuario y contexto custom. |
| debug(...args) | Log nivel debug. |
| info(...args) | Log nivel info. |
| warn(...args) | Log nivel warn. |
| error(...args) | Log nivel error. |
| critical(...args) | Log nivel critical. |
| logEvent(name, data?) | Evento custom de negocio. |
| addBreadcrumb(action, data?) | Breadcrumb manual. |
| logRequest(options) | Registra request de red manualmente. |
| setScreen(name) | Cambia la pantalla actual + breadcrumb + loop detection. |
| startPackage(screenName) | Inicia un Event Package para esa pantalla. |
| endPackage() | Cierra el package activo y lo envía. |
| flush() | Envía inmediatamente todos los eventos en cola offline. |
| enableRageClickDetection(options?) | Activa detección de rage clicks. |
| trackUIInteraction(target) | Registra tap para rage click. |
| enableNavigationLoopDetection(threshold?) | Activa detección de loops de navegación. |
| trackDataState(check) | Detecta silent failures (API 200 con datos inválidos). |
| captureScreen(label?) | Captura la pantalla completa. |
| captureRef(ref, label?) | Captura un componente específico. |
| configureHTTP(config) | Actualiza config HTTP en runtime. |
| getDeviceId() | Devuelve el deviceId de la sesión. |
| getStatus() | Estado de la conexión WebSocket. |
| getQueueSize() | Número de eventos en cola offline. |
| destroy() | Limpia interceptores y desconecta. |
Hook:
| Hook | Descripción |
|------|-------------|
| useTrackedScreen(name) | 1 línea: inicia y cierra el package automáticamente al montar/desmontar la pantalla. |
🚨 Troubleshooting
Los logs no aparecen en el dashboard
- Verifica que
apiKeysea correcto (formato:qlk_<64 hex>) - Verifica que
enabled: true - Revisa la consola:
QALink.getStatus()debe ser'connected'
Screen capture no funciona
- Instala:
npm install react-native-view-shot - Habilita:
enableScreenCapture: trueen config - iOS:
cd ios && pod install
Axios no se intercepta
- Llama
QALink.interceptAxios(axiosInstance)después deQALink.init()
📖 Recursos
- Dashboard: https://qalink.airbites.org
- Documentación completa: (próximamente)
- GitHub: (próximamente)
📄 Licencia
MIT
🤝 Contribuir
Pull requests son bienvenidos. Para cambios mayores, abre primero un issue.
