Skip to content

Commit

Permalink
feat(subscribe): emit PNDisconnectedUnexpectedlyCategory
Browse files Browse the repository at this point in the history
Emit 'PNDisconnectedUnexpectedlyCategory'  in cases when client receives bad request or
unexpected / malformed service response.

refactor(request): move deserialized error parse into abstract request

Move error / malformed response handling into `AbstractRequest` to simplify actual endpoint
classes.
  • Loading branch information
parfeon committed Feb 18, 2025
1 parent 2a0535c commit a14fd49
Show file tree
Hide file tree
Showing 129 changed files with 664 additions and 1,686 deletions.
501 changes: 150 additions & 351 deletions dist/web/pubnub.js

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions dist/web/pubnub.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,15 @@
const clients = getClients();
if (clients.length === 0)
return;
failure(clients, error);
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');
}
failure(clients, fetchError);
});
}))();
};
Expand All @@ -564,7 +572,7 @@
delete serviceRequests[requestId];
// Abort request if possible.
if (controller)
controller.abort();
controller.abort('Cancel request');
}
};
/**
Expand Down Expand Up @@ -1081,12 +1089,12 @@
message = error.message;
name = error.name;
}
if (name === 'AbortError') {
if (message.toLowerCase().includes('timeout'))
type = 'TIMEOUT';
else if (name === 'AbortError' || message.toLowerCase().includes('cancel')) {
message = 'Request aborted';
type = 'ABORTED';
}
else if (message === 'Request timeout')
type = 'TIMEOUT';
return {
type: 'request-process-error',
clientIdentifier: '',
Expand Down
4 changes: 2 additions & 2 deletions dist/web/pubnub.worker.min.js

Large diffs are not rendered by default.

34 changes: 23 additions & 11 deletions lib/core/components/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractRequest = void 0;
const transport_request_1 = require("../types/transport-request");
const pubnub_error_1 = require("../../errors/pubnub-error");
const uuid_1 = __importDefault(require("./uuid"));
const pubnub_api_error_1 = require("../../errors/pubnub-api-error");
/**
* Base REST API request class.
*
Expand Down Expand Up @@ -62,10 +64,12 @@ class AbstractRequest {
}
/**
* Abort request if possible.
*
* @param [reason] Information about why request has been cancelled.
*/
abort() {
abort(reason) {
if (this && this.cancellationController)
this.cancellationController.abort();
this.cancellationController.abort(reason);
}
/**
* Target REST API endpoint operation type.
Expand All @@ -84,11 +88,11 @@ class AbstractRequest {
/**
* Parse service response.
*
* @param _response - Raw service response which should be parsed.
* @param response - Raw service response which should be parsed.
*/
parse(_response) {
parse(response) {
return __awaiter(this, void 0, void 0, function* () {
throw Error('Should be implemented by subclass.');
return this.deserializeResponse(response);
});
}
/**
Expand Down Expand Up @@ -160,21 +164,29 @@ class AbstractRequest {
*
* @param response - Transparent response object with headers and body information.
*
* @returns Deserialized data or `undefined` in case of `JSON.parse(..)` error.
* @returns Deserialized service response data.
*
* @throws {Error} if received service response can't be processed (has unexpected content-type or can't be parsed as
* JSON).
*/
deserializeResponse(response) {
const responseText = AbstractRequest.decoder.decode(response.body);
const contentType = response.headers['content-type'];
let parsedJson;
if (!contentType || (contentType.indexOf('javascript') === -1 && contentType.indexOf('json') === -1))
return undefined;
const json = AbstractRequest.decoder.decode(response.body);
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createMalformedResponseError)(responseText, response.status));
try {
const parsedJson = JSON.parse(json);
return parsedJson;
parsedJson = JSON.parse(responseText);
}
catch (error) {
console.error('Error parsing JSON response:', error);
return undefined;
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createMalformedResponseError)(responseText, response.status));
}
// Throw and exception in case of client / server error.
if ('status' in parsedJson && typeof parsedJson.status === 'number' && parsedJson.status >= 400) {
throw pubnub_api_error_1.PubNubAPIError.create(response);
}
return parsedJson;
}
}
exports.AbstractRequest = AbstractRequest;
Expand Down
14 changes: 8 additions & 6 deletions lib/core/components/subscription-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriptionManager = void 0;
const reconnection_manager_1 = require("./reconnection_manager");
const categories_1 = __importDefault(require("../constants/categories"));
const deduping_manager_1 = require("./deduping_manager");
const categories_2 = __importDefault(require("../constants/categories"));
const deduping_manager_1 = require("./deduping_manager");
/**
* Subscription loop manager.
*
Expand Down Expand Up @@ -256,13 +256,15 @@ class SubscriptionManager {
this.reconnectionManager.startPolling();
this.listenerManager.announceStatus(status);
}
else if (status.category === categories_2.default.PNBadRequestCategory) {
this.stopHeartbeatTimer();
this.listenerManager.announceStatus(status);
else if (status.category === categories_2.default.PNBadRequestCategory ||
status.category == categories_2.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 {
else
this.listenerManager.announceStatus(status);
}
return;
}
if (this.storedTimetoken) {
Expand Down
8 changes: 8 additions & 0 deletions lib/core/constants/categories.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ var StatusCategory;
* Some API endpoints respond with request processing status w/o useful data.
*/
StatusCategory["PNAcknowledgmentCategory"] = "PNAcknowledgmentCategory";
/**
* PubNub service or intermediate "actor" returned unexpected response.
*
* There can be few sources of unexpected return with success code:
* - proxy server / VPN;
* - Wi-Fi hotspot authorization page.
*/
StatusCategory["PNMalformedResponseCategory"] = "PNMalformedResponseCategory";
/**
* Something strange happened; please check the logs.
*/
Expand Down
10 changes: 1 addition & 9 deletions lib/core/endpoints/access_manager/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuditRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
// --------------------------------------------------------
Expand Down Expand Up @@ -54,13 +52,7 @@ class AuditRequest extends request_1.AbstractRequest {
}
parse(response) {
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return serviceResponse.payload;
return this.deserializeResponse(response).payload;
});
}
get path() {
Expand Down
10 changes: 1 addition & 9 deletions lib/core/endpoints/access_manager/grant.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GrantRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
// --------------------------------------------------------
Expand Down Expand Up @@ -96,13 +94,7 @@ class GrantRequest extends request_1.AbstractRequest {
}
parse(response) {
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return serviceResponse.payload;
return this.deserializeResponse(response).payload;
});
}
get path() {
Expand Down
10 changes: 1 addition & 9 deletions lib/core/endpoints/access_manager/grant_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GrantTokenRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const transport_request_1 = require("../../types/transport-request");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
Expand Down Expand Up @@ -77,13 +75,7 @@ class GrantTokenRequest extends request_1.AbstractRequest {
}
parse(response) {
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return serviceResponse.data.token;
return this.deserializeResponse(response).data.token;
});
}
get path() {
Expand Down
13 changes: 4 additions & 9 deletions lib/core/endpoints/access_manager/revoke_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RevokeTokenRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const transport_request_1 = require("../../types/transport-request");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
Expand Down Expand Up @@ -47,14 +45,11 @@ class RevokeTokenRequest extends request_1.AbstractRequest {
return "token can't be empty";
}
parse(response) {
const _super = Object.create(null, {
parse: { get: () => super.parse }
});
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return {};
return _super.parse.call(this, response).then(() => ({}));
});
}
get path() {
Expand Down
13 changes: 4 additions & 9 deletions lib/core/endpoints/actions/add_message_action.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AddMessageActionRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const transport_request_1 = require("../../types/transport-request");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
Expand Down Expand Up @@ -56,14 +54,11 @@ class AddMessageActionRequest extends request_1.AbstractRequest {
return 'Action.type value exceed maximum length of 15';
}
parse(response) {
const _super = Object.create(null, {
parse: { get: () => super.parse }
});
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return { data: serviceResponse.data };
return _super.parse.call(this, response).then(({ data }) => ({ data }));
});
}
get path() {
Expand Down
7 changes: 0 additions & 7 deletions lib/core/endpoints/actions/get_message_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GetMessageActionsRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
const utils_1 = require("../../utils");
Expand All @@ -46,11 +44,6 @@ class GetMessageActionsRequest extends request_1.AbstractRequest {
parse(response) {
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
let start = null;
let end = null;
if (serviceResponse.data.length > 0) {
Expand Down
13 changes: 4 additions & 9 deletions lib/core/endpoints/actions/remove_message_action.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RemoveMessageAction = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const transport_request_1 = require("../../types/transport-request");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
Expand Down Expand Up @@ -50,14 +48,11 @@ class RemoveMessageAction extends request_1.AbstractRequest {
return 'Missing action timetoken';
}
parse(response) {
const _super = Object.create(null, {
parse: { get: () => super.parse }
});
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return { data: serviceResponse.data };
return _super.parse.call(this, response).then(({ data }) => ({ data }));
});
}
get path() {
Expand Down
13 changes: 4 additions & 9 deletions lib/core/endpoints/channel_groups/add_channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AddChannelGroupChannelsRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
const utils_1 = require("../../utils");
Expand Down Expand Up @@ -47,14 +45,11 @@ class AddChannelGroupChannelsRequest extends request_1.AbstractRequest {
return 'Missing channels';
}
parse(response) {
const _super = Object.create(null, {
parse: { get: () => super.parse }
});
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return {};
return _super.parse.call(this, response).then((_) => ({}));
});
}
get path() {
Expand Down
13 changes: 4 additions & 9 deletions lib/core/endpoints/channel_groups/delete_group.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeleteChannelGroupRequest = void 0;
const pubnub_error_1 = require("../../../errors/pubnub-error");
const pubnub_api_error_1 = require("../../../errors/pubnub-api-error");
const request_1 = require("../../components/request");
const operations_1 = __importDefault(require("../../constants/operations"));
const utils_1 = require("../../utils");
Expand All @@ -44,14 +42,11 @@ class DeleteChannelGroupRequest extends request_1.AbstractRequest {
return 'Missing Channel Group';
}
parse(response) {
const _super = Object.create(null, {
parse: { get: () => super.parse }
});
return __awaiter(this, void 0, void 0, function* () {
const serviceResponse = this.deserializeResponse(response);
if (!serviceResponse) {
throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createValidationError)('Unable to deserialize service response'));
}
else if (serviceResponse.status >= 400)
throw pubnub_api_error_1.PubNubAPIError.create(response);
return {};
return _super.parse.call(this, response).then((_) => ({}));
});
}
get path() {
Expand Down
Loading

0 comments on commit a14fd49

Please sign in to comment.