Skip to content

Commit

Permalink
Fix fetch issue with empty object rejection (#439)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
parfeon authored Feb 26, 2025
1 parent bd69c80 commit b36cf02
Show file tree
Hide file tree
Showing 21 changed files with 570 additions and 76 deletions.
13 changes: 10 additions & 3 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
---
changelog:
- date: 2025-02-26
version: v8.9.1
changes:
- type: bug
text: "Fix issue because of which code doesn't handle edge case when `fetch` reject with empty object and not `Error`."
- type: improvement
text: "Remove `-pnpres` channels and groups from presence `leave` and `heartbeat` requests."
- date: 2025-02-18
version: v8.9.0
changes:
Expand Down Expand Up @@ -1144,7 +1151,7 @@ supported-platforms:
- 'Ubuntu 14.04 and up'
- 'Windows 7 and up'
version: 'Pubnub Javascript for Node'
version: '8.9.0'
version: '8.9.1'
sdks:
- full-name: PubNub Javascript SDK
short-name: Javascript
Expand All @@ -1160,7 +1167,7 @@ sdks:
- distribution-type: source
distribution-repository: GitHub release
package-name: pubnub.js
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.9.0.zip
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.9.1.zip
requires:
- name: 'agentkeepalive'
min-version: '3.5.2'
Expand Down Expand Up @@ -1831,7 +1838,7 @@ sdks:
- distribution-type: library
distribution-repository: GitHub release
package-name: pubnub.js
location: https://github.com/pubnub/javascript/releases/download/v8.9.0/pubnub.8.9.0.js
location: https://github.com/pubnub/javascript/releases/download/v8.9.1/pubnub.8.9.1.js
requires:
- name: 'agentkeepalive'
min-version: '3.5.2'
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## v8.9.1
February 26 2025

#### Fixed
- Fix issue because of which code doesn't handle edge case when `fetch` reject with empty object and not `Error`.

#### Modified
- Remove `-pnpres` channels and groups from presence `leave` and `heartbeat` requests.

## v8.9.0
February 18 2025

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,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.9.0.js
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.9.0.min.js
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.9.1.js
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.9.1.min.js
2. Configure your keys:
Expand Down
113 changes: 94 additions & 19 deletions dist/web/pubnub.js
Original file line number Diff line number Diff line change
Expand Up @@ -2890,7 +2890,7 @@
*
* @param errorOrResponse - `Error` or service error response object from which error information
* should be extracted.
* @param data - Preprocessed service error response.
* @param [data] - Preprocessed service error response.
*
* @returns `PubNubAPIError` object with known error category and additional information (if
* available).
Expand Down Expand Up @@ -2969,7 +2969,7 @@
*
* @param response - Service error response object from which error information should be
* extracted.
* @param data - Preprocessed service error response.
* @param [data] - Preprocessed service error response.
*
* @returns `PubNubAPIError` object with known error category and additional information (if
* available).
Expand All @@ -2990,6 +2990,11 @@
category = StatusCategory$1.PNAccessDeniedCategory;
message = 'Access denied';
}
if (typeof response === 'object' && Object.keys(response).length === 0) {
category = StatusCategory$1.PNMalformedResponseCategory;
message = 'Malformed response (network issues)';
status = 400;
}
// Try to get more information about error from service response.
if (data && data.byteLength > 0) {
const decoded = new TextDecoder().decode(data);
Expand Down Expand Up @@ -3042,7 +3047,7 @@
* @param message - Short API call error description.
* @param category - Error category.
* @param statusCode - Response HTTP status code.
* @param errorData - Error information.
* @param [errorData] - Error information.
*/
constructor(message, category, statusCode, errorData) {
super(message);
Expand All @@ -3065,19 +3070,58 @@
operation,
statusCode: this.statusCode,
errorData: this.errorData,
// @ts-expect-error Inner helper for JSON.stringify.
toJSON: function () {
let normalizedErrorData;
const errorData = this.errorData;
if (errorData) {
try {
if (typeof errorData === 'object') {
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);
normalizedErrorData = JSON.parse(JSON.stringify(errorObject, PubNubAPIError.circularReplacer()));
}
else
normalizedErrorData = errorData;
}
catch (_) {
normalizedErrorData = { error: 'Could not serialize the error object' };
}
}
// Make sure to exclude `toJSON` function from the final object.
const _a = this, status = __rest(_a, ["toJSON"]);
return JSON.stringify(Object.assign(Object.assign({}, status), { errorData: normalizedErrorData }));
},
};
}
/**
* Convert API error object to PubNub client error object.
*
* @param operation - Request operation during which error happened.
* @param message - Custom error message.
* @param [message] - Custom error message.
*
* @returns Client-facing pre-formatted endpoint call error.
*/
toPubNubError(operation, message) {
return new PubNubError(message !== null && message !== void 0 ? message : this.message, this.toStatus(operation));
}
/**
* Function which handles circular references in serialized JSON.
*
* @returns Circular reference replacer function.
*
* @internal
*/
static circularReplacer() {
const visited = new WeakSet();
return function (_, value) {
if (typeof value === 'object' && value !== null) {
if (visited.has(value))
return '[Circular]';
visited.add(value);
}
return value;
};
}
}

