Skip to content

Instantly share code, notes, and snippets.

@jcubic
Last active November 19, 2022 00:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcubic/06837021480f4843f54e6896d7dfd213 to your computer and use it in GitHub Desktop.
Save jcubic/06837021480f4843f54e6896d7dfd213 to your computer and use it in GitHub Desktop.
Server-Sent Events with Service worker issue
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="sse_start" disabled>Start</button>
<button id="sse_stop" disabled>Stop</button>
<pre id="output">Loading Service Worker</pre>
<script>
if ('serviceWorker' in navigator) {
var scope = location.pathname.replace(/\/[^\/]+$/, '/');
navigator.serviceWorker.register('sw.js', { scope })
.then(function(reg) {
reg.addEventListener('updatefound', function() {
var installingWorker = reg.installing;
console.log('A new service worker is being installed:',
installingWorker);
});
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
sse_start.disabled = sse_stop.disabled = false;
set('Ready');
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
function set(text) {
output.innerHTML = text;
}
function clear() {
output.innerHTML = '';
}
function log(text) {
output.innerHTML += '\n' + text;
}
let see_source;
sse_start.addEventListener('click', () => {
see_source = new EventSource("./sse");
clear();
see_source.onmessage = event => {
log(event.data);
};
});
sse_stop.addEventListener('click', () => {
if (see_source) {
see_source.close();
see_source = null;
}
});
</script>
</body>
</html>
let timerId;
let count = 10;
function startStream(send) {
timerId = setInterval(function() {
if (--count <= 0) {
throw new Error('Nasty Error');
}
const now = (new Date()).toString();
send({ data: now });
}, 1000);
}
function stopStream() {
clearInterval(timerId);
}
self.addEventListener('fetch', (event) => {
event.respondWith(new Promise((resolve, reject) => {
const req = event.request;
const url = new URL(req.url);
console.log({url});
if (url.pathname.match(/sse/)) {
let send, close, defunct;
const stream = new ReadableStream({
cancel() {
defunct = true;
stopStream();
},
start: controller => {
send = function(event) {
if (!defunct) {
const chunk = createChunk(event);
const payload = new TextEncoder().encode(chunk);
controller.enqueue(payload);
}
};
close = function close() {
controller.close();
stream = null;
stopStream();
};
}
});
resolve(new Response(stream, {
headers: {
'Content-Type': 'text/event-stream; charset=utf-8',
'Transfer-Encoding': 'chunked',
'Connection': 'keep-alive'
}
}));
startStream(send);
}
if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') {
return;
}
fetch(event.request).then(resolve).catch(reject);
}));
});
function createChunk({ data, event, retry, id }) {
return Object.entries({ event, id, data, retry })
.filter(([, value]) => value)
.map(([key, value]) => `${key}: ${value}`)
.join('\n') + '\n\n';
}
@jcubic
Copy link
Author

jcubic commented Nov 18, 2022

The code works fine, this is the proper way to implement Server-Sent Events inside the Service worker.
But there is a problem with:

  • Google Chrome: when setInterval throws an exception the script doesn't stop. And Server-Sent Events keep throwing errors in a loop.

  • Firefox: the Stream silently stops and the Server-Sent Event Stream is restarted.

@jcubic
Copy link
Author

jcubic commented Nov 18, 2022

Here is working demo https://jcu.bi/sse

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment