
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@elefunc/fetcheventsource
Advanced tools
FetchEventSource - combines the full power of fetch() with EventSource streaming. Supports ALL HTTP methods, request bodies, headers, and fetch options.
The FetchEventSource interface represents a connection to server-sent events that supports the fetch() API options. Unlike EventSource, FetchEventSource accepts all fetch() options including request bodies and custom HTTP methods.
Like EventSource, FetchEventSource automatically reconnects after network errors with a default delay of 5 seconds (5000ms). When the server responds with HTTP status 204 (No Content) or any other non-ok HTTP status, the connection is closed and no reconnection attempt is made. When called without method, headers, or body parameters, the constructor returns a standard EventSource instance for backward compatibility.
[!NOTE]
ReadableStream.prototype.events()Response.prototype.events()↑If you don't need strict
EventSourceAPI compatibility, consider usingResponse.prototype.events()instead. It's a simpler method that directly parses SSE streams from fetch responses:const response = await fetch('https://api.example.com/events'); for await (const { type, data } of response.body.events()) { console.log(type, data); }Use
FetchEventSourcewhen you need the full EventSource API (readyState, automatic reconnection, onerror/onopen handlers, etc.).
FetchEventSource preserves 100% API compatibility with both fetch() and EventSource. This is a core design principle:
fetch() and EventSource specificationsEventSource under all conditionsThis commitment ensures that FetchEventSource can serve as a drop-in replacement for EventSource while adding fetch() capabilities, without introducing any non-standard behaviors or API surface.
[!WARNING] When not used over HTTP/2, SSE suffers from a limitation to the maximum number of open connections, which can be specially painful when opening various tabs as the limit is per browser and set to a very low number (6). The issue has been marked as "Won't fix" in Chrome and Firefox. This limit is per browser + domain, so that means that you can open 6 SSE connections across all of the tabs to
www.example1.comand another 6 SSE connections towww.example2.com. When using HTTP/2, the maximum number of simultaneous HTTP streams is negotiated between the server and the client (defaults to 100).
FetchEventSource object.0, indicating that the connection has not yet been established.1, indicating that the connection is open and ready to receive events.2, indicating that the connection is closed.0 (CONNECTING), 1 (OPEN), or 2 (CLOSED).FetchEventSource object was instantiated with cross-origin (CORS) credentials set (true), or not (false, the default).event field is received from the source.Additionally, the server can send custom event types with an event field, which will create named events.
FetchEventSource implements the full SSE protocol, including:
: are ignored: character, with optional space after colon removedevent: Sets the event type (defaults to "message")data: Accumulates message data (multiple lines joined with \n)id: Sets the last event ID (rejected if contains null, newline, or carriage return)retry: Updates reconnection delay in milliseconds (must contain only ASCII digits)data: lines are concatenated with newline charactersconst eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ channel: 'updates' })
});
eventSource.onopen = (event) => {
console.log('Connection opened');
};
eventSource.onmessage = (event) => {
console.log('Message:', event.data);
};
eventSource.onerror = (event) => {
console.log('Error occurred');
};
// Close the connection after 30 seconds
setTimeout(() => {
eventSource.close();
}, 30000);
FetchEventSource is not part of any specification. It combines the EventSource API with fetch() request options.
The implementation strictly adheres to:
All behaviors, including reconnection logic, event parsing, and error handling, match the standards exactly.
Requires support for:
The exhaustive test suite verifies compatibility across all major browsers and ensures that FetchEventSource behaves identically to native EventSource in every scenario, including edge cases and error conditions.
FetchEventSource preserves the original request body and attempts to clone it for each reconnection attempt. This works with cloneable body types like Blob, Response.body, and FormData. Non-cloneable bodies may not work correctly across reconnections.
When an external signal is provided in options, FetchEventSource uses AbortSignal.any() to combine it with an internal abort controller. This allows both external cancellation and internal connection management.
The response Content-Type header must be exactly text/event-stream (case-insensitive, before any semicolon). Other content types will close the connection without retry.
The implementation includes a polyfill for URL.parse() to support environments where it's not available (pre-Baseline September 2024).
The FetchEventSource() constructor creates a new FetchEventSource object to establish a connection to a server to receive server-sent events.
new FetchEventSource(url)
new FetchEventSource(url, options)
url
options (optional)
method (optional)
"GET", "POST", "PUT", "DELETE".headers (optional)
body (optional)
Blob, an ArrayBuffer, a TypedArray, a DataView, a FormData, a URLSearchParams, string object or literal, or a ReadableStream object. Important: When specifying a body, you must also specify a method (e.g., POST, PUT) as GET/HEAD requests cannot have a body.mode (optional)
cors, no-cors, or same-origin.credentials (optional)
signal (optional)
withCredentials (optional)
body is not provided, for backward compatibility with standard EventSource.A FetchEventSource object configured with the provided options. If method, headers, and body are all undefined, returns a standard EventSource instance instead.
[!NOTE] When
method,headers, andbodyare allundefined, the constructor returns a standard EventSource for backward compatibility. An empty string ("") is considered a valid body and will create aFetchEventSourceinstance. The class includes protection against malicious servers that send extremely large messages - any message exceeding 50MB will be skipped and an error event will be dispatched.Body Requires Method: When providing a
bodyparameter, you must also specify amethod(typically POST or PUT). Attempting to send a body with the default GET method will result in a TypeError, as GET/HEAD requests cannot have a body according to the HTTP specification.Automatic Headers: FetchEventSource automatically sets the following headers:
Cache-Control: no-store(to prevent caching)Accept: text/event-stream(to request SSE format)Last-Event-ID: <id>(if reconnecting after receiving an event ID)Credentials Handling: The
withCredentialsoption is mapped to the fetchcredentialsoption:
withCredentials: true→credentials: 'include'withCredentials: false→credentials: 'same-origin'(default)
// Returns a standard EventSource (no method, headers, or body)
const eventSource = new FetchEventSource('https://api.example.com/events');
eventSource.onmessage = (event) => {
console.log(event.data);
};
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: 'updates',
userId: '12345'
})
});
eventSource.onopen = () => {
console.log('Connection established');
};
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
// Returns FetchEventSource instance because headers are provided
const eventSource = new FetchEventSource('https://api.example.com/events', {
headers: {
'Authorization': 'Bearer token123',
'X-Custom-Header': 'value'
}
});
eventSource.onmessage = (event) => {
console.log('Authenticated message:', event.data);
};
const controller = new AbortController();
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true',
signal: controller.signal
});
// Abort the connection after 5 seconds
setTimeout(() => {
controller.abort();
}, 5000);
// ❌ WRONG - This will throw a TypeError
const eventSource = new FetchEventSource('https://api.example.com/events', {
body: 'subscribe=true' // Missing method!
});
// Error: "Request with GET/HEAD method cannot have body"
// ✅ CORRECT - Always specify method when using body
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
The readyState read-only property of the FetchEventSource interface returns a number representing the state of the connection.
A number which can be one of three values:
0 — connecting1 — open2 — closedconst eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
console.log(eventSource.readyState); // 0 (connecting)
eventSource.onopen = () => {
console.log(eventSource.readyState); // 1 (open)
};
eventSource.onerror = () => {
if (eventSource.readyState === FetchEventSource.CONNECTING) {
console.log('Reconnecting...');
} else if (eventSource.readyState === FetchEventSource.CLOSED) {
console.log('Connection closed');
}
};
The url read-only property of the FetchEventSource interface returns a string containing the URL of the source.
A string.
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
console.log(eventSource.url); // "https://api.example.com/events"
The withCredentials read-only property of the FetchEventSource interface returns a boolean value indicating whether the FetchEventSource object was instantiated with CORS credentials set.
A boolean value indicating whether the FetchEventSource object was instantiated with CORS credentials set (true), or not (false, the default).
const eventSource = new FetchEventSource('https://api.example.com/events', {
credentials: 'include'
});
console.log(eventSource.withCredentials); // true
const eventSource2 = new FetchEventSource('https://api.example.com/events');
console.log(eventSource2.withCredentials); // false
The close() method of the FetchEventSource interface closes the connection, if one is made, and sets the FetchEventSource.readyState attribute to 2 (closed).
If the connection is already closed, the method does nothing.
When closing, the method:
readyState to CLOSEDclose()
None.
None (undefined).
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
eventSource.onopen = () => {
console.log('Connected');
};
// Close the connection after 10 seconds
setTimeout(() => {
eventSource.close();
console.log('Connection closed');
}, 10000);
The error event of the FetchEventSource interface is fired when a connection to an event source fails to be opened or when an error occurs during communication.
Like EventSource, FetchEventSource automatically attempts to reconnect after network errors, with the connection state changing to CONNECTING during reconnection attempts. However, no reconnection is attempted for HTTP error responses (including 204 No Content) or Content-Type mismatches.
Use the event name in methods like addEventListener(), or set an event handler property.
addEventListener("error", (event) => {});
onerror = (event) => {};
When a network error occurs:
readyState changes to CONNECTING (0)No reconnection occurs if:
close()An ErrorEvent with the following properties:
message: A string describing the errorerror: The actual Error object (when available)const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
// Using addEventListener
eventSource.addEventListener('error', (event) => {
if (eventSource.readyState === FetchEventSource.CONNECTING) {
console.log('Connection lost. Attempting to reconnect...');
} else if (eventSource.readyState === FetchEventSource.CLOSED) {
console.log('Connection closed');
}
});
// Using the onerror property
eventSource.onerror = (event) => {
console.error('An error occurred');
};
The message event of the FetchEventSource interface is fired when data is received from the event source.
A message event is fired when the event source sends an event that either has no event field or has the event field set to message.
Use the event name in methods like addEventListener(), or set an event handler property.
addEventListener("message", (event) => {});
onmessage = (event) => {};
A MessageEvent. Inherits from Event.
This interface also inherits properties from its parent, Event.
null for server-sent events.const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
// Using addEventListener
eventSource.addEventListener('message', (event) => {
console.log('Received data:', event.data);
console.log('Event ID:', event.lastEventId);
});
// Using the onmessage property
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log('Parsed data:', data);
} catch (e) {
console.log('Raw data:', event.data);
}
};
The open event of the FetchEventSource interface is fired when a connection to an event source is opened.
Use the event name in methods like addEventListener(), or set an event handler property.
addEventListener("open", (event) => {});
onopen = (event) => {};
A generic Event.
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
// Using addEventListener
eventSource.addEventListener('open', (event) => {
console.log('Connection opened');
console.log('Ready state:', eventSource.readyState); // 1
});
// Using the onopen property
eventSource.onopen = (event) => {
console.log('Successfully connected to event stream');
};
The onopen property of the FetchEventSource interface is an event handler for the open event; this event is fired when a connection to the server is opened.
eventSource.onopen = function;
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
eventSource.onopen = (event) => {
console.log('Connection opened');
console.log('Ready state:', eventSource.readyState); // 1 (OPEN)
};
The onmessage property of the FetchEventSource interface is an event handler for the message event; this event is fired when data is received from the server with no event field or with the event field set to message.
eventSource.onmessage = function;
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
eventSource.onmessage = (event) => {
console.log('Message received:', event.data);
console.log('Last event ID:', event.lastEventId);
try {
const data = JSON.parse(event.data);
console.log('Parsed data:', data);
} catch (e) {
console.log('Non-JSON data:', event.data);
}
};
The onerror property of the FetchEventSource interface is an event handler for the error event; this event is fired when an error occurs.
When a network error occurs, FetchEventSource automatically attempts to reconnect. During reconnection, the readyState changes to CONNECTING (0).
eventSource.onerror = function;
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
eventSource.onerror = (event) => {
if (eventSource.readyState === FetchEventSource.CONNECTING) {
console.log('Connection lost. Attempting to reconnect...');
} else if (eventSource.readyState === FetchEventSource.CLOSED) {
console.log('Connection closed permanently');
}
};
The FetchEventSource interface supports custom event types sent by the server. When the server sends an event with a custom event field, FetchEventSource dispatches a named event of that type.
addEventListener("eventname", (event) => {});
A MessageEvent with the same properties as the message event.
const eventSource = new FetchEventSource('https://api.example.com/events', {
method: 'POST',
body: 'subscribe=true'
});
// Listen for custom 'update' events
eventSource.addEventListener('update', (event) => {
console.log('Update event:', event.data);
});
// Listen for custom 'notification' events
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
console.log('Notification:', notification);
});
// Listen for custom 'ping' events
eventSource.addEventListener('ping', (event) => {
console.log('Ping received at:', new Date());
});
The server sends custom events by including an event field:
event: update
data: {"status": "processing"}
event: notification
data: {"type": "info", "message": "Task completed"}
event: ping
data: {"timestamp": 1234567890}
FAQs
FetchEventSource - combines the full power of fetch() with EventSource streaming. Supports ALL HTTP methods, request bodies, headers, and fetch options.
We found that @elefunc/fetcheventsource demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.