/**
Expand Down Expand Up @@ -3759,7 +3803,7 @@
return base.PubNubFile;
},
get version() {
return '8.9.0';
return '8.9.1';
},
getVersion() {
return this.version;
Expand Down Expand Up @@ -4273,10 +4317,9 @@
let fetchError = error;
if (typeof error === 'string') {
const errorMessage = error.toLowerCase();
if (errorMessage.includes('timeout') || !errorMessage.includes('cancel'))
fetchError = new Error(error);
else if (errorMessage.includes('cancel'))
fetchError = new DOMException('Aborted', 'AbortError');
fetchError = new Error(error);
if (!errorMessage.includes('timeout') && errorMessage.includes('cancel'))
fetchError.name = 'AbortError';
}
throw PubNubAPIError.create(fetchError);
});
Expand Down Expand Up @@ -5036,7 +5079,8 @@
if (status.category === StatusCategory$1.PNTimeoutCategory) {
this.startSubscribeLoop();
}
else if (status.category === StatusCategory$1.PNNetworkIssuesCategory) {
else if (status.category === StatusCategory$1.PNNetworkIssuesCategory ||
status.category === StatusCategory$1.PNMalformedResponseCategory) {
this.disconnect();
if (status.error && this.configuration.autoNetworkDetection && this.isOnline) {
this.isOnline = false;
Expand All @@ -5058,14 +5102,11 @@
this.listenerManager.announceStatus(reconnectedAnnounce);
});
this.reconnectionManager.startPolling();
this.listenerManager.announceStatus(status);
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category: StatusCategory$1.PNNetworkIssuesCategory }));
}
else if (status.category === StatusCategory$1.PNBadRequestCategory ||
status.category == StatusCategory$1.PNMalformedResponseCategory) {
const category = this.isOnline ? StatusCategory$1.PNDisconnectedUnexpectedlyCategory : status.category;
this.isOnline = false;
this.disconnect();
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category }));
else if (status.category === StatusCategory$1.PNBadRequestCategory) {
this.stopHeartbeatTimer();
this.listenerManager.announceStatus(status);
}
else
this.listenerManager.announceStatus(status);
Expand Down Expand Up @@ -13319,7 +13360,22 @@
*/
makeUnsubscribe(parameters, callback) {
{
this.sendRequest(new PresenceLeaveRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet })), callback);
// Filtering out presence channels and groups.
let { channels, channelGroups } = parameters;
if (channelGroups)
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
if (channels)
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
// Complete immediately request only for presence channels.
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
return callback({
error: false,
operation: RequestOperation$1.PNUnsubscribeOperation,
category: StatusCategory$1.PNAcknowledgmentCategory,
statusCode: 200,
});
}
this.sendRequest(new PresenceLeaveRequest({ channels, channelGroups, keySet: this._configuration.keySet }), callback);
}
}
/**
Expand Down Expand Up @@ -13670,7 +13726,26 @@
heartbeat(parameters, callback) {
return __awaiter(this, void 0, void 0, function* () {
{
const request = new HeartbeatRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet }));
// Filtering out presence channels and groups.
let { channels, channelGroups } = parameters;
if (channelGroups)
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
if (channels)
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
// Complete immediately request only for presence channels.
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
const responseStatus = {
error: false,
operation: RequestOperation$1.PNHeartbeatOperation,
category: StatusCategory$1.PNAcknowledgmentCategory,
statusCode: 200,
};
if (callback)
return callback(responseStatus, {});
return Promise.resolve(responseStatus);
}
const request = new HeartbeatRequest(Object.assign(Object.assign({}, parameters), { channels,
channelGroups, keySet: this._configuration.keySet }));
if (callback)
return this.sendRequest(request, callback);
return this.sendRequest(request);
Expand Down
4 changes: 2 additions & 2 deletions dist/web/pubnub.min.js

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions dist/web/pubnub.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,9 @@
let fetchError = error;
if (typeof error === 'string') {
const errorMessage = error.toLowerCase();
if (errorMessage.includes('timeout') || !errorMessage.includes('cancel'))
fetchError = new Error(error);
else if (errorMessage.includes('cancel'))
fetchError = new DOMException('Aborted', 'AbortError');
fetchError = new Error(error);
if (!errorMessage.includes('timeout') && errorMessage.includes('cancel'))
fetchError.name = 'AbortError';
}
failure(clients, fetchError);
});
Expand Down Expand Up @@ -1089,9 +1088,10 @@
message = error.message;
name = error.name;
}
if (message.toLowerCase().includes('timeout'))
const errorMessage = message.toLowerCase();
if (errorMessage.includes('timeout'))
type = 'TIMEOUT';
else if (name === 'AbortError' || message.toLowerCase().includes('cancel')) {
else if (name === 'AbortError' || errorMessage.includes('aborted') || errorMessage.includes('cancel')) {
message = 'Request aborted';
type = 'ABORTED';
}
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.9.0';
return '8.9.1';
},
getVersion() {
return this.version;
Expand Down
14 changes: 6 additions & 8 deletions lib/core/components/subscription-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ class SubscriptionManager {
if (status.category === categories_1.default.PNTimeoutCategory) {
this.startSubscribeLoop();
}
else if (status.category === categories_1.default.PNNetworkIssuesCategory) {
else if (status.category === categories_1.default.PNNetworkIssuesCategory ||
status.category === categories_1.default.PNMalformedResponseCategory) {
this.disconnect();
if (status.error && this.configuration.autoNetworkDetection && this.isOnline) {
this.isOnline = false;
Expand All @@ -253,14 +254,11 @@ class SubscriptionManager {
this.listenerManager.announceStatus(reconnectedAnnounce);
});
this.reconnectionManager.startPolling();
this.listenerManager.announceStatus(status);
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category: categories_1.default.PNNetworkIssuesCategory }));
}
else if (status.category === categories_1.default.PNBadRequestCategory ||
status.category == categories_1.default.PNMalformedResponseCategory) {
const category = this.isOnline ? categories_1.default.PNDisconnectedUnexpectedlyCategory : status.category;
this.isOnline = false;
this.disconnect();
this.listenerManager.announceStatus(Object.assign(Object.assign({}, status), { category }));
else if (status.category === categories_1.default.PNBadRequestCategory) {
this.stopHeartbeatTimer();
this.listenerManager.announceStatus(status);
}
else
this.listenerManager.announceStatus(status);
Expand Down
38 changes: 36 additions & 2 deletions lib/core/pubnub-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,22 @@ class PubNubCore {
*/
makeUnsubscribe(parameters, callback) {
if (process.env.PRESENCE_MODULE !== 'disabled') {
this.sendRequest(new leave_1.PresenceLeaveRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet })), callback);
// Filtering out presence channels and groups.
let { channels, channelGroups } = parameters;
if (channelGroups)
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
if (channels)
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
// Complete immediately request only for presence channels.
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
return callback({
error: false,
operation: operations_1.default.PNUnsubscribeOperation,
category: categories_1.default.PNAcknowledgmentCategory,
statusCode: 200,
});
}
this.sendRequest(new leave_1.PresenceLeaveRequest({ channels, channelGroups, keySet: this._configuration.keySet }), callback);
}
else
throw new Error('Unsubscription error: presence module disabled');
Expand Down Expand Up @@ -1284,7 +1299,26 @@ class PubNubCore {
heartbeat(parameters, callback) {
return __awaiter(this, void 0, void 0, function* () {
if (process.env.PRESENCE_MODULE !== 'disabled') {
const request = new heartbeat_1.HeartbeatRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet }));
// Filtering out presence channels and groups.
let { channels, channelGroups } = parameters;
if (channelGroups)
channelGroups = channelGroups.filter((channelGroup) => !channelGroup.endsWith('-pnpres'));
if (channels)
channels = channels.filter((channel) => !channel.endsWith('-pnpres'));
// Complete immediately request only for presence channels.
if ((channelGroups !== null && channelGroups !== void 0 ? channelGroups : []).length === 0 && (channels !== null && channels !== void 0 ? channels : []).length === 0) {
const responseStatus = {
error: false,
operation: operations_1.default.PNHeartbeatOperation,
category: categories_1.default.PNAcknowledgmentCategory,
statusCode: 200,
};
if (callback)
return callback(responseStatus, {});
return Promise.resolve(responseStatus);
}
const request = new heartbeat_1.HeartbeatRequest(Object.assign(Object.assign({}, parameters), { channels,
channelGroups, keySet: this._configuration.keySet }));
if (callback)
return this.sendRequest(request, callback);
return this.sendRequest(request);
Expand Down
Loading

0 comments on commit b36cf02

Please sign in to comment.