Skip to content

Commit b36cf02

Browse files
authored
Fix fetch issue with empty object rejection (#439)
fix(fetch): fix fetch issue with empty object rejection Fix issue because of which code doesn't handle edge case when `fetch` reject with empty object and not `Error`. refactor(presence): remove `-pnpres` entries from presence requests Remove `-pnpres` channels and groups from presence `leave` and `heartbeat` requests.
1 parent bd69c80 commit b36cf02

21 files changed

+570
-76
lines changed

.pubnub.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
---
22
changelog:
3+
- date: 2025-02-26
4+
version: v8.9.1
5+
changes:
6+
- type: bug
7+
text: "Fix issue because of which code doesn't handle edge case when `fetch` reject with empty object and not `Error`."
8+
- type: improvement
9+
text: "Remove `-pnpres` channels and groups from presence `leave` and `heartbeat` requests."
310
- date: 2025-02-18
411
version: v8.9.0
512
changes:
@@ -1144,7 +1151,7 @@ supported-platforms:
11441151
- 'Ubuntu 14.04 and up'
11451152
- 'Windows 7 and up'
11461153
version: 'Pubnub Javascript for Node'
1147-
version: '8.9.0'
1154+
version: '8.9.1'
11481155
sdks:
11491156
- full-name: PubNub Javascript SDK
11501157
short-name: Javascript
@@ -1160,7 +1167,7 @@ sdks:
11601167
- distribution-type: source
11611168
distribution-repository: GitHub release
11621169
package-name: pubnub.js
1163-
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.9.0.zip
1170+
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.9.1.zip
11641171
requires:
11651172
- name: 'agentkeepalive'
11661173
min-version: '3.5.2'
@@ -1831,7 +1838,7 @@ sdks:
18311838
- distribution-type: library
18321839
distribution-repository: GitHub release
18331840
package-name: pubnub.js
1834-
location: https://github.com/pubnub/javascript/releases/download/v8.9.0/pubnub.8.9.0.js
1841+
location: https://github.com/pubnub/javascript/releases/download/v8.9.1/pubnub.8.9.1.js
18351842
requires:
18361843
- name: 'agentkeepalive'
18371844
min-version: '3.5.2'

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## v8.9.1
2+
February 26 2025
3+
4+
#### Fixed
5+
- Fix issue because of which code doesn't handle edge case when `fetch` reject with empty object and not `Error`.
6+
7+
#### Modified
8+
- Remove `-pnpres` channels and groups from presence `leave` and `heartbeat` requests.
9+
110
## v8.9.0
211
February 18 2025
312

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.9.0.js
32-
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.9.0.min.js
31+
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.9.1.js
32+
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.9.1.min.js
3333
3434
2. Configure your keys:
3535

dist/web/pubnub.js

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2890,7 +2890,7 @@
28902890
*
28912891
* @param errorOrResponse - `Error` or service error response object from which error information
28922892
* should be extracted.
2893-
* @param data - Preprocessed service error response.
2893+
* @param [data] - Preprocessed service error response.
28942894
*
28952895
* @returns `PubNubAPIError` object with known error category and additional information (if
28962896
* available).
@@ -2969,7 +2969,7 @@
29692969
*
29702970
* @param response - Service error response object from which error information should be
29712971
* extracted.
2972-
* @param data - Preprocessed service error response.
2972+
* @param [data] - Preprocessed service error response.
29732973
*
29742974
* @returns `PubNubAPIError` object with known error category and additional information (if
29752975
* available).
@@ -2990,6 +2990,11 @@
29902990
category = StatusCategory$1.PNAccessDeniedCategory;
29912991
message = 'Access denied';
29922992
}
2993+
if (typeof response === 'object' && Object.keys(response).length === 0) {
2994+
category = StatusCategory$1.PNMalformedResponseCategory;
2995+
message = 'Malformed response (network issues)';
2996+
status = 400;
2997+
}
29932998
// Try to get more information about error from service response.
29942999
if (data && data.byteLength > 0) {
29953000
const decoded = new TextDecoder().decode(data);
@@ -3042,7 +3047,7 @@
30423047
* @param message - Short API call error description.
30433048
* @param category - Error category.
30443049
* @param statusCode - Response HTTP status code.
3045-
* @param errorData - Error information.
3050+
* @param [errorData] - Error information.
30463051
*/
30473052
constructor(message, category, statusCode, errorData) {
30483053
super(message);
@@ -3065,19 +3070,58 @@
30653070
operation,
30663071
statusCode: this.statusCode,
30673072
errorData: this.errorData,
3073+
// @ts-expect-error Inner helper for JSON.stringify.
3074+
toJSON: function () {
3075+
let normalizedErrorData;
3076+
const errorData = this.errorData;
3077+
if (errorData) {
3078+
try {
3079+
if (typeof errorData === 'object') {
3080+
const errorObject = Object.assign(Object.assign(Object.assign(Object.assign({}, ('name' in errorData ? { name: errorData.name } : {})), ('message' in errorData ? { message: errorData.message } : {})), ('stack' in errorData ? { stack: errorData.stack } : {})), errorData);
3081+
normalizedErrorData = JSON.parse(JSON.stringify(errorObject, PubNubAPIError.circularReplacer()));
3082+
}
3083+
else
3084+
normalizedErrorData = errorData;
3085+
}
3086+
catch (_) {
3087+
normalizedErrorData = { error: 'Could not serialize the error object' };
3088+
}
3089+
}
3090+
// Make sure to exclude `toJSON` function from the final object.
3091+
const _a = this, status = __rest(_a, ["toJSON"]);
3092+
return JSON.stringify(Object.assign(Object.assign({}, status), { errorData: normalizedErrorData }));
3093+
},
30683094
};
30693095
}
30703096
/**
30713097
* Convert API error object to PubNub client error object.
30723098
*
30733099
* @param operation - Request operation during which error happened.
3074-
* @param message - Custom error message.
3100+
* @param [message] - Custom error message.
30753101
*
30763102
* @returns Client-facing pre-formatted endpoint call error.
30773103
*/
30783104
toPubNubError(operation, message) {
30793105
return new PubNubError(message !== null && message !== void 0 ? message : this.message, this.toStatus(operation));
30803106
}
3107+
/**
3108+
* Function which handles circular references in serialized JSON.
3109+
*
3110+
* @returns Circular reference replacer function.
3111+
*
3112+
* @internal
3113+
*/
3114+
static circularReplacer() {
3115+
const visited = new WeakSet();
3116+
return function (_, value) {
3117+
if (typeof value === 'object' && value !== null) {
3118+
if (visited.has(value))
3119+
return '[Circular]';
3120+
visited.add(value);
3121+
}
3122+
return value;
3123+
};
3124+
}
30813125
}
30823126

