LEARNING OBJECTIVES ⌵
- Understand why native
EventSourceis not available directly inside Service Worker global scopes. - Implement robust SSE stream proxying via
fetch()andReadableStreamwithin Service Workers. - Synchronize real-time streaming updates into Cache Storage and IndexedDB offline databases.
- Bridge background streaming events to active client windows via
BroadcastChannelandClient.postMessage().
📖 The Mental Model & Story
Think of a Service Worker as a company's mailroom operating in the basement of a high-rise building. Even when all office floors (browser tabs) are empty or dark, the mailroom keeps running.
While the high-level EventSource interface was designed strictly for visible DOM windows, modern Service Workers can open persistent HTTP streams using low-level fetch() with ReadableStream. As updates arrive from the cloud, the mailroom catches them, stores them in the warehouse (IndexedDB), and rings the bell (via BroadcastChannel) on any open office desk currently in session.
+-----------------------------------------------------------------------+
| BACKGROUND SERVICE WORKER |
| |
| Server Stream ===> fetch('/stream') ===> parse SSE chunks |
| | | |
| v v |
| IndexedDB Cache BroadcastChannel / PostMsg |
| | |
+-----------------------------------------------------|-----------------+
v
+---------------------------+
| OPEN BROWSER TAB / DOM |
| UI updates reactively |
+---------------------------+
Technical Deep Dive & Specifications
Why EventSource is Omitted from ServiceWorkerGlobalScope
Under the W3C EventSource specification, EventSource relies on window-bound lifecycle models. In a Service Worker, you instead use fetch() with the Streaming API:
// Inside Service Worker (sw.js)
async function listenToSSEStream() {
const response = await fetch('/api/live-stream', {
headers: { 'Accept': 'text/event-stream' }
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Process complete SSE chunks delimited by double newlines
const lines = buffer.split('\n\n');
buffer = lines.pop(); // Retain remainder in buffer
for (const chunk of lines) {
handleSSEChunk(chunk);
}
}
}
💻 Interactive Code Playground
🏋️ Hands-On Exercise
Scenario: Build a fallback listener that opens a direct window EventSource if the Service Worker registration fails or is unsupported.
- ⚠️ Buffer Slicing: Never assume an incoming stream chunk contains an entire SSE packet. Packets can arrive split across arbitrary network boundaries. Always buffer and split on
\n\n. - 💡 Service Worker Termination: Browsers terminate idle Service Workers after ~30 seconds of inactivity. Streaming active
fetchrequests keeps the worker alive while the network connection remains active.
📌 Key Takeaways
EventSourceis not supported in Service Workers, butfetch()withReadableStreamprovides full streaming capabilities.BroadcastChannelprovides zero-overhead pub/sub fan-out between background workers and multiple active browser tabs.- Always implement an
\n\nchunk parsing buffer when decoding raw byte streams. - --