summaryrefslogtreecommitdiffstats
path: root/assets/js/sse.js
diff options
context:
space:
mode:
authorOmar Roth <omarroth@protonmail.com>2019-05-05 07:46:01 -0500
committerOmar Roth <omarroth@protonmail.com>2019-06-01 16:09:17 -0500
commit0338fd42e15ee9803068e6d6eeb04d78b94f321c (patch)
tree44794e8e271b25105fbd79d60643f229940ce653 /assets/js/sse.js
parentb3788bc1431aea47b7a9ffb325984f4a58c21125 (diff)
downloadinvidious-0338fd42e15ee9803068e6d6eeb04d78b94f321c.tar.gz
invidious-0338fd42e15ee9803068e6d6eeb04d78b94f321c.tar.bz2
invidious-0338fd42e15ee9803068e6d6eeb04d78b94f321c.zip
Add support for Web notifications
Diffstat (limited to 'assets/js/sse.js')
-rw-r--r--assets/js/sse.js200
1 files changed, 200 insertions, 0 deletions
diff --git a/assets/js/sse.js b/assets/js/sse.js
new file mode 100644
index 00000000..3601b5af
--- /dev/null
+++ b/assets/js/sse.js
@@ -0,0 +1,200 @@
+/**
+ * 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._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;
+}