30833127
/**
@@ -3759,7 +3803,7 @@
37593803
return base.PubNubFile;
37603804
},
37613805
get version() {
3762-
return '8.9.0';
3806+
return '8.9.1';
37633807
},
37643808
getVersion() {
37653809
return this.version;
@@ -4273,10 +4317,9 @@
42734317
let fetchError = error;
42744318
if (typeof error === 'string') {
42754319
const errorMessage = error.toLowerCase();
4276-
if (errorMessage.includes('timeout') || !errorMessage.includes('cancel'))
4277-
fetchError = new Error(error);
4278-
else if (errorMessage.includes('cancel'))
4279-
fetchError = new DOMException('Aborted', 'AbortError');
4320+
fetchError = new Error(error);
4321+
if (!errorMessage.includes('timeout') && errorMessage.includes('cancel'))
4322+
fetchError.name = 'AbortError';
42804323
}
42814324
throw PubNubAPIError.create(fetchError);
42824325
});
@@ -5036,7 +5079,8 @@
50365079
if (status.category === StatusCategory$1.PNTimeoutCategory) {
50375080
this.startSubscribeLoop();
50385081
}
5039-
else if (status.category === StatusCategory$1.PNNetworkIssuesCategory) {
5082+
else if (status.category === StatusCategory$1.PNNetworkIssuesCategory ||
5083+
status.category === StatusCategory$1.PNMalformedResponseCategory) {
50405084
this.disconnect();
50415085
if (status.error && this.configuration.autoNetworkDetection && this.isOnline) {
50425086
this.isOnline = false;
@@ -5058,14 +5102,11 @@
50585102
this.listenerManager.announceStatus(reconnectedAnnounce);
50595103
});
50605104
this.reconnectionManager.startPolling();
5061-
this.listenerManager.announceStatus(status);
5105+
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category: StatusCategory$1.PNNetworkIssuesCategory }));
50625106
}
5063-
else if (status.category === StatusCategory$1.PNBadRequestCategory ||
5064-
status.category == StatusCategory$1.PNMalformedResponseCategory) {
5065-
const category = this.isOnline ? StatusCategory$1.PNDisconnectedUnexpectedlyCategory : status.category;
5066-
this.isOnline = false;
5067-
this.disconnect();
5068-
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category }));
5107+
else if (status.category === StatusCategory$1.PNBadRequestCategory) {
5108+
this.stopHeartbeatTimer();
5109+
this.listenerManager.announceStatus(status);
50695110
}
50705111
else
50715112
this.listenerManager.announceStatus(status);
@@ -13319,7 +13360,22 @@
1331913360
*/
1332013361
makeUnsubscribe(parameters, callback) {
1332113362
{
13322-
this.sendRequest(new PresenceLeaveRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet })), callback);
13363+
// Filtering out presence channels and groups.
13364+
let { channels, channelGroups } = parameters;
13365+
if (channelGroups)
13366+
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
13367+
if (channels)
13368+
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
13369+
// Complete immediately request only for presence channels.
13370+
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
13371+
return callback({
13372+
error: false,
13373+
operation: RequestOperation$1.PNUnsubscribeOperation,
13374+
category: StatusCategory$1.PNAcknowledgmentCategory,
13375+
statusCode: 200,
13376+
});
13377+
}
13378+
this.sendRequest(new PresenceLeaveRequest({ channels, channelGroups, keySet: this._configuration.keySet }), callback);
1332313379
}
1332413380
}
1332513381
/**
@@ -13670,7 +13726,26 @@
1367013726
heartbeat(parameters, callback) {
1367113727
return __awaiter(this, void 0, void 0, function* () {
1367213728
{
13673-
const request = new HeartbeatRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet }));
13729+
// Filtering out presence channels and groups.
13730+
let { channels, channelGroups } = parameters;
13731+
if (channelGroups)
13732+
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
13733+
if (channels)
13734+
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
13735+
// Complete immediately request only for presence channels.
13736+
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
13737+
const responseStatus = {
13738+
error: false,
13739+
operation: RequestOperation$1.PNHeartbeatOperation,
13740+
category: StatusCategory$1.PNAcknowledgmentCategory,
13741+
statusCode: 200,
13742+
};
13743+
if (callback)
13744+
return callback(responseStatus, {});
13745+
return Promise.resolve(responseStatus);
13746+
}
13747+
const request = new HeartbeatRequest(Object.assign(Object.assign({}, parameters), { channels,
13748+
channelGroups, keySet: this._configuration.keySet }));
1367413749
if (callback)
1367513750
return this.sendRequest(request, callback);
1367613751
return this.sendRequest(request);

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.

dist/web/pubnub.worker.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -550,10 +550,9 @@
550550
let fetchError = error;
551551
if (typeof error === 'string') {
552552
const errorMessage = error.toLowerCase();
553-
if (errorMessage.includes('timeout') || !errorMessage.includes('cancel'))
554-
fetchError = new Error(error);
555-
else if (errorMessage.includes('cancel'))
556-
fetchError = new DOMException('Aborted', 'AbortError');
553+
fetchError = new Error(error);
554+
if (!errorMessage.includes('timeout') && errorMessage.includes('cancel'))
555+
fetchError.name = 'AbortError';
557556
}
558557
failure(clients, fetchError);
559558
});
@@ -1089,9 +1088,10 @@
10891088
message = error.message;
10901089
name = error.name;
10911090
}
1092-
if (message.toLowerCase().includes('timeout'))
1091+
const errorMessage = message.toLowerCase();
1092+
if (errorMessage.includes('timeout'))
10931093
type = 'TIMEOUT';
1094-
else if (name === 'AbortError' || message.toLowerCase().includes('cancel')) {
1094+
else if (name === 'AbortError' || errorMessage.includes('aborted') || errorMessage.includes('cancel')) {
10951095
message = 'Request aborted';
10961096
type = 'ABORTED';
10971097
}

dist/web/pubnub.worker.min.js

Lines changed: 1 addition & 1 deletion
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
@@ -120,7 +120,7 @@ const makeConfiguration = (base, setupCryptoModule) => {
120120
return base.PubNubFile;
121121
},
122122
get version() {
123-
return '8.9.0';
123+
return '8.9.1';
124124
},
125125
getVersion() {
126126
return this.version;

lib/core/components/subscription-manager.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ class SubscriptionManager {
231231
if (status.category === categories_1.default.PNTimeoutCategory) {
232232
this.startSubscribeLoop();
233233
}
234-
else if (status.category === categories_1.default.PNNetworkIssuesCategory) {
234+
else if (status.category === categories_1.default.PNNetworkIssuesCategory ||
235+
status.category === categories_1.default.PNMalformedResponseCategory) {
235236
this.disconnect();
236237
if (status.error && this.configuration.autoNetworkDetection && this.isOnline) {
237238
this.isOnline = false;
@@ -253,14 +254,11 @@ class SubscriptionManager {
253254
this.listenerManager.announceStatus(reconnectedAnnounce);
254255
});
255256
this.reconnectionManager.startPolling();
256-
this.listenerManager.announceStatus(status);
257+
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category: categories_1.default.PNNetworkIssuesCategory }));
257258
}
258-
else if (status.category === categories_1.default.PNBadRequestCategory ||
259-
status.category == categories_1.default.PNMalformedResponseCategory) {
260-
const category = this.isOnline ? categories_1.default.PNDisconnectedUnexpectedlyCategory : status.category;
261-
this.isOnline = false;
262-
this.disconnect();
263-
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category }));
259+
else if (status.category === categories_1.default.PNBadRequestCategory) {
260+
this.stopHeartbeatTimer();
261+
this.listenerManager.announceStatus(status);
264262
}
265263
else
266264
this.listenerManager.announceStatus(status);

lib/core/pubnub-common.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,22 @@ class PubNubCore {
895895
*/
896896
makeUnsubscribe(parameters, callback) {
897897
if (process.env.PRESENCE_MODULE !== 'disabled') {
898-
this.sendRequest(new leave_1.PresenceLeaveRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet })), callback);
898+
// Filtering out presence channels and groups.
899+
let { channels, channelGroups } = parameters;
900+
if (channelGroups)
901+
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
902+
if (channels)
903+
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
904+
// Complete immediately request only for presence channels.
905+
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
906+
return callback({
907+
error: false,
908+
operation: operations_1.default.PNUnsubscribeOperation,
909+
category: categories_1.default.PNAcknowledgmentCategory,
910+
statusCode: 200,
911+
});
912+
}
913+
this.sendRequest(new leave_1.PresenceLeaveRequest({ channels, channelGroups, keySet: this._configuration.keySet }), callback);
899914
}
900915
else
901916
throw new Error('Unsubscription error: presence module disabled');
@@ -1284,7 +1299,26 @@ class PubNubCore {
12841299
heartbeat(parameters, callback) {
12851300
return __awaiter(this, void 0, void 0, function* () {
12861301
if (process.env.PRESENCE_MODULE !== 'disabled') {
1287-
const request = new heartbeat_1.HeartbeatRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet }));
1302+
// Filtering out presence channels and groups.
1303+
let { channels, channelGroups } = parameters;
1304+
if (channelGroups)
1305+
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
1306+
if (channels)
1307+
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
1308+
// Complete immediately request only for presence channels.
1309+
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
1310+
const responseStatus = {
1311+
error: false,
1312+
operation: operations_1.default.PNHeartbeatOperation,
1313+
category: categories_1.default.PNAcknowledgmentCategory,
1314+
statusCode: 200,
1315+
};
1316+
if (callback)
1317+
return callback(responseStatus, {});
1318+
return Promise.resolve(responseStatus);
1319+
}
1320+
const request = new heartbeat_1.HeartbeatRequest(Object.assign(Object.assign({}, parameters), { channels,
1321+
channelGroups, keySet: this._configuration.keySet }));
12881322
if (callback)
12891323
return this.sendRequest(request, callback);
12901324
return this.sendRequest(request);

0 commit comments

Comments
 (0)