Skip to content

Commit d9af3ec

Browse files
authored
Fix presence leave call on used channels (#425)
fix(presence): fix presence leave call on used channels Fix issue with `Subscription` and `SubscriptionSet` when one can unsubscribe channel / group which is still in use by another. fix(network): fix fetch resource error report as bad request Fix particular `TypeError` emitted when browser forcefully closes long-poll connection before its timeout and reported as bad request. This type of error will be reported as a network error.
1 parent f51f759 commit d9af3ec

File tree

16 files changed

+254
-28
lines changed

16 files changed

+254
-28
lines changed

.pubnub.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
---
22
changelog:
3+
- date: 2024-12-12
4+
version: v8.3.2
5+
changes:
6+
- type: bug
7+
text: "Fix issue with `Subscription` and `SubscriptionSet` when one can unsubscribe channel / group which is still in use by another."
8+
- type: bug
9+
text: "Fix particular `TypeError` emitted when browser forcefully closes long-poll connection before its timeout and reported as bad request. This type of error will be reported as a network error."
10+
- type: bug
11+
text: "Fix issue because of which `node-fetch` used default agent, which after Node.js 19+ has `keepAlive` enabled by default."
312
- date: 2024-11-18
413
version: v8.3.1
514
changes:
@@ -1072,7 +1081,7 @@ supported-platforms:
10721081
- 'Ubuntu 14.04 and up'
10731082
- 'Windows 7 and up'
10741083
version: 'Pubnub Javascript for Node'
1075-
version: '8.3.1'
1084+
version: '8.3.2'
10761085
sdks:
10771086
- full-name: PubNub Javascript SDK
10781087
short-name: Javascript
@@ -1088,7 +1097,7 @@ sdks:
10881097
- distribution-type: source
10891098
distribution-repository: GitHub release
10901099
package-name: pubnub.js
1091-
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.3.1.zip
1100+
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.3.2.zip
10921101
requires:
10931102
- name: 'agentkeepalive'
10941103
min-version: '3.5.2'
@@ -1759,7 +1768,7 @@ sdks:
17591768
- distribution-type: library
17601769
distribution-repository: GitHub release
17611770
package-name: pubnub.js
1762-
location: https://github.com/pubnub/javascript/releases/download/v8.3.1/pubnub.8.3.1.js
1771+
location: https://github.com/pubnub/javascript/releases/download/v8.3.2/pubnub.8.3.2.js
17631772
requires:
17641773
- name: 'agentkeepalive'
17651774
min-version: '3.5.2'

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## v8.3.2
2+
December 12 2024
3+
4+
#### Fixed
5+
- Fix issue with `Subscription` and `SubscriptionSet` when one can unsubscribe channel / group which is still in use by another.
6+
- Fix particular `TypeError` emitted when browser forcefully closes long-poll connection before its timeout and reported as bad request. This type of error will be reported as a network error.
7+
- Fix issue because of which `node-fetch` used default agent, which after Node.js 19+ has `keepAlive` enabled by default.
8+
19
## v8.3.1
210
November 18 2024
311

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ Watch [Getting Started with PubNub JS SDK](https://app.dashcam.io/replay/64ee0d2
2828
npm install pubnub
2929
```
3030
* or download one of our builds from our CDN:
31-
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.3.1.js
32-
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.3.1.min.js
31+
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.3.2.js
32+
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.3.2.min.js
3333
3434
2. Configure your keys:
3535

dist/web/pubnub.js

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2916,7 +2916,10 @@
29162916
message = 'Network issues';
29172917
}
29182918
else if (errorName === 'TypeError') {
2919-
category = StatusCategory$1.PNBadRequestCategory;
2919+
if (message.indexOf('Load failed') !== -1 || message.indexOf('Failed to fetch') != -1)
2920+
category = StatusCategory$1.PNNetworkIssuesCategory;
2921+
else
2922+
category = StatusCategory$1.PNBadRequestCategory;
29202923
}
29212924
else if (errorName === 'FetchError') {
29222925
const errorCode = error.code;
@@ -3948,7 +3951,7 @@
39483951
return base.PubNubFile;
39493952
},
39503953
get version() {
3951-
return '8.3.1';
3954+
return '8.3.2';
39523955
},
39533956
getVersion() {
39543957
return this.version;
@@ -9892,15 +9895,23 @@
98929895
*/
98939896
subscribe(subscribeParameters) {
98949897
const timetoken = subscribeParameters === null || subscribeParameters === void 0 ? void 0 : subscribeParameters.timetoken;
9898+
this.pubnub.registerSubscribeCapable(this);
98959899
this.pubnub.subscribe(Object.assign({ channels: this.channelNames, channelGroups: this.groupNames }, (timetoken !== null && timetoken !== '' && { timetoken: timetoken })));
98969900
}
98979901
/**
98989902
* Stop real-time events processing.
98999903
*/
99009904
unsubscribe() {
9905+
this.pubnub.unregisterSubscribeCapable(this);
9906+
const { channels, channelGroups } = this.pubnub.getSubscribeCapableEntities();
9907+
// Identify channels and groups from which PubNub client can safely unsubscribe.
9908+
const filteredChannelGroups = this.groupNames.filter((cg) => !channelGroups.includes(cg));
9909+
const filteredChannels = this.channelNames.filter((ch) => !channels.includes(ch));
9910+
if (filteredChannels.length === 0 && filteredChannelGroups.length === 0)
9911+
return;
99019912
this.pubnub.unsubscribe({
9902-
channels: this.channelNames,
9903-
channelGroups: this.groupNames,
9913+
channels: filteredChannels,
9914+
channelGroups: filteredChannelGroups,
99049915
});
99059916
}
99069917
/**
@@ -12566,6 +12577,7 @@
1256612577
// Prepare for real-time events announcement.
1256712578
this.listenerManager = new ListenerManager();
1256812579
this.eventEmitter = new EventEmitter(this.listenerManager);
12580+
this.subscribeCapable = new Set();
1256912581
if (this._configuration.enableEventEngine) {
1257012582
{
1257112583
let heartbeatInterval = this._configuration.getHeartbeatInterval();
@@ -12998,7 +13010,9 @@
1299813010
* @param [isOffline] - Whether `offline` presence should be notified or not.
1299913011
*/
1300013012
destroy(isOffline) {
13013+
var _a;
1300113014
{
13015+
(_a = this.subscribeCapable) === null || _a === void 0 ? void 0 : _a.clear();
1300213016
if (this.subscriptionManager) {
1300313017
this.subscriptionManager.unsubscribeAll(isOffline);
1300413018
this.subscriptionManager.disconnect();
@@ -13127,6 +13141,53 @@
1312713141
}
1312813142
return [];
1312913143
}
13144+
/**
13145+
* Register subscribe capable object with active subscription.
13146+
*
13147+
* @param subscribeCapable - {@link Subscription} or {@link SubscriptionSet} object.
13148+
*
13149+
* @internal
13150+
*/
13151+
registerSubscribeCapable(subscribeCapable) {
13152+
{
13153+
if (!this.subscribeCapable || this.subscribeCapable.has(subscribeCapable))
13154+
return;
13155+
this.subscribeCapable.add(subscribeCapable);
13156+
}
13157+
}
13158+
/**
13159+
* Unregister subscribe capable object with inactive subscription.
13160+
*
13161+
* @param subscribeCapable - {@link Subscription} or {@link SubscriptionSet} object.
13162+
*
13163+
* @internal
13164+
*/
13165+
unregisterSubscribeCapable(subscribeCapable) {
13166+
{
13167+
if (!this.subscribeCapable || !this.subscribeCapable.has(subscribeCapable))
13168+
return;
13169+
this.subscribeCapable.delete(subscribeCapable);
13170+
}
13171+
}
13172+
/**
13173+
* Retrieve list of subscribe capable entities currently used in subscription.
13174+
*
13175+
* @returns Channels and channel groups currently used in subscription.
13176+
*
13177+
* @internal
13178+
*/
13179+
getSubscribeCapableEntities() {
13180+
{
13181+
const entities = { channels: [], channelGroups: [] };
13182+
if (!this.subscribeCapable)
13183+
return entities;
13184+
for (const subscribeCapable of this.subscribeCapable) {
13185+
entities.channelGroups.push(...subscribeCapable.channelGroups);
13186+
entities.channels.push(...subscribeCapable.channels);
13187+
}
13188+
return entities;
13189+
}
13190+
}
1313013191
/**
1313113192
* Subscribe to specified channels and groups real-time events.
1313213193
*
@@ -13205,7 +13266,9 @@
1320513266
* Unsubscribe from all channels and groups.
1320613267
*/
1320713268
unsubscribeAll() {
13269+
var _a;
1320813270
{
13271+
(_a = this.subscribeCapable) === null || _a === void 0 ? void 0 : _a.clear();
1320913272
if (this.subscriptionManager)
1321013273
this.subscriptionManager.unsubscribeAll();
1321113274
else if (this.eventEngine)

dist/web/pubnub.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/core/components/configuration.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ const makeConfiguration = (base, setupCryptoModule) => {
112112
return base.PubNubFile;
113113
},
114114
get version() {
115-
return '8.3.1';
115+
return '8.3.2';
116116
},
117117
getVersion() {
118118
return this.version;

lib/core/pubnub-common.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ class PubNubCore {
171171
// Prepare for real-time events announcement.
172172
this.listenerManager = new listener_manager_1.ListenerManager();
173173
this.eventEmitter = new eventEmitter_1.default(this.listenerManager);
174+
this.subscribeCapable = new Set();
174175
if (this._configuration.enableEventEngine) {
175176
if (process.env.SUBSCRIBE_EVENT_ENGINE_MODULE !== 'disabled') {
176177
let heartbeatInterval = this._configuration.getHeartbeatInterval();
@@ -609,7 +610,10 @@ class PubNubCore {
609610
* @param [isOffline] - Whether `offline` presence should be notified or not.
610611
*/
611612
destroy(isOffline) {
613+
var _a;
612614
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
615+
if (process.env.SUBSCRIBE_EVENT_ENGINE_MODULE !== 'disabled')
616+
(_a = this.subscribeCapable) === null || _a === void 0 ? void 0 : _a.clear();
613617
if (this.subscriptionManager) {
614618
this.subscriptionManager.unsubscribeAll(isOffline);
615619
this.subscriptionManager.disconnect();
@@ -755,6 +759,59 @@ class PubNubCore {
755759
throw new Error('Subscription error: subscription module disabled');
756760
return [];
757761
}
762+
/**
763+
* Register subscribe capable object with active subscription.
764+
*
765+
* @param subscribeCapable - {@link Subscription} or {@link SubscriptionSet} object.
766+
*
767+
* @internal
768+
*/
769+
registerSubscribeCapable(subscribeCapable) {
770+
if (process.env.SUBSCRIBE_EVENT_ENGINE_MODULE !== 'disabled') {
771+
if (!this.subscribeCapable || this.subscribeCapable.has(subscribeCapable))
772+
return;
773+
this.subscribeCapable.add(subscribeCapable);
774+
}
775+
else
776+
throw new Error('Subscription error: subscription event engine module disabled');
777+
}
778+
/**
779+
* Unregister subscribe capable object with inactive subscription.
780+
*
781+
* @param subscribeCapable - {@link Subscription} or {@link SubscriptionSet} object.
782+
*
783+
* @internal
784+
*/
785+
unregisterSubscribeCapable(subscribeCapable) {
786+
if (process.env.SUBSCRIBE_EVENT_ENGINE_MODULE !== 'disabled') {
787+
if (!this.subscribeCapable || !this.subscribeCapable.has(subscribeCapable))
788+
return;
789+
this.subscribeCapable.delete(subscribeCapable);
790+
}
791+
else
792+
throw new Error('Subscription error: subscription event engine module disabled');
793+
}
794+
/**
795+
* Retrieve list of subscribe capable entities currently used in subscription.
796+
*
797+
* @returns Channels and channel groups currently used in subscription.
798+
*
799+
* @internal
800+
*/
801+
getSubscribeCapableEntities() {
802+
if (process.env.SUBSCRIBE_EVENT_ENGINE_MODULE !== 'disabled') {
803+
const entities = { channels: [], channelGroups: [] };
804+
if (!this.subscribeCapable)
805+
return entities;
806+
for (const subscribeCapable of this.subscribeCapable) {
807+
entities.channelGroups.push(...subscribeCapable.channelGroups);
808+
entities.channels.push(...subscribeCapable.channels);
809+
}
810+
return entities;
811+
}
812+
else
813+
throw new Error('Subscription error: subscription event engine module disabled');
814+
}
758815
/**
759816
* Subscribe to specified channels and groups real-time events.
760817
*
@@ -841,7 +898,10 @@ class PubNubCore {
841898
* Unsubscribe from all channels and groups.
842899
*/
843900
unsubscribeAll() {
901+
var _a;
844902
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
903+
if (process.env.SUBSCRIBE_EVENT_ENGINE_MODULE !== 'disabled')
904+
(_a = this.subscribeCapable) === null || _a === void 0 ? void 0 : _a.clear();
845905
if (this.subscriptionManager)
846906
this.subscriptionManager.unsubscribeAll();
847907
else if (this.eventEngine)

lib/entities/SubscribeCapable.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,23 @@ class SubscribeCapable {
1010
*/
1111
subscribe(subscribeParameters) {
1212
const timetoken = subscribeParameters === null || subscribeParameters === void 0 ? void 0 : subscribeParameters.timetoken;
13+
this.pubnub.registerSubscribeCapable(this);
1314
this.pubnub.subscribe(Object.assign({ channels: this.channelNames, channelGroups: this.groupNames }, (timetoken !== null && timetoken !== '' && { timetoken: timetoken })));
1415
}
1516
/**
1617
* Stop real-time events processing.
1718
*/
1819
unsubscribe() {
20+
this.pubnub.unregisterSubscribeCapable(this);
21+
const { channels, channelGroups } = this.pubnub.getSubscribeCapableEntities();
22+
// Identify channels and groups from which PubNub client can safely unsubscribe.
23+
const filteredChannelGroups = this.groupNames.filter((cg) => !channelGroups.includes(cg));
24+
const filteredChannels = this.channelNames.filter((ch) => !channels.includes(ch));
25+
if (filteredChannels.length === 0 && filteredChannelGroups.length === 0)
26+
return;
1927
this.pubnub.unsubscribe({
20-
channels: this.channelNames,
21-
channelGroups: this.groupNames,
28+
channels: filteredChannels,
29+
channelGroups: filteredChannelGroups,
2230
});
2331
}
2432
/**

lib/errors/pubnub-api-error.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ class PubNubAPIError extends Error {
6666
message = 'Network issues';
6767
}
6868
else if (errorName === 'TypeError') {
69-
category = categories_1.default.PNBadRequestCategory;
69+
if (message.indexOf('Load failed') !== -1 || message.indexOf('Failed to fetch') != -1)
70+
category = categories_1.default.PNNetworkIssuesCategory;
71+
else
72+
category = categories_1.default.PNBadRequestCategory;
7073
}
7174
else if (errorName === 'FetchError') {
7275
const errorCode = error.code;

lib/transport/node-transport.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,16 @@ class NodeTransport {
182182
* @internal
183183
*/
184184
agentForTransportRequest(req) {
185-
// Don't configure any agents if keep alive not requested.
186-
if (!this.keepAlive && !this.proxyConfiguration)
187-
return undefined;
188185
// Create proxy agent (if possible).
189186
if (this.proxyConfiguration)
190187
return this.proxyAgent ? this.proxyAgent : (this.proxyAgent = new proxy_agent_1.ProxyAgent(this.proxyConfiguration));
191188
// Create keep alive agent.
192189
const useSecureAgent = req.origin.startsWith('https:');
190+
const agentOptions = Object.assign({ keepAlive: this.keepAlive }, (this.keepAlive ? this.keepAliveSettings : {}));
193191
if (useSecureAgent && this.httpsAgent === undefined)
194-
this.httpsAgent = new https_1.Agent(Object.assign({ keepAlive: true }, this.keepAliveSettings));
195-
else if (!useSecureAgent && this.httpAgent === undefined) {
196-
this.httpAgent = new http_1.Agent(Object.assign({ keepAlive: true }, this.keepAliveSettings));
197-
}
192+
this.httpsAgent = new https_1.Agent(agentOptions);
193+
else if (!useSecureAgent && this.httpAgent === undefined)
194+
this.httpAgent = new http_1.Agent(agentOptions);
198195
return useSecureAgent ? this.httpsAgent : this.httpAgent;
199196
}
200197
/**

0 commit comments

Comments
 (0)