@epicdm/flowstate-obs-browser
v1.0.0
Published
Browser SDK for Epic Flow Observability Platform
Maintainers
Readme
@epic-flow/obs-browser
Browser SDK for the Epic Flow Observability Platform. Capture errors, logs, and user interactions in real-time with automatic error tracking and WebSocket-based event streaming.
Features
- Automatic Error Capture: Global error and unhandled rejection handlers
- Manual Error Reporting: Programmatic error capture with context
- Structured Logging: Multiple log levels (debug, info, warn, error)
- Breadcrumb Tracking: Track user actions leading to errors
- Context Management: Attach custom data and tags to all events
- User Tracking: Associate events with user sessions
- WebSocket Connection: Low-latency real-time event streaming
- Auto Reconnection: Automatic reconnection with exponential backoff
- Message Buffering: Queue events during disconnection
- TypeScript Support: Full type definitions included
- Lightweight: ~15KB minified, ~5KB gzipped
Installation
NPM
npm install @epic-flow/obs-browserYarn
yarn add @epic-flow/obs-browserCDN (UMD build)
<script src="https://unpkg.com/@epic-flow/obs-browser@latest/dist/index.js"></script>
<script>
const client = new ObservabilityPlatform.ObservabilityClient({
url: 'ws://your-server.com/ws',
apiKey: 'your-api-key',
projectId: 'your-project-id'
});
client.init();
</script>Quick Start
import { ObservabilityClient } from '@epic-flow/obs-browser';
// Initialize the client
const client = new ObservabilityClient({
url: 'ws://localhost:8080/ws',
apiKey: 'your-api-key-123',
projectId: 'my-app',
environment: 'production',
release: '1.0.0',
autoCapture: true, // Automatically capture errors
});
// Start observability
client.init();
// That's it! Errors are now automatically captured
// Add custom logging as needed
client.info('Application started', { userId: '123' });API Reference
Constructor
new ObservabilityClient(config: ObservabilityConfig)
Creates a new observability client instance.
Parameters:
| Name | Type | Required | Description |
|------|------|----------|-------------|
| config.url | string | Yes | WebSocket server URL (e.g., ws://localhost:8080/ws) |
| config.apiKey | string | Yes | Project API key for authentication |
| config.projectId | string | Yes | Unique project identifier |
| config.environment | string | No | Environment name (e.g., production, staging) |
| config.release | string | No | Release version (e.g., 1.0.0) |
| config.userId | string | No | Initial user ID to associate with events |
| config.autoCapture | boolean | No | Enable automatic error capture (default: true) |
Example:
const client = new ObservabilityClient({
url: 'ws://localhost:8080/ws',
apiKey: 'your-api-key-123',
projectId: 'my-app',
environment: 'production',
release: '1.0.0',
userId: 'user-456',
autoCapture: true,
});Methods
init(): void
Initializes the client, connects to the server, and installs error handlers.
Example:
client.init();shutdown(): void
Disconnects from the server and removes error handlers. Call this when your application is shutting down.
Example:
// On app cleanup or navigation
client.shutdown();captureError(error: Error, context?: Record<string, any>): void
Manually capture an error with optional additional context.
Parameters:
error(Error): The error object to capturecontext(Record<string, any>, optional): Additional context data
Example:
try {
// Your code
throw new Error('Something went wrong');
} catch (error) {
client.captureError(error, {
action: 'checkout',
orderId: '12345',
paymentMethod: 'credit-card'
});
}captureException(error: Error, context?: Record<string, any>): void
Alias for captureError(). Provided for compatibility with other error tracking SDKs.
Example:
client.captureException(new Error('Payment failed'), {
userId: '123',
amount: 99.99
});log(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: Record<string, any>): void
Generic logging method for structured logs.
Parameters:
level: Log severity levelmessage: Log messagedata(optional): Additional structured data
Example:
client.log('info', 'User action', {
action: 'button_click',
buttonId: 'submit',
timestamp: Date.now()
});debug(message: string, data?: any): void
Log a debug message.
Example:
client.debug('Component rendered', { componentName: 'Header', props: {...} });info(message: string, data?: any): void
Log an info message.
Example:
client.info('User logged in', { userId: '123', method: 'oauth' });warn(message: string, data?: any): void
Log a warning message.
Example:
client.warn('API response slow', { endpoint: '/api/users', duration: 5000 });error(message: string, data?: any): void
Log an error message (not an exception).
Example:
client.error('Validation failed', { field: 'email', value: 'invalid' });addBreadcrumb(breadcrumb: Breadcrumb): void
Add a breadcrumb to track user actions and events. Breadcrumbs are included with error events to provide context.
Parameters:
interface Breadcrumb {
timestamp: number;
category: 'navigation' | 'click' | 'console' | 'http' | 'error';
message: string;
data?: Record<string, any>;
}Example:
// Track navigation
client.addBreadcrumb({
timestamp: Date.now(),
category: 'navigation',
message: 'User navigated to checkout',
data: { from: '/cart', to: '/checkout' }
});
// Track user interaction
client.addBreadcrumb({
timestamp: Date.now(),
category: 'click',
message: 'Clicked submit button',
data: { buttonId: 'checkout-submit' }
});
// Track HTTP request
client.addBreadcrumb({
timestamp: Date.now(),
category: 'http',
message: 'POST /api/orders',
data: { status: 200, duration: 245 }
});Note: The SDK maintains a maximum of 30 breadcrumbs. Older breadcrumbs are automatically removed when the limit is exceeded.
setUser(userId: string, userData?: Record<string, any>): void
Set the current user context. All subsequent events will include this user ID.
Parameters:
userId: Unique user identifieruserData(optional): Additional user data (reserved for future use)
Example:
client.setUser('user-123', {
email: '[email protected]',
subscription: 'premium'
});clearUser(): void
Clear the current user context.
Example:
// On logout
client.clearUser();setContext(key: string, value: any): void
Set custom context data that will be included with all events.
Parameters:
key: Context keyvalue: Context value (any JSON-serializable data)
Example:
client.setContext('theme', 'dark');
client.setContext('experiment', { variant: 'A', id: 'exp-123' });
client.setContext('feature-flags', {
newCheckout: true,
betaFeatures: false
});clearContext(key?: string): void
Clear custom context. If key is provided, clears that specific context. If omitted, clears all context.
Example:
// Clear specific context
client.clearContext('theme');
// Clear all context
client.clearContext();setTag(key: string, value: string): void
Set a tag for filtering and grouping events. Tags are key-value string pairs.
Parameters:
key: Tag keyvalue: Tag value (must be string)
Example:
client.setTag('server-region', 'us-east-1');
client.setTag('customer-tier', 'enterprise');
client.setTag('feature', 'checkout-v2');Configuration Reference
ObservabilityConfig
Complete configuration interface:
interface ObservabilityConfig {
// Required fields
url: string; // WebSocket server URL
apiKey: string; // Project API key
projectId: string; // Project identifier
// Optional fields
environment?: string; // Environment (e.g., 'production', 'staging', 'development')
release?: string; // Release version (e.g., '1.0.0', 'v2.3.1-beta')
userId?: string; // Initial user ID
autoCapture?: boolean; // Auto-capture errors (default: true)
}Event Types
ErrorEvent
interface ErrorEvent {
id: string; // Unique event ID
type: 'error';
projectId: string; // Project identifier
timestamp: number; // Unix timestamp (ms)
level: 'error' | 'fatal'; // Error severity
message: string; // Error message
stackTrace: string[]; // Parsed stack trace lines
stack?: string; // Raw stack trace
environment: {
platform: 'browser' | 'node' | 'react-native' | 'electron';
userAgent?: string; // Browser user agent
os?: string; // Operating system
appVersion?: string; // Application version
};
release?: string; // Release version
userId?: string; // User identifier
url?: string; // Page URL where error occurred
breadcrumbs?: Breadcrumb[]; // User action trail
context?: Record<string, any>; // Custom context data
tags?: Record<string, string>; // Event tags
metadata: Record<string, any>; // Additional metadata
}LogEvent
interface LogEvent {
id: string; // Unique event ID
type: 'log';
projectId: string; // Project identifier
timestamp: number; // Unix timestamp (ms)
level: 'debug' | 'info' | 'warn' | 'error';
message: string; // Log message
environment?: string; // Environment name
release?: string; // Release version
userId?: string; // User identifier
data?: Record<string, any>; // Custom log data
context?: Record<string, any>; // Custom context
tags?: Record<string, string>; // Event tags
}Breadcrumb
interface Breadcrumb {
timestamp: number; // Unix timestamp (ms)
category: 'navigation' | 'click' | 'console' | 'http' | 'error';
message: string; // Breadcrumb description
data?: Record<string, any>; // Additional data
}Usage Examples
React Integration
import { ObservabilityClient } from '@epic-flow/obs-browser';
import { useEffect } from 'react';
// Create client instance (outside component)
const observability = new ObservabilityClient({
url: 'ws://localhost:8080/ws',
apiKey: process.env.REACT_APP_OBS_API_KEY,
projectId: 'my-react-app',
environment: process.env.NODE_ENV,
release: process.env.REACT_APP_VERSION,
});
// Initialize in app root
function App() {
useEffect(() => {
observability.init();
return () => {
observability.shutdown();
};
}, []);
return <YourApp />;
}
// Use in components
function CheckoutButton() {
const handleClick = async () => {
observability.addBreadcrumb({
timestamp: Date.now(),
category: 'click',
message: 'Checkout button clicked'
});
try {
await processCheckout();
observability.info('Checkout completed', { orderId: '123' });
} catch (error) {
observability.captureError(error, { step: 'checkout' });
}
};
return <button onClick={handleClick}>Checkout</button>;
}Vue Integration
import { ObservabilityClient } from '@epic-flow/obs-browser';
// Create plugin
const ObservabilityPlugin = {
install(app, options) {
const client = new ObservabilityClient(options);
client.init();
// Add to global properties
app.config.globalProperties.$obs = client;
// Error handler
app.config.errorHandler = (error, instance, info) => {
client.captureError(error, { component: instance?.$options.name, info });
};
}
};
// Use in main.js
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.use(ObservabilityPlugin, {
url: 'ws://localhost:8080/ws',
apiKey: import.meta.env.VITE_OBS_API_KEY,
projectId: 'my-vue-app',
environment: import.meta.env.MODE,
});
app.mount('#app');
// Use in components
export default {
methods: {
async submitForm() {
try {
await api.submit(this.formData);
this.$obs.info('Form submitted', { form: 'contact' });
} catch (error) {
this.$obs.captureError(error, { form: 'contact' });
}
}
}
};Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<script src="https://unpkg.com/@epic-flow/obs-browser/dist/index.js"></script>
</head>
<body>
<button id="myButton">Click Me</button>
<script>
// Initialize
const obs = new ObservabilityPlatform.ObservabilityClient({
url: 'ws://localhost:8080/ws',
apiKey: 'your-api-key',
projectId: 'my-app',
autoCapture: true
});
obs.init();
// Track interactions
document.getElementById('myButton').addEventListener('click', () => {
obs.addBreadcrumb({
timestamp: Date.now(),
category: 'click',
message: 'Button clicked',
data: { buttonId: 'myButton' }
});
obs.info('User clicked button');
});
</script>
</body>
</html>With TypeScript
import {
ObservabilityClient,
type ObservabilityConfig,
type Breadcrumb,
type ErrorEvent,
type LogEvent
} from '@epic-flow/obs-browser';
const config: ObservabilityConfig = {
url: 'ws://localhost:8080/ws',
apiKey: 'your-api-key',
projectId: 'my-app',
environment: 'production',
release: '1.0.0',
};
const client = new ObservabilityClient(config);
client.init();
// Type-safe breadcrumb
const breadcrumb: Breadcrumb = {
timestamp: Date.now(),
category: 'http',
message: 'API request',
data: { endpoint: '/api/users', status: 200 }
};
client.addBreadcrumb(breadcrumb);Browser Compatibility
| Browser | Minimum Version | |---------|----------------| | Chrome | 90+ | | Firefox | 88+ | | Safari | 14+ | | Edge | 90+ | | iOS Safari | 14+ | | Chrome Mobile | Latest |
Requirements:
- WebSocket support (required)
- ES6 support (required)
- Promise support (required)
Not Supported:
- Internet Explorer (all versions)
- Legacy browsers without WebSocket
Performance Considerations
Memory Usage
- Baseline: ~5MB
- Per Breadcrumb: ~1KB (max 30 breadcrumbs = 30KB)
- Per Buffered Event: ~1-2KB
- Buffer Limit: 100 events during disconnection
Network Usage
- Average Event Size: 500 bytes (before compression)
- With gzip: ~200 bytes
- Heartbeat: Minimal ping/pong every 30 seconds
CPU Impact
- Error Capture: < 1ms overhead
- Log Call: < 0.5ms overhead
- Breadcrumb Addition: < 0.1ms overhead
Best Practices
- Use appropriate log levels: Debug logs only in development
- Limit breadcrumb data: Keep data payloads small
- Batch when possible: Events are automatically batched over WebSocket
- Set release version: Helps with debugging and filtering
- Clean up on unmount: Always call
shutdown()when done
Troubleshooting
Connection Issues
Problem: "WebSocket connection failed"
Solutions:
- Verify server is running and accessible
- Check URL uses
ws://(orwss://for secure) - Verify API key is correct
- Check CORS settings on server
- Ensure no firewall blocking WebSocket
Events Not Appearing
Problem: Events sent but not visible
Solutions:
- Check server logs for validation errors
- Verify projectId matches server configuration
- Check rate limit (1000 events/min)
- Ensure WebSocket is connected (check state)
- Verify event schema matches server requirements
TypeScript Errors
Problem: Type errors when using SDK
Solutions:
// Ensure types are imported
import type { ObservabilityConfig } from '@epic-flow/obs-browser';
// Or use inline types
const config = {
url: 'ws://...',
apiKey: 'key',
projectId: 'id',
} satisfies ObservabilityConfig;Memory Leaks
Problem: Memory usage grows over time
Solutions:
- Always call
client.shutdown()on cleanup - Limit breadcrumb data size
- Check for reconnection loops (verify server is stable)
- Monitor buffered message count during disconnections
Advanced Usage
Custom Error Boundaries (React)
import { Component, ReactNode } from 'react';
class ErrorBoundary extends Component {
componentDidCatch(error: Error, errorInfo: any) {
observability.captureError(error, {
errorInfo,
componentStack: errorInfo.componentStack
});
}
render() {
return this.props.children;
}
}Rate Limiting on Client
// Debounce high-frequency events
let lastLog = 0;
const logRateLimit = 1000; // 1 event per second
function throttledLog(message: string) {
const now = Date.now();
if (now - lastLog >= logRateLimit) {
client.info(message);
lastLog = now;
}
}Environment-Specific Configuration
const config = {
url: process.env.NODE_ENV === 'production'
? 'wss://obs.production.com/ws'
: 'ws://localhost:8080/ws',
apiKey: process.env.OBS_API_KEY,
projectId: 'my-app',
environment: process.env.NODE_ENV,
autoCapture: process.env.NODE_ENV === 'production',
};Contributing
License
MIT
