Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
---
changelog:
- date: 2025-03-10
version: v9.0.0
changes:
- type: feature
text: "BREAKING CHANGES: `SubscriptionSet` will subscribe / unsubscribe added / removed `Subscription` or `SubscriptionSet` objects if the set itself already subscribed."
- type: bug
text: "Fix issue because of which throttle didn't consider difference in client settings (throttled only by user ID and subscribe key, which is not enough)."
- type: bug
text: "With the fix, smart heartbeat as feature has been added to the SDK, and it is disabled by default."
- date: 2025-03-06
version: v8.10.0
changes:
Expand Down Expand Up @@ -1160,7 +1169,7 @@ supported-platforms:
- 'Ubuntu 14.04 and up'
- 'Windows 7 and up'
version: 'Pubnub Javascript for Node'
version: '8.10.0'
version: '9.0.0'
sdks:
- full-name: PubNub Javascript SDK
short-name: Javascript
Expand All @@ -1176,7 +1185,7 @@ sdks:
- distribution-type: source
distribution-repository: GitHub release
package-name: pubnub.js
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.10.0.zip
location: https://github.com/pubnub/javascript/archive/refs/tags/v9.0.0.zip
requires:
- name: 'agentkeepalive'
min-version: '3.5.2'
Expand Down Expand Up @@ -1847,7 +1856,7 @@ sdks:
- distribution-type: library
distribution-repository: GitHub release
package-name: pubnub.js
location: https://github.com/pubnub/javascript/releases/download/v8.10.0/pubnub.8.10.0.js
location: https://github.com/pubnub/javascript/releases/download/v9.0.0/pubnub.9.0.0.js
requires:
- name: 'agentkeepalive'
min-version: '3.5.2'
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## v9.0.0
March 10 2025

#### Added
- BREAKING CHANGES: `SubscriptionSet` will subscribe / unsubscribe added / removed `Subscription` or `SubscriptionSet` objects if the set itself already subscribed.

#### Fixed
- Fix issue because of which throttle didn't consider difference in client settings (throttled only by user ID and subscribe key, which is not enough).
- With the fix, smart heartbeat as feature has been added to the SDK, and it is disabled by default.

## v8.10.0
March 06 2025

Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# PubNub JavaScript SDK (V4)

