Server-Sent Events (SSE) provide a simple, one-way communication channel for real-time updates from the server to the client.
Overview
The SnackBase SDK automatically falls back to SSE when WebSocket is not available:
await client.realtime.connect();
await client.realtime.subscribe("posts");
client.realtime.on("posts.create", (data) => {
console.log("New post:", data);
});
SSE vs WebSocket
| Feature | SSE | WebSocket |
|---|
| Direction | One-way | Bidirectional |
| Browser Support | All | Modern |
| Latency | Low | Very low |
| Server Load | Lower | Higher |
| Auto Reconnect | Yes | Manual |
| Use Case | Updates | Interactive |
The SDK prefers WebSocket but falls back to SSE automatically.
SSE URL
The SDK constructs the SSE URL from your base URL:
const client = new SnackBaseClient({
baseUrl: "https://api.example.com",
});
// SSE URL: https://api.example.com/api/v1/realtime/subscribe?token=xxx&collections=posts:create,update
Subscriptions with SSE
SSE subscriptions are specified at connection time:
// Subscribe to collections
await client.realtime.subscribe("posts", ["create", "update"]);
await client.realtime.subscribe("comments", ["create"]);
// When SSE connects, it includes all subscriptions
// ?collections=posts:create,update&collections=comments:create
Connection Lifecycle
1. Connection
await client.realtime.connect();
// Creates EventSource with subscriptions
2. State Changes
client.realtime.on("connecting", () => {
console.log("SSE connecting...");
});
client.realtime.on("connected", () => {
console.log("SSE connected");
});
client.realtime.on("disconnected", () => {
console.log("SSE disconnected");
});
client.realtime.on("error", (error) => {
console.error("SSE error:", error);
});
3. Disconnection
client.realtime.disconnect();
// Closes EventSource
Messages are received as text/event-stream:
data: {"type":"posts.create","data":{"id":"xxx","title":"..."}}
data: {"type":"posts.update","data":{"id":"xxx","title":"..."}}
data: {"type":"posts.delete","data":{"id":"xxx"}}
Automatic Reconnection
SSE has built-in reconnection:
// Browser automatically reconnects SSE
// SDK also handles reconnection with exponential backoff
const client = new SnackBaseClient({
baseUrl: "https://api.example.com",
maxRealTimeRetries: 10,
realTimeReconnectionDelay: 1000,
});
Authentication
SSE connections include the authentication token in the URL:
// After login
await client.auth.loginWithPassword({
email: "[email protected]",
password: "password",
});
// SSE URL includes token
await client.realtime.connect();
// ?token=eyJhbGc...&collections=posts:create,update
Tokens in URLs may be logged in server access logs. This is a limitation
of the SSE protocol.
SSE Limitations
1. One-Way Communication
SSE is server-to-client only:
// You CANNOT send messages to server with SSE
// Use WebSocket for bidirectional communication
2. Limited Subscriptions After Connection
With SSE, subscriptions are set at connection time:
// Subscribe before connecting
await client.realtime.subscribe("posts");
await client.realtime.subscribe("comments");
// Then connect
await client.realtime.connect();
// Changing subscriptions requires reconnection
await client.realtime.subscribe("likes"); // May not work until reconnect
await client.realtime.connect(); // Reconnect to apply new subscriptions
3. No Heartbeat
SSE uses connection keep-alive instead of heartbeat:
// SSE doesn't have ping/pong like WebSocket
// Browser automatically detects dead connections
When to Use SSE
Use SSE when:
- WebSocket is unavailable - Older browsers or restrictive networks
- Simple updates needed - One-way server updates are sufficient
- Lower server load - SSE uses fewer resources than WebSocket
// SDK automatically uses SSE when appropriate
await client.realtime.connect();
// Uses WebSocket if available, falls back to SSE
React Example
import { useEffect, useState } from "react";
import { useSnackBase } from "@snackbase/sdk/react";
function useRealtimePosts() {
const client = useSnackBase();
const [posts, setPosts] = useState<Post[]>([]);
useEffect(() => {
let mounted = true;
async function setup() {
// Subscribe to posts
await client.realtime.subscribe("posts", ["create", "update", "delete"]);
// Connect
await client.realtime.connect();
// Listen for events
client.realtime.on("posts.create", (post: Post) => {
if (mounted) {
setPosts((prev) => [...prev, post]);
}
});
client.realtime.on("posts.update", (post: Post) => {
if (mounted) {
setPosts((prev) =>
prev.map((p) => (p.id === post.id ? post : p))
);
}
});
client.realtime.on("posts.delete", (post: Post) => {
if (mounted) {
setPosts((prev) => prev.filter((p) => p.id !== post.id));
}
});
}
setup();
return () => {
mounted = false;
client.realtime.disconnect();
};
}, [client]);
return posts;
}
function PostList() {
const posts = useRealtimePosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Cross-Browser Considerations
SSE is supported in most browsers:
| Browser | SSE Support |
|---|
| Chrome | Yes |
| Firefox | Yes |
| Safari | Yes |
| Edge | Yes |
| IE 11 | No (use polyfill) |
| Opera | Yes |
For IE 11, use an SSE polyfill:
// Install polyfill
npm install event-source-polyfill
// Use polyfill
import { EventSourcePolyfill } from 'event-source-polyfill';
// Configure SDK to use polyfill
// (SDK will use native EventSource when available)
Debugging SSE
Enable logging to debug SSE issues:
const client = new SnackBaseClient({
baseUrl: "https://api.example.com",
enableLogging: true,
logLevel: "debug",
});
// Logs will show:
// RealTimeService: Connecting...
// RealTimeService: SSE connected
// RealTimeService: Received message
Check SSE connection in browser DevTools:
- Open DevTools (F12)
- Go to Network tab
- Filter by EventStream
- Select the SSE connection
- View messages in Response tab
Common Issues
Problem: SSE connection closes after opening
Solutions:
- Check authentication token is valid
- Verify server allows SSE connections
- Check for proxy/load balancer issues
2. No Events Received
Problem: Connection stays open but no events received
Solutions:
- Verify subscriptions are set before connection
- Check collection names are correct
- Ensure server has events to send
3. Frequent Reconnections
Problem: SSE keeps reconnecting
Solutions:
- Check network stability
- Verify server timeout settings
- Increase reconnection delay
1. Minimize Subscriptions
Only subscribe to what you need:
// Good - specific operations
await client.realtime.subscribe("posts", ["create"]);
// Avoid - all operations if not needed
await client.realtime.subscribe("posts");
2. Debounce Updates
Debounce rapid updates:
import { debounce } from "lodash";
const debouncedUpdate = debounce((posts) => {
// Update UI
renderPosts(posts);
}, 100);
client.realtime.on("posts.update", debouncedUpdate);
3. Use Connection Pooling
For multiple tabs, use BroadcastChannel:
const channel = new BroadcastChannel("snackbase-updates");
client.realtime.on("posts.create", (data) => {
channel.postMessage({ type: "posts.create", data });
});
// Other tabs listen to channel
channel.onmessage = (event) => {
// Update UI in other tabs
};
Next Steps