pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
1,111 lines • 152 kB
JavaScript
"use strict";
/**
* Core PubNub API module.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PubNubCore = void 0;
// region Imports
// region Components
const event_dispatcher_1 = require("./components/event-dispatcher");
const subscription_manager_1 = require("./components/subscription-manager");
const push_payload_1 = __importDefault(require("./components/push_payload"));
const base64_codec_1 = require("./components/base64_codec");
const uuid_1 = __importDefault(require("./components/uuid"));
// endregion
// region Constants
const operations_1 = __importDefault(require("./constants/operations"));
const categories_1 = __importDefault(require("./constants/categories"));
// endregion
const pubnub_error_1 = require("../errors/pubnub-error");
const pubnub_api_error_1 = require("../errors/pubnub-api-error");
const retry_policy_1 = require("./components/retry-policy");
// region Event Engine
const presence_1 = require("../event-engine/presence/presence");
const event_engine_1 = require("../event-engine");
// endregion
// region Publish & Signal
const Publish = __importStar(require("./endpoints/publish"));
const Signal = __importStar(require("./endpoints/signal"));
// endregion
// region Subscription
const subscribe_1 = require("./endpoints/subscribe");
const receiveMessages_1 = require("./endpoints/subscriptionUtils/receiveMessages");
const handshake_1 = require("./endpoints/subscriptionUtils/handshake");
const subscription_1 = require("../entities/subscription");
// endregion
// region Presence
const get_state_1 = require("./endpoints/presence/get_state");
const set_state_1 = require("./endpoints/presence/set_state");
const heartbeat_1 = require("./endpoints/presence/heartbeat");
const leave_1 = require("./endpoints/presence/leave");
const where_now_1 = require("./endpoints/presence/where_now");
const here_now_1 = require("./endpoints/presence/here_now");
// endregion
// region Message Storage
const delete_messages_1 = require("./endpoints/history/delete_messages");
const message_counts_1 = require("./endpoints/history/message_counts");
const get_history_1 = require("./endpoints/history/get_history");
const fetch_messages_1 = require("./endpoints/fetch_messages");
// endregion
// region Message Actions
const get_message_actions_1 = require("./endpoints/actions/get_message_actions");
const add_message_action_1 = require("./endpoints/actions/add_message_action");
const remove_message_action_1 = require("./endpoints/actions/remove_message_action");
// endregion
// region File sharing
const publish_file_1 = require("./endpoints/file_upload/publish_file");
const get_file_url_1 = require("./endpoints/file_upload/get_file_url");
const delete_file_1 = require("./endpoints/file_upload/delete_file");
const list_files_1 = require("./endpoints/file_upload/list_files");
const send_file_1 = require("./endpoints/file_upload/send_file");
// endregion
// region PubNub Access Manager
const revoke_token_1 = require("./endpoints/access_manager/revoke_token");
const grant_token_1 = require("./endpoints/access_manager/grant_token");
const grant_1 = require("./endpoints/access_manager/grant");
const audit_1 = require("./endpoints/access_manager/audit");
// endregion
// region Entities
const subscription_capable_1 = require("../entities/interfaces/subscription-capable");
const channel_metadata_1 = require("../entities/channel-metadata");
const subscription_set_1 = require("../entities/subscription-set");
const channel_group_1 = require("../entities/channel-group");
const user_metadata_1 = require("../entities/user-metadata");
const channel_1 = require("../entities/channel");
// endregion
// region Channel Groups
const pubnub_channel_groups_1 = __importDefault(require("./pubnub-channel-groups"));
// endregion
// region Push Notifications
const pubnub_push_1 = __importDefault(require("./pubnub-push"));
const pubnub_objects_1 = __importDefault(require("./pubnub-objects"));
// endregion
// region Time
const Time = __importStar(require("./endpoints/time"));
const download_file_1 = require("./endpoints/file_upload/download_file");
const subscription_2 = require("./types/api/subscription");
const logger_1 = require("./interfaces/logger");
const utils_1 = require("./utils");
const categories_2 = __importDefault(require("./constants/categories"));
// endregion
/**
* Platform-agnostic PubNub client core.
*/
class PubNubCore {
/**
* Construct notification payload which will trigger push notification.
*
* @param title - Title which will be shown on notification.
* @param body - Payload which will be sent as part of notification.
*
* @returns Pre-formatted message payload which will trigger push notification.
*/
static notificationPayload(title, body) {
if (process.env.PUBLISH_MODULE !== 'disabled') {
return new push_payload_1.default(title, body);
}
else
throw new Error('Notification Payload error: publish module disabled');
}
/**
* Generate unique identifier.
*
* @returns Unique identifier.
*/
static generateUUID() {
return uuid_1.default.createUUID();
}
// endregion
/**
* Create and configure PubNub client core.
*
* @param configuration - PubNub client core configuration.
* @returns Configured and ready to use PubNub client.
*
* @internal
*/
constructor(configuration) {
/**
* List of subscribe capable objects with active subscriptions.
*
* Track list of {@link Subscription} and {@link SubscriptionSet} objects with active
* subscription.
*
* @internal
*/
this.eventHandleCapable = {};
/**
* Created entities.
*
* Map of entities which have been created to access.
*
* @internal
*/
this.entities = {};
this._configuration = configuration.configuration;
this.cryptography = configuration.cryptography;
this.tokenManager = configuration.tokenManager;
this.transport = configuration.transport;
this.crypto = configuration.crypto;
this.logger.debug('PubNub', () => ({
messageType: 'object',
message: configuration.configuration,
details: 'Create with configuration:',
ignoredKeys(key, obj) {
return typeof obj[key] === 'function' || key.startsWith('_');
},
}));
// API group entry points initialization.
if (process.env.APP_CONTEXT_MODULE !== 'disabled')
this._objects = new pubnub_objects_1.default(this._configuration, this.sendRequest.bind(this));
if (process.env.CHANNEL_GROUPS_MODULE !== 'disabled')
this._channelGroups = new pubnub_channel_groups_1.default(this._configuration.logger(), this._configuration.keySet, this.sendRequest.bind(this));
if (process.env.MOBILE_PUSH_MODULE !== 'disabled')
this._push = new pubnub_push_1.default(this._configuration.logger(), this._configuration.keySet, this.sendRequest.bind(this));
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
// Prepare for a real-time events announcement.
this.eventDispatcher = new event_dispatcher_1.EventDispatcher();
if (this._configuration.enableEventEngine) {
if (process.env.SUBSCRIBE_EVENT_ENGINE_MODULE !== 'disabled') {
this.logger.debug('PubNub', 'Using new subscription loop management.');
let heartbeatInterval = this._configuration.getHeartbeatInterval();
this.presenceState = {};
if (process.env.PRESENCE_MODULE !== 'disabled') {
if (heartbeatInterval) {
this.presenceEventEngine = new presence_1.PresenceEventEngine({
heartbeat: (parameters, callback) => {
this.logger.trace('PresenceEventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Heartbeat with parameters:',
}));
return this.heartbeat(parameters, callback);
},
leave: (parameters) => {
this.logger.trace('PresenceEventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Leave with parameters:',
}));
this.makeUnsubscribe(parameters, () => { });
},
heartbeatDelay: () => new Promise((resolve, reject) => {
heartbeatInterval = this._configuration.getHeartbeatInterval();
if (!heartbeatInterval)
reject(new pubnub_error_1.PubNubError('Heartbeat interval has been reset.'));
else
setTimeout(resolve, heartbeatInterval * 1000);
}),
emitStatus: (status) => this.emitStatus(status),
config: this._configuration,
presenceState: this.presenceState,
});
}
}
this.eventEngine = new event_engine_1.EventEngine({
handshake: (parameters) => {
this.logger.trace('EventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Handshake with parameters:',
ignoredKeys: ['abortSignal', 'crypto', 'timeout', 'keySet', 'getFileUrl'],
}));
return this.subscribeHandshake(parameters);
},
receiveMessages: (parameters) => {
this.logger.trace('EventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Receive messages with parameters:',
ignoredKeys: ['abortSignal', 'crypto', 'timeout', 'keySet', 'getFileUrl'],
}));
return this.subscribeReceiveMessages(parameters);
},
delay: (amount) => new Promise((resolve) => setTimeout(resolve, amount)),
join: (parameters) => {
var _a, _b;
this.logger.trace('EventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Join with parameters:',
}));
if (parameters && ((_a = parameters.channels) !== null && _a !== void 0 ? _a : []).length === 0 && ((_b = parameters.groups) !== null && _b !== void 0 ? _b : []).length === 0) {
this.logger.trace('EventEngine', "Ignoring 'join' announcement request.");
return;
}
this.join(parameters);
},
leave: (parameters) => {
var _a, _b;
this.logger.trace('EventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Leave with parameters:',
}));
if (parameters && ((_a = parameters.channels) !== null && _a !== void 0 ? _a : []).length === 0 && ((_b = parameters.groups) !== null && _b !== void 0 ? _b : []).length === 0) {
this.logger.trace('EventEngine', "Ignoring 'leave' announcement request.");
return;
}
this.leave(parameters);
},
leaveAll: (parameters) => {
this.logger.trace('EventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Leave all with parameters:',
}));
this.leaveAll(parameters);
},
presenceReconnect: (parameters) => {
this.logger.trace('EventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Reconnect with parameters:',
}));
this.presenceReconnect(parameters);
},
presenceDisconnect: (parameters) => {
this.logger.trace('EventEngine', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Disconnect with parameters:',
}));
this.presenceDisconnect(parameters);
},
presenceState: this.presenceState,
config: this._configuration,
emitMessages: (cursor, events) => {
try {
this.logger.debug('EventEngine', () => {
const hashedEvents = events.map((event) => {
const pn_mfp = event.type === subscribe_1.PubNubEventType.Message || event.type === subscribe_1.PubNubEventType.Signal
? (0, utils_1.messageFingerprint)(event.data.message)
: undefined;
return pn_mfp ? { type: event.type, data: Object.assign(Object.assign({}, event.data), { pn_mfp }) } : event;
});
return { messageType: 'object', message: hashedEvents, details: 'Received events:' };
});
events.forEach((event) => this.emitEvent(cursor, event));
}
catch (e) {
const errorStatus = {
error: true,
category: categories_1.default.PNUnknownCategory,
errorData: e,
statusCode: 0,
};
this.emitStatus(errorStatus);
}
},
emitStatus: (status) => this.emitStatus(status),
});
}
else
throw new Error('Event Engine error: subscription event engine module disabled');
}
else {
if (process.env.SUBSCRIBE_MANAGER_MODULE !== 'disabled') {
this.logger.debug('PubNub', 'Using legacy subscription loop management.');
this.subscriptionManager = new subscription_manager_1.SubscriptionManager(this._configuration, (cursor, event) => {
try {
this.emitEvent(cursor, event);
}
catch (e) {
const errorStatus = {
error: true,
category: categories_1.default.PNUnknownCategory,
errorData: e,
statusCode: 0,
};
this.emitStatus(errorStatus);
}
}, this.emitStatus.bind(this), (parameters, callback) => {
this.logger.trace('SubscriptionManager', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Subscribe with parameters:',
ignoredKeys: ['crypto', 'timeout', 'keySet', 'getFileUrl'],
}));
this.makeSubscribe(parameters, callback);
}, (parameters, callback) => {
this.logger.trace('SubscriptionManager', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Heartbeat with parameters:',
ignoredKeys: ['crypto', 'timeout', 'keySet', 'getFileUrl'],
}));
return this.heartbeat(parameters, callback);
}, (parameters, callback) => {
this.logger.trace('SubscriptionManager', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Leave with parameters:',
}));
this.makeUnsubscribe(parameters, callback);
}, this.time.bind(this));
}
else
throw new Error('Subscription Manager error: subscription manager module disabled');
}
}
}
// --------------------------------------------------------
// -------------------- Configuration ----------------------
// --------------------------------------------------------
// region Configuration
/**
* PubNub client configuration.
*
* @returns Currently user PubNub client configuration.
*/
get configuration() {
return this._configuration;
}
/**
* Current PubNub client configuration.
*
* @returns Currently user PubNub client configuration.
*
* @deprecated Use {@link configuration} getter instead.
*/
get _config() {
return this.configuration;
}
/**
* REST API endpoint access authorization key.
*
* It is required to have `authorization key` with required permissions to access REST API
* endpoints when `PAM` enabled for user key set.
*/
get authKey() {
var _a;
return (_a = this._configuration.authKey) !== null && _a !== void 0 ? _a : undefined;
}
/**
* REST API endpoint access authorization key.
*
* It is required to have `authorization key` with required permissions to access REST API
* endpoints when `PAM` enabled for user key set.
*/
getAuthKey() {
return this.authKey;
}
/**
* Change REST API endpoint access authorization key.
*
* @param authKey - New authorization key which should be used with new requests.
*/
setAuthKey(authKey) {
this.logger.debug('PubNub', `Set auth key: ${authKey}`);
this._configuration.setAuthKey(authKey);
if (this.onAuthenticationChange)
this.onAuthenticationChange(authKey);
}
/**
* Get a PubNub client user identifier.
*
* @returns Current PubNub client user identifier.
*/
get userId() {
return this._configuration.userId;
}
/**
* Change the current PubNub client user identifier.
*
* **Important:** Change won't affect ongoing REST API calls.
* **Warning:** Because ongoing REST API calls won't be canceled there could happen unexpected events like implicit
* `join` event for the previous `userId` after a long-poll subscribe request will receive a response. To avoid this
* it is advised to unsubscribe from all/disconnect before changing `userId`.
*
* @param value - New PubNub client user identifier.
*
* @throws Error empty user identifier has been provided.
*/
set userId(value) {
if (!value || typeof value !== 'string' || value.trim().length === 0) {
const error = new Error('Missing or invalid userId parameter. Provide a valid string userId');
this.logger.error('PubNub', () => ({ messageType: 'error', message: error }));
throw error;
}
this.logger.debug('PubNub', `Set user ID: ${value}`);
this._configuration.userId = value;
if (this.onUserIdChange)
this.onUserIdChange(this._configuration.userId);
}
/**
* Get a PubNub client user identifier.
*
* @returns Current PubNub client user identifier.
*/
getUserId() {
return this._configuration.userId;
}
/**
* Change the current PubNub client user identifier.
*
* **Important:** Change won't affect ongoing REST API calls.
*
* @param value - New PubNub client user identifier.
*
* @throws Error empty user identifier has been provided.
*/
setUserId(value) {
this.userId = value;
}
/**
* Real-time updates filtering expression.
*
* @returns Filtering expression.
*/
get filterExpression() {
var _a;
return (_a = this._configuration.getFilterExpression()) !== null && _a !== void 0 ? _a : undefined;
}
/**
* Real-time updates filtering expression.
*
* @returns Filtering expression.
*/
getFilterExpression() {
return this.filterExpression;
}
/**
* Update real-time updates filtering expression.
*
* @param expression - New expression which should be used or `undefined` to disable filtering.
*/
set filterExpression(expression) {
this.logger.debug('PubNub', `Set filter expression: ${expression}`);
this._configuration.setFilterExpression(expression);
}
/**
* Update real-time updates filtering expression.
*
* @param expression - New expression which should be used or `undefined` to disable filtering.
*/
setFilterExpression(expression) {
this.logger.debug('PubNub', `Set filter expression: ${expression}`);
this.filterExpression = expression;
}
/**
* Dta encryption / decryption key.
*
* @returns Currently used key for data encryption / decryption.
*/
get cipherKey() {
return this._configuration.getCipherKey();
}
/**
* Change data encryption / decryption key.
*
* @param key - New key which should be used for data encryption / decryption.
*/
set cipherKey(key) {
this._configuration.setCipherKey(key);
}
/**
* Change data encryption / decryption key.
*
* @param key - New key which should be used for data encryption / decryption.
*/
setCipherKey(key) {
this.logger.debug('PubNub', `Set cipher key: ${key}`);
this.cipherKey = key;
}
/**
* Change a heartbeat requests interval.
*
* @param interval - New presence request heartbeat intervals.
*/
set heartbeatInterval(interval) {
var _a;
this.logger.debug('PubNub', `Set heartbeat interval: ${interval}`);
this._configuration.setHeartbeatInterval(interval);
if (this.onHeartbeatIntervalChange)
this.onHeartbeatIntervalChange((_a = this._configuration.getHeartbeatInterval()) !== null && _a !== void 0 ? _a : 0);
}
/**
* Change a heartbeat requests interval.
*
* @param interval - New presence request heartbeat intervals.
*/
setHeartbeatInterval(interval) {
this.heartbeatInterval = interval;
}
/**
* Get registered loggers' manager.
*
* @returns Registered loggers' manager.
*/
get logger() {
return this._configuration.logger();
}
/**
* Get PubNub SDK version.
*
* @returns Current SDK version.
*/
getVersion() {
return this._configuration.getVersion();
}
/**
* Add framework's prefix.
*
* @param name - Name of the framework which would want to add own data into `pnsdk` suffix.
* @param suffix - Suffix with information about a framework.
*/
_addPnsdkSuffix(name, suffix) {
this.logger.debug('PubNub', `Add '${name}' 'pnsdk' suffix: ${suffix}`);
this._configuration._addPnsdkSuffix(name, suffix);
}
// --------------------------------------------------------
// ---------------------- Deprecated ----------------------
// --------------------------------------------------------
// region Deprecated
/**
* Get a PubNub client user identifier.
*
* @returns Current PubNub client user identifier.
*
* @deprecated Use the {@link getUserId} or {@link userId} getter instead.
*/
getUUID() {
return this.userId;
}
/**
* Change the current PubNub client user identifier.
*
* **Important:** Change won't affect ongoing REST API calls.
*
* @param value - New PubNub client user identifier.
*
* @throws Error empty user identifier has been provided.
*
* @deprecated Use the {@link PubNubCore#setUserId setUserId} or {@link PubNubCore#userId userId} setter instead.
*/
setUUID(value) {
this.logger.warn('PubNub', "'setUserId` is deprecated, please use 'setUserId' or 'userId' setter instead.");
this.logger.debug('PubNub', `Set UUID: ${value}`);
this.userId = value;
}
/**
* Custom data encryption method.
*
* @deprecated Instead use {@link cryptoModule} for data encryption.
*/
get customEncrypt() {
return this._configuration.getCustomEncrypt();
}
/**
* Custom data decryption method.
*
* @deprecated Instead use {@link cryptoModule} for data decryption.
*/
get customDecrypt() {
return this._configuration.getCustomDecrypt();
}
// endregion
// endregion
// --------------------------------------------------------
// ---------------------- Entities ------------------------
// --------------------------------------------------------
// region Entities
/**
* Create a `Channel` entity.
*
* Entity can be used for the interaction with the following API:
* - `subscribe`
*
* @param name - Unique channel name.
* @returns `Channel` entity.
*/
channel(name) {
let channel = this.entities[`${name}_ch`];
if (!channel)
channel = this.entities[`${name}_ch`] = new channel_1.Channel(name, this);
return channel;
}
/**
* Create a `ChannelGroup` entity.
*
* Entity can be used for the interaction with the following API:
* - `subscribe`
*
* @param name - Unique channel group name.
* @returns `ChannelGroup` entity.
*/
channelGroup(name) {
let channelGroup = this.entities[`${name}_chg`];
if (!channelGroup)
channelGroup = this.entities[`${name}_chg`] = new channel_group_1.ChannelGroup(name, this);
return channelGroup;
}
/**
* Create a `ChannelMetadata` entity.
*
* Entity can be used for the interaction with the following API:
* - `subscribe`
*
* @param id - Unique channel metadata object identifier.
* @returns `ChannelMetadata` entity.
*/
channelMetadata(id) {
let metadata = this.entities[`${id}_chm`];
if (!metadata)
metadata = this.entities[`${id}_chm`] = new channel_metadata_1.ChannelMetadata(id, this);
return metadata;
}
/**
* Create a `UserMetadata` entity.
*
* Entity can be used for the interaction with the following API:
* - `subscribe`
*
* @param id - Unique user metadata object identifier.
* @returns `UserMetadata` entity.
*/
userMetadata(id) {
let metadata = this.entities[`${id}_um`];
if (!metadata)
metadata = this.entities[`${id}_um`] = new user_metadata_1.UserMetadata(id, this);
return metadata;
}
/**
* Create subscriptions set object.
*
* @param parameters - Subscriptions set configuration parameters.
*/
subscriptionSet(parameters) {
var _a, _b;
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
// Prepare a list of entities for a set.
const entities = [];
(_a = parameters.channels) === null || _a === void 0 ? void 0 : _a.forEach((name) => entities.push(this.channel(name)));
(_b = parameters.channelGroups) === null || _b === void 0 ? void 0 : _b.forEach((name) => entities.push(this.channelGroup(name)));
return new subscription_set_1.SubscriptionSet({ client: this, entities, options: parameters.subscriptionOptions });
}
else
throw new Error('Subscription set error: subscription module disabled');
}
/**
* Schedule request execution.
*
* @internal
*
* @param request - REST API request.
* @param [callback] - Request completion handler callback.
*
* @returns Asynchronous request execution and response parsing result or `void` in case if
* `callback` provided.
*
* @throws PubNubError in case of request processing error.
*/
sendRequest(request, callback) {
return __awaiter(this, void 0, void 0, function* () {
// Validate user-input.
const validationResult = request.validate();
if (validationResult) {
const validationError = (0, pubnub_error_1.createValidationError)(validationResult);
this.logger.error('PubNub', () => ({ messageType: 'error', message: validationError }));
if (callback)
return callback(validationError, null);
throw new pubnub_error_1.PubNubError('Validation failed, check status for details', validationError);
}
// Complete request configuration.
const transportRequest = request.request();
const operation = request.operation();
if ((transportRequest.formData && transportRequest.formData.length > 0) ||
operation === operations_1.default.PNDownloadFileOperation) {
// Set file upload / download request delay.
transportRequest.timeout = this._configuration.getFileTimeout();
}
else {
if (operation === operations_1.default.PNSubscribeOperation ||
operation === operations_1.default.PNReceiveMessagesOperation)
transportRequest.timeout = this._configuration.getSubscribeTimeout();
else
transportRequest.timeout = this._configuration.getTransactionTimeout();
}
// API request processing status.
const status = {
error: false,
operation,
category: categories_1.default.PNAcknowledgmentCategory,
statusCode: 0,
};
const [sendableRequest, cancellationController] = this.transport.makeSendable(transportRequest);
/**
* **Important:** Because of multiple environments where JS SDK can be used, control over
* cancellation had to be inverted to let the transport provider solve a request cancellation task
* more efficiently. As a result, cancellation controller can be retrieved and used only after
* the request will be scheduled by the transport provider.
*/
request.cancellationController = cancellationController ? cancellationController : null;
return sendableRequest
.then((response) => {
status.statusCode = response.status;
// Handle a special case when request completed but not fully processed by PubNub service.
if (response.status !== 200 && response.status !== 204) {
const responseText = PubNubCore.decoder.decode(response.body);
const contentType = response.headers['content-type'];
if (contentType || contentType.indexOf('javascript') !== -1 || contentType.indexOf('json') !== -1) {
const json = JSON.parse(responseText);
if (typeof json === 'object' && 'error' in json && json.error && typeof json.error === 'object')
status.errorData = json.error;
}
else
status.responseText = responseText;
}
return request.parse(response);
})
.then((parsed) => {
// Notify callback (if possible).
if (callback)
return callback(status, parsed);
return parsed;
})
.catch((error) => {
const apiError = !(error instanceof pubnub_api_error_1.PubNubAPIError) ? pubnub_api_error_1.PubNubAPIError.create(error) : error;
// Notify callback (if possible).
if (callback) {
if (apiError.category !== categories_2.default.PNCancelledCategory) {
this.logger.error('PubNub', () => ({
messageType: 'error',
message: apiError.toPubNubError(operation, 'REST API request processing error, check status for details'),
}));
}
return callback(apiError.toStatus(operation), null);
}
const pubNubError = apiError.toPubNubError(operation, 'REST API request processing error, check status for details');
if (apiError.category !== categories_2.default.PNCancelledCategory)
this.logger.error('PubNub', () => ({ messageType: 'error', message: pubNubError }));
throw pubNubError;
});
});
}
/**
* Unsubscribe from all channels and groups.
*
* @param [isOffline] - Whether `offline` presence should be notified or not.
*/
destroy(isOffline = false) {
this.logger.info('PubNub', 'Destroying PubNub client.');
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
if (this._globalSubscriptionSet) {
this._globalSubscriptionSet.invalidate(true);
this._globalSubscriptionSet = undefined;
}
Object.values(this.eventHandleCapable).forEach((subscription) => subscription.invalidate(true));
this.eventHandleCapable = {};
if (this.subscriptionManager) {
this.subscriptionManager.unsubscribeAll(isOffline);
this.subscriptionManager.disconnect();
}
else if (this.eventEngine)
this.eventEngine.unsubscribeAll(isOffline);
}
if (process.env.PRESENCE_MODULE !== 'disabled') {
if (this.presenceEventEngine)
this.presenceEventEngine.leaveAll(isOffline);
}
}
/**
* Unsubscribe from all channels and groups.
*
* @deprecated Use {@link destroy} method instead.
*/
stop() {
this.logger.warn('PubNub', "'stop' is deprecated, please use 'destroy' instead.");
this.destroy();
}
/**
* Publish data to a specific channel.
*
* @param parameters - Request configuration parameters.
* @param [callback] - Request completion handler callback.
*
* @returns Asynchronous publish data response or `void` in case if `callback` provided.
*/
publish(parameters, callback) {
return __awaiter(this, void 0, void 0, function* () {
if (process.env.PUBLISH_MODULE !== 'disabled') {
this.logger.debug('PubNub', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Publish with parameters:',
}));
const isFireRequest = parameters.replicate === false && parameters.storeInHistory === false;
const request = new Publish.PublishRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet, crypto: this._configuration.getCryptoModule() }));
const logResponse = (response) => {
if (!response)
return;
this.logger.debug('PubNub', `${isFireRequest ? 'Fire' : 'Publish'} success with timetoken: ${response.timetoken}`);
};
if (callback)
return this.sendRequest(request, (status, response) => {
logResponse(response);
callback(status, response);
});
return this.sendRequest(request).then((response) => {
logResponse(response);
return response;
});
}
else
throw new Error('Publish error: publish module disabled');
});
}
/**
* Signal data to a specific channel.
*
* @param parameters - Request configuration parameters.
* @param [callback] - Request completion handler callback.
*
* @returns Asynchronous signal data response or `void` in case if `callback` provided.
*/
signal(parameters, callback) {
return __awaiter(this, void 0, void 0, function* () {
if (process.env.PUBLISH_MODULE !== 'disabled') {
this.logger.debug('PubNub', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Signal with parameters:',
}));
const request = new Signal.SignalRequest(Object.assign(Object.assign({}, parameters), { keySet: this._configuration.keySet }));
const logResponse = (response) => {
if (!response)
return;
this.logger.debug('PubNub', `Publish success with timetoken: ${response.timetoken}`);
};
if (callback)
return this.sendRequest(request, (status, response) => {
logResponse(response);
callback(status, response);
});
return this.sendRequest(request).then((response) => {
logResponse(response);
return response;
});
}
else
throw new Error('Publish error: publish module disabled');
});
}
/**
* `Fire` a data to a specific channel.
*
* @param parameters - Request configuration parameters.
* @param [callback] - Request completion handler callback.
*
* @returns Asynchronous signal data response or `void` in case if `callback` provided.
*
* @deprecated Use {@link publish} method instead.
*/
fire(parameters, callback) {
return __awaiter(this, void 0, void 0, function* () {
this.logger.debug('PubNub', () => ({
messageType: 'object',
message: Object.assign({}, parameters),
details: 'Fire with parameters:',
}));
callback !== null && callback !== void 0 ? callback : (callback = () => { });
return this.publish(Object.assign(Object.assign({}, parameters), { replicate: false, storeInHistory: false }), callback);
});
}
// endregion
// --------------------------------------------------------
// -------------------- Subscribe API ---------------------
// --------------------------------------------------------
// region Subscribe API
/**
* Global subscription set which supports legacy subscription interface.
*
* @returns Global subscription set.
*
* @internal
*/
get globalSubscriptionSet() {
if (!this._globalSubscriptionSet)
this._globalSubscriptionSet = this.subscriptionSet({});
return this._globalSubscriptionSet;
}
/**
* Subscription-based current timetoken.
*
* @returns Timetoken based on current timetoken plus diff between current and loop start time.
*
* @internal
*/
get subscriptionTimetoken() {
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
if (this.subscriptionManager)
return this.subscriptionManager.subscriptionTimetoken;
else if (this.eventEngine)
return this.eventEngine.subscriptionTimetoken;
}
return undefined;
}
/**
* Get list of channels on which PubNub client currently subscribed.
*
* @returns List of active channels.
*/
getSubscribedChannels() {
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
if (this.subscriptionManager)
return this.subscriptionManager.subscribedChannels;
else if (this.eventEngine)
return this.eventEngine.getSubscribedChannels();
}
else
throw new Error('Subscription error: subscription module disabled');
return [];
}
/**
* Get list of channel groups on which PubNub client currently subscribed.
*
* @returns List of active channel groups.
*/
getSubscribedChannelGroups() {
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
if (this.subscriptionManager)
return this.subscriptionManager.subscribedChannelGroups;
else if (this.eventEngine)
return this.eventEngine.getSubscribedChannelGroups();
}
else
throw new Error('Subscription error: subscription module disabled');
return [];
}
/**
* Register an events handler object ({@link Subscription} or {@link SubscriptionSet}) with an active subscription.
*
* @param subscription - {@link Subscription} or {@link SubscriptionSet} object.
* @param [cursor] - Subscription catchup timetoken.
* @param [subscriptions] - List of subscriptions for partial subscription loop update.
*
* @internal
*/
registerEventHandleCapable(subscription, cursor, subscriptions) {
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
this.logger.trace('PubNub', () => ({
messageType: 'object',
message: Object.assign(Object.assign({ subscription: subscription }, (cursor ? { cursor } : [])), (subscriptions ? { subscriptions } : {})),
details: `Register event handle capable:`,
}));
if (!this.eventHandleCapable[subscription.state.id])
this.eventHandleCapable[subscription.state.id] = subscription;
let subscriptionInput;
if (!subscriptions || subscriptions.length === 0)
subscriptionInput = subscription.subscriptionInput(false);
else {
subscriptionInput = new subscription_2.SubscriptionInput({});
subscriptions.forEach((subscription) => subscriptionInput.add(subscription.subscriptionInput(false)));
}
const parameters = {};
parameters.channels = subscriptionInput.channels;
parameters.channelGroups = subscriptionInput.channelGroups;
if (cursor)
parameters.timetoken = cursor.timetoken;
if (this.subscriptionManager)
this.subscriptionManager.subscribe(parameters);
else if (this.eventEngine)
this.eventEngine.subscribe(parameters);
}
}
/**
* Unregister an events handler object ({@link Subscription} or {@link SubscriptionSet}) with inactive subscription.
*
* @param subscription - {@link Subscription} or {@link SubscriptionSet} object.
* @param [subscriptions] - List of subscriptions for partial subscription loop update.
*
* @internal
*/
unregisterEventHandleCapable(subscription, subscriptions) {
if (process.env.SUBSCRIBE_MODULE !== 'disabled') {
if (!this.eventHandleCapable[subscription.state.id])
return;
const inUseSubscriptions = [];
this.logger.trace('PubNub', () => ({
messageType: 'object',
message: { subscription: subscription, subscriptions },
details: `Unregister event handle capable:`,
}));
// Check whether only subscription object has been passed to be unregistered.
let shouldDeleteEventHandler = !subscriptions || subscriptions.length === 0;
// Check whether subscription set is unregistering with all managed Subscription objects,
if (!shouldDeleteEventHandler &&
subscription instanceof subscription_set_1.SubscriptionSet &&
subscription.subscriptions.length === (subscriptions === null || subscriptions === void 0 ? void 0 : subscriptions.length))
shouldDeleteEventHandler = subscription.subscriptions.every((sub) => subscriptions.includes(sub));
if (shouldDeleteEventHandler)
delete this.eventHandleCapable[subscription.state.id];
let subscriptionInput;
if (!subscriptions || subscriptions.length === 0) {
subscriptionInput = subscription.subscriptionInput(true);
if (subscriptionInput.isEmpty)
inUseSubscriptions.push(subscription);
}
else {
subscriptionInput = new subscription_2.SubscriptionInput({});
subscriptions.forEach((subscription) => {
const input = subscription.subscriptionInput(true);
if (input.isEmpty)
inUseSubscriptions.push(subscription);
else
subscriptionInput.add(input);
});
}
if (inUseSubscriptions.length > 0) {
this.logger.trace('PubNub', () => {
const entities = [];
if (inUseSubscriptions[0] instanceof subscription_set_1.SubscriptionSet) {
inUseSubscriptions[0].subscriptions.forEach((subscription) => entities.push(subscription.state.entity));
}
else
inUseSubscriptions.forEach((subscription) => entities.push(subscr