/susie

Server-sent events with hapi

Primary LanguageJavaScriptBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

SuSiE - Server-Sent Events with hapi

Build Status

Above example under /examples. Start with npm start

This is a plugin that adds simple Server-Sent Events (aka EventSource) capabilities to hapi. It decorates the toolkit with a new method h.event(). You can send individual events as objects, or you can simply pass a stream and some options and SuSiE will make things work as you expect.

You probably already know this but install it with: npm install --save susie

Usage

First load and register the plugin:

await server.register(require('susie'));

With event objects

In a route handler you can now call h.event() to start an SSE response:

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, h) {

        return h.event({ data: 'my data' });
    }
});

The first time you call h.event(), appropriate HTTP response headers will be sent, along with your first event. Subsequent calls to h.event() will send more events.

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, h) {

        const response = h.event({ id: 1, data: 'my data' });

        setTimeout(function () {

            h.event({ id: 2, data: { a: 1 } }); // object datum
        }, 500);

        return response;
    }
});

If any of your datum are objects, they will be stringified for you. Make sure to parse them again in the client, if you're expecting JSON.

With a readable stream

A really nice way to provide an EventSource is using a ReadableStream. This is really simple with SuSiE. Just call h.event(stream):

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, h) {

        var Readable = require('stream').Readable;
        var rs = Readable();

        var c = 97;
        rs._read = function () {
            rs.push(String.fromCharCode(c++));
            if (c > 'z'.charCodeAt(0)) rs.push(null);
        };

        return h.event(rs);
    }
});

http://cl.ly/d5XT/Screen%20Shot%202015-09-13%20at%2015.50.25.png

Each chunk coming off the stream will be sent as an event. The content of the chunk will be the data parameter. You can provide an optional event option and id generator. By default the id will be the number of chunks received:

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, h) {

        var i = 0;
        var generateId = function (chunk) { return i += 10; }
        return h.event(stream, null, { event: 'update', generateId });
    }
});

Object mode streams

If the stream is in objectMode, each object that comes off the stream will be stringified and the resulting string will be used as the data parameter. See example under examples for example.

Considerations

How do I finish a SSE stream for good?

In the SSE spec, it says that when the HTTP response ends, the browser will try to reconnect, sending another request to the endpoint. You may want this. Or you may really want to stop to the events being streamed altogether.

When you call h.event(null) or your stream emits its end event, the HTTP response will conclude. However, SuSiE will send one last event to the browser before it closes. You should listen for this end event in your client code and close the EventSource, before the browser attempts to reconnect:

<script>
    var source = new EventSource('/events');
    ...
    source.addEventListener('end', function (event) {

        this.close();
    });
</script>