sse.js: Flexible Server-Sent Events Client

raw JSON →
2.8.0 verified Sat Apr 25 auth: no javascript

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.

error TypeError: SSE is not a constructor
cause Attempting to import `SSE` as a default export using `import SSE from 'sse.js';` or `const SSE = require('sse.js');` when it is a named export.
fix
Use a named import for 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'.
cause Trying to access `SSE` globally (e.g., `window.SSE`) in a TypeScript project without proper global declaration or module bundling.
fix
If 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
cause The SSE server URL provided to the `SSE` constructor is unreachable, incorrect, or the server is not running or misconfigured (e.g., wrong port, CORS issues).
fix
Verify the 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.
cause The SSE endpoint is hosted on a different origin than the client, and the server is not sending appropriate Cross-Origin Resource Sharing (CORS) headers.
fix
Configure your SSE server to include the 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.
breaking Version 2.4.0 mistakenly removed the TypeScript type definition for the `SSE` constructor, leading to compilation errors in TypeScript projects.
fix Do not use version 2.4.0 in TypeScript projects. Upgrade to v2.4.1 or later to resolve the missing type definitions.
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.
fix Deploy your SSE server over HTTP/2 or HTTP/3 to mitigate the connection limit, which increases to around 100 simultaneous streams. Alternatively, consider using techniques like sub-domains or reusing connections across tabs if HTTP/1.1 is unavoidable.
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.
fix Review server-side SSE event formatting and client-side logic, particularly around event IDs, retry delays, and data parsing, to ensure compatibility with the updated specification compliance.
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.
fix Implement 'keep-alive' messages (e.g., empty comment lines `:\n\n`) from the server every 15-30 seconds to prevent proxy timeouts. Consider using HTTP/2 or HTTP/3 where possible, as they handle streaming more robustly. Advise users encountering issues to check their network or firewall settings.
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.
fix Ensure `useLastEventId: true` is set in the `SSE` constructor options, especially if using `autoReconnect`, to leverage the `Last-Event-ID` header for seamless stream resumption. Upgrade to v2.3.0 or later for improved `Last-Event-ID` support.
npm install sse.js
yarn add sse.js
pnpm add sse.js

This quickstart demonstrates how to establish an SSE connection using `sse.js` with custom headers, POST requests, and robust auto-reconnect logic. It includes event listeners for 'open', 'message', 'error', and 'abort' events, showing how to handle incoming JSON data and reconnection attempts, all within a TypeScript context. The stream is manually started and closed after a timeout.

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();