Clearly communicate system status and errors to users, improving experience and reducing support.
npm install @upreport/oops-widget> β οΈ EXPERIMENTAL: This is an experimental version and the API may change significantly in future releases.
Clearly communicate system status and errors to users, improving experience and reducing support.
``bashUsing npm
npm install @upreport/oops-widget
β¨ Features
- π Real-time system status monitoring
- π¨ Customizable Web Component alert UI
- πΈοΈ Fetch and XHR request interceptors for error and slow-request detection
- π Internationalization support with auto-detection
- π± Mobile-friendly placement and responsive behavior
- π Optional incident details display with names, statuses, and links
β‘ Quickstart
`ts
import {
createOopsWidget,
LogLevel,
FetchInterceptor,
XHRInterceptor,
} from '@upreport/oops-widget';
import { StatusAlert } from '@upreport/oops-widget/statusAlert';
import { createAutoDetectI18nProvider } from '@upreport/oops-widget/i18n';// Configure request interceptors
const interceptOptions = {
timeoutMs: 20000,
showSlowRequestAlerts: true,
showErrorAlerts: true,
monitoredServicePatterns: ['/api/*'],
monitoredServiceOptions: {
requireExplicitPatterns: true,
includeCurrentOrigin: true,
treatRelativeAsInternal: true,
},
};
const widget = createOopsWidget({
// Status polling endpoint (expects query param
s)
statusUrl: 'https://status-api.example.com', // Alert component to render
alertComponent: StatusAlert,
// Placement on screen: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
placement: 'bottom-right',
// Internationalization provider
i18nProvider: createAutoDetectI18nProvider(),
// Request interceptors
interceptors: [new FetchInterceptor(interceptOptions), new XHRInterceptor(interceptOptions)],
// Mobile-specific configuration
mobile: {
enabled: true,
placement: 'top',
},
// Logging verbosity
logLevel: LogLevel.DEBUG,
// Whether to display incident details when available
displayIncidentDetails: false,
});
widget
.start()
.then(() => console.log('Widget started'))
.catch((error) => console.error('Error starting widget:', error));
`βοΈ Configuration Options
$3
| Option | Type | Default | Description |
| ------------------------ | --------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------------------------- |
|
statusUrl | string | _required_ | URL endpoint for polling system status (with query param s) |
| alertComponent | CustomElementConstructor | _required_ | Web Component class for rendering alerts (e.g., StatusAlert) |
| placement | 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' | 'bottom-right' | Screen placement of the alert |
| i18nProvider | I18nProvider | _required_ | Provider for localized status messages |
| interceptors | Interceptor[] | [] | Array of request interceptors (FetchInterceptor, XHRInterceptor) |
| mobile | { enabled: boolean; placement?: string; mediaQuery?: string } | { enabled: false } | Mobile-specific configuration |
| logLevel | LogLevel | LogLevel.INFO | Logging verbosity |
| alertClosureBehavior | Partial | β | Custom timing for alert close and status check intervals |
| displayIncidentDetails | boolean | false | Whether to display incident details (names, statuses, links) in alerts |$3
| Option | Type | Default | Description |
| -------------------------- | ---------- | ------- | ------------------------------------------------ |
|
timeoutMs | number | 30000 | Milliseconds before a request is considered slow |
| showSlowRequestAlerts | boolean | true | Emit slow-request alerts |
| showErrorAlerts | boolean | true | Emit non-2xx HTTP status alerts |
| monitoredServicePatterns | string[] | [] | URL patterns to monitor |
| monitoredServiceOptions | object | {} | Options for monitored pattern matching |#### monitoredServiceOptions
| Option | Type | Default | Description |
| ------------------------- | --------- | ------- | ------------------------------------------------------------------ |
|
requireExplicitPatterns | boolean | false | Only monitor URLs explicitly matching patterns |
| includeCurrentOrigin | boolean | true | Include same-origin requests as monitored if no patterns specified |
| treatRelativeAsInternal | boolean | true | Treat relative URLs as internal (same-origin) |π API Reference
$3
Instantiate a new widget with the provided configuration.
Returns:
OopsWidgetInstance with methods:- π
start(): Promise β Perform initial status check and start monitoring.
- π§Ή destroy(): void β Stop monitoring and cleanup resources.$3
Both implement the
Interceptor interface:`ts
interface Interceptor {
name: string;
setup(): () => void; // Returns cleanup function
}
`Use:
`ts
new FetchInterceptor(config);
new XHRInterceptor(config);
`Pass instances in the
interceptors array to the widget.π¨ UI Customization & Event System
The OopsWidget uses a web component-based UI system with a well-defined event architecture, allowing for complete customization of the alert appearance while maintaining functionality.
$3
The default
StatusAlert component provides a ready-to-use alert UI with configurable themes for each status type. When using this component, everything is handled automatically.$3
You can replace the built-in component with your own implementation. Custom components must:
1. Be implemented as a Web Component (Custom Element)
2. Handle the required attributes and events
#### Event Communication
The communication between the core widget and alert components happens through these custom events:
| Event Name | Direction | Description |
| -------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
oops-widget-closed | Alert β Core | Dispatched when user clicks the close button. The core widget handles recording the closure to prevent showing the same alert again too soon. |
| oops-widget-timer-start | Core β Alert | Sent when an alert timer starts. Includes a detail object with duration in milliseconds, used for progress animations. |
| oops-widget-timer-cancel | Core β Alert | Sent when a timer is cancelled, allowing the alert to reset its progress animation. |#### Implementation Example
`ts
class MyCustomAlert extends HTMLElement {
private shadow: ShadowRoot; constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
}
// These attributes will be set by the core widget
static get observedAttributes(): string[] {
return [
'status-type', // Current system status (e.g. 'MajorOutage')
'title', // Alert title text
'message', // Alert message text
'placement', // Position on screen
'mobile-enabled', // Whether mobile mode is enabled
'mobile-placement', // Mobile-specific placement
'progress-duration', // For timer progress animation
'incident-names', // JSON string of incident names
'incident-statuses', // JSON string of incident statuses
'incident-links', // JSON string of incident detail links
'display-incident-details', // Whether to show incident details
'close-button-label', // Accessibility label for close button
];
}
connectedCallback(): void {
this.render();
this.setupEvents();
}
attributeChangedCallback(): void {
this.render(); // Re-render when any attribute changes
}
private setupEvents(): void {
// 1. Setup close button event
const closeBtn = this.shadow.querySelector('.close-btn');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
// Dispatch event to notify core widget about closure
this.dispatchEvent(
new CustomEvent('oops-widget-closed', {
bubbles: true, // Must bubble up through the DOM
composed: true, // Must cross shadow DOM boundary
}),
);
});
}
// 2. Listen for timer events from core widget
this.addEventListener('oops-widget-timer-start', (e: Event) => {
const { duration } = (e as CustomEvent<{ duration: number }>).detail;
const progress = this.shadow.querySelector('.progress-bar');
if (progress instanceof HTMLElement) {
progress.style.transition =
width ${duration}ms linear;
progress.style.width = '100%';
}
}); this.addEventListener('oops-widget-timer-cancel', () => {
const progress = this.shadow.querySelector('.progress-bar');
if (progress instanceof HTMLElement) {
progress.style.transition = 'none';
progress.style.width = '0';
}
});
}
private render(): void {
const status = this.getAttribute('status-type') || 'DegradedPerformance';
const title = this.getAttribute('title') || 'Status Alert';
const message = this.getAttribute('message') || '';
const closeLabel = this.getAttribute('close-button-label') || 'Close alert';
// Simple styling based on status
const colors: Record = {
MajorOutage: '#f8d7da',
PartialOutage: '#ffebd6',
DegradedPerformance: '#fff3cd',
UnderMaintenance: '#e7f1fc',
Operational: '#d4edda',
};
const bgColor = colors[status] || colors.DegradedPerformance;
this.shadow.innerHTML =
${message}
;
}
}// Use in widget configuration
const widget = createOopsWidget({
statusUrl: 'https://status-api.example.com',
alertComponent: MyCustomAlert,
// ... other options
});
`π£ Types
-
SystemStatus: Enum of possible statuses (β
Operational, π‘ DegradedPerformance, π PartialOutage, π΄ MajorOutage, π§ UnderMaintenance).
- SystemStatusResponse: { status: SystemStatus; incident?: IncidentInfo; rawData: Record.
- I18nProvider: Interface providing getStatusDetails(status: SystemStatus): Promise.
- LogLevel: DEBUG | INFO | WARN | ERROR | NONE.π Internationalization
- π
createAutoDetectI18nProvider(): Auto-detect browser locale.
- π createStaticI18nProvider(translations: RecordThis project is licensed under the MIT License - see the LICENSE file for details.