Skip To Main Content

JavaScript EventSource: Now available in Firefox!

Posted by Rick Waldron

Jun 07 2010

…Not natively… but this will work in the meantime:


Git it here


firefox-event-source.js

;(function (w) {
  if ( !w['EventSource'] ) {
    // parseUri 1.2.2
    // (c) Steven Levithan <stevenlevithan.com>
    // MIT License
    var parseUri = function(str) {
      var o   = {
          key: ['source','protocol','authority','userInfo','user','password','host','port','relative','path','directory','file','query','anchor'],
          q:   {
            name:   'queryKey',
            parser: /(?:^|&amp;)([^&amp;=]*)=?([^&amp;]*)/g
          },
          parser: {
            strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/
          }
        },
        m   = o.parser.strict.exec(str),
        uri = {},
        i   = 14;

      while (i--) uri[o.key[i]] = m[i] || '';

      uri[o.q.name] = {};
      uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
        if ($1) uri[o.q.name][$1] = $2;
      });

      return uri;
    };


    //   EventSource implementation
    function EventSource( resource ) {

      var that        = (this === window) ? {} : this,
          retry       = 1000, offset  = 0,
          boundary    = "\n", queue   = [],   origin  = '',
          lastEventId = null, xhr     = null, source  = null, matches   = null, resourceLocation  = null;

      that.toString           = function () { return '[object EventSource]' };

      //  EventSource listener
      that.addEventListener   = function (type, listener, useCapture) {
        document.addEventListener(type, listener, useCapture);
      };
      //  EventSource dispatcher
      that.dispatchEvent      = function (event) {
        document.dispatchEvent(event);
      };

      resourceLocation  = parseUri(resource);

      //  same origin policy
      if ( resource.match(/http/) &amp;&amp; resource.match(/http/).length ) {
        if ( resourceLocation.host !== location.host ) {
          throw DOMException;//"SECURITY_ERR: DOM Exception";
        }
      }
      that.URL  = resourceLocation.source;

      var openConnectionXHR     = function() {

        xhr = new XMLHttpRequest();
        xhr.open('GET', that.URL, true);

        //  FIRE OPEN EVENT
        var openEvent = document.createEvent('UIEvents');
            openEvent.initEvent('open', true, true);
            openEvent.origin      = document.domain;
            openEvent.source      = null;
            that.dispatchEvent(openEvent);


        if ( lastEventId ) {
          xhr.setRequestHeader('Last-Event-ID', lastEventId);
        }
        xhr.onreadystatechange = function() {
          switch (xhr.readyState) {
            case 4: // disconnect case
              pseudoDispatchEvent();
              reOpenConnectionXHR();
              break;
            case 3: // new data
              processMessageFromXHR();
              break;
          }
        }

        xhr.send(null);
      }

      var reOpenConnectionXHR   = function() {
        xhr       = null;
        offset    = 0;
        setTimeout(openConnectionXHR, 1000);
      }

      var processMessageFromXHR = function() {
        var stream = xhr.responseText.split(boundary);

        //  Abandon if empty
        if ( stream.length < offset )  {
          return;
        }

        for ( var i = 0; i < stream.length; i++ ) {
          queue.push(stream[i]);
        }

        pseudoDispatchEvent();
      }

      var pseudoDispatchEvent = function() {
        var data = '', name = 'message';

        queue.reverse();

        while (queue.length > 0) {
          line = queue.pop();
          var dataIndex = line.indexOf(':'), field = null, value = '';

          if (dataIndex == -1) {
            field = line;
            value = '';
          }
          else if (dataIndex == 0) {
            //  Ignore Comment lines
            continue;
          }
          else {
            field = line.slice(0, dataIndex)
            value = line.slice(dataIndex+1)
          }

          if (field == 'event') {
            name = value;
          }

          if (field == 'id') {
            lastEventId = value;
          }

          if (field == 'retry') {
            value = parseInt(value);

            if (!isNaN(value)) {
              retry = value;
            }
          }

          if (field == 'data') {
            if (data.length > 0) {
              data += "\n";
            }

            data += value;
          }
        }

        var fireEvent = document.createEvent('UIEvents');
        //  MessageEvent causes "setting a property that has only a getter" Errors


        if ( data.length > 0 ) {

          fireEvent.initEvent(name, true, true);
          fireEvent.lastEventId = lastEventId;
          fireEvent.data        = data.replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g, '');
          fireEvent.origin      = document.domain;
          fireEvent.source      = null;
          that.dispatchEvent(fireEvent);
        }
      }

      //  INIT EVENT SOURCE CONNECTION
      openConnectionXHR();
    };
    window['EventSource']  = EventSource;
  }
})(window);

firefox-event-source.html

<script src="firefox-event-source.js"></script>
<script>
console.log(EventSource);


var eventSource = new EventSource('event.php');

console.group('eventSource')
  console.log(eventSource);
  console.log(eventSource.constructor);
console.groupEnd();

eventSource.addEventListener('open', function (event) {
  console.log('OPEN!');
}, false);

eventSource.addEventListener('message', function (event) {
  var message = JSON.parse(event.data);
    console.log(message);
}, false);


</script>

event.php

<?php
header("Content-Type: text/event-stream\n\n");

echo 'data: ' . json_encode(
                  array(
                    0 => array(
                      'time' => time(),
                      'message' => 'Some kind of foo'
                    ),
                    1 => array(
                      'time' => time(),
                      'message' => 'Some kind of quux'
                    )
                  )
                ) . "\n";

?>
Posted by
Rick Waldron
on June 7th, 2010

Comments

We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.

Contact Us

We'd love to hear from you. Get in touch!