UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

369 lines (368 loc) 15.3 kB
"use strict"; /** * Subscription REST API module. */ 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.SubscribeRequest = exports.BaseSubscribeRequest = exports.PubNubEventType = void 0; const pubnub_error_1 = require("../../errors/pubnub-error"); const request_1 = require("../components/request"); const operations_1 = __importDefault(require("../constants/operations")); const utils_1 = require("../utils"); // -------------------------------------------------------- // ---------------------- Defaults ------------------------ // -------------------------------------------------------- // region Defaults /** * Whether should subscribe to channels / groups presence announcements or not. */ const WITH_PRESENCE = false; // endregion // -------------------------------------------------------- // ------------------------ Types ------------------------- // -------------------------------------------------------- // region Types /** * PubNub-defined event types by payload. */ var PubNubEventType; (function (PubNubEventType) { /** * Presence change event. */ PubNubEventType[PubNubEventType["Presence"] = -2] = "Presence"; /** * Regular message event. * * **Note:** This is default type assigned for non-presence events if `e` field is missing. */ PubNubEventType[PubNubEventType["Message"] = -1] = "Message"; /** * Signal data event. */ PubNubEventType[PubNubEventType["Signal"] = 1] = "Signal"; /** * App Context object event. */ PubNubEventType[PubNubEventType["AppContext"] = 2] = "AppContext"; /** * Message reaction event. */ PubNubEventType[PubNubEventType["MessageAction"] = 3] = "MessageAction"; /** * Files event. */ PubNubEventType[PubNubEventType["Files"] = 4] = "Files"; })(PubNubEventType || (exports.PubNubEventType = PubNubEventType = {})); // endregion /** * Base subscription request implementation. * * Subscription request used in small variations in two cases: * - subscription manager * - event engine * * @internal */ class BaseSubscribeRequest extends request_1.AbstractRequest { constructor(parameters) { var _a, _b, _c; var _d, _e, _f; super({ cancellable: true }); this.parameters = parameters; // Apply default request parameters. (_a = (_d = this.parameters).withPresence) !== null && _a !== void 0 ? _a : (_d.withPresence = WITH_PRESENCE); (_b = (_e = this.parameters).channelGroups) !== null && _b !== void 0 ? _b : (_e.channelGroups = []); (_c = (_f = this.parameters).channels) !== null && _c !== void 0 ? _c : (_f.channels = []); } operation() { return operations_1.default.PNSubscribeOperation; } validate() { const { keySet: { subscribeKey }, channels, channelGroups, } = this.parameters; if (!subscribeKey) return 'Missing Subscribe Key'; if (!channels && !channelGroups) return '`channels` and `channelGroups` both should not be empty'; } parse(response) { return __awaiter(this, void 0, void 0, function* () { let serviceResponse; let responseText; try { responseText = request_1.AbstractRequest.decoder.decode(response.body); const parsedJson = JSON.parse(responseText); serviceResponse = parsedJson; } catch (error) { console.error('Error parsing JSON response:', error); } if (!serviceResponse) { throw new pubnub_error_1.PubNubError('Service response error, check status for details', (0, pubnub_error_1.createMalformedResponseError)(responseText, response.status)); } const events = serviceResponse.m .filter((envelope) => { const subscribable = envelope.b === undefined ? envelope.c : envelope.b; return ((this.parameters.channels && this.parameters.channels.includes(subscribable)) || (this.parameters.channelGroups && this.parameters.channelGroups.includes(subscribable))); }) .map((envelope) => { let { e: eventType } = envelope; // Resolve missing event type. eventType !== null && eventType !== void 0 ? eventType : (eventType = envelope.c.endsWith('-pnpres') ? PubNubEventType.Presence : PubNubEventType.Message); // Check whether payload is string (potentially encrypted data). if (eventType != PubNubEventType.Signal && typeof envelope.d === 'string') { if (eventType == PubNubEventType.Message) { return { type: PubNubEventType.Message, data: this.messageFromEnvelope(envelope), }; } return { type: PubNubEventType.Files, data: this.fileFromEnvelope(envelope), }; } else if (eventType == PubNubEventType.Message) { return { type: PubNubEventType.Message, data: this.messageFromEnvelope(envelope), }; } else if (eventType === PubNubEventType.Presence) { return { type: PubNubEventType.Presence, data: this.presenceEventFromEnvelope(envelope), }; } else if (eventType == PubNubEventType.Signal) { return { type: PubNubEventType.Signal, data: this.signalFromEnvelope(envelope), }; } else if (eventType === PubNubEventType.AppContext) { return { type: PubNubEventType.AppContext, data: this.appContextFromEnvelope(envelope), }; } else if (eventType === PubNubEventType.MessageAction) { return { type: PubNubEventType.MessageAction, data: this.messageActionFromEnvelope(envelope), }; } return { type: PubNubEventType.Files, data: this.fileFromEnvelope(envelope), }; }); return { cursor: { timetoken: serviceResponse.t.t, region: serviceResponse.t.r }, messages: events, }; }); } get headers() { var _a; return Object.assign(Object.assign({}, ((_a = super.headers) !== null && _a !== void 0 ? _a : {})), { accept: 'text/javascript' }); } // -------------------------------------------------------- // ------------------ Envelope parsing -------------------- // -------------------------------------------------------- // region Envelope parsing presenceEventFromEnvelope(envelope) { var _a; const { d: payload } = envelope; const [channel, subscription] = this.subscriptionChannelFromEnvelope(envelope); // Clean up channel and subscription name from presence suffix. const trimmedChannel = channel.replace('-pnpres', ''); // Backward compatibility with deprecated properties. const actualChannel = subscription !== null ? trimmedChannel : null; const subscribedChannel = subscription !== null ? subscription : trimmedChannel; if (typeof payload !== 'string') { if ('data' in payload) { // @ts-expect-error This is `state-change` object which should have `state` field. payload['state'] = payload.data; delete payload.data; } else if ('action' in payload && payload.action === 'interval') { payload.hereNowRefresh = (_a = payload.here_now_refresh) !== null && _a !== void 0 ? _a : false; delete payload.here_now_refresh; } } return Object.assign({ channel: trimmedChannel, subscription, actualChannel, subscribedChannel, timetoken: envelope.p.t }, payload); } messageFromEnvelope(envelope) { const [channel, subscription] = this.subscriptionChannelFromEnvelope(envelope); const [message, decryptionError] = this.decryptedData(envelope.d); // Backward compatibility with deprecated properties. const actualChannel = subscription !== null ? channel : null; const subscribedChannel = subscription !== null ? subscription : channel; // Basic message event payload. const event = { channel, subscription, actualChannel, subscribedChannel, timetoken: envelope.p.t, publisher: envelope.i, message, }; if (envelope.u) event.userMetadata = envelope.u; if (envelope.cmt) event.customMessageType = envelope.cmt; if (decryptionError) event.error = decryptionError; return event; } signalFromEnvelope(envelope) { const [channel, subscription] = this.subscriptionChannelFromEnvelope(envelope); const event = { channel, subscription, timetoken: envelope.p.t, publisher: envelope.i, message: envelope.d, }; if (envelope.u) event.userMetadata = envelope.u; if (envelope.cmt) event.customMessageType = envelope.cmt; return event; } messageActionFromEnvelope(envelope) { const [channel, subscription] = this.subscriptionChannelFromEnvelope(envelope); const action = envelope.d; return { channel, subscription, timetoken: envelope.p.t, publisher: envelope.i, event: action.event, data: Object.assign(Object.assign({}, action.data), { uuid: envelope.i }), }; } appContextFromEnvelope(envelope) { const [channel, subscription] = this.subscriptionChannelFromEnvelope(envelope); const object = envelope.d; return { channel, subscription, timetoken: envelope.p.t, message: object, }; } fileFromEnvelope(envelope) { const [channel, subscription] = this.subscriptionChannelFromEnvelope(envelope); const [file, decryptionError] = this.decryptedData(envelope.d); let errorMessage = decryptionError; // Basic file event payload. const event = { channel, subscription, timetoken: envelope.p.t, publisher: envelope.i, }; if (envelope.u) event.userMetadata = envelope.u; if (!file) errorMessage !== null && errorMessage !== void 0 ? errorMessage : (errorMessage = `File information payload is missing.`); else if (typeof file === 'string') errorMessage !== null && errorMessage !== void 0 ? errorMessage : (errorMessage = `Unexpected file information payload data type.`); else { event.message = file.message; if (file.file) { event.file = { id: file.file.id, name: file.file.name, url: this.parameters.getFileUrl({ id: file.file.id, name: file.file.name, channel }), }; } } if (envelope.cmt) event.customMessageType = envelope.cmt; if (errorMessage) event.error = errorMessage; return event; } // endregion subscriptionChannelFromEnvelope(envelope) { return [envelope.c, envelope.b === undefined ? envelope.c : envelope.b]; } /** * Decrypt provided `data`. * * @param [data] - Message or file information which should be decrypted if possible. * * @returns Tuple with decrypted data and decryption error (if any). */ decryptedData(data) { if (!this.parameters.crypto || typeof data !== 'string') return [data, undefined]; let payload; let error; try { const decryptedData = this.parameters.crypto.decrypt(data); payload = decryptedData instanceof ArrayBuffer ? JSON.parse(SubscribeRequest.decoder.decode(decryptedData)) : decryptedData; } catch (err) { payload = null; error = `Error while decrypting message content: ${err.message}`; } return [(payload !== null && payload !== void 0 ? payload : data), error]; } } exports.BaseSubscribeRequest = BaseSubscribeRequest; /** * Subscribe request. * * @internal */ class SubscribeRequest extends BaseSubscribeRequest { get path() { var _a; const { keySet: { subscribeKey }, channels, } = this.parameters; return `/v2/subscribe/${subscribeKey}/${(0, utils_1.encodeNames)((_a = channels === null || channels === void 0 ? void 0 : channels.sort()) !== null && _a !== void 0 ? _a : [], ',')}/0`; } get queryParameters() { const { channelGroups, filterExpression, heartbeat, state, timetoken, region } = this.parameters; const query = {}; if (channelGroups && channelGroups.length > 0) query['channel-group'] = channelGroups.sort().join(','); if (filterExpression && filterExpression.length > 0) query['filter-expr'] = filterExpression; if (heartbeat) query.heartbeat = heartbeat; if (state && Object.keys(state).length > 0) query['state'] = JSON.stringify(state); if (timetoken !== undefined && typeof timetoken === 'string') { if (timetoken.length > 0 && timetoken !== '0') query['tt'] = timetoken; } else if (timetoken !== undefined && timetoken > 0) query['tt'] = timetoken; if (region) query['tr'] = region; return query; } } exports.SubscribeRequest = SubscribeRequest;