@olimpo/capture-client
v2.0.2
Published
Cliente de captura de errores e interacciones para aplicaciones Olimpo.
Readme
@olimpo/capture-client
Cliente de captura de errores e interacciones para aplicaciones Olimpo.
Instalación
npm install @olimpo/capture-client
# o
pnpm add @olimpo/capture-clientUso
Inicialización
import { OlimpoCaptureClient } from "@olimpo/capture-client";
import { SessionManager } from "@calimaco/base_v2";
import cfg from "./utils/Config";
// Inicializar con SessionManager (opcional)
const session = new SessionManager(cfg);
OlimpoCaptureClient.init({
sessionProvider: session,
apiUrl: "https://api.example.com/api", // opcional
projectSlug: "my-project",
environmentName: "production",
token: "your-auth-token",
country: "PE",
});Captura de Errores
import { OlimpoCaptureClient } from "@olimpo/capture-client";
// Captura manual
OlimpoCaptureClient.captureException({
event_type: "error",
message: "Error description",
level: "error",
fingerprint: "unique-error-id",
stack_trace: [
{
file: "component.tsx",
line: 42,
function: "handleClick",
},
],
});
// Captura segura (no lanza excepciones)
OlimpoCaptureClient.captureExceptionSafe({
event_type: "error",
message: "Error description",
level: "warning",
});Hook de Captura de Errores
import { useCaptureError } from "@olimpo/capture-client";
function MyComponent() {
const { captureError } = useCaptureError();
const handleAction = () => {
try {
// código que puede fallar
} catch (error) {
captureError(error, {
event_type: "user_action_error",
level: "error",
tags: { action: "submit_form" },
});
}
};
}Captura de Interacciones
import { OlimpoCaptureClient } from "@olimpo/capture-client";
// Captura manual
OlimpoCaptureClient.captureInteraction({
event_type: "click",
component_name: "LoginButton",
action: "click",
metadata: {
page: "/login",
element: "submit-btn",
},
});
// Captura segura
OlimpoCaptureClient.captureInteractionSafe({
event_type: "page_view",
component_name: "HomePage",
});Hook de Captura de Interacciones
import { useCaptureInteraction } from "@olimpo/capture-client";
function MyComponent() {
const captureInteraction = useCaptureInteraction();
const handleClick = () => {
captureInteraction({
event_type: "click",
component_name: "ProductCard",
action: "add_to_cart",
metadata: { productId: "123" },
});
};
}ErrorBoundary
import { ErrorBoundary } from "@olimpo/capture-client";
function App() {
return (
<ErrorBoundary>
<YourApp />
</ErrorBoundary>
);
}API
OlimpoCaptureClient
init(options)
Inicializa el cliente.
Opciones:
sessionProvider?: SessionProvider- Proveedor de sesión de usuarioapiUrl?: string- URL base de la API (fallback:LAMBDA_NOTIFICATION_URL)projectSlug: string- Slug del proyecto (fallback:REACT_APP_OLIMPO_CAPTURE_ERROR_PROJECT_SLUG)environmentName: string- Nombre del entorno (fallback:REACT_APP_OLIMPO_CAPTURE_ERROR_ENVIRONMENT)token: string- Token de autenticación (fallback:LAMBDA_NOTIFICATION_URL_X_API_KEY)country?: string- Código de país (fallback:REACT_APP_COUNTRY, por defectoPE)fingerprint?: FingerprintConfig- Configuración del fingerprint (ver sección Fingerprint)captureGlobalErrors?: boolean- Instalawindow.onerroryunhandledrejection
Los fallbacks solo aplican cuando se captura un evento sin haber llamado a init() antes
(por ejemplo, un error lanzado antes de montar _app). Si llamas a init(), las variables
de entorno se ignoran.
isInitialized(): boolean
Verifica si el cliente está inicializado.
captureException(payload): Promise<ErrorEventResponse>
Captura un error de forma asíncrona.
captureExceptionSafe(payload): void
Captura un error de forma segura (no lanza excepciones).
captureInteraction(payload): Promise<void>
Captura una interacción de usuario.
captureInteractionSafe(payload): void
Captura una interacción de forma segura.
isPWA(): boolean
Detecta si la aplicación se ejecuta como PWA.
getPlatformInfo()
Obtiene información de la plataforma (OS, navegador, tipo de dispositivo).
resolveFingerprintContext({ fingerprint_id, project_slug?, environment_name? })
Consulta la API para resolver el contexto del usuario asociado a un fingerprint_id.
Contrato esperado del backend:
POST /fingerprints/resolve
{
"fingerprint_id": "sha256:...",
"project_slug": "my-project",
"environment_name": "production"
}Respuesta sugerida:
{
"fingerprint_id": "sha256:...",
"user_context": {
"user_id": "123",
"username": "jdoe",
"email": "[email protected]"
},
"payload": {}
}Fingerprint (fp.v2)
El fingerprint identifica dispositivos, no personas. Requiere consentimiento explícito y está deshabilitado por defecto.
Por qué cambió el esquema
En fp.v1 el fingerprint_id se derivaba solo de atributos de modelo: user agent, pantalla, canvas_hash, webgl_renderer, fonts_sig. Todos esos valores son idénticos entre equipos del mismo modelo y navegador, así que decenas de miles de usuarios compartían una misma huella y el cruce con eventos era inservible.
fp.v2 separa las señales en tres niveles y solo usa las de unidad para construir el id:
| Campo | Nivel | Entra al fingerprint_id |
| --- | --- | --- |
| device_unit_id | Unidad (aleatorio persistido) | Sí |
| device_hw_sig | Unidad (hardware + configuración) | Sí |
| device_model_sig | Modelo / gama | No — solo para segmentar |
fingerprint_id = sha256(salt | dominio | purpose | { device_unit_id, device_hw_sig, native })
Configuración
OlimpoCaptureClient.init({
// ...resto de la configuración
fingerprint: {
enabled: true,
consentGiven: false, // activar solo tras consentimiento
includeAttributes: false,
salt: "valor-por-proyecto",
purpose: "telemetry",
useDeviceUnitId: true, // principal fuente de unicidad
storagePrefix: "olimpo",
},
});
// Tras obtener consentimiento:
OlimpoCaptureClient.setFingerprintConsent(true);
await OlimpoCaptureClient.registerFingerprint();| Opción | Default | Descripción |
| --- | --- | --- |
| enabled | false | Habilita la recolección |
| consentGiven | false | Sin esto no se genera nada |
| includeAttributes | false | Envía los atributos crudos además del id |
| salt | "" | Separa espacios de identidad entre proyectos |
| purpose | "telemetry" | Propósito declarado |
| schemaVersion | "fp.v2" | Cambiarlo altera todos los ids |
| ttlSeconds | 30 días | TTL declarado |
| useDeviceUnitId | true | Identificador aleatorio persistido |
| storagePrefix | "olimpo" | Prefijo de las claves de almacenamiento |
| nativeIdentifiers | () => ({}) | Identificadores del host (ver abajo) |
device_unit_id
Son 128 bits de crypto.getRandomValues persistidos en localStorage, cookie (400 días) y sessionStorage a la vez, para sobrevivir limpiezas parciales como el ITP de Safari o una WebView efímera. Si el navegador bloquea todo almacenamiento, el id se genera igual pero fingerprint_confidence baja a medium.
fingerprint_confidence indica de dónde vino la entropía:
high— hay identificadores nativos, o eldevice_unit_idquedó persistido.medium— se generó pero no se pudo persistir; rotará en la próxima sesión.low— sindevice_unit_id; el id es de nivel modelo y no debe usarse para atribuir usuarios.
Identificadores nativos (WebView)
IMEI, número de serie o identifierForVendor no son accesibles desde la web. Si la app corre dentro de una WebView con puente nativo, se pueden inyectar y entran al hash. Nunca viajan en claro:
fingerprint: {
enabled: true,
consentGiven: true,
nativeIdentifiers: () => ({
androidId: window.NativeBridge?.getAndroidId(),
vendorId: window.NativeBridge?.getVendorId(),
}),
}Son datos personales en la mayoría de marcos legales: requieren consentimiento explícito e informado.
Dirección IP
La librería no recolecta la IP y no debe hacerlo: es falsificable desde el cliente, y en móvil rota por CGNAT mientras una IP corporativa la comparten miles de personas. El backend la deriva del request y la persiste como ip_hash, solo como señal de corroboración — nunca como componente del fingerprint_id.
Migración desde fp.v1
Todos los fingerprint_id cambian al pasar a fp.v2. Los ids de ambos esquemas no son comparables: filtra siempre por fingerprint_schema_version y no cruces datos entre versiones. Las huellas fp.v1 con alta cardinalidad de usuarios conviene descartarlas del análisis.
Variables de Entorno
LAMBDA_NOTIFICATION_URL=https://api.example.com/api
LAMBDA_NOTIFICATION_URL_X_API_KEY=your-api-key
REACT_APP_OLIMPO_CAPTURE_ERROR_PROJECT_SLUG=my-project
REACT_APP_OLIMPO_CAPTURE_ERROR_ENVIRONMENT=production
REACT_APP_COUNTRY=PETipos
interface SessionProvider {
getUser: () => UserContext | undefined;
}
interface UserContext {
user?: string;
alias?: string;
email?: string;
mobile?: string;
national_id?: string;
groups?: string[];
}Licencia
Privado - Olimpo
