UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

1,111 lines 152 kB
"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