sse.js: Flexible Server-Sent Events Client
raw JSON →sse.js is a robust JavaScript library designed as a flexible replacement for the standard `EventSource` API, enabling more control over Server-Sent Events (SSE) streams. Unlike `EventSource`, it supports POST requests and allows custom HTTP headers, making it suitable for authenticated or complex SSE integrations. The library is currently at version 2.8.0 and receives active maintenance, with recent releases focusing on spec compliance, type definition fixes, and enhanced auto-reconnect capabilities. It differentiates itself by providing a comprehensive EventSource polyfill that addresses the limitations of the native API, such as the inability to send payloads or custom headers, while offering features like configurable reconnection logic and exposure of HTTP response details.
Common errors
error TypeError: SSE is not a constructor ↓
SSE: import { SSE } from 'sse.js'; for ESM or const { SSE } = require('sse.js'); for CommonJS. error Property 'SSE' does not exist on type 'Window & typeof globalThis'. ↓
sse.js is bundled for browser use and intended to be global, ensure its types are globally declared or accessed as a module. For module-based projects, stick to import { SSE } from 'sse.js';. The README shows an async import to attach to window for non-module contexts: (async () => { const { SSE } = await import('./sse.js'); window.SSE = SSE; })(); error Failed to load resource: net::ERR_CONNECTION_REFUSED ↓
SSE_SERVER_URL is correct and accessible. Check if the server is running and configured to handle SSE requests (e.g., Content-Type: text/event-stream, Connection: keep-alive). Also, ensure proper CORS headers are set on the server if the client is on a different origin. error Access to XMLHttpRequest at 'http://example.com/events' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ↓
Access-Control-Allow-Origin header in its response, allowing requests from your client's origin. For example, res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080'); or res.setHeader('Access-Control-Allow-Origin', '*'); for development. Warnings
breaking Version 2.4.0 mistakenly removed the TypeScript type definition for the `SSE` constructor, leading to compilation errors in TypeScript projects. ↓
gotcha When not using HTTP/2, native Server-Sent Events (and thus `sse.js` as a polyfill) are subject to a browser-imposed limit of 6 concurrent connections per domain. This limit applies across all tabs and can lead to connection failures if exceeded. ↓
breaking Version 2.8.0 introduced several changes to improve SSE specification compliance, which might alter behavior if previous implementations relied on non-compliant aspects. Specifically, the `retry` field now correctly updates the reconnect delay, `id` fields containing NULL are ignored, UTF-8 BOM is stripped, and `lastEventId` is updated even when no event is dispatched. ↓
gotcha Proxies or corporate firewalls can sometimes interfere with SSE streams, causing unexpected disconnections or delayed event delivery, even if the server is correctly configured. This is due to the `text/event-stream` nature and lack of `Content-Length` header in SSE, which some proxies might misinterpret. ↓
gotcha Older versions of `EventSource` (and thus `sse.js` if configured not to send `Last-Event-ID`) or certain browsers might not send the `Last-Event-ID` header on reconnection attempts, leading to potential data loss if the server expects it to resume the stream. ↓
Install
npm install sse.js yarn add sse.js pnpm add sse.js Imports
- SSE wrong
import SSE from 'sse.js'; // SSE is a named export, not default.correctimport { SSE } from 'sse.js'; - SSEOptions wrong
import { SSEOptions } from 'sse.js'; // 'type' keyword is for type-only imports.correctimport type { SSEOptions } from 'sse.js'; - SSE (CommonJS) wrong
const SSE = require('sse.js'); // Assuming default export, but it's a named export.correctconst { SSE } = require('sse.js');
Quickstart
import { SSE } from 'sse.js';
const SSE_SERVER_URL = process.env.SSE_SERVER_URL ?? 'http://localhost:3000/events';
const AUTH_TOKEN = process.env.AUTH_TOKEN ?? 'your-secret-token';
interface MyEventData {
message: string;
timestamp: number;
}
async function connectToSSE() {
const source = new SSE(SSE_SERVER_URL, {
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Accept': 'text/event-stream',
},
method: 'POST',
payload: JSON.stringify({ initialData: 'client-hello' }),
autoReconnect: true,
reconnectDelay: 5000,
maxRetries: 5,
useLastEventId: true, // Recommended for resuming streams
start: false, // Don't start streaming immediately
});
source.addEventListener('open', (event: Event) => {
const openEvent = event as Event & { responseCode?: number; headers?: Record<string, string[]> };
console.log(`SSE connection opened. HTTP Status: ${openEvent.responseCode}`);
if (openEvent.headers) {
console.log('Response Headers:', openEvent.headers);
}
});
source.addEventListener('message', (event: MessageEvent) => {
try {
const payload: MyEventData = JSON.parse(event.data);
console.log(`Received event (id: ${event.lastEventId}):`, payload.message, `at ${new Date(payload.timestamp).toLocaleTimeString()}`);
} catch (e) {
console.error('Failed to parse event data:', e, event.data);
}
});
source.addEventListener('error', (event: Event) => {
const errorEvent = event as Event & { responseCode?: number; message?: string };
console.error(`SSE connection error. HTTP Status: ${errorEvent.responseCode}, Message: ${errorEvent.message}`);
if (source.autoReconnect && source.retryCount < (source.maxRetries ?? Infinity)) {
console.log(`Attempting to reconnect in ${source.reconnectDelay}ms (attempt ${source.retryCount + 1}/${source.maxRetries ?? '∞'})...`);
} else if (source.autoReconnect && source.maxRetries && source.retryCount >= source.maxRetries) {
console.log('Max reconnection retries reached. Connection permanently closed.');
} else {
console.log('Connection closed or not configured for auto-reconnect.');
}
});
source.addEventListener('abort', () => {
console.log('SSE connection aborted by client.');
});
// Manually start the stream after setting up listeners
console.log('Starting SSE stream...');
source.stream();
// Example of closing the connection after some time
setTimeout(() => {
console.log('Closing SSE connection after 60 seconds...');
source.close();
}, 60000);
}
connectToSSE();