EventSource Polyfill

A spec-compliant EventSource polyfill that adds custom headers, auth tokens, and legacy-browser support to Server-Sent Events.

Library
npm
v1.0.31
2,184stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
48/100Fair
Development Activity0
Maintenance20
Community72
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
58/100Fair
Architecture62
Code Quality45
Innovation58
Learning Curve65

EventSource Polyfill is a drop-in replacement for the native browser EventSource API used to consume Server-Sent Events (SSE) streams. Native EventSource has no way to attach custom request headers, which makes it impossible to send bearer tokens or other auth headers on the initial connection or any reconnect — this library closes that gap by reimplementing the full SSE protocol on top of either fetch with ReadableStream (modern browsers) or XMLHttpRequest/XDomainRequest (legacy IE and older mobile browsers), selected automatically via feature detection.

Beyond header support, it handles the parts of the spec that are easy to get wrong by hand: automatic reconnect with server-controlled retry intervals, Last-Event-ID replay, heartbeat-based dead-connection detection, and a hand-written incremental SSE field parser that works across chunked responses. It ships as a single UMD file with zero runtime dependencies, so it drops into a <script> tag, an npm/bower install, or a bundler-based import with no build step required to consume it.

What You Get

  • A drop-in EventSourcePolyfill class with the same onopen/onmessage/onerror/close() surface as native EventSource
  • Custom request header support (e.g. Authorization: Bearer <token>) that native EventSource cannot provide
  • Automatic transport selection between fetch+ReadableStream and XMLHttpRequest/XDomainRequest based on feature detection
  • Built-in reconnect with server-controlled retry: intervals and Last-Event-ID replay on reconnect
  • Heartbeat-based stale-connection detection via a configurable heartbeatTimeout option
  • Inline polyfills for AbortController and streaming TextDecoder for browsers missing them

Common Use Cases

  • Streaming authenticated SSE endpoints where the server requires a bearer token or API key header
  • Consuming Server-Sent Events in older browsers (IE, older mobile WebViews) that lack native EventSource
  • Building real-time notification, log-tailing, or LLM token-streaming UIs that need custom headers on the stream request
  • Replacing native EventSource in apps that already need withCredentials and custom header support together

Under The Hood

Architecture The entire library lives in one closure-scoped UMD file (src/eventsource.js) with no external dependency graph. It layers three concerns: a transport abstraction (XHRTransport vs FetchTransport, both implementing the same open(xhr, onStart, onProgress, onFinish, url, withCredentials, headers) interface), a hand-rolled incremental SSE parser (onProgress walks the response character-by-character through a FIELD_START/FIELD/VALUE_START/VALUE state machine per the spec’s field-parsing algorithm), and an EventTarget-based EventSourcePolyfill class that exposes the same onopen/onmessage/onerror surface as native EventSource. Feature detection at load time picks FetchTransport when fetch+ReadableStream+AbortController are available, otherwise falls back to an XHRWrapper that normalizes IE/Opera/Firefox XHR quirks (documented inline with links to the relevant browser bug trackers). Since both transports must satisfy the same callback contract, that shared interface is the one abstraction the reconnect/backoff logic in start() depends on; changing it would require updating both transports in lockstep.

Tech Stack Plain ES5-style JavaScript with a UMD wrapper (CommonJS/AMD/global), zero runtime dependencies. The only devDependencies are Grunt and grunt-contrib-uglify, used solely to produce the minified src/eventsource.min.js — consumers need no build step to use the library itself, whether via <script> tag, npm/bower install, or bundler import.

Code Quality Functional tests exist (tests/tests.js, QUnit-based, covering reconnect, heartbeat timeout, custom headers, and Last-Event-ID behavior) paired with a small Node HTTP server (tests/server.js) that simulates SSE responses with configurable delays, but they run manually by opening tests/tests.html in a browser rather than through any automated CI — there is no .github/workflows directory and package.json defines no test script. Error handling is explicit and defensive throughout, with try/catch blocks around cross-browser property access (xhr.status, responseText) annotated with the specific browser bug each guard works around. There is no TypeScript or bundled type definitions (the README explicitly notes consumers must declare EventSourcePolyfill as any in TypeScript projects), and the repo has no linter or formatter configuration beyond legacy jslint directive comments.

What Makes It Unique Its distinguishing choice is the dual-transport strategy: a fetch+ReadableStream transport for modern browsers that unlocks custom headers and credentialed requests, paired with an XMLHttpRequest/XDomainRequest transport — including a from-scratch UTF-8 streaming decoder polyfill — for browsers that support neither natively. This directly solves the reason most people reach for a polyfill over native EventSource in the first place: the inability to send auth headers on an SSE connection. It’s a compatibility-shim pattern rather than a novel architecture, but it solves a narrow, real gap in the browser platform that persists even in current browsers.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search