Server-sent events implementation for unidirectional streaming from server to client with automatic reconnection support and standards compliance.
Creates a new EventSource connection for streaming server-sent events to clients.
/**
* Creates an EventSource connection for server-sent events
* @param {Object} request - HTTP request object
* @param {Object} response - HTTP response object
* @param {Object} [options] - Configuration options
*/
const WebSocket = require('faye-websocket');
const EventSource = WebSocket.EventSource;
const es = new EventSource(request, response, options);Usage Example:
const WebSocket = require('faye-websocket');
const http = require('http');
const server = http.createServer();
server.on('request', function(request, response) {
if (EventSource.isEventSource(request)) {
const es = new EventSource(request, response, {
retry: 10, // Client should retry every 10 seconds
ping: 30 // Send ping every 30 seconds
});
console.log('EventSource connected:', es.url);
console.log('Last event ID:', es.lastEventId);
// Send periodic updates
const interval = setInterval(function() {
es.send('Current time: ' + new Date().toISOString());
}, 1000);
es.on('close', function() {
clearInterval(interval);
console.log('EventSource closed');
});
}
});
server.listen(8000);Checks if an HTTP request accepts EventSource (text/event-stream).
/**
* Checks if HTTP request accepts server-sent events
* @param {Object} request - HTTP request object
* @returns {boolean} True if request accepts text/event-stream
*/
EventSource.isEventSource(request);Usage Example:
server.on('request', function(request, response) {
if (EventSource.isEventSource(request)) {
// Handle as EventSource
const es = new EventSource(request, response);
} else {
// Handle as regular HTTP request
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end('<h1>Not an EventSource request</h1>');
}
});es.url; // String - The URL from the client request
es.lastEventId; // String - Last event ID sent by client (for reconnection)
es.readyState; // Number - Connection state (CONNECTING=0, OPEN=1, CLOSED=3)
es.writable; // Boolean - Stream writable stateEventSource.prototype.DEFAULT_PING; // 10 - Default ping interval (seconds)
EventSource.prototype.DEFAULT_RETRY; // 5 - Default retry interval (seconds)Sends a server-sent event to the client.
/**
* Sends a server-sent event to the client
* @param {string} message - Event data
* @param {Object} [options] - Event options
* @param {string} [options.event] - Event type (default: 'message')
* @param {string} [options.id] - Event ID for client tracking
* @returns {boolean} True if message was sent successfully
*/
es.send(message, options);Usage Examples:
// Simple message
es.send('Hello, client!');
// Typed event with ID
es.send('User logged in', {
event: 'user-login',
id: '12345'
});
// JSON data
es.send(JSON.stringify({
user: 'alice',
action: 'login',
timestamp: Date.now()
}), {
event: 'user-event',
id: 'evt-' + Date.now()
});Sends a keep-alive ping to maintain the connection.
/**
* Sends a keep-alive ping to the client
* @returns {boolean} True if ping was sent
*/
es.ping();Closes the EventSource connection.
/**
* Closes the EventSource connection
* @returns {boolean} True if connection was closed
*/
es.close();// Stream interface methods
es.write(message); // Alias for send()
es.end(message); // Send final message and closeFired when the EventSource connection is established (internal event).
es.on('open', function(event) {
console.log('EventSource connection opened');
});Fired when the connection is closed.
es.on('close', function(event) {
console.log('EventSource connection closed');
});Fired when the underlying stream is ready for more data.
es.on('drain', function() {
console.log('Ready for more data');
});// EventSource configuration options
{
headers: Object, // Custom HTTP response headers
retry: number, // Client reconnect interval in seconds (default: 5)
ping: number // Server ping interval in seconds (default: 10)
}Usage Example:
const es = new EventSource(request, response, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true',
'X-Custom-Header': 'streaming-api'
},
retry: 15, // Client retries every 15 seconds
ping: 45 // Server pings every 45 seconds
});const WebSocket = require('faye-websocket');
const http = require('http');
const server = http.createServer();
server.on('request', function(request, response) {
if (EventSource.isEventSource(request)) {
const es = new EventSource(request, response, {
headers: {
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-cache'
},
retry: 5,
ping: 30
});
console.log('Client connected:', es.url);
// Handle reconnection by checking last event ID
let lastId = parseInt(es.lastEventId) || 0;
console.log('Resuming from event ID:', lastId);
// Send missed events if reconnecting
if (lastId > 0) {
es.send('Reconnected successfully', {
event: 'reconnect',
id: (++lastId).toString()
});
}
// Send periodic updates
const timer = setInterval(function() {
const eventData = {
timestamp: new Date().toISOString(),
counter: ++lastId,
message: 'Server update #' + lastId
};
es.send(JSON.stringify(eventData), {
event: 'update',
id: lastId.toString()
});
}, 2000);
// Send different event types
setTimeout(function() {
es.send('Special announcement!', {
event: 'announcement',
id: (++lastId).toString()
});
}, 5000);
// Handle client disconnect
es.on('close', function() {
console.log('Client disconnected from:', es.url);
clearInterval(timer);
});
// Handle connection errors
['error', 'end'].forEach(function(event) {
es.on(event, function() {
console.log('EventSource', event);
clearInterval(timer);
});
});
} else {
// Serve client test page
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(`
<!DOCTYPE html>
<html>
<head><title>EventSource Test</title></head>
<body>
<div id="messages"></div>
<script>
const es = new EventSource('/');
const messages = document.getElementById('messages');
es.onmessage = function(event) {
messages.innerHTML += '<p>Message: ' + event.data + '</p>';
};
es.addEventListener('update', function(event) {
messages.innerHTML += '<p>Update: ' + event.data + '</p>';
});
es.addEventListener('announcement', function(event) {
messages.innerHTML += '<p><strong>Announcement:</strong> ' + event.data + '</p>';
});
es.onerror = function(event) {
messages.innerHTML += '<p style="color:red">Error occurred</p>';
};
</script>
</body>
</html>
`);
}
});
server.listen(8000, function() {
console.log('Server listening on http://localhost:8000');
});Server-sent events follow a specific format:
// Basic message
es.send('Hello World');
// Sends: "data: Hello World\r\n\r\n"
// Message with event type
es.send('User data', { event: 'user-update' });
// Sends: "event: user-update\r\ndata: User data\r\n\r\n"
// Message with ID
es.send('Important data', { id: '123' });
// Sends: "id: 123\r\ndata: Important data\r\n\r\n"
// Complete message
es.send('Full event', { event: 'custom', id: '456' });
// Sends: "event: custom\r\nid: 456\r\ndata: Full event\r\n\r\n"// Multiline messages are automatically formatted
es.send('Line 1\nLine 2\nLine 3');
// Sends: "data: Line 1\r\ndata: Line 2\r\ndata: Line 3\r\n\r\n"const es = new EventSource(request, response, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control',
'Access-Control-Allow-Credentials': 'true'
}
});// Check connection state
if (es.readyState === 1) { // OPEN
es.send('Connection is active');
}
// Graceful shutdown
process.on('SIGTERM', function() {
es.close();
});