123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- /**
- * Copyright (C) 2016 Maxime Petazzoni <maxime.petazzoni@bulix.org>.
- * All rights reserved.
- */
- var SSE = function (url, options) {
- if (!(this instanceof SSE)) {
- return new SSE(url, options);
- }
- this.INITIALIZING = -1;
- this.CONNECTING = 0;
- this.OPEN = 1;
- this.CLOSED = 2;
- this.url = url;
- options = options || {};
- this.headers = options.headers || {};
- this.payload = options.payload !== undefined ? options.payload : '';
- this.method = options.method || (this.payload && 'POST' || 'GET');
- this.FIELD_SEPARATOR = ':';
- this.listeners = {};
- this.xhr = null;
- this.readyState = this.INITIALIZING;
- this.progress = 0;
- this.chunk = '';
- this.addEventListener = function(type, listener) {
- if (this.listeners[type] === undefined) {
- this.listeners[type] = [];
- }
- if (this.listeners[type].indexOf(listener) === -1) {
- this.listeners[type].push(listener);
- }
- };
- this.removeEventListener = function(type, listener) {
- if (this.listeners[type] === undefined) {
- return;
- }
- var filtered = [];
- this.listeners[type].forEach(function(element) {
- if (element !== listener) {
- filtered.push(element);
- }
- });
- if (filtered.length === 0) {
- delete this.listeners[type];
- } else {
- this.listeners[type] = filtered;
- }
- };
- this.dispatchEvent = function(e) {
- if (!e) {
- return true;
- }
- e.source = this;
- var onHandler = 'on' + e.type;
- if (this.hasOwnProperty(onHandler)) {
- this[onHandler].call(this, e);
- if (e.defaultPrevented) {
- return false;
- }
- }
- if (this.listeners[e.type]) {
- return this.listeners[e.type].every(function(callback) {
- callback(e);
- return !e.defaultPrevented;
- });
- }
- return true;
- };
- this._setReadyState = function (state) {
- var event = new CustomEvent('readystatechange');
- event.readyState = state;
- this.readyState = state;
- this.dispatchEvent(event);
- };
- this._onStreamFailure = function(e) {
- this.dispatchEvent(new CustomEvent('error'));
- this.close();
- }
- this._onStreamProgress = function(e) {
- if (this.xhr.status !== 200 && this.readyState !== this.CLOSED) {
- this._onStreamFailure(e);
- return;
- }
- if (this.readyState == this.CONNECTING) {
- this.dispatchEvent(new CustomEvent('open'));
- this._setReadyState(this.OPEN);
- }
- var data = this.xhr.responseText.substring(this.progress);
- this.progress += data.length;
- data.split(/(\r\n|\r|\n){2}/g).forEach(function(part) {
- if (part.trim().length === 0) {
- this.dispatchEvent(this._parseEventChunk(this.chunk.trim()));
- this.chunk = '';
- } else {
- this.chunk += part;
- }
- }.bind(this));
- };
- this._onStreamLoaded = function(e) {
- this._onStreamProgress(e);
- // Parse the last chunk.
- this.dispatchEvent(this._parseEventChunk(this.chunk));
- this.chunk = '';
- };
- /**
- * Parse a received SSE event chunk into a constructed event object.
- */
- this._parseEventChunk = function(chunk) {
- if (!chunk || chunk.length === 0) {
- return null;
- }
- var e = {'id': null, 'retry': null, 'data': '', 'event': 'message'};
- chunk.split(/\n|\r\n|\r/).forEach(function(line) {
- line = line.trimRight();
- var index = line.indexOf(this.FIELD_SEPARATOR);
- if (index <= 0) {
- // Line was either empty, or started with a separator and is a comment.
- // Either way, ignore.
- return;
- }
- var field = line.substring(0, index);
- if (!(field in e)) {
- return;
- }
- var value = line.substring(index + 1).trimLeft();
- if (field === 'data') {
- e[field] += value;
- } else {
- e[field] = value;
- }
- }.bind(this));
- var event = new CustomEvent(e.event);
- event.data = e.data;
- event.id = e.id;
- return event;
- };
- this._checkStreamClosed = function() {
- if (this.xhr.readyState === XMLHttpRequest.DONE) {
- this._setReadyState(this.CLOSED);
- }
- };
- this.stream = function() {
- this._setReadyState(this.CONNECTING);
- this.xhr = new XMLHttpRequest();
- this.xhr.addEventListener('progress', this._onStreamProgress.bind(this));
- this.xhr.addEventListener('load', this._onStreamLoaded.bind(this));
- this.xhr.addEventListener('readystatechange', this._checkStreamClosed.bind(this));
- this.xhr.addEventListener('error', this._onStreamFailure.bind(this));
- this.xhr.addEventListener('abort', this._onStreamFailure.bind(this));
- this.xhr.open(this.method, this.url);
- for (var header in this.headers) {
- this.xhr.setRequestHeader(header, this.headers[header]);
- }
- this.xhr.send(this.payload);
- };
- this.close = function() {
- if (this.readyState === this.CLOSED) {
- return;
- }
- this.xhr.abort();
- this.xhr = null;
- this._setReadyState(this.CLOSED);
- };
- };
- // Export our SSE module for npm.js
- if (typeof exports !== 'undefined') {
- exports.SSE = SSE;
- }
|