UNPKG

centrifuge

Version:

JavaScript client SDK for bidirectional communication with Centrifugo and Centrifuge-based server from browser, NodeJS and React Native

1,471 lines (1,377 loc) 384 kB
'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var events = {exports: {}}; var R = typeof Reflect === 'object' ? Reflect : null; var ReflectApply = R && typeof R.apply === 'function' ? R.apply : function ReflectApply(target, receiver, args) { return Function.prototype.apply.call(target, receiver, args); }; var ReflectOwnKeys; if (R && typeof R.ownKeys === 'function') { ReflectOwnKeys = R.ownKeys; } else if (Object.getOwnPropertySymbols) { ReflectOwnKeys = function ReflectOwnKeys(target) { return Object.getOwnPropertyNames(target) .concat(Object.getOwnPropertySymbols(target)); }; } else { ReflectOwnKeys = function ReflectOwnKeys(target) { return Object.getOwnPropertyNames(target); }; } function ProcessEmitWarning(warning) { if (console && console.warn) console.warn(warning); } var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { return value !== value; }; function EventEmitter$1() { EventEmitter$1.init.call(this); } events.exports = EventEmitter$1; events.exports.once = once; // Backwards-compat with node 0.10.x EventEmitter$1.EventEmitter = EventEmitter$1; EventEmitter$1.prototype._events = undefined; EventEmitter$1.prototype._eventsCount = 0; EventEmitter$1.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. var defaultMaxListeners = 10; function checkListener(listener) { if (typeof listener !== 'function') { throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); } } Object.defineProperty(EventEmitter$1, 'defaultMaxListeners', { enumerable: true, get: function() { return defaultMaxListeners; }, set: function(arg) { if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); } defaultMaxListeners = arg; } }); EventEmitter$1.init = function() { if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) { this._events = Object.create(null); this._eventsCount = 0; } this._maxListeners = this._maxListeners || undefined; }; // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter$1.prototype.setMaxListeners = function setMaxListeners(n) { if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); } this._maxListeners = n; return this; }; function _getMaxListeners(that) { if (that._maxListeners === undefined) return EventEmitter$1.defaultMaxListeners; return that._maxListeners; } EventEmitter$1.prototype.getMaxListeners = function getMaxListeners() { return _getMaxListeners(this); }; EventEmitter$1.prototype.emit = function emit(type) { var args = []; for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); var doError = (type === 'error'); var events = this._events; if (events !== undefined) doError = (doError && events.error === undefined); else if (!doError) return false; // If there is no 'error' event listener then throw. if (doError) { var er; if (args.length > 0) er = args[0]; if (er instanceof Error) { // Note: The comments on the `throw` lines are intentional, they show // up in Node's output if this results in an unhandled exception. throw er; // Unhandled 'error' event } // At least give some kind of context to the user var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); err.context = er; throw err; // Unhandled 'error' event } var handler = events[type]; if (handler === undefined) return false; if (typeof handler === 'function') { ReflectApply(handler, this, args); } else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) ReflectApply(listeners[i], this, args); } return true; }; function _addListener(target, type, listener, prepend) { var m; var events; var existing; checkListener(listener); events = target._events; if (events === undefined) { events = target._events = Object.create(null); target._eventsCount = 0; } else { // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (events.newListener !== undefined) { target.emit('newListener', type, listener.listener ? listener.listener : listener); // Re-assign `events` because a newListener handler could have caused the // this._events to be assigned to a new object events = target._events; } existing = events[type]; } if (existing === undefined) { // Optimize the case of one listener. Don't need the extra array object. existing = events[type] = listener; ++target._eventsCount; } else { if (typeof existing === 'function') { // Adding the second element, need to change to array. existing = events[type] = prepend ? [listener, existing] : [existing, listener]; // If we've already got an array, just append. } else if (prepend) { existing.unshift(listener); } else { existing.push(listener); } // Check for listener leak m = _getMaxListeners(target); if (m > 0 && existing.length > m && !existing.warned) { existing.warned = true; // No error code for this since it is a Warning // eslint-disable-next-line no-restricted-syntax var w = new Error('Possible EventEmitter memory leak detected. ' + existing.length + ' ' + String(type) + ' listeners ' + 'added. Use emitter.setMaxListeners() to ' + 'increase limit'); w.name = 'MaxListenersExceededWarning'; w.emitter = target; w.type = type; w.count = existing.length; ProcessEmitWarning(w); } } return target; } EventEmitter$1.prototype.addListener = function addListener(type, listener) { return _addListener(this, type, listener, false); }; EventEmitter$1.prototype.on = EventEmitter$1.prototype.addListener; EventEmitter$1.prototype.prependListener = function prependListener(type, listener) { return _addListener(this, type, listener, true); }; function onceWrapper() { if (!this.fired) { this.target.removeListener(this.type, this.wrapFn); this.fired = true; if (arguments.length === 0) return this.listener.call(this.target); return this.listener.apply(this.target, arguments); } } function _onceWrap(target, type, listener) { var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; var wrapped = onceWrapper.bind(state); wrapped.listener = listener; state.wrapFn = wrapped; return wrapped; } EventEmitter$1.prototype.once = function once(type, listener) { checkListener(listener); this.on(type, _onceWrap(this, type, listener)); return this; }; EventEmitter$1.prototype.prependOnceListener = function prependOnceListener(type, listener) { checkListener(listener); this.prependListener(type, _onceWrap(this, type, listener)); return this; }; // Emits a 'removeListener' event if and only if the listener was removed. EventEmitter$1.prototype.removeListener = function removeListener(type, listener) { var list, events, position, i, originalListener; checkListener(listener); events = this._events; if (events === undefined) return this; list = events[type]; if (list === undefined) return this; if (list === listener || list.listener === listener) { if (--this._eventsCount === 0) this._events = Object.create(null); else { delete events[type]; if (events.removeListener) this.emit('removeListener', type, list.listener || listener); } } else if (typeof list !== 'function') { position = -1; for (i = list.length - 1; i >= 0; i--) { if (list[i] === listener || list[i].listener === listener) { originalListener = list[i].listener; position = i; break; } } if (position < 0) return this; if (position === 0) list.shift(); else { spliceOne(list, position); } if (list.length === 1) events[type] = list[0]; if (events.removeListener !== undefined) this.emit('removeListener', type, originalListener || listener); } return this; }; EventEmitter$1.prototype.off = EventEmitter$1.prototype.removeListener; EventEmitter$1.prototype.removeAllListeners = function removeAllListeners(type) { var listeners, events, i; events = this._events; if (events === undefined) return this; // not listening for removeListener, no need to emit if (events.removeListener === undefined) { if (arguments.length === 0) { this._events = Object.create(null); this._eventsCount = 0; } else if (events[type] !== undefined) { if (--this._eventsCount === 0) this._events = Object.create(null); else delete events[type]; } return this; } // emit removeListener for all listeners on all events if (arguments.length === 0) { var keys = Object.keys(events); var key; for (i = 0; i < keys.length; ++i) { key = keys[i]; if (key === 'removeListener') continue; this.removeAllListeners(key); } this.removeAllListeners('removeListener'); this._events = Object.create(null); this._eventsCount = 0; return this; } listeners = events[type]; if (typeof listeners === 'function') { this.removeListener(type, listeners); } else if (listeners !== undefined) { // LIFO order for (i = listeners.length - 1; i >= 0; i--) { this.removeListener(type, listeners[i]); } } return this; }; function _listeners(target, type, unwrap) { var events = target._events; if (events === undefined) return []; var evlistener = events[type]; if (evlistener === undefined) return []; if (typeof evlistener === 'function') return unwrap ? [evlistener.listener || evlistener] : [evlistener]; return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); } EventEmitter$1.prototype.listeners = function listeners(type) { return _listeners(this, type, true); }; EventEmitter$1.prototype.rawListeners = function rawListeners(type) { return _listeners(this, type, false); }; EventEmitter$1.listenerCount = function(emitter, type) { if (typeof emitter.listenerCount === 'function') { return emitter.listenerCount(type); } else { return listenerCount.call(emitter, type); } }; EventEmitter$1.prototype.listenerCount = listenerCount; function listenerCount(type) { var events = this._events; if (events !== undefined) { var evlistener = events[type]; if (typeof evlistener === 'function') { return 1; } else if (evlistener !== undefined) { return evlistener.length; } } return 0; } EventEmitter$1.prototype.eventNames = function eventNames() { return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; }; function arrayClone(arr, n) { var copy = new Array(n); for (var i = 0; i < n; ++i) copy[i] = arr[i]; return copy; } function spliceOne(list, index) { for (; index + 1 < list.length; index++) list[index] = list[index + 1]; list.pop(); } function unwrapListeners(arr) { var ret = new Array(arr.length); for (var i = 0; i < ret.length; ++i) { ret[i] = arr[i].listener || arr[i]; } return ret; } function once(emitter, name) { return new Promise(function (resolve, reject) { function errorListener(err) { emitter.removeListener(name, resolver); reject(err); } function resolver() { if (typeof emitter.removeListener === 'function') { emitter.removeListener('error', errorListener); } resolve([].slice.call(arguments)); } eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); if (name !== 'error') { addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); } }); } function addErrorHandlerIfEventEmitter(emitter, handler, flags) { if (typeof emitter.on === 'function') { eventTargetAgnosticAddListener(emitter, 'error', handler, flags); } } function eventTargetAgnosticAddListener(emitter, name, listener, flags) { if (typeof emitter.on === 'function') { if (flags.once) { emitter.once(name, listener); } else { emitter.on(name, listener); } } else if (typeof emitter.addEventListener === 'function') { // EventTarget does not have `error` event semantics like Node // EventEmitters, we do not listen for `error` events here. emitter.addEventListener(name, function wrapListener(arg) { // IE does not have builtin `{ once: true }` support so we // have to do it manually. if (flags.once) { emitter.removeEventListener(name, wrapListener); } listener(arg); }); } else { throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); } } var eventsExports = events.exports; var EventEmitter$2 = /*@__PURE__*/getDefaultExportFromCjs(eventsExports); exports.errorCodes = void 0; (function (errorCodes) { errorCodes[errorCodes["timeout"] = 1] = "timeout"; errorCodes[errorCodes["transportClosed"] = 2] = "transportClosed"; errorCodes[errorCodes["clientDisconnected"] = 3] = "clientDisconnected"; errorCodes[errorCodes["clientClosed"] = 4] = "clientClosed"; errorCodes[errorCodes["clientConnectToken"] = 5] = "clientConnectToken"; errorCodes[errorCodes["clientRefreshToken"] = 6] = "clientRefreshToken"; errorCodes[errorCodes["subscriptionUnsubscribed"] = 7] = "subscriptionUnsubscribed"; errorCodes[errorCodes["subscriptionSubscribeToken"] = 8] = "subscriptionSubscribeToken"; errorCodes[errorCodes["subscriptionRefreshToken"] = 9] = "subscriptionRefreshToken"; errorCodes[errorCodes["transportWriteError"] = 10] = "transportWriteError"; errorCodes[errorCodes["connectionClosed"] = 11] = "connectionClosed"; errorCodes[errorCodes["badConfiguration"] = 12] = "badConfiguration"; })(exports.errorCodes || (exports.errorCodes = {})); exports.connectingCodes = void 0; (function (connectingCodes) { connectingCodes[connectingCodes["connectCalled"] = 0] = "connectCalled"; connectingCodes[connectingCodes["transportClosed"] = 1] = "transportClosed"; connectingCodes[connectingCodes["noPing"] = 2] = "noPing"; connectingCodes[connectingCodes["subscribeTimeout"] = 3] = "subscribeTimeout"; connectingCodes[connectingCodes["unsubscribeError"] = 4] = "unsubscribeError"; })(exports.connectingCodes || (exports.connectingCodes = {})); exports.disconnectedCodes = void 0; (function (disconnectedCodes) { disconnectedCodes[disconnectedCodes["disconnectCalled"] = 0] = "disconnectCalled"; disconnectedCodes[disconnectedCodes["unauthorized"] = 1] = "unauthorized"; disconnectedCodes[disconnectedCodes["badProtocol"] = 2] = "badProtocol"; disconnectedCodes[disconnectedCodes["messageSizeLimit"] = 3] = "messageSizeLimit"; })(exports.disconnectedCodes || (exports.disconnectedCodes = {})); exports.subscribingCodes = void 0; (function (subscribingCodes) { subscribingCodes[subscribingCodes["subscribeCalled"] = 0] = "subscribeCalled"; subscribingCodes[subscribingCodes["transportClosed"] = 1] = "transportClosed"; })(exports.subscribingCodes || (exports.subscribingCodes = {})); exports.unsubscribedCodes = void 0; (function (unsubscribedCodes) { unsubscribedCodes[unsubscribedCodes["unsubscribeCalled"] = 0] = "unsubscribeCalled"; unsubscribedCodes[unsubscribedCodes["unauthorized"] = 1] = "unauthorized"; unsubscribedCodes[unsubscribedCodes["clientClosed"] = 2] = "clientClosed"; })(exports.unsubscribedCodes || (exports.unsubscribedCodes = {})); /** State of client. */ exports.State = void 0; (function (State) { State["Disconnected"] = "disconnected"; State["Connecting"] = "connecting"; State["Connected"] = "connected"; })(exports.State || (exports.State = {})); /** State of Subscription */ exports.SubscriptionState = void 0; (function (SubscriptionState) { SubscriptionState["Unsubscribed"] = "unsubscribed"; SubscriptionState["Subscribing"] = "subscribing"; SubscriptionState["Subscribed"] = "subscribed"; })(exports.SubscriptionState || (exports.SubscriptionState = {})); /** @internal */ function startsWith(value, prefix) { return value.lastIndexOf(prefix, 0) === 0; } /** @internal */ function isFunction(value) { if (value === undefined || value === null) { return false; } return typeof value === 'function'; } /** @internal */ function log(level, args) { if (globalThis.console) { const logger = globalThis.console[level]; if (isFunction(logger)) { logger.apply(globalThis.console, args); } } } function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } /** @internal */ function backoff(step, min, max) { // Full jitter technique, see: // https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ if (step > 31) { step = 31; } const interval = randomInt(0, Math.min(max, min * Math.pow(2, step))); return Math.min(max, min + interval); } /** @internal */ function errorExists(data) { return 'error' in data && data.error !== null; } /** @internal */ function ttlMilliseconds(ttl) { // https://stackoverflow.com/questions/12633405/what-is-the-maximum-delay-for-setinterval return Math.min(ttl * 1000, 2147483647); } /** Subscription to a channel */ class Subscription extends EventEmitter$2 { /** Subscription constructor should not be used directly, create subscriptions using Client method. */ constructor(centrifuge, channel, options) { super(); this._resubscribeTimeout = null; this._refreshTimeout = null; this.channel = channel; this.state = exports.SubscriptionState.Unsubscribed; this._centrifuge = centrifuge; this._token = ''; this._getToken = null; this._data = null; this._getData = null; this._recover = false; this._offset = null; this._epoch = null; this._recoverable = false; this._positioned = false; this._joinLeave = false; this._minResubscribeDelay = 500; this._maxResubscribeDelay = 20000; this._resubscribeTimeout = null; this._resubscribeAttempts = 0; this._promises = {}; this._promiseId = 0; this._inflight = false; this._refreshTimeout = null; this._delta = ''; this._delta_negotiated = false; this._prevValue = null; this._unsubPromise = Promise.resolve(); this._setOptions(options); // @ts-ignore – we are hiding some symbols from public API autocompletion. if (this._centrifuge._debugEnabled) { this.on('state', (ctx) => { this._debug('subscription state', channel, ctx.oldState, '->', ctx.newState); }); this.on('error', (ctx) => { this._debug('subscription error', channel, ctx); }); } else { // Avoid unhandled exception in EventEmitter for non-set error handler. this.on('error', function () { Function.prototype(); }); } } /** ready returns a Promise which resolves upon subscription goes to Subscribed * state and rejects in case of subscription goes to Unsubscribed state. * Optional timeout can be passed.*/ ready(timeout) { if (this.state === exports.SubscriptionState.Unsubscribed) { return Promise.reject({ code: exports.errorCodes.subscriptionUnsubscribed, message: this.state }); } if (this.state === exports.SubscriptionState.Subscribed) { return Promise.resolve(); } return new Promise((res, rej) => { const ctx = { resolve: res, reject: rej }; if (timeout) { ctx.timeout = setTimeout(function () { rej({ code: exports.errorCodes.timeout, message: 'timeout' }); }, timeout); } this._promises[this._nextPromiseId()] = ctx; }); } /** subscribe to a channel.*/ subscribe() { if (this._isSubscribed()) { return; } this._resubscribeAttempts = 0; this._setSubscribing(exports.subscribingCodes.subscribeCalled, 'subscribe called'); } /** unsubscribe from a channel, keeping position state.*/ unsubscribe() { this._unsubPromise = this._setUnsubscribed(exports.unsubscribedCodes.unsubscribeCalled, 'unsubscribe called', true); } /** publish data to a channel.*/ publish(data) { const self = this; return this._methodCall().then(function () { return self._centrifuge.publish(self.channel, data); }); } /** get online presence for a channel.*/ presence() { const self = this; return this._methodCall().then(function () { return self._centrifuge.presence(self.channel); }); } /** presence stats for a channel (num clients and unique users).*/ presenceStats() { const self = this; return this._methodCall().then(function () { return self._centrifuge.presenceStats(self.channel); }); } /** history for a channel. By default it does not return publications (only current * StreamPosition data) – provide an explicit limit > 0 to load publications.*/ history(opts) { const self = this; return this._methodCall().then(function () { return self._centrifuge.history(self.channel, opts); }); } _methodCall() { if (this._isSubscribed()) { return Promise.resolve(); } if (this._isUnsubscribed()) { return Promise.reject({ code: exports.errorCodes.subscriptionUnsubscribed, message: this.state }); } return new Promise((res, rej) => { const timeout = setTimeout(function () { rej({ code: exports.errorCodes.timeout, message: 'timeout' }); // @ts-ignore – we are hiding some symbols from public API autocompletion. }, this._centrifuge._config.timeout); this._promises[this._nextPromiseId()] = { timeout: timeout, resolve: res, reject: rej }; }); } _nextPromiseId() { return ++this._promiseId; } _needRecover() { return this._recover === true; } _isUnsubscribed() { return this.state === exports.SubscriptionState.Unsubscribed; } _isSubscribing() { return this.state === exports.SubscriptionState.Subscribing; } _isSubscribed() { return this.state === exports.SubscriptionState.Subscribed; } _setState(newState) { if (this.state !== newState) { const oldState = this.state; this.state = newState; this.emit('state', { newState, oldState, channel: this.channel }); return true; } return false; } _usesToken() { return this._token !== '' || this._getToken !== null; } _clearSubscribingState() { this._resubscribeAttempts = 0; this._clearResubscribeTimeout(); } _clearSubscribedState() { this._clearRefreshTimeout(); } _setSubscribed(result) { if (!this._isSubscribing()) { return; } this._clearSubscribingState(); if (result.recoverable) { this._recover = true; this._offset = result.offset || 0; this._epoch = result.epoch || ''; } if (result.delta) { this._delta_negotiated = true; } else { this._delta_negotiated = false; } this._setState(exports.SubscriptionState.Subscribed); // @ts-ignore – we are hiding some methods from public API autocompletion. const ctx = this._centrifuge._getSubscribeContext(this.channel, result); this.emit('subscribed', ctx); this._resolvePromises(); const pubs = result.publications; if (pubs && pubs.length > 0) { for (const i in pubs) { if (!pubs.hasOwnProperty(i)) { continue; } this._handlePublication(pubs[i]); } } if (result.expires === true) { this._refreshTimeout = setTimeout(() => this._refresh(), ttlMilliseconds(result.ttl)); } } _setSubscribing(code, reason) { return __awaiter(this, void 0, void 0, function* () { if (this._isSubscribing()) { return; } if (this._isSubscribed()) { this._clearSubscribedState(); } if (this._setState(exports.SubscriptionState.Subscribing)) { this.emit('subscribing', { channel: this.channel, code: code, reason: reason }); } // @ts-ignore – for performance reasons only await _unsubPromise for emulution case where it's required. if (this._centrifuge._transport && this._centrifuge._transport.emulation()) { yield this._unsubPromise; } if (!this._isSubscribing()) { return; } this._subscribe(); }); } _subscribe() { this._debug('subscribing on', this.channel); if (!this._isTransportOpen()) { this._debug('delay subscribe on', this.channel, 'till connected'); return null; } if (this._inflight) { return null; } this._inflight = true; if (this._canSubscribeWithoutGettingToken()) { return this._subscribeWithoutToken(); } this._getSubscriptionToken() .then(token => this._handleTokenResponse(token)) .catch(e => this._handleTokenError(e)); return null; } _isTransportOpen() { // @ts-ignore – we are hiding some symbols from public API autocompletion. return this._centrifuge._transportIsOpen; } _canSubscribeWithoutGettingToken() { return !this._usesToken() || !!this._token; } _subscribeWithoutToken() { if (this._getData) { this._getDataAndSubscribe(this._token); return null; } else { return this._sendSubscribe(this._token); } } _getDataAndSubscribe(token) { if (!this._getData) { this._inflight = false; return; } this._getData({ channel: this.channel }) .then(data => { if (!this._isSubscribing()) { this._inflight = false; return; } this._data = data; this._sendSubscribe(token); }) .catch(e => this._handleGetDataError(e)); } _handleGetDataError(error) { if (!this._isSubscribing()) { this._inflight = false; return; } if (error instanceof UnauthorizedError) { this._inflight = false; this._failUnauthorized(); return; } this.emit('error', { type: 'subscribeData', channel: this.channel, error: { code: exports.errorCodes.badConfiguration, message: (error === null || error === void 0 ? void 0 : error.toString()) || '' } }); this._inflight = false; this._scheduleResubscribe(); } _handleTokenResponse(token) { if (!this._isSubscribing()) { this._inflight = false; return; } if (!token) { this._inflight = false; this._failUnauthorized(); return; } this._token = token; if (this._getData) { this._getDataAndSubscribe(token); } else { this._sendSubscribe(token); } } _handleTokenError(error) { if (!this._isSubscribing()) { this._inflight = false; return; } if (error instanceof UnauthorizedError) { this._inflight = false; this._failUnauthorized(); return; } this.emit('error', { type: 'subscribeToken', channel: this.channel, error: { code: exports.errorCodes.subscriptionSubscribeToken, message: (error === null || error === void 0 ? void 0 : error.toString()) || '' } }); this._inflight = false; this._scheduleResubscribe(); } _sendSubscribe(token) { if (!this._isTransportOpen()) { this._inflight = false; return null; } const cmd = this._buildSubscribeCommand(token); // @ts-ignore – we are hiding some symbols from public API autocompletion. this._centrifuge._call(cmd).then(resolveCtx => { this._inflight = false; // @ts-ignore - improve later. const result = resolveCtx.reply.subscribe; this._handleSubscribeResponse(result); // @ts-ignore - improve later. if (resolveCtx.next) { // @ts-ignore - improve later. resolveCtx.next(); } }, rejectCtx => { this._inflight = false; this._handleSubscribeError(rejectCtx.error); if (rejectCtx.next) { rejectCtx.next(); } }); return cmd; } _buildSubscribeCommand(token) { const req = { channel: this.channel }; if (token) req.token = token; if (this._data) req.data = this._data; if (this._positioned) req.positioned = true; if (this._recoverable) req.recoverable = true; if (this._joinLeave) req.join_leave = true; if (this._needRecover()) { req.recover = true; const offset = this._getOffset(); if (offset) req.offset = offset; const epoch = this._getEpoch(); if (epoch) req.epoch = epoch; } if (this._delta) req.delta = this._delta; return { subscribe: req }; } _debug(...args) { // @ts-ignore – we are hiding some symbols from public API autocompletion. this._centrifuge._debug(...args); } _handleSubscribeError(error) { if (!this._isSubscribing()) { return; } if (error.code === exports.errorCodes.timeout) { // @ts-ignore – we are hiding some symbols from public API autocompletion. this._centrifuge._disconnect(exports.connectingCodes.subscribeTimeout, 'subscribe timeout', true); return; } this._subscribeError(error); } _handleSubscribeResponse(result) { if (!this._isSubscribing()) { return; } this._setSubscribed(result); } _setUnsubscribed(code, reason, sendUnsubscribe) { if (this._isUnsubscribed()) { return Promise.resolve(); } let promise = Promise.resolve(); if (this._isSubscribed()) { if (sendUnsubscribe) { // @ts-ignore – we are hiding some methods from public API autocompletion. promise = this._centrifuge._unsubscribe(this); } this._clearSubscribedState(); } else if (this._isSubscribing()) { if (this._inflight && sendUnsubscribe) { // @ts-ignore – we are hiding some methods from public API autocompletion. promise = this._centrifuge._unsubscribe(this); } this._clearSubscribingState(); } this._inflight = false; if (this._setState(exports.SubscriptionState.Unsubscribed)) { this.emit('unsubscribed', { channel: this.channel, code: code, reason: reason }); } this._rejectPromises({ code: exports.errorCodes.subscriptionUnsubscribed, message: this.state }); return promise; } _handlePublication(pub) { if (this._delta && this._delta_negotiated) { // @ts-ignore – we are hiding some methods from public API autocompletion. const { newData, newPrevValue } = this._centrifuge._codec.applyDeltaIfNeeded(pub, this._prevValue); pub.data = newData; this._prevValue = newPrevValue; } // @ts-ignore – we are hiding some methods from public API autocompletion. const ctx = this._centrifuge._getPublicationContext(this.channel, pub); this.emit('publication', ctx); if (pub.offset) { this._offset = pub.offset; } } _handleJoin(join) { // @ts-ignore – we are hiding some methods from public API autocompletion. const info = this._centrifuge._getJoinLeaveContext(join.info); this.emit('join', { channel: this.channel, info: info }); } _handleLeave(leave) { // @ts-ignore – we are hiding some methods from public API autocompletion. const info = this._centrifuge._getJoinLeaveContext(leave.info); this.emit('leave', { channel: this.channel, info: info }); } _resolvePromises() { for (const id in this._promises) { if (!this._promises.hasOwnProperty(id)) { continue; } if (this._promises[id].timeout) { clearTimeout(this._promises[id].timeout); } this._promises[id].resolve(); delete this._promises[id]; } } _rejectPromises(err) { for (const id in this._promises) { if (!this._promises.hasOwnProperty(id)) { continue; } if (this._promises[id].timeout) { clearTimeout(this._promises[id].timeout); } this._promises[id].reject(err); delete this._promises[id]; } } _scheduleResubscribe() { if (!this._isSubscribing()) { this._debug('not in subscribing state, skip resubscribe scheduling', this.channel); return; } const self = this; const delay = this._getResubscribeDelay(); this._resubscribeTimeout = setTimeout(function () { if (self._isSubscribing()) { self._subscribe(); } }, delay); this._debug('resubscribe scheduled after ' + delay, this.channel); } _subscribeError(err) { if (!this._isSubscribing()) { return; } if (err.code < 100 || err.code === 109 || err.temporary === true) { if (err.code === 109) { // Token expired error. this._token = ''; } const errContext = { channel: this.channel, type: 'subscribe', error: err }; if (this._centrifuge.state === exports.State.Connected) { this.emit('error', errContext); } this._scheduleResubscribe(); } else { this._setUnsubscribed(err.code, err.message, false); } } _getResubscribeDelay() { const delay = backoff(this._resubscribeAttempts, this._minResubscribeDelay, this._maxResubscribeDelay); this._resubscribeAttempts++; return delay; } _setOptions(options) { if (!options) { return; } if (options.since) { this._offset = options.since.offset; this._epoch = options.since.epoch; this._recover = true; } if (options.data) { this._data = options.data; } if (options.getData) { this._getData = options.getData; } if (options.minResubscribeDelay !== undefined) { this._minResubscribeDelay = options.minResubscribeDelay; } if (options.maxResubscribeDelay !== undefined) { this._maxResubscribeDelay = options.maxResubscribeDelay; } if (options.token) { this._token = options.token; } if (options.getToken) { this._getToken = options.getToken; } if (options.positioned === true) { this._positioned = true; } if (options.recoverable === true) { this._recoverable = true; } if (options.joinLeave === true) { this._joinLeave = true; } if (options.delta) { if (options.delta !== 'fossil') { throw new Error('unsupported delta format'); } this._delta = options.delta; } } _getOffset() { const offset = this._offset; if (offset !== null) { return offset; } return 0; } _getEpoch() { const epoch = this._epoch; if (epoch !== null) { return epoch; } return ''; } _clearRefreshTimeout() { if (this._refreshTimeout !== null) { clearTimeout(this._refreshTimeout); this._refreshTimeout = null; } } _clearResubscribeTimeout() { if (this._resubscribeTimeout !== null) { clearTimeout(this._resubscribeTimeout); this._resubscribeTimeout = null; } } _getSubscriptionToken() { this._debug('get subscription token for channel', this.channel); const ctx = { channel: this.channel }; const getToken = this._getToken; if (getToken === null) { this.emit('error', { type: 'configuration', channel: this.channel, error: { code: exports.errorCodes.badConfiguration, message: 'provide a function to get channel subscription token' } }); throw new UnauthorizedError(''); } return getToken(ctx); } _refresh() { this._clearRefreshTimeout(); const self = this; this._getSubscriptionToken().then(function (token) { if (!self._isSubscribed()) { return; } if (!token) { self._failUnauthorized(); return; } self._token = token; const req = { channel: self.channel, token: token }; const msg = { 'sub_refresh': req }; // @ts-ignore – we are hiding some symbols from public API autocompletion. self._centrifuge._call(msg).then(resolveCtx => { // @ts-ignore - improve later. const result = resolveCtx.reply.sub_refresh; self._refreshResponse(result); // @ts-ignore - improve later. if (resolveCtx.next) { // @ts-ignore - improve later. resolveCtx.next(); } }, rejectCtx => { self._refreshError(rejectCtx.error); if (rejectCtx.next) { rejectCtx.next(); } }); }).catch(function (e) { if (e instanceof UnauthorizedError) { self._failUnauthorized(); return; } self.emit('error', { type: 'refreshToken', channel: self.channel, error: { code: exports.errorCodes.subscriptionRefreshToken, message: e !== undefined ? e.toString() : '' } }); self._refreshTimeout = setTimeout(() => self._refresh(), self._getRefreshRetryDelay()); }); } _refreshResponse(result) { if (!this._isSubscribed()) { return; } this._debug('subscription token refreshed, channel', this.channel); this._clearRefreshTimeout(); if (result.expires === true) { this._refreshTimeout = setTimeout(() => this._refresh(), ttlMilliseconds(result.ttl)); } } _refreshError(err) { if (!this._isSubscribed()) { return; } if (err.code < 100 || err.temporary === true) { this.emit('error', { type: 'refresh', channel: this.channel, error: err }); this._refreshTimeout = setTimeout(() => this._refresh(), this._getRefreshRetryDelay()); } else { this._setUnsubscribed(err.code, err.message, true); } } _getRefreshRetryDelay() { return backoff(0, 10000, 20000); } _failUnauthorized() { this._setUnsubscribed(exports.unsubscribedCodes.unauthorized, 'unauthorized', true); } } /** @internal */ class SockjsTransport { constructor(endpoint, options) { this.endpoint = endpoint; this.options = options; this._transport = null; } name() { return 'sockjs'; } subName() { return 'sockjs-' + this._transport.transport; } emulation() { return false; } supported() { return this.options.sockjs !== null; } initialize(_protocol, callbacks) { this._transport = new this.options.sockjs(this.endpoint, null, this.options.sockjsOptions); this._transport.onopen = () => { callbacks.onOpen(); }; this._transport.onerror = e => { callbacks.onError(e); }; this._transport.onclose = closeEvent => { callbacks.onClose(closeEvent); }; this._transport.onmessage = event => { callbacks.onMessage(event.data); }; } close() { this._transport.close(); } send(data) { this._transport.send(data); } } /** @internal */ class WebsocketTransport { constructor(endpoint, options) { this.endpoint = endpoint; this.options = options; this._transport = null; } name() { return 'websocket'; } subName() { return 'websocket'; } emulation() { return false; } supported() { return this.options.websocket !== undefined && this.options.websocket !== null; } initialize(protocol, callbacks) { let subProtocol = ''; if (protocol === 'protobuf') { subProtocol = 'centrifuge-protobuf'; } if (subProtocol !== '') { this._transport = new this.options.websocket(this.endpoint, subProtocol); } else { this._transport = new this.options.websocket(this.endpoint); } if (protocol === 'protobuf') { this._transport.binaryType = 'arraybuffer'; } this._transport.onopen = () => { callbacks.onOpen(); }; this._transport.onerror = e => { callbacks.onError(e); }; this._transport.onclose = closeEvent => { callbacks.onClose(closeEvent); }; this._transport.onmessage = event => { callbacks.onMessage(event.data); }; } close() { this._transport.close(); } send(data) { this._transport.send(data); } } /** @internal */ class HttpStreamTransport { constructor(endpoint, options) { this.endpoint = endpoint; this.options = options; this._abortController = null; this._utf8decoder = new TextDecoder(); this._protocol = 'json'; } name() { return 'http_stream'; } subName() { return 'http_stream'; } emulation() { return true; } _handleErrors(response) { if (!response.ok) throw new Error(response.status); return response; } _fetchEventTarget(self, endpoint, options) { const eventTarget = new EventTarget(); // fetch with connection timeout maybe? https://github.com/github/fetch/issues/175 const fetchFunc = self.options.fetch; fetchFunc(endpoint, options) .then(self._handleErrors) .then(response => { eventTarget.dispatchEvent(new Event('open')); let jsonStreamBuf = ''; let jsonStreamPos = 0; let protoStreamBuf = new Uint8Array(); const reader = response.body.getReader(); return new self.options.readableStream({ start(controller) { function pump() { return reader.read().then(({ done, value }) => { // When no more data needs to be consumed, close the stream if (done) { eventTarget.dispatchEvent(new Event('close')); controller.close(); return; } try { if (self._protocol === 'json') { jsonStreamBuf += self._utf8decoder.decode(value); while (jsonStreamPos < jsonStreamBuf.length) { if (jsonStreamBuf[jsonStreamPos] === '\n') { const line = jsonStreamBuf.substring(0, jsonStreamPos); eventTarget.dispatchEvent(new MessageEvent('message', { data: line })); jsonStreamBuf = jsonStreamBuf.substring(jsonStreamPos + 1); jsonStreamPos = 0; } else { ++jsonStreamPos; } } } else { const mergedArray = new Uint8Array(protoStreamBuf.length + value.length); mergedArray.set(protoStreamBuf); mergedArray.set(value, protoStreamBuf.length); protoStreamBuf = mergedArray; while (true) {