eventsource
js/globals/eventsource.ts
EventSource global for Server-Sent Events (SSE) clients.
Implements the browser-shaped EventSource client over Fino's server-side
networking stack. The global handles connection management, automatic
reconnection with Last-Event-ID resumption, and EventTarget-based event
dispatch. It extends EventTarget so addEventListener() /
removeEventListener() work as expected.
The client opens direct HTTP/1 socket or TLS connections and parses the SSE
wire stream itself. It intentionally does not share the fetch() connection
pool or provide HTTP/2 or HTTP/3 transport behavior in this release baseline.
Transport-agnostic SSE parser and formatter primitives are documented in
fino:net/http/eventstream.
EventSource / SSE specification: https://html.spec.whatwg.org/multipage/server-sent-events.html
Example
const es = new EventSource('http://localhost:3000/events');
es.onopen = () => { ... };
es.onmessage = (e) => { console.log(e.data); };
es.onerror = (e) => { ... };
es.addEventListener('update', (e) => { ... });
// later:
es.close();
SSE wire format
Each event is one or more field lines followed by a blank line: event: <type>n ← optional; defaults to "message" data: <line1>n data: <line2>n ← multi-line data: joined with "n" id: <id>n ← optional; updates lastEventId buffer retry: <ms>n ← optional; integer ms reconnection interval n ← blank line dispatches the event
Lines starting with : are comments (ignored). Unknown field names are
ignored. Line terminators: LF, CRLF, or bare CR (per W3C spec).
Parser behavior
id:field value must not contain null (U+0000); otherwise ignored.retry:value must be all ASCII digits; otherwise ignored.- An empty
id:field (i.e.id:\n) sets lastEventId to "". - Events without any
data:lines are not dispatched. - A field name with no colon means the entire line is the field name and the value is empty string.
- A single leading space after the colon is stripped from field values.
EventSource reconnection (W3C spec + pragmatic additions)
- Reconnects on: EOF (stream ended normally), network errors, and HTTP status codes 429, 500, 502, 503, 504 (retriable errors).
- Follows local HTTP redirects for 301, 302, 303, 307, and 308 responses,
resolving relative
Locationvalues against the current request URL. Redirects always remain GET requests and are capped to prevent loops. - Does NOT reconnect on: wrong Content-Type, other HTTP error statuses.
- HTTP 204 closes the stream gracefully without reconnecting.
- Reconnection uses a configurable retry interval (default: 3000ms),
updated dynamically by
retry:fields in the event stream. - The
Last-Event-IDheader is sent on every reconnect attempt once anyid:field has been seen — including an emptyid:field, which is sent as an empty header value per the WHATWG spec rather than omitted.
Credentials, CORS, and TLS
This is a server-side EventSource implementation. It supports explicit
caller-provided headers for credentials such as bearer tokens, but it does
not implement browser cookie credential modes, an implicit cookie jar, or
browser CORS enforcement. Set-Cookie response headers are ignored; callers
that need cookies must provide a Cookie header explicitly. TLS verification
is enabled by default for https: URLs. Tests and private deployments may
pass a pinned CA path through tls.ca; disabling certificate verification
with tls.rejectUnauthorized: false should be limited to local development.
Interfaces
interface EventSourceInit {
Options for the EventSource client connection.
Headers are sent on the initial request and reconnect attempts. The client
also adds Last-Event-ID during reconnect when an ID has been seen.
const es = new EventSource('https://example.com/events', {
headers: { authorization: 'Bearer token' },
});
Properties
withCredentials?: boolean
Reflects the HTML EventSource credential mode flag.
Fino does not maintain a browser cookie jar or enforce browser CORS policy; this option is exposed for standards-shaped API compatibility.
new EventSource(url, { withCredentials: true }).withCredentials; // true
headers?: Record<string, string> | Headers
Extra HTTP headers for the SSE request.
new EventSource(url, { headers: new Headers({ authorization: 'Bearer t' }) });
tls?: {
ca?: string;
rejectUnauthorized?: boolean;
}
TLS trust options for https: EventSource connections.
rejectUnauthorized defaults to true; ca points at a PEM CA file.
These options are ignored for http: URLs.
new EventSource('https://localhost/events', { tls: { ca: '/tmp/test-ca.pem' } });
Classes
class EventSource extends EventTarget {
W3C EventSource — a spec-compliant SSE client.
Manages its own HTTP/1.1 connection (plain or TLS), reconnects after stream
EOF, network errors, and retriable HTTP statuses, resumes from the last
event ID, and dispatches events through the EventTarget interface. The
reconnection delay is a fixed interval — 3000ms by default, updated when the
server sends a retry: field — not an exponential backoff.
Instances begin connecting as soon as they are constructed. Named events
(event: update) are delivered to addEventListener('update', ...)
listeners; events without an event: field dispatch as message. Call
close() to stop the stream and suppress any further reconnection.
const es = new EventSource('http://localhost:3000/events');
es.onopen = () => console.log('connected');
es.onmessage = (e) => console.log('message:', e.data);
es.onerror = () => console.log('disconnected; will retry');
es.addEventListener('update', (e) => console.log('update:', e.data));
// later:
es.close();
Static Properties
static CONNECTING
Ready state value while connecting or reconnecting.
if (es.readyState === EventSource.CONNECTING) console.log('connecting');
static OPEN
Ready state value while the stream is open.
if (es.readyState === EventSource.OPEN) console.log('open');
static CLOSED
Ready state value after close() or terminal failure.
if (es.readyState === EventSource.CLOSED) console.log('closed');
Constructors
constructor(url: string, init?: EventSourceInit)
Create and immediately start an SSE client.
Only http: and https: URLs are supported. Connection errors dispatch
error and reconnect for retriable statuses unless close() is called.
Throws a SyntaxError DOMException if the URL cannot be parsed (relative
URLs resolve against globalThis.location when a host defines one).
const es = new EventSource('https://example.com/events', {
headers: { authorization: 'Bearer token' },
});
es.onmessage = (event) => console.log(event.data);
Getters
get readyState()
Current ready state: CONNECTING (0), OPEN (1), or CLOSED (2).
console.log(es.readyState);
get url()
The URL passed to the constructor.
console.log(es.url);
get lastEventId()
The last event ID received from the server.
Sent as Last-Event-ID on reconnect. Returns an empty string before any
id: field is received.
console.log(es.lastEventId);
get withCredentials()
Whether the constructor was created with withCredentials: true.
Fino exposes the standards-shaped reflected property, but does not add browser-managed cookies or CORS enforcement.
const es = new EventSource('/events', { withCredentials: true });
console.log(es.withCredentials);
get onopen()
Callback for open events (connection established).
es.onopen = () => console.log('open');
get onmessage()
Callback for message events (default-type SSE events).
es.onmessage = (event) => console.log(event.data);
get onerror()
Callback for error events (connection errors and fatal failures).
es.onerror = () => console.log('stream error');
Setters
set onopen(fn: ((e: Event) => void) | null)
Set the open event callback, or null to clear it.
es.onopen = null;
set onmessage(fn: ((e: MessageEvent) => void) | null)
Set the message event callback, or null to clear it.
es.onmessage = null;
set onerror(fn: ((e: Event) => void) | null)
Set the error event callback, or null to clear it.
es.onerror = null;
Methods
close()
Close the connection and prevent any further reconnection. Idempotent — safe to call multiple times.
es.close();