17

I have push notifications in my JavaScript client app using EventSource. I can attach event listeners like this:

source.addEventListener('my_custom_event_type', function(e) {
  console.log(e.data);
}, false);

But I want to monitor all events that are being pushed from the server (basically for debugging), so if some event is sent but it has no event listener I can easily find it. I mean, I don't want to just "ignore" all events that have no eventListeners binded.

I would expect to do something like this:

source.addEventListener('*', function(e) {
  console.debug('Event with no listener attached: ', e);
}, false);

But the specification and tutorials like the one at html5rocks don't specify if this is possible or not.

In the other hand, it may be some firefox/chrome extension that allows to monitor all server events or something. Those things would really help on developing push notifications.

Thanks!

tothemario
  • 5,851
  • 3
  • 44
  • 39

4 Answers4

31

I figure out a solution myself, that also improves tremendously the EventSource interface.

Server side: Do not send the event type, just include an additional data field (having that I always use json). So instead of

event: eventName
data: {mykey: 'myvalue'}

I send this from the server instead:

data: {mykey: 'myvalue', eventName: 'eventName'}

Client side: Now I can use EventSource onmessage callback, that is fired on every message that does not have an event type.

And for bind event listeners, I create a wrapper class with Backbone.Event functionality. The result:

// Server Sent Events (Event Source wrapper class)
var MyEventSource = (function() {

  function MyEventSource(url) {
    var self = this;
    _.extend(this, Backbone.Events);

    this.source = new EventSource(url);
    this.source.onmessage = function(event) {
      var data, eventName;
      var data = JSON.parse(event.data);
      var eventName = data.eventName; delete data.eventName;

      // Now we can monitor all server sent events
      console.log('app.server.on ', eventName, '. Data: ', data);

      self.trigger(eventName, data);
    };
  }

  return MyEventSource;
})();

Now with this wrapper class, I can easily extend the functionality, all server sent events can be easily monitored and thanks to extending Backbone.Events the event handling in this class is much more powerful.

Usage example:

var source = new MyEventSource('url/of/source');

// Add event listener
source.on('eventName', function(data) {
  console.log(data);
});

// Fire a event (also very useful for testing and debugging!!)
source.trigger('eventName', { mykey: 'myvalue' });

// Unbind event listener (very important for complex applications)
source.off('eventName');

Now I have a component that is easy to handle, extend, debug and test.

tothemario
  • 5,851
  • 3
  • 44
  • 39
  • 18
    "Onmessage callback is fired on every message that **does not have an event type**". That was seriously useful information to me. Thanks. – Matthew Walker Sep 06 '13 at 03:14
  • Just an fyi: calling `onmessage = some_function;` is exactly the same as calling `addEventListener("message", some_function);`. This makes it obvious that messages without an event type are the same as messages with an event type of "message". – Ashitaka Nov 07 '13 at 21:48
  • Hello tothemario. For some reason, JSON.parse(event.data) doesn't work for me. Would you be so kind as to provide your server-side way to generate data: {mykey: 'myvalue', eventName: 'eventName'} ? Thanks in advance. – pouzzler Jan 26 '14 at 17:13
  • `tothemario`! thanks for the answer, based on your guidance I tried `source.addEventListener('eventName', MyHander, false);` this works without the wrapper. (see my answer below for a full example) – Alex C May 24 '19 at 19:09
0
 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js" type="text/javascript"></script>  
  <script>
    var content = '';
    if(typeof(EventSource)!=="undefined")
    {
      var source = new EventSource("demo_sse.php");
      source.onmessage = function(event)
      {
        content+=event.data + "<br>";
        $("#result").html(content);
      };
    }
    else
    {
      $("#result").html("Sorry, your browser does not support server-sent events...");
    }
  </script>
gilcierweb
  • 13
  • 2
  • 2
    This will not work since `onmessage` handles only events without the type https://developer.mozilla.org/ru/docs/Web/API/EventSource – Grief Jul 13 '15 at 13:36
0

I know this isn't an EventSource, but I was looking for the same thing (a way to catch all incoming events without knowing their type). Without any control over the server sending these events, I ended up just writing it with an XHR, in case anyone else comes across this:

function eventStream(path, callback){
    //Create XHR object
    var xhr = new XMLHttpRequest();

    //initialize storage for previously fetched information
    var fetched='';

    //Set readystatechange handler
    xhr.onreadystatechange=function(){

        //If the connection has been made and we have 200, process the data
        if(xhr.readyState>2 && xhr.status==200){
            //save the current response text
            var newFetched=xhr.responseText;

            //this is a stream, so responseText always contains everything
            //from the start of the stream, we only want the latest
            var lastFetch=xhr.responseText.replace(fetched, '');

            //Set the complete response text to be removed next time 
            var fetched=newFetched;

            //callback to allow parsing of the fetched data
            callback(lastFetch);
        }
    };

    //open and send to begin the stream;
    xhr.open('GET', path, true);
    xhr.send();
}

parseEvents=function(response){
    var events=[];
    //split out by line break
    var lines=response.split("\n");

    //loop through the lines
    for(var i=0;i<lines.length;i++){

        //each event consists of 2 lines, one begins with
        //"name:", the other with "data"
        //if we hit data, process it and the previous line
        if(lines[i].substr(0, lines[i].indexOf(':'))=='data'){

            //add this event to our list for return
            events.push({

               //get the event name
               name: lines[i-1].split(':')[1].trim(),
               //parse the event data
               data: $.parseJSON(lines[i].substr(lines[i].indexOf(':')+1).trim())
            });
        }
    }
    //return the parsed events
    return events;
};

evenStream('http://example.com/myEventPath', function(response){
    var events=parseEvents(response);
});
Trey
  • 5,480
  • 4
  • 23
  • 30
0

Credit to user tothemario above for the clue I needed to figure this out.

It appears that you CAN send events back to the browser with a custom type, but in order to trigger the MESSAGE event you must assign a listener to the new type rather than the message type.

If you look at the client side code below it will hopefully illustrate.

For context, my server sends an event with the custom type CustomType. Therefore I subscribe with an event listener to that type, and I add another listener for message as a catch all for everything else.

In this workflow, an event that comes to the browser with the CustomType a different listener fires.

 <script type="text/javascript">
    var CustomTypeList = [];

    function EventSystemOpen(e) {
        console.log("EventSystemOpen", e);
    }

    function EventSystemError(e) {
        console.log("EventSystemOpen", e);
        if (e.readyState == EventSource.CLOSED) {
            //
        }
    }

    function GotServerEventMessage(e) {
        console.log("GotServerEventMessage", e);
    }

    function GotCustomType(e) {
        CustomTypeList.push(JSON.parse(e.data));
        console.log("Added CustomType", e, JSON.parse(e.data), CustomTypeList);
    }

    if (!!window.EventSource) {
        var source = new EventSource('api/listen');
        source.addEventListener('open', EventSystemOpen, false);
        source.addEventListener('error', EventSystemError, false);
        source.addEventListener('message', GotServerEventMessage, false);
        source.addEventListener('CustomType', GotCustomType, false);
    }
 </script>
Alex C
  • 16,624
  • 18
  • 66
  • 98