pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
450 lines (449 loc) • 19.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriptionSet = void 0;
const subscription_base_1 = require("./subscription-base");
const Subscription = __importStar(require("../core/types/api/subscription"));
/**
* {@link SubscriptionSet} state object.
*
* State object used across multiple {@link SubscriptionSet} object clones.
*
* @internal
*/
class SubscriptionSetState extends subscription_base_1.SubscriptionBaseState {
/**
* Create a subscription state object.
*
* @param parameters - State configuration options
* @param parameters.client - PubNub client which will work with a subscription object.
* @param parameters.subscriptions - List of subscriptions managed by set.
* @param [parameters.options] - Subscription behavior options.
*/
constructor(parameters) {
const subscriptionInput = new Subscription.SubscriptionInput({});
parameters.subscriptions.forEach((subscription) => subscriptionInput.add(subscription.state.subscriptionInput));
super(parameters.client, subscriptionInput, parameters.options, parameters.client.subscriptionTimetoken);
this.subscriptions = parameters.subscriptions;
}
/**
* Add a single subscription object to the set.
*
* @param subscription - Another entity's subscription object, which should be added.
*/
addSubscription(subscription) {
if (this.subscriptions.includes(subscription))
return;
subscription.state.addParentState(this);
this.subscriptions.push(subscription);
// Update subscription input.
this.subscriptionInput.add(subscription.state.subscriptionInput);
}
/**
* Remove a single subscription object from the set.
*
* @param subscription - Another entity's subscription object, which should be removed.
* @param clone - Whether a target subscription is a clone.
*/
removeSubscription(subscription, clone) {
const index = this.subscriptions.indexOf(subscription);
if (index === -1)
return;
this.subscriptions.splice(index, 1);
if (!clone)
subscription.state.removeParentState(this);
// Update subscription input.
this.subscriptionInput.remove(subscription.state.subscriptionInput);
}
/**
* Remove any registered subscription object.
*/
removeAllSubscriptions() {
this.subscriptions.forEach((subscription) => subscription.state.removeParentState(this));
this.subscriptions.splice(0, this.subscriptions.length);
this.subscriptionInput.removeAll();
}
}
/**
* Multiple entities subscription set object which can be used to receive and handle real-time
* updates.
*
* Subscription set object represents a collection of per-entity subscription objects and allows
* processing them at once for subscription loop and events handling.
*/
class SubscriptionSet extends subscription_base_1.SubscriptionBase {
/**
* Create entities' subscription set object.
*
* Subscription set object represents a collection of per-entity subscription objects and allows
* processing them at once for subscription loop and events handling.
*
* @param parameters - Subscription set object configuration.
*
* @returns Ready to use entities' subscription set object.
*
* @internal
*/
constructor(parameters) {
let state;
if ('client' in parameters) {
let subscriptions = [];
if (!parameters.subscriptions && parameters.entities) {
parameters.entities.forEach((entity) => subscriptions.push(entity.subscription(parameters.options)));
}
else if (parameters.subscriptions)
subscriptions = parameters.subscriptions;
state = new SubscriptionSetState({ client: parameters.client, subscriptions, options: parameters.options });
subscriptions.forEach((subscription) => subscription.state.addParentState(state));
state.client.logger.debug('SubscriptionSet', () => ({
messageType: 'object',
details: 'Create subscription set with parameters:',
message: Object.assign({ subscriptions: state.subscriptions }, (parameters.options ? parameters.options : {})),
}));
}
else {
state = parameters.state;
state.client.logger.debug('SubscriptionSet', 'Create subscription set clone');
}
super(state, 'SubscriptionSet');
this.state.storeClone(this.id, this);
// Update a parent sets list for original set subscriptions.
state.subscriptions.forEach((subscription) => subscription.addParentSet(this));
}
/**
* Get a {@link SubscriptionSet} object state.
*
* @returns: {@link SubscriptionSet} object state.
*
* @internal
*/
get state() {
return super.state;
}
/**
* Get a list of entities' subscription objects registered in a subscription set.
*
* @returns Entities' subscription objects list.
*/
get subscriptions() {
return this.state.subscriptions.slice(0);
}
// --------------------------------------------------------
// -------------------- Event handler ---------------------
// --------------------------------------------------------
// region Event handler
/**
* Dispatch received a real-time update.
*
* @param cursor - A time cursor for the next portion of events.
* @param event - A real-time event from multiplexed subscription.
*
* @return `true` if receiver has consumed event.
*
* @internal
*/
handleEvent(cursor, event) {
var _a;
// Check whether an event is not designated for this subscription set.
if (!this.state.subscriptionInput.contains((_a = event.data.subscription) !== null && _a !== void 0 ? _a : event.data.channel))
return;
// Check whether `event` can be processed or not.
if (!this.state._isSubscribed) {
this.state.client.logger.trace(this.subscriptionType, `Subscription set ${this.id} is not subscribed. Ignoring event.`);
return;
}
super.handleEvent(cursor, event);
if (this.state.subscriptions.length > 0) {
this.state.client.logger.trace(this.subscriptionType, `Notify ${this.id} subscription set subscriptions (count: ${this.state.subscriptions.length}) about received event.`);
}
this.state.subscriptions.forEach((subscription) => subscription.handleEvent(cursor, event));
}
// endregion
/**
User-provided subscription input associated with this {@link SubscriptionSet} object.
*
* @param forUnsubscribe - Whether list subscription input created for unsubscription (means entity should be free).
*
* @returns Subscription input object.
*
* @internal
*/
subscriptionInput(forUnsubscribe = false) {
let subscriptionInput = this.state.subscriptionInput;
this.state.subscriptions.forEach((subscription) => {
if (forUnsubscribe && subscription.state.entity.subscriptionsCount > 0)
subscriptionInput = subscriptionInput.without(subscription.state.subscriptionInput);
});
return subscriptionInput;
}
/**
* Make a bare copy of the {@link SubscriptionSet} object.
*
* Copy won't have any type-specific listeners or added listener objects but will have the same internal state as
* the source object.
*
* @returns Bare copy of a {@link SubscriptionSet} object.
*/
cloneEmpty() {
return new SubscriptionSet({ state: this.state });
}
/**
* Graceful {@link SubscriptionSet} destruction.
*
* This is an instance destructor, which will properly deinitialize it:
* - remove and unset all listeners,
* - try to unsubscribe (if subscribed and there are no more instances interested in the same data stream).
*
* **Note:** Disposed instance won't call the dispatcher to deliver updates to the listeners.
*/
dispose() {
const isLastClone = this.state.isLastClone;
this.state.subscriptions.forEach((subscription) => {
subscription.removeParentSet(this);
if (isLastClone)
subscription.state.removeParentState(this.state);
});
super.dispose();
}
/**
* Invalidate {@link SubscriptionSet} object.
*
* Clean up resources used by a subscription object. All {@link SubscriptionObject subscription} objects will be
* removed.
*
* **Important:** This method is used only when a global subscription set is used (backward compatibility).
*
* **Note:** An invalidated instance won't call the dispatcher to deliver updates to the listeners.
*
* @param forDestroy - Whether subscription object invalidated as part of PubNub client destroy process or not.
*
* @internal
*/
invalidate(forDestroy = false) {
const subscriptions = forDestroy ? this.state.subscriptions.slice(0) : this.state.subscriptions;
subscriptions.forEach((subscription) => {
if (forDestroy) {
subscription.state.entity.decreaseSubscriptionCount(this.state.id);
subscription.removeParentSet(this);
}
subscription.invalidate(forDestroy);
});
if (forDestroy)
this.state.removeAllSubscriptions();
super.invalidate();
}
/**
* Add an entity's subscription to the subscription set.
*
* **Important:** Changes will be effective immediately if {@link SubscriptionSet} already subscribed.
*
* @param subscription - Another entity's subscription object, which should be added.
*/
addSubscription(subscription) {
this.addSubscriptions([subscription]);
}
/**
* Add an entity's subscriptions to the subscription set.
*
* **Important:** Changes will be effective immediately if {@link SubscriptionSet} already subscribed.
*
* @param subscriptions - List of entity's subscription objects, which should be added.
*/
addSubscriptions(subscriptions) {
const inactiveSubscriptions = [];
const activeSubscriptions = [];
this.state.client.logger.debug(this.subscriptionType, () => {
const ignoredSubscriptions = [];
const subscriptionsToAdd = [];
subscriptions.forEach((subscription) => {
if (!this.state.subscriptions.includes(subscription))
subscriptionsToAdd.push(subscription);
else
ignoredSubscriptions.push(subscription);
});
return {
messageType: 'object',
details: `Add subscriptions to ${this.id} (subscriptions count: ${this.state.subscriptions.length + subscriptionsToAdd.length}):`,
message: { addedSubscriptions: subscriptionsToAdd, ignoredSubscriptions },
};
});
subscriptions
.filter((subscription) => !this.state.subscriptions.includes(subscription))
.forEach((subscription) => {
if (subscription.state.isSubscribed)
activeSubscriptions.push(subscription);
else
inactiveSubscriptions.push(subscription);
subscription.addParentSet(this);
this.state.addSubscription(subscription);
});
// Check whether there are any subscriptions for which the subscription loop should be changed or not.
if ((activeSubscriptions.length === 0 && inactiveSubscriptions.length === 0) || !this.state.isSubscribed)
return;
activeSubscriptions.forEach(({ state }) => state.entity.increaseSubscriptionCount(this.state.id));
if (inactiveSubscriptions.length > 0)
this.updateSubscription({ subscribing: true, subscriptions: inactiveSubscriptions });
}
/**
* Remove an entity's subscription object from the set.
*
* **Important:** Changes will be effective immediately if {@link SubscriptionSet} already subscribed.
*
* @param subscription - Another entity's subscription object, which should be removed.
*/
removeSubscription(subscription) {
this.removeSubscriptions([subscription]);
}
/**
* Remove an entity's subscription objects from the set.
*
* **Important:** Changes will be effective immediately if {@link SubscriptionSet} already subscribed.
*
* @param subscriptions - List entity's subscription objects, which should be removed.
*/
removeSubscriptions(subscriptions) {
const activeSubscriptions = [];
this.state.client.logger.debug(this.subscriptionType, () => {
const ignoredSubscriptions = [];
const subscriptionsToRemove = [];
subscriptions.forEach((subscription) => {
if (this.state.subscriptions.includes(subscription))
subscriptionsToRemove.push(subscription);
else
ignoredSubscriptions.push(subscription);
});
return {
messageType: 'object',
details: `Remove subscriptions from ${this.id} (subscriptions count: ${this.state.subscriptions.length}):`,
message: { removedSubscriptions: subscriptionsToRemove, ignoredSubscriptions },
};
});
subscriptions
.filter((subscription) => this.state.subscriptions.includes(subscription))
.forEach((subscription) => {
if (subscription.state.isSubscribed)
activeSubscriptions.push(subscription);
subscription.removeParentSet(this);
this.state.removeSubscription(subscription, subscription.parentSetsCount > 1);
});
// Check whether there are any subscriptions for which the subscription loop should be changed or not.
if (activeSubscriptions.length === 0 || !this.state.isSubscribed)
return;
this.updateSubscription({ subscribing: false, subscriptions: activeSubscriptions });
}
/**
* Merge with another {@link SubscriptionSet} object.
*
* **Important:** Changes will be effective immediately if {@link SubscriptionSet} already subscribed.
*
* @param subscriptionSet - Other entities' subscription set, which should be joined.
*/
addSubscriptionSet(subscriptionSet) {
this.addSubscriptions(subscriptionSet.subscriptions);
}
/**
* Subtract another {@link SubscriptionSet} object.
*
* **Important:** Changes will be effective immediately if {@link SubscriptionSet} already subscribed.
*
* @param subscriptionSet - Other entities' subscription set, which should be subtracted.
*/
removeSubscriptionSet(subscriptionSet) {
this.removeSubscriptions(subscriptionSet.subscriptions);
}
/**
* Register {@link SubscriptionSet} object for real-time events' retrieval.
*
* @param parameters - Object registration parameters.
* @param [parameters.cursor] - Subscription real-time events catch-up cursor.
* @param [parameters.subscriptions] - List of subscription objects which should be registered (in case of partial
* modification).
*
* @internal
*/
register(parameters) {
var _a;
const subscriptions = ((_a = parameters.subscriptions) !== null && _a !== void 0 ? _a : this.state.subscriptions);
subscriptions.forEach(({ state }) => state.entity.increaseSubscriptionCount(this.state.id));
this.state.client.logger.trace(this.subscriptionType, () => ({
messageType: 'text',
message: `Register subscription for real-time events: ${this}`,
}));
this.state.client.registerEventHandleCapable(this, parameters.cursor, subscriptions);
}
/**
* Unregister {@link SubscriptionSet} object from real-time events' retrieval.
*
* @param [subscriptions] - List of subscription objects which should be unregistered (in case of partial
* modification).
*
* @internal
*/
unregister(subscriptions) {
const activeSubscriptions = (subscriptions !== null && subscriptions !== void 0 ? subscriptions : this.state.subscriptions);
activeSubscriptions.forEach(({ state }) => state.entity.decreaseSubscriptionCount(this.state.id));
this.state.client.logger.trace(this.subscriptionType, () => {
if (!subscriptions) {
return {
messageType: 'text',
message: `Unregister subscription from real-time events: ${this}`,
};
}
else {
return {
messageType: 'object',
message: {
subscription: this,
subscriptions,
},
details: 'Unregister subscriptions of subscription set from real-time events:',
};
}
});
this.state.client.unregisterEventHandleCapable(this, activeSubscriptions);
}
/**
* Stringify subscription object.
*
* @returns Serialized subscription object.
*/
toString() {
const state = this.state;
return `${this.subscriptionType} { id: ${this.id}, stateId: ${state.id}, clonesCount: ${Object.keys(this.state.clones).length}, isSubscribed: ${state.isSubscribed}, subscriptions: [${state.subscriptions
.map((sub) => sub.toString())
.join(', ')}] }`;
}
}
exports.SubscriptionSet = SubscriptionSet;