If you are building real-time AI applications, Server-Sent Events (SSE) provides a lightweight alternative to WebSockets for streaming responses from server to client. In this hands-on guide, I will walk you through every aspect of EventSource compatibility across browsers, highlight the differences you need to understand, and provide working polyfill solutions that work seamlessly with the HolySheheep AI streaming API. By the end, you will have a production-ready streaming implementation that works on every browser your users might have.
Understanding Server-Sent Events and EventSource
Server-Sent Events allow a server to push data to your web application automatically. Unlike traditional HTTP requests where the client initiates communication, SSE enables the server to send new data whenever it becomes available. This makes SSE perfect for streaming AI responses, live notifications, progress updates, and real-time dashboards.
The EventSource API is the browser built-in interface for handling SSE. Here is the fundamental concept: your browser creates a one-way connection to a server endpoint, and the server sends events formatted as plain text. The browser parses these events and dispatches them to your JavaScript event handlers.
The Basic EventSource Pattern
// Basic EventSource connection to HolySheep AI streaming endpoint
const eventSource = new EventSource('https://api.holysheep.ai/v1/chat/stream', {
headers: {
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY',
'Content-Type': 'application/json'
}
});
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
eventSource.onerror = function(error) {
console.error('EventSource failed:', error);
eventSource.close();
};
Browser Compatibility: What You Need to Know
EventSource has excellent support across modern browsers, but implementation differences can cause subtle bugs. From my experience testing across multiple browsers during production deployments, here are the critical differences you must handle.
Full EventSource Support (No Polyfill Needed)
- Chrome 9+ (released 2011)
- Firefox 6+
- Safari 4+ (with caveats)
- Edge 79+
- Opera 11+
Limited or No Native Support (Requires Polyfill)
- Internet Explorer (all versions)
- Older mobile browsers
- Safari on older iOS versions (pre-iOS 5)
- Some embedded WebViews on Android
The Cross-Origin Request Problem
One of the biggest compatibility issues involves Cross-Origin Resource Sharing (CORS). EventSource supports CORS, but the preflight request behavior varies between browsers. Chrome and Firefox send standard CORS preflight requests, while Safari handles them differently in certain configurations.
When connecting to HolySheep AI from your application, you need to ensure your server or proxy handles CORS headers correctly. The HolySheep AI API supports CORS for streaming endpoints, but you must include the appropriate headers in your requests.
Implementing SSE Streaming with HolySheep AI
The HolySheep AI API provides streaming endpoints that support SSE for real-time AI responses. With pricing at just $1 per dollar equivalent (compared to ¥7.3 elsewhere, saving over 85%), and latency under 50ms, it provides an excellent foundation for building responsive AI applications.
Let me walk you through a complete implementation step by step.
Step 1: Setting Up the Request
// Complete streaming implementation with EventSource
// Using HolySheep AI API - pricing: GPT-4.1 $8/MTok, Claude Sonnet 4.5 $15/MTok
function createStreamingConnection(messages, onChunk, onComplete, onError) {
const url = 'https://api.holysheep.ai/v1/chat/completions';
// Prepare the request body
const body = JSON.stringify({
model: 'gpt-4.1',
messages: messages,
stream: true
});
// Use fetch with ReadableStream for modern browsers
fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY',
'Content-Type': 'application/json'
},
body: body
})
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function read() {
reader.read().then(({ done, value }) => {
if (done) {
if (buffer.length > 0) {
processBuffer(buffer, onChunk);
}
onComplete();
return;
}
buffer += decoder.decode(value, { stream: true });
processBuffer(buffer, onChunk);
read();
});
}
read();
})
.catch(error => {
onError(error);
});
}
function processBuffer(buffer, callback) {
// Split by newlines and process complete lines
const lines = buffer.split('\n');
buffer = lines.pop(); // Keep incomplete line in buffer
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
if (parsed.choices && parsed.choices[0].delta.content) {
callback(parsed.choices[0].delta.content);
}
} catch (e) {
console.warn('Parse error:', e);
}
}
}
}
// Usage example
createStreamingConnection(
[{ role: 'user', content: 'Explain SSE in simple terms' }],
(chunk) => {
document.getElementById('output').textContent += chunk;
},
() => console.log('Stream complete'),
(error) => console.error('Error:', error)
);
Step 2: Adding Reconnection Logic
Network connections drop unexpectedly. A robust SSE implementation must handle reconnection gracefully. Here is how to implement automatic reconnection with exponential backoff.
// Robust EventSource wrapper with automatic reconnection
class RobustEventSource {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.eventSource = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
this.baseReconnectDelay = options.baseReconnectDelay || 1000;
this.isConnecting = false;
}
connect() {
if (this.isConnecting) return;
this.isConnecting = true;
try {
this.eventSource = new EventSource(this.url, this.options);
this.eventSource.onopen = () => {
console.log('Connected to SSE stream');
this.reconnectAttempts = 0;
this.isConnecting = false;
if (this.options.onOpen) this.options.onOpen();
};
this.eventSource.onmessage = (event) => {
if (this.options.onMessage) {
this.options.onMessage(event);
}
};
this.eventSource.onerror = (error) => {
console.error('EventSource error:', error);
this.isConnecting = false;
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.scheduleReconnect();
} else {
console.error('Max reconnection attempts reached');
if (this.options.onError) this.options.onError(error);
}
};
// Handle custom event types
if (this.options.eventTypes) {
for (const eventType of this.options.eventTypes) {
this.eventSource.addEventListener(eventType, (event) => {
if (this.options.onCustomEvent) {
this.options.onCustomEvent(eventType, event);
}
});
}
}
} catch (error) {
console.error('Failed to create EventSource:', error);
this.isConnecting = false;
if (this.options.onError) this.options.onError(error);
}
}
scheduleReconnect() {
const delay = this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts);
console.log(Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}));
setTimeout(() => {
this.reconnectAttempts++;
this.eventSource.close();
this.connect();
}, delay);
}
close() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
this.isConnecting = false;
}
}
// Usage with HolySheep AI
const stream = new RobustEventSource(
'https://api.holysheep.ai/v1/stream',
{
headers: {
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
eventTypes: ['chunk', 'done', 'error'],
maxReconnectAttempts: 3,
baseReconnectDelay: 1000,
onMessage: (event) => {
console.log('Received:', event.data);
},
onError: (error) => {
console.error('Stream error:', error);
}
}
);
stream.connect();
Creating a Polyfill for Legacy Browsers
When you need to support Internet Explorer or older mobile browsers, you need a polyfill that simulates EventSource behavior using XMLHttpRequest or the Fetch API with streaming support. Here is a complete, production-ready polyfill.
// EventSource Polyfill for legacy browser support
(function(global) {
'use strict';
if (typeof global.EventSource !== 'undefined') {
return;
}
function EventSourcePolyfill(url, options = {}) {
this.url = url;
this.readyState = 0;
this.onopen = null;
this.onmessage = null;
this.onerror = null;
this.listeners = {};
this.xhr = null;
this.buffer = '';
this.isClosed = false;
this.connect();
}
EventSourcePolyfill.CONNECTING = 0;
EventSourcePolyfill.OPEN = 1;
EventSourcePolyfill.CLOSED = 2;
EventSourcePolyfill.prototype.connect = function() {
var self = this;
this.readyState = 0;
// Use XDomainRequest for IE8/9, XMLHttpRequest otherwise
if (typeof XDomainRequest !== 'undefined') {
this.xhr = new XDomainRequest();
this.xhr.open('GET', this.url, true);
} else {
this.xhr = new XMLHttpRequest();
this.xhr.open('GET', this.url, true);
}
this.xhr.onprogress = function() {
self.handleProgress();
};
this.xhr.onload = function() {
self.readyState = 2;
if (self.onload) self.onload();
};
this.xhr.onerror = function() {
self.readyState = 2;
if (self.onerror) self.onerror({ type: 'error' });
self.dispatchEvent('error', { type: 'error' });
};
// Set headers if provided
if (options.headers) {
for (var header in options.headers) {
try {
this.xhr.setRequestHeader(header, options.headers[header]);
} catch (e) {
console.warn('Cannot set header:', header);
}
}
}
this.xhr.send();
};
EventSourcePolyfill.prototype.handleProgress = function() {
if (this.isClosed) return;
var data = this.xhr.responseText || '';
var newData = data.slice(this.buffer.length);
this.buffer = data;
if (newData) {
this.processData(newData);
}
};
EventSourcePolyfill.prototype.processData = function(data) {
var lines = data.split(/\r\n|\n/);
var eventType = 'message';
var eventData = '';
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var colonIndex = line.indexOf(':');
if (colonIndex === 0) continue;
if (colonIndex > 0) {
var field = line.substring(0, colonIndex);
var value = line.substring(colonIndex + 1).trim();
if (field === 'event') {
eventType = value;
} else if (field === 'data') {
eventData += value + '\n';
}
} else if (line === '') {
var event = new MessageEvent(eventType, {
data: eventData.slice(0, -1),
origin: this.url
});
if (eventType === 'message' && this.onmessage) {
this.onmessage(event);
}
this.dispatchEvent(eventType, event);
eventData = '';
eventType = 'message';
}
}
};
EventSourcePolyfill.prototype.close = function() {
this.isClosed = true;
this.readyState = 2;
if (this.xhr) {
this.xhr.abort();
}
};
EventSourcePolyfill.prototype.addEventListener = function(type, listener) {
if (!this.listeners[type]) {
this.listeners[type] = [];
}
this.listeners[type].push(listener);
};
EventSourcePolyfill.prototype.removeEventListener = function(type, listener) {
if (!this.listeners[type]) return;
var index = this.listeners[type].indexOf(listener);
if (index > -1) {
this.listeners[type].splice(index, 1);
}
};
EventSourcePolyfill.prototype.dispatchEvent = function(type, event) {
if (!this.listeners[type]) return;
for (var i = 0; i < this.listeners[type].length; i++) {
this.listeners[type][i].call(this, event);
}
};
global.EventSource = EventSourcePolyfill;
})(typeof window !== 'undefined' ? window : this);
Testing Your Implementation
I always recommend testing your SSE implementation across multiple browsers before deploying to production. Here is a simple test harness you can use.
// Browser compatibility test suite
function testSSECompatibility() {
const results = {
nativeEventSource: false,
polyfillLoaded: false,
streamingSupported: false,
corsSupported: false
};
// Test 1: Check for native EventSource
results.nativeEventSource = typeof EventSource !== 'undefined';
console.log('Native EventSource:', results.nativeEventSource ? 'PASS' : 'FAIL');
// Test 2: Check if polyfill loaded
results.polyfillLoaded = typeof EventSource !== 'undefined';
console.log('EventSource available:', results.polyfillLoaded ? 'PASS' : 'FAIL');
// Test 3: Test streaming with Fetch API
fetch('https://api.holysheep.ai/v1/models', {
headers: { 'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY' }
})
.then(response => {
results.streamingSupported = response.body && response.body.getReader !== undefined;
console.log('Fetch Streaming:', results.streamingSupported ? 'PASS' : 'FAIL');
})
.catch(() => {
console.log('Fetch Streaming: FAIL');
});
// Test 4: Test CORS support
const testCORS = new EventSource('https://api.holysheep.ai/v1/health');
testCORS.onopen = () => {
results.corsSupported = true;
console.log('CORS Support: PASS');
testCORS.close();
};
testCORS.onerror = () => {
console.log('CORS Support: FAIL (or endpoint does not exist)');
};
return results;
}
// Run tests
document.addEventListener('DOMContentLoaded', () => {
const testResults = testSSECompatibility();
// Display results in UI
const resultsDiv = document.createElement('div');
resultsDiv.innerHTML = `
SSE Compatibility Test Results
- Native EventSource: ${testResults.nativeEventSource ? '✓' : '✗'}
- EventSource Available: ${testResults.polyfillLoaded ? '✓' : '✗'}
- Streaming Supported: ${testResults.streamingSupported ? '✓' : '✗'}
- CORS Supported: ${testResults.corsSupported ? '✓' : '✗'}
`;
document.body.appendChild(resultsDiv);
});
Common Errors and Fixes
Error 1: "EventSource is not defined" in Internet Explorer
Internet Explorer does not support EventSource natively. You must include the polyfill before any code that uses EventSource.
<!-- Solution: Include polyfill first in your HTML -->
<script src="eventsource-polyfill.js"></script>
<script src="your-app-code.js"></script>
Error 2: CORS Error When Connecting to API
If you see CORS errors in the console, ensure your server is setting the correct headers. For HolySheep AI, the API already handles CORS, but if you are proxying requests through your own server, add these headers:
// Server-side solution for CORS headers
// Example for Node.js Express
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
// Handle preflight requests
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
Error 3: "Failed to construct EventSource: URL scheme must be 'http' or 'https'"
This error occurs when using EventSource with data: URLs or file: URLs. EventSource only works with HTTP and HTTPS endpoints. If you are testing locally without HTTPS, use http://localhost instead of file://.
// Wrong - will fail
const source = new EventSource('file:///path/to/stream');
// Correct - use HTTP/HTTPS
const source = new EventSource('https://api.holysheep.ai/v1/stream');
// For local development
const source = new EventSource('http://localhost:3000/api/stream');
Error 4: Stream Stops After 30 Seconds
Some proxies and load balancers have default timeout settings that close long-running connections. Implement heartbeat messages from the server and client-side reconnection logic to handle this.
// Client-side heartbeat to keep connection alive
class HeartbeatEventSource {
constructor(url, options = {}) {
this.url = url;
this.eventSource = new EventSource(url, options);
this.heartbeatInterval = options.heartbeatInterval || 15000;
this.lastActivity = Date.now();
this.intervalId = null;
// Listen for any message to track activity
this.eventSource.addEventListener('message', () => {
this.lastActivity = Date.now();
});
this.startHeartbeat();
}
startHeartbeat() {
this.intervalId = setInterval(() => {
const idle = Date.now() - this.lastActivity;
if (idle > this.heartbeatInterval * 2) {
console.log('Connection appears dead, reconnecting...');
this.reconnect();
}
}, this.heartbeatInterval);
}
reconnect() {
this.eventSource.close();
this.eventSource = new EventSource(this.url);
}
close() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
this.eventSource.close();
}
}
Best Practices for Production Deployments
After testing and implementing SSE streaming, follow these practices to ensure reliability in production environments.
- Always implement reconnection logic: Network conditions change, and users will thank you for automatic recovery.
- Use the Fetch API with streaming as your primary method: It has better error handling and is more widely supported for advanced use cases.
- Include the EventSource polyfill for maximum compatibility: Even if you target only modern browsers, including the polyfill costs nothing.
- Monitor connection quality: Track reconnection attempts and error rates to identify issues before users report them.
- Set appropriate timeouts: Configure your application and server to handle connections that run for extended periods.
Conclusion
Server-Sent Events provide a robust, standards-based way to stream data from servers to browsers. While browser support is generally good, understanding the implementation differences and having a solid polyfill strategy ensures your applications work for all users. The techniques covered in this guide, combined with the HolySheep AI streaming API, give you everything you need to build responsive, real-time AI applications.
With HolySheep AI pricing at $1 per dollar equivalent (compared to ¥7.3 elsewhere), support for WeChat and Alipay payments, latency under 50ms, and free credits on signup, you have a cost-effective and reliable backend for your streaming applications. The