[![Build Status](https://travis-ci.com/pubnub/javascript.svg?branch=master)](https://travis-ci.com/pubnub/javascript)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/2859917905c549b8bfa27630ff276fce)](https://www.codacy.com/app/PubNub/javascript?utm_source=github.com&utm_medium=referral&utm_content=pubnub/javascript&utm_campaign=Badge_Grade)
[![npm](https://img.shields.io/npm/v/pubnub.svg)]()
[![Bower](https://img.shields.io/bower/v/pubnub.svg)]()
Expand Down Expand Up @@ -28,8 +27,8 @@ Watch [Getting Started with PubNub JS SDK](https://app.dashcam.io/replay/64ee0d2
npm install pubnub
```
* or download one of our builds from our CDN:
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.10.0.js
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.10.0.min.js
* https://cdn.pubnub.com/sdk/javascript/pubnub.9.0.0.js
* https://cdn.pubnub.com/sdk/javascript/pubnub.9.0.0.min.js

2. Configure your keys:

Expand Down
86 changes: 79 additions & 7 deletions dist/web/pubnub.js
Original file line number Diff line number Diff line change
Expand Up @@ -3438,7 +3438,7 @@
/**
* Whether heartbeat should be postponed on successful subscribe response or not.
*/
const USE_SMART_HEARTBEAT = true;
const USE_SMART_HEARTBEAT = false;
/**
* Whether PubNub client should try to utilize existing TCP connection for new requests or not.
*/
Expand Down Expand Up @@ -3818,7 +3818,7 @@
return base.PubNubFile;
},
get version() {
return '8.10.0';
return '9.0.0';
},
getVersion() {
return this.version;
Expand Down Expand Up @@ -4944,8 +4944,9 @@
* unsubscribe or not.
*/
reconnect(forUnsubscribe = false) {
this.startSubscribeLoop();
if (!forUnsubscribe && this.configuration.useSmartHeartbeat)
this.startSubscribeLoop(forUnsubscribe);
// Starting heartbeat loop for provided channels and groups.
if (!forUnsubscribe && !this.configuration.useSmartHeartbeat)
this.startHeartbeatTimer();
}
/**
Expand Down Expand Up @@ -5053,7 +5054,15 @@
channelGroups: this.subscribedChannelGroups,
}, isOffline);
}
startSubscribeLoop() {
/**
* Start next subscription loop.
*
* @param restartOnUnsubscribe - Whether restarting subscription loop as part of channels list change on
* unsubscribe or not.
*
* @internal
*/
startSubscribeLoop(restartOnUnsubscribe = false) {
this.stopSubscribeLoop();
const channelGroups = [...Object.keys(this.channelGroups)];
const channels = [...Object.keys(this.channels)];
Expand All @@ -5073,6 +5082,8 @@
}, (status, result) => {
this.processSubscribeResponse(status, result);
});
if (!restartOnUnsubscribe && this.configuration.useSmartHeartbeat)
this.startHeartbeatTimer();
}
stopSubscribeLoop() {
if (this._subscribeAbort) {
Expand Down Expand Up @@ -5224,7 +5235,9 @@
const heartbeatInterval = this.configuration.getHeartbeatInterval();
if (!heartbeatInterval || heartbeatInterval === 0)
return;
this.sendHeartbeat();
// Sending immediate heartbeat only if not working as smart heartbeat.
if (!this.configuration.useSmartHeartbeat)
this.sendHeartbeat();
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), heartbeatInterval * 1000);
}
/**
Expand Down Expand Up @@ -10084,13 +10097,17 @@
subscribe(subscribeParameters) {
const timetoken = subscribeParameters === null || subscribeParameters === void 0 ? void 0 : subscribeParameters.timetoken;
this.pubnub.registerSubscribeCapable(this);
this.subscribedAutomatically = false;
this.subscribed = true;
this.pubnub.subscribe(Object.assign({ channels: this.channelNames, channelGroups: this.groupNames }, (timetoken !== null && timetoken !== '' && { timetoken: timetoken })));
}
/**
* Stop real-time events processing.
*/
unsubscribe() {
this.pubnub.unregisterSubscribeCapable(this);
this.subscribedAutomatically = false;
this.subscribed = false;
const { channels, channelGroups } = this.pubnub.getSubscribeCapableEntities();
// Identify channels and groups from which PubNub client can safely unsubscribe.
const filteredChannelGroups = this.groupNames.filter((cg) => !channelGroups.includes(cg));
Expand Down Expand Up @@ -10251,6 +10268,19 @@
* @internal
*/
this.subscriptionList = [];
/**
* Whether subscribed ({@link SubscribeCapable#subscribe}) automatically during subscription
* object / sets manipulation or not.
*
* @internal
*/
this.subscribedAutomatically = false;
/**
* Whether subscribable object subscribed ({@link SubscribeCapable#subscribe}) or not.
*
* @internal
*/
this.subscribed = false;
this.options = subscriptionOptions;
this.eventEmitter = eventEmitter;
this.pubnub = pubnub;
Expand Down Expand Up @@ -10280,6 +10310,13 @@
this.channelNames = [...this.channelNames, ...subscription.channels];
this.groupNames = [...this.groupNames, ...subscription.channelGroups];
this.eventEmitter.addListener(this.listener, subscription.channels, subscription.channelGroups);
// Subscribe subscription object if subscription set already subscribed.
// @ts-expect-error: Required access of protected field.
if (this.subscribed && !subscription.subscribed) {
subscription.subscribe();
// @ts-expect-error: Required modification of protected field.
subscription.subscribedAutomatically = true; // should be placed after .subscribe() call.
}
}
/**
* Remove entity's subscription object from the set.
Expand All @@ -10296,6 +10333,9 @@
this.groupNames = this.groupNames.filter((cg) => !groupsToRemove.includes(cg));
this.subscriptionList = this.subscriptionList.filter((s) => s !== subscription);
this.eventEmitter.removeListener(this.listener, channelsToRemove, groupsToRemove);
// @ts-expect-error: Required access of protected field.
if (subscription.subscribedAutomatically)
subscription.unsubscribe();
}
/**
* Merge with other subscription set object.
Expand All @@ -10310,6 +10350,11 @@
this.channelNames = [...this.channelNames, ...subscriptionSet.channels];
this.groupNames = [...this.groupNames, ...subscriptionSet.channelGroups];
this.eventEmitter.addListener(this.listener, subscriptionSet.channels, subscriptionSet.channelGroups);
// Subscribe subscription object if subscription set already subscribed.
if (this.subscribed && !subscriptionSet.subscribed) {
subscriptionSet.subscribe();
subscriptionSet.subscribedAutomatically = true; // should be placed after .subscribe() call.
}
}
/**
* Subtract other subscription set object.
Expand All @@ -10326,6 +10371,8 @@
this.groupNames = this.groupNames.filter((cg) => !groupsToRemove.includes(cg));
this.subscriptionList = this.subscriptionList.filter((s) => !subscriptionSet.subscriptions.includes(s));
this.eventEmitter.removeListener(this.listener, channelsToRemove, groupsToRemove);
if (subscriptionSet.subscribedAutomatically)
subscriptionSet.unsubscribe();
}
/**
* Get list of entities' subscription objects registered in subscription set.
Expand Down Expand Up @@ -10381,6 +10428,19 @@
* @internal
*/
this.groupNames = [];
/**
* Whether subscribed ({@link SubscribeCapable#subscribe}) automatically during subscription
* object / sets manipulation or not.
*
* @internal
*/
this.subscribedAutomatically = false;
/**
* Whether subscribable object subscribed ({@link SubscribeCapable#subscribe}) or not.
*
* @internal
*/
this.subscribed = false;
this.channelNames = channels;
this.groupNames = channelGroups;
this.options = subscriptionOptions;
Expand All @@ -10396,13 +10456,25 @@
* @return Subscription set which contains both receiver and other entities' subscription objects.
*/
addSubscription(subscription) {
return new SubscriptionSet({
const subscriptionSet = new SubscriptionSet({
channels: [...this.channelNames, ...subscription.channels],
channelGroups: [...this.groupNames, ...subscription.channelGroups],
subscriptionOptions: Object.assign(Object.assign({}, this.options), subscription === null || subscription === void 0 ? void 0 : subscription.options),
eventEmitter: this.eventEmitter,
pubnub: this.pubnub,
});
// Subscribe whole subscription set if it has been created with receiving subscription object
// which is already subscribed.
if (this.subscribed) {
if (!subscription.subscribed) {
subscription.subscribe();
subscription.subscribedAutomatically = true; // should be placed after .subscribe() call.
}
this.pubnub.registerSubscribeCapable(subscriptionSet);
// @ts-expect-error: Required modification of protected field.
subscriptionSet.subscribed = true;
}
return subscriptionSet;
}
}

Expand Down
4 changes: 2 additions & 2 deletions dist/web/pubnub.min.js

Large diffs are not rendered by default.

49 changes: 41 additions & 8 deletions dist/web/pubnub.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,21 @@
updateClientSubscribeStateIfRequired(data);
const client = pubNubClients[data.clientIdentifier];
if (client) {
const timerIdentifier = `${client.userId}-${client.subscriptionKey}`;
// Check whether we need to start new aggregation timer or not.
if (!aggregationTimers.has(timerIdentifier)) {
const aggregationTimer = setTimeout(() => {
handleSendSubscribeRequestEvent(data);
aggregationTimers.delete(timerIdentifier);
}, subscribeAggregationTimeout);
aggregationTimers.set(timerIdentifier, aggregationTimer);
// Check whether there are more clients which may schedule next subscription loop and they need to be
// aggregated or not.
if (hasClientsForSendAggregatedSubscribeRequestEvent(client, data)) {
const timerIdentifier = aggregateTimerId(client);
// Check whether we need to start new aggregation timer or not.
if (!aggregationTimers.has(timerIdentifier)) {
const aggregationTimer = setTimeout(() => {
handleSendSubscribeRequestEvent(data);
aggregationTimers.delete(timerIdentifier);
}, subscribeAggregationTimeout);
aggregationTimers.set(timerIdentifier, aggregationTimer);
}
}
else
handleSendSubscribeRequestEvent(data);
}
}
else if (data.request.path.endsWith('/heartbeat')) {
Expand Down Expand Up @@ -1477,6 +1483,18 @@ which has started by '${client.clientIdentifier}' client. Waiting for existing '
}
return undefined;
};
/**
* Check whether there are any clients which can be used for subscribe request aggregation or not.
*
* @param client - PubNub client state which will be checked.
* @param event - Send subscribe request event information.
*
* @returns `true` in case there is more than 1 client which has same parameters for subscribe request to aggregate.
*/
const hasClientsForSendAggregatedSubscribeRequestEvent = (client, event) => {
var _a, _b;
return clientsForSendSubscribeRequestEvent((_b = ((_a = client.subscription) !== null && _a !== void 0 ? _a : {}).timetoken) !== null && _b !== void 0 ? _b : '0', event).length > 1;
};
/**
* Find PubNub client states with configuration compatible with the one in request.
*
Expand Down Expand Up @@ -1607,6 +1625,21 @@ which has started by '${client.clientIdentifier}' client. Waiting for existing '
pingTimeouts[subscriptionKey] = setTimeout(() => pingClients(subscriptionKey), interval * 500 - 1);
}
};
/**
* Compose clients' aggregation key.
*
* Aggregation key includes key parameters which differentiate clients between each other.
*
* @param client - Client for which identifier should be composed.
*
* @returns Aggregation timeout identifier string.
*/
const aggregateTimerId = (client) => {
let id = `${client.userId}-${client.subscriptionKey}${client.authKey ? `-${client.authKey}` : ''}`;
if (client.subscription && client.subscription.filterExpression)
id += `-${client.subscription.filterExpression}`;
return id;
};
/**
* Print message on the worker's clients console.
*
Expand Down
2 changes: 1 addition & 1 deletion dist/web/pubnub.worker.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/core/components/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const makeConfiguration = (base, setupCryptoModule) => {
return base.PubNubFile;
},
get version() {
return '8.10.0';
return '9.0.0';
},
getVersion() {
return this.version;
Expand Down
21 changes: 17 additions & 4 deletions lib/core/components/subscription-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ class SubscriptionManager {
* unsubscribe or not.
*/
reconnect(forUnsubscribe = false) {
this.startSubscribeLoop();
if (!forUnsubscribe && this.configuration.useSmartHeartbeat)
this.startSubscribeLoop(forUnsubscribe);
// Starting heartbeat loop for provided channels and groups.
if (!forUnsubscribe && !this.configuration.useSmartHeartbeat)
this.startHeartbeatTimer();
}
/**
Expand Down Expand Up @@ -190,7 +191,15 @@ class SubscriptionManager {
channelGroups: this.subscribedChannelGroups,
}, isOffline);
}
startSubscribeLoop() {
/**
* Start next subscription loop.
*
* @param restartOnUnsubscribe - Whether restarting subscription loop as part of channels list change on
* unsubscribe or not.
*
* @internal
*/
startSubscribeLoop(restartOnUnsubscribe = false) {
this.stopSubscribeLoop();
const channelGroups = [...Object.keys(this.channelGroups)];
const channels = [...Object.keys(this.channels)];
Expand All @@ -210,6 +219,8 @@ class SubscriptionManager {
}, (status, result) => {
this.processSubscribeResponse(status, result);
});
if (!restartOnUnsubscribe && this.configuration.useSmartHeartbeat)
this.startHeartbeatTimer();
}
stopSubscribeLoop() {
if (this._subscribeAbort) {
Expand Down Expand Up @@ -361,7 +372,9 @@ class SubscriptionManager {
const heartbeatInterval = this.configuration.getHeartbeatInterval();
if (!heartbeatInterval || heartbeatInterval === 0)
return;
this.sendHeartbeat();
// Sending immediate heartbeat only if not working as smart heartbeat.
if (!this.configuration.useSmartHeartbeat)
this.sendHeartbeat();
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), heartbeatInterval * 1000);
}
/**
Expand Down
Loading
Loading