@scalable-labs/sentinel
Version:
Frontend error monitoring and tracking client with local storage, Microsoft Teams integration, and visual error viewer
436 lines (329 loc) • 14.6 kB
Markdown
# @scalable-labs/sentinel
[](https://github.com/scalabledk/sentineljs/actions/workflows/ci.yml)
[](https://github.com/scalabledk/sentineljs/actions/workflows/codeql.yml)
[](https://www.npmjs.com/package/@scalable-labs/sentinel)
[](https://opensource.org/licenses/MIT)
[](https://www.npmjs.com/package/@scalable-labs/sentinel)
> **⚠️ Pre-1.0 Notice**: This package is currently in active development and has not reached v1.0 yet. APIs may change and stability is not guaranteed. Use in production at your own risk. We welcome feedback and contributions!
Frontend error monitoring client for Sentinel by Scalable Labs. Automatically captures and reports failed API requests with support for local storage, Microsoft Teams integration, and visual error tracking.
## 🔗 Related Projects
- **Backend Service**: [sentinel-server](https://github.com/scalabledk/sentinel-server)
- **Website**: [sentinel-website](https://github.com/scalabledk/sentinel-website)
> **Note**: This repository (`sentineljs`) contains the source code for the npm package published as `@scalable-labs/sentinel`.
## Installation
```bash
npm install @scalable-labs/sentinel
```
## Quick Start
### Local Mode with UI (No Backend Required)
Perfect for development or when you want to track errors locally without a backend:
```typescript
import { createSentinel } from '@scalable-labs/sentinel';
const sentinel = createSentinel({
mode: 'local', // Store errors in browser IndexedDB
showUI: true, // Show floating error viewer button
uiPosition: 'bottom-right',
teamMapping: {
'/api/users': 'platform',
'/api/orders': 'commerce',
'/api/products': 'catalog',
},
teamsChannelUrl: 'https://teams.microsoft.com/l/channel/...',
captureHeaders: ['x-correlation-id', 'x-request-id'],
getUserName: () => getCurrentUser()?.email,
});
// Errors are automatically captured and stored locally
// Click the ⚠️ button to view, copy, or send to Teams
```
### Remote Mode (With Sentinel Backend)
```typescript
import { createSentinel } from '@scalable-labs/sentinel';
const sentinel = createSentinel({
mode: 'remote',
backendUrl: 'https://sentinel.yourcompany.com',
apiKey: 'your-api-key',
enabled: process.env.NODE_ENV === 'production',
teamMapping: {
'/api/users': 'platform',
'/api/orders': 'commerce',
},
getUserName: () => getCurrentUser()?.username,
});
// Fetch interceptor is automatically installed!
// All failed requests will be batched and sent to backend
```
## Configuration
| Option | Type | Required | Default | Description |
| ------------------ | ----------------------------- | --------------- | --------------- | ---------------------------------------------- |
| `mode` | `'local' \| 'remote'` | No | `'local'` | Operating mode |
| `teamMapping` | `Record<string, string>` | Yes | - | Map endpoints to teams |
| `enabled` | `boolean` | No | `true` | Enable/disable reporting |
| `backendUrl` | `string` | Yes (remote) | - | Sentinel backend URL (remote mode only) |
| `apiKey` | `string` | Yes (remote) | - | API key for backend (remote mode only) |
| `showUI` | `boolean` | No | `false` | Show visual error viewer (local mode only) |
| `uiPosition` | `'bottom-right' \| ...` | No | `'bottom-right'`| Position of UI button |
| `teamsChannelUrl` | `string` | No | - | Microsoft Teams channel URL for notifications |
| `captureHeaders` | `string[]` | No | - | HTTP headers to capture from responses |
| `getUserName` | `() => string \| undefined` | No | `undefined` | Function to get current username |
| `batchSize` | `number` | No | `50` | Batch size (remote mode) |
| `batchInterval` | `number` | No | `10000` | Batch interval in ms (remote mode) |
| `defaultTeam` | `string` | No | `'unknown'` | Team for unmapped endpoints |
| `dbName` | `string` | No | `'sentinel'` | IndexedDB database name (local mode) |
| `maxLocalErrors` | `number` | No | `1000` | Max errors to store locally |
## Features
### 🎯 Automatic Error Detection
Automatically captures failed HTTP requests (status >= 400) or network errors.
### 📦 Two Operating Modes
**Local Mode:**
- Stores errors in browser IndexedDB
- No backend required
- Perfect for development
- View errors in built-in UI
- Export as JSON
- Send to Microsoft Teams
**Remote Mode:**
- Sends errors to Sentinel backend
- Batched transmission
- Prometheus metrics
- Teams webhook alerts
### 🎨 Visual Error Viewer (Local Mode)
When `showUI: true`, a floating button (⚠️) appears to:
- View all tracked errors grouped by team
- Copy individual errors in JSON format
- Copy all errors in JSON format
- Send all errors to Microsoft Teams
- Clear all tracked errors
- Refresh error list
### 📊 Microsoft Teams Integration
Send errors directly to a Teams channel:
```typescript
teamsChannelUrl: 'https://teams.microsoft.com/l/channel/19%3a...thread.tacv2/General?groupId=...&tenantId=...'
```
Errors are formatted as JSON with team tags for easy tracking. All errors are sent to the configured channel.
### 📋 Header Capture
Capture specific headers from HTTP responses for distributed tracing:
```typescript
captureHeaders: ['x-correlation-id', 'x-request-id', 'x-trace-id']
```
Headers are included in error reports for debugging.
### 🏷️ Team Mapping
Map endpoints to teams using exact match, prefix matching, full URLs, or regex:
```typescript
teamMapping: {
// Path-based matching
'/api/users': 'platform', // Exact: /api/users
'/api/users/': 'platform', // Prefix: /api/users/123, /api/users/456
'/api/orders': 'commerce', // Prefix: /api/orders/...
// Full URL matching
'https://api.example.com/users': 'platform', // Exact hostname match
'https://api.example.[tld]/users': 'platform', // TLD variation: matches .com, .dk, .co.uk, etc.
'https://payments.stripe.[tld]/': 'payments', // TLD variation with path prefix
// Regex patterns
'/^\\/api\\/products\\/\\d+$/': 'catalog', // Regex: /api/products/123
'/^\\/api\\/payments/': 'payments', // Regex: starts with /api/payments
}
```
**Matching Priority:**
1. **Exact match** - checked first
2. **Prefix/URL/Regex** - checked in order (first match wins)
3. **Default team** - if no match found
**Pattern Types:**
- **Path patterns**: String starting with `/` (but not ending with `/` for regex)
- Exact: `/api/users` matches only `/api/users`
- Prefix: `/api/users/` matches `/api/users/123`, `/api/users/456/profile`, etc.
- **Full URL patterns**: String starting with `http://` or `https://`
- Exact hostname: `https://api.example.com/users` matches only `api.example.com`
- TLD variation: `https://api.example.[tld]/users` matches:
- `https://api.example.com/users`
- `https://api.example.dk/users`
- `https://api.example.co.uk/users`
- Use `[tld]` placeholder in hostname to enable TLD variation
- Path must match (prefix matching on path)
- **Regex patterns**: String starting and ending with `/`
- Pattern between slashes is used as the regex
- Example: `/^\\/api\\/v\\d+\\/users/` matches `/api/v1/users`, `/api/v2/users`, etc.
- Invalid regex patterns are skipped with a warning
**Combined Example:**
```typescript
teamMapping: {
// Path patterns
'/api/users': 'platform', // Exact match
'/api/users/': 'platform', // Prefix match
// Full URL patterns (exact hostname)
'https://api.internal.com/data': 'backend', // Internal API
'https://payment-gateway.company.com/': 'payments', // Payment service
// Full URL patterns (with TLD variation)
'https://api.example.[tld]/products': 'catalog', // Multi-region product API
'https://cdn.assets.[tld]/images': 'media', // Multi-region CDN
// Regex patterns for paths
'/^\\/api\\/orders\\/\\d+$/': 'commerce', // /api/orders/123
'/^\\/api\\/v[12]\\/users/': 'platform', // /api/v1/users, /api/v2/users
// Regex patterns for full URLs
'/^https:\\/\\/.*\\.stripe\\.com\\/.*$/': 'payments', // Any Stripe subdomain
'/^https:\\/\\/[a-z0-9-]+\\.cloudfront\\.net\\/.*$/': 'cdn', // CloudFront URLs
}
```
**This configuration will match:**
*Path patterns:*
- `/api/users` → `platform` (exact)
- `/api/users/123` → `platform` (prefix)
*Full URL patterns (exact):*
- `https://api.internal.com/data` → `backend`
- `https://payment-gateway.company.com/checkout` → `payments`
*Full URL patterns (TLD variation):*
- `https://api.example.com/products` → `catalog`
- `https://api.example.dk/products` → `catalog`
- `https://api.example.co.uk/products/item` → `catalog`
- `https://cdn.assets.com/images/logo.png` → `media`
- `https://cdn.assets.fr/images/logo.png` → `media`
*Regex patterns (paths):*
- `/api/orders/456` → `commerce`
- `/api/v1/users` → `platform`
- `/api/v2/users/search` → `platform`
*Regex patterns (full URLs):*
- `https://payments.stripe.com/v1/charges` → `payments`
- `https://connect.stripe.com/oauth` → `payments`
- `https://d1234567890.cloudfront.net/assets/app.js` → `cdn`
### 👤 Optional User Tracking
Track which users experience errors:
```typescript
getUserName: () => {
const user = localStorage.getItem('user');
return user ? JSON.parse(user).username : undefined;
}
```
### 🔐 Environment-based Toggling
Enable/disable based on environment:
```typescript
enabled: process.env.NODE_ENV === 'production'
// or
enabled: process.env.SENTINEL_ENABLED === 'true'
```
### 🛡️ Graceful Failure
If the Sentinel backend is unavailable, errors are logged to console but don't break your app.
## Advanced Usage
### Axios Integration
```typescript
import axios from 'axios';
import { SentinelClient, installAxiosInterceptor } from '@scalable-labs/sentinel';
const sentinel = new SentinelClient({
mode: 'remote',
backendUrl: 'https://sentinel.yourcompany.com',
apiKey: 'your-api-key',
teamMapping: { '/api/users': 'platform' },
});
installAxiosInterceptor(axios, sentinel);
```
### Manual Error Reporting
```typescript
import { SentinelClient } from '@scalable-labs/sentinel';
const sentinel = new SentinelClient({
mode: 'local',
teamMapping: { '/api/users': 'platform' },
});
// Manually report an error with headers
try {
const response = await fetch('/api/users/123');
if (!response.ok) {
const payload = await response.text();
const headers = {
'x-correlation-id': response.headers.get('x-correlation-id') || '',
};
sentinel.reportError('/api/users/123', 'GET', response.status, payload, headers);
}
} catch (error) {
sentinel.reportError('/api/users/123', 'GET', 0, 'Network error');
}
```
### Export Local Errors
```typescript
const sentinel = createSentinel({ mode: 'local', teamMapping: {...} });
// Get all errors as array
const errors = await sentinel.getLocalErrors();
// Export as JSON string
const json = await sentinel.exportLocalErrors();
console.log(json);
// Clear all errors
await sentinel.clearLocalErrors();
```
### Manual Flush (Remote Mode)
Force send all queued errors immediately:
```typescript
window.addEventListener('beforeunload', () => {
sentinel.flush();
});
```
## Examples
### React App with Local Mode
```typescript
// src/sentinel.ts
import { createSentinel } from '@scalable-labs/sentinel';
export const sentinel = createSentinel({
mode: 'local',
showUI: true,
uiPosition: 'bottom-right',
teamMapping: {
'/api/auth': 'platform',
'/api/users': 'platform',
'/api/products': 'catalog',
},
teamsChannelUrl: 'https://teams.microsoft.com/l/channel/...',
captureHeaders: ['x-correlation-id', 'x-request-id'],
getUserName: () => {
const userStr = localStorage.getItem('user');
return userStr ? JSON.parse(userStr).email : undefined;
},
});
// src/main.tsx
import { sentinel } from './sentinel';
// Sentinel is now monitoring all fetch requests!
```
### Next.js App with Remote Mode
```typescript
// lib/sentinel.ts
import { createSentinel } from '@scalable-labs/sentinel';
export const sentinel = createSentinel({
mode: 'remote',
backendUrl: process.env.NEXT_PUBLIC_SENTINEL_URL!,
apiKey: process.env.NEXT_PUBLIC_SENTINEL_API_KEY!,
enabled: process.env.NODE_ENV === 'production',
teamMapping: {
'/api/auth': 'platform',
'/api/blog': 'content',
'/api/products': 'ecommerce',
},
captureHeaders: ['x-trace-id'],
getUserName: () => {
if (typeof window !== 'undefined') {
return sessionStorage.getItem('username') || undefined;
}
},
});
// app/layout.tsx or pages/_app.tsx
import { sentinel } from '@/lib/sentinel';
// Auto-monitoring enabled!
```
## Development
```bash
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run watch
# Run tests
npm test
# Lint
npm run lint
# Format
npm run format
```
## Publishing
This package is published to npm as `@scalable-labs/sentinel`.
```bash
npm publish --access public
```
## License
MIT
## Contributing
Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/scalabledk/sentineljs).