@spectrayan/sse-client
v2.1.0
Published
Type-safe, framework-agnostic Server-Sent Events (SSE) client for browser and Node.js with auto-reconnect, exponential backoff, jitter, and typed streams.
Maintainers
Readme
⚡ @spectrayan/sse-client
Enterprise-grade, type-safe, framework-agnostic Server-Sent Events client for Browser and Node.js
Zero dependencies · Web Streams API · Reconnection with full jitter · Async Iterable · Dual ESM/CJS
💡 Why @spectrayan/sse-client?
The native browser EventSource API is limited: it lacks custom headers support (cannot pass Authorization headers without URL query hacks), offers no control over exponential backoff or jitter, and only works in browser environments.
@spectrayan/sse-client provides a universal, modern alternative:
| Capability | Browser EventSource | @spectrayan/sse-client |
|:---|:---|:---|
| Runtime Environment | Browser only | Universal (Browser, Node.js 18+, Bun, Deno, Edge Workers) |
| Custom Headers | ❌ No custom headers | ✅ Full header support (Authorization tokens, API keys, tracing) |
| Async Iteration | ❌ Event listener only | ✅ Native for await (const event of client.iterate(...)) |
| Reconnection Strategy | ⚠️ Fixed browser retry | ✅ Exponential backoff, configurable max delay, and random jitter |
| Heartbeat Filtering | ⚠️ Emits raw comments | ✅ Discards :keepalive and comment frames per spec |
| Last-Event-ID | ⚠️ Browser-dependent | ✅ Automatic tracking, header injection, and query fallback |
| Type Safety | ❌ Raw string payloads | ✅ Strongly typed generics with built-in JSON deserialization |
📦 Installation
npm install @spectrayan/sse-client🚀 Quick Start
1. Modern Async Iteration (for await)
import { createSseClient } from '@spectrayan/sse-client';
const client = createSseClient({
headers: {
Authorization: 'Bearer my-auth-token',
},
});
interface OrderEvent {
orderId: string;
status: string;
}
async function watchOrders() {
for await (const order of client.iterate<OrderEvent>('https://api.example.com/sse/orders')) {
console.log('Order status updated:', order.orderId, order.status);
}
}2. Observer Subscription Pattern
import { createSseClient } from '@spectrayan/sse-client';
const client = createSseClient();
const subscription = client.stream<string>('https://api.example.com/sse/ticks', {
reconnection: {
initialDelayMs: 1500,
maxDelayMs: 60000,
backoffMultiplier: 2.0,
jitterRatio: 0.25,
},
onOpen: ({ url, attempt }) => console.log('Connected to', url),
onError: ({ error, willRetry, nextDelayMs }) => console.warn('Stream interrupted:', error.message),
}, {
next: (data) => console.log('Received payload:', data),
error: (err) => console.error('Stream failed permanently:', err),
complete: () => console.log('Stream completed cleanly'),
});
// To disconnect at any time:
subscription.unsubscribe();3. Named Event Demultiplexing
client.streamEvent<Notification>('https://api.example.com/sse/feed', 'user_notification', {}, {
next: (notification) => alert(notification.title),
});🔄 Reconnection & Jitter
Automatic reconnection calculates delay using bounded exponential backoff with full jitter:
$$\text{Delay} = \min(\text{initialDelay} \times \text{backoffMultiplier}^{\text{attempt}}, \text{maxDelay}) \times (1 \pm \text{jitterRatio})$$
const client = createSseClient({
reconnection: {
enabled: true,
maxRetries: -1, // -1 = infinite
initialDelayMs: 1000, // Start at 1s
maxDelayMs: 30000, // Cap at 30s
backoffMultiplier: 2.0, // Double delay each attempt
jitterRatio: 0.2, // +/- 20% random spread
},
});📡 API Reference
createSseClient(config?: SseClientConfig): SseClient
| Config Option | Type | Default | Description |
|:---|:---|:---|:---|
| baseUrl | string | undefined | Prefix applied to relative stream URLs. |
| headers | Record<string, string> \| (() => Promise<Record<string, string>>) | undefined | Static headers or async provider function. |
| withCredentials | boolean | false | Includes cookies on cross-origin requests. |
| lastEventIdParamName | string | 'lastEventId' | Query parameter key used on reconnect resumption. |
| reconnection | Partial<SseReconnectionConfig> | (defaults) | Global retry policy. |
| fetch | typeof fetch | globalThis.fetch | Custom fetch polyfill if needed. |
📄 License
Distributed under the Apache License 2.0. See LICENSE for details.
