reconnecting-eventsource
Version:
wrapper library around the JavaScript EventSource API to ensure it maintains a connection to the server.
263 lines • 11 kB
JavaScript
// MIT License:
//
// Copyright (C) 2022 Fanout, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var EventSourceNotAvailableError = /** @class */ (function (_super) {
__extends(EventSourceNotAvailableError, _super);
function EventSourceNotAvailableError() {
return _super.call(this, 'EventSource not available.\n' +
'Consider loading an EventSource polyfill and making it available globally as EventSource, ' +
'or passing one in as eventSourceClass to the ReconnectingEventSource constructor.') || this;
}
return EventSourceNotAvailableError;
}(Error));
export { EventSourceNotAvailableError };
var ReconnectingEventSource = /** @class */ (function () {
function ReconnectingEventSource(url, configuration) {
var _this = this;
this.CONNECTING = 0;
this.OPEN = 1;
this.CLOSED = 2;
this._configuration = configuration != null ? Object.assign({}, configuration) : undefined;
this.withCredentials = false;
this._eventSource = null;
this._lastEventId = null;
this._timer = null;
this._listeners = {};
this.url = url.toString();
this.readyState = this.CONNECTING;
this.max_retry_time = 3000;
this.eventSourceClass = globalThis.EventSource;
if (this._configuration != null) {
if (this._configuration.lastEventId) {
this._lastEventId = this._configuration.lastEventId;
delete this._configuration['lastEventId'];
}
if (this._configuration.max_retry_time) {
this.max_retry_time = this._configuration.max_retry_time;
delete this._configuration['max_retry_time'];
}
if (this._configuration.eventSourceClass) {
this.eventSourceClass = this._configuration.eventSourceClass;
delete this._configuration['eventSourceClass'];
}
}
if (this.eventSourceClass == null || typeof this.eventSourceClass !== 'function') {
throw new EventSourceNotAvailableError();
}
this._onevent_wrapped = function (event) { _this._onevent(event); };
this._start();
}
ReconnectingEventSource.prototype.dispatchEvent = function (event) {
throw new Error("Method not implemented.");
};
ReconnectingEventSource.prototype._start = function () {
var e_1, _a;
var _this = this;
var url = this.url;
if (this._lastEventId) {
if (url.indexOf('?') === -1) {
url += '?';
}
else {
url += '&';
}
url += 'lastEventId=' + encodeURIComponent(this._lastEventId);
}
this._eventSource = new this.eventSourceClass(url, this._configuration);
this._eventSource.onopen = function (event) { _this._onopen(event); };
this._eventSource.onerror = function (event) { _this._onerror(event); };
this._eventSource.onmessage = function (event) { _this.onmessage(event); };
try {
// apply listen types
for (var _b = __values(Object.keys(this._listeners)), _c = _b.next(); !_c.done; _c = _b.next()) {
var type = _c.value;
this._eventSource.addEventListener(type, this._onevent_wrapped);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
};
ReconnectingEventSource.prototype._onopen = function (event) {
if (this.readyState === 0) {
this.readyState = 1;
this.onopen(event);
}
};
ReconnectingEventSource.prototype._onerror = function (event) {
var _this = this;
if (this.readyState === 1) {
this.readyState = 0;
this.onerror(event);
}
if (this._eventSource) {
if (this._eventSource.readyState === 2) {
// reconnect with new object
this._eventSource.close();
this._eventSource = null;
// reconnect after random timeout < max_retry_time
var timeout = Math.round(this.max_retry_time * Math.random());
this._timer = setTimeout(function () { return _this._start(); }, timeout);
}
}
};
ReconnectingEventSource.prototype._onevent = function (event) {
var e_2, _a;
if (event instanceof MessageEvent) {
this._lastEventId = event.lastEventId;
}
var listenersForType = this._listeners[event.type];
if (listenersForType != null) {
try {
// operate on a copy
for (var _b = __values(__spreadArray([], __read(listenersForType), false)), _c = _b.next(); !_c.done; _c = _b.next()) {
var listener = _c.value;
listener.call(this, event);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_2) throw e_2.error; }
}
}
if (event.type === 'message') {
this.onmessage(event);
}
};
ReconnectingEventSource.prototype.onopen = function (event) {
// may be overridden
};
ReconnectingEventSource.prototype.onerror = function (event) {
// may be overridden
};
ReconnectingEventSource.prototype.onmessage = function (event) {
// may be overridden
};
ReconnectingEventSource.prototype.close = function () {
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
if (this._eventSource) {
this._eventSource.close();
this._eventSource = null;
}
this.readyState = 2;
};
ReconnectingEventSource.prototype.addEventListener = function (type, listener, options) {
// We don't support the options arg at the moment
if (!(type in this._listeners)) {
this._listeners[type] = [];
if (this._eventSource != null) {
this._eventSource.addEventListener(type, this._onevent_wrapped);
}
}
var listenersForType = this._listeners[type];
if (Array.isArray(listenersForType) && !listenersForType.includes(listener)) {
listenersForType.push(listener);
}
};
ReconnectingEventSource.prototype.removeEventListener = function (type, listener, options) {
// We don't support the options arg at the moment
var listenersForType = this._listeners[type];
if (listenersForType != null) {
// eslint-disable-next-line no-constant-condition
while (true) {
var index = listenersForType.indexOf(listener);
if (index === -1) {
break;
}
listenersForType.splice(index, 1);
}
if (listenersForType.length <= 0) {
delete this._listeners[type];
if (this._eventSource != null) {
this._eventSource.removeEventListener(type, this._onevent_wrapped);
}
}
}
};
// Evidently these are to exist both on the class and the instance
ReconnectingEventSource.CONNECTING = 0;
ReconnectingEventSource.OPEN = 1;
ReconnectingEventSource.CLOSED = 2;
return ReconnectingEventSource;
}());
export default ReconnectingEventSource;
//# sourceMappingURL=reconnecting-eventsource.js.map