UNPKG

@twilio/voice-sdk

Version:
274 lines 20.7 kB
/** * @packageDocumentation * @module Voice * @internalapi */ // @ts-nocheck import { EventEmitter } from 'events'; import Log from './log'; import request from './request'; /** * Builds Endpoint Analytics (EA) event payloads and sends them to * the EA server. * @constructor * @param {String} productName - Name of the product publishing events. * @param {String} token - The JWT token to use to authenticate with * the EA server. * @param {EventPublisher.Options} options * @property {Boolean} isEnabled - Whether or not this publisher is publishing * to the server. Currently ignores the request altogether, in the future this * may store them in case publishing is re-enabled later. Defaults to true. */ /** * @typedef {Object} EventPublisher.Options * @property {Object} [metadata=undefined] - A publisher_metadata object to send * with each payload. * @property {String} [host='eventgw.twilio.com'] - The host address of the EA * server to publish to. * @property {Object|Function} [defaultPayload] - A default payload to extend * when creating and sending event payloads. Also takes a function that * should return an object representing the default payload. This is * useful for fields that should always be present when they are * available, but are not always available. */ class EventPublisher extends EventEmitter { constructor(productName, token, options) { super(); if (!(this instanceof EventPublisher)) { return new EventPublisher(productName, token, options); } // Apply default options options = Object.assign({ defaultPayload() { return {}; } }, options); let defaultPayload = options.defaultPayload; if (typeof defaultPayload !== 'function') { defaultPayload = () => Object.assign({}, options.defaultPayload); } let isEnabled = true; const metadata = Object.assign({ app_name: undefined, app_version: undefined }, options.metadata); Object.defineProperties(this, { _defaultPayload: { value: defaultPayload }, _host: { value: options.host, writable: true }, _isEnabled: { get() { return isEnabled; }, set(_isEnabled) { isEnabled = _isEnabled; }, }, _log: { value: new Log('EventPublisher') }, _request: { value: options.request || request, writable: true }, _token: { value: token, writable: true }, isEnabled: { enumerable: true, get() { return isEnabled; }, }, metadata: { enumerable: true, get() { return metadata; }, }, productName: { enumerable: true, value: productName }, token: { enumerable: true, get() { return this._token; }, }, }); } } /** * Post to an EA server. * @private * @param {String} endpointName - Endpoint to post the event to * @param {String} level - ['debug', 'info', 'warning', 'error'] * @param {String} group - The name of the group the event belongs to. * @param {String} name - The designated event name. * @param {?Object} [payload=null] - The payload to pass. This will be extended * onto the default payload object, if one exists. * @param {?Connection} [connection=null] - The {@link Connection} which is posting this payload. * @param {?Boolean} [force=false] - Whether or not to send this even if * publishing is disabled. * @returns {Promise} Fulfilled if the HTTP response is 20x. */ EventPublisher.prototype._post = function _post(endpointName, level, group, name, payload, connection, force) { if ((!this.isEnabled && !force) || !this._host) { this._log.debug('Publishing cancelled', JSON.stringify({ isEnabled: this.isEnabled, force, host: this._host })); return Promise.resolve(); } if (!connection || ((!connection.parameters || !connection.parameters.CallSid) && !connection.outboundConnectionId)) { if (!connection) { this._log.debug('Publishing cancelled. Missing connection object'); } else { this._log.debug('Publishing cancelled. Missing connection info', JSON.stringify({ outboundConnectionId: connection.outboundConnectionId, parameters: connection.parameters, })); } return Promise.resolve(); } const event = { group, level: level.toUpperCase(), name, payload: (payload && payload.forEach) ? payload.slice(0) : Object.assign(this._defaultPayload(connection), payload), payload_type: 'application/json', private: false, publisher: this.productName, timestamp: (new Date()).toISOString(), }; if (this.metadata) { event.publisher_metadata = this.metadata; } if (endpointName === 'EndpointEvents') { this._log.debug('Publishing insights', JSON.stringify({ endpointName, event, force, host: this._host })); } const requestParams = { body: event, headers: { 'Content-Type': 'application/json', 'X-Twilio-Token': this.token, }, url: `https://${this._host}/v4/${endpointName}`, }; return new Promise((resolve, reject) => { this._request.post(requestParams, err => { if (err) { this.emit('error', err); reject(err); } else { resolve(); } }); }).catch(e => { this._log.error(`Unable to post ${group} ${name} event to Insights. Received error: ${e}`); }); }; /** * Post an event to the EA server. Use this method when the level * is dynamic. Otherwise, it's better practice to use the sugar * methods named for the specific level. * @param {String} level - ['debug', 'info', 'warning', 'error'] * @param {String} group - The name of the group the event belongs to. * @param {String} name - The designated event name. * @param {?Object} [payload=null] - The payload to pass. This will be extended * onto the default payload object, if one exists. * @param {?Connection} [connection=null] - The {@link Connection} which is posting this payload. * @returns {Promise} Fulfilled if the HTTP response is 20x. */ EventPublisher.prototype.post = function post(level, group, name, payload, connection, force) { return this._post('EndpointEvents', level, group, name, payload, connection, force); }; /** * Post a debug-level event to the EA server. * @param {String} group - The name of the group the event belongs to. * @param {String} name - The designated event name. * @param {?Object} [payload=null] - The payload to pass. This will be extended * onto the default payload object, if one exists. * @param {?Connection} [connection=null] - The {@link Connection} which is posting this payload. * @returns {Promise} Fulfilled if the HTTP response is 20x. */ EventPublisher.prototype.debug = function debug(group, name, payload, connection) { return this.post('debug', group, name, payload, connection); }; /** * Post an info-level event to the EA server. * @param {String} group - The name of the group the event belongs to. * @param {String} name - The designated event name. * @param {?Object} [payload=null] - The payload to pass. This will be extended * onto the default payload object, if one exists. * @param {?Connection} [connection=null] - The {@link Connection} which is posting this payload. * @returns {Promise} Fulfilled if the HTTP response is 20x. */ EventPublisher.prototype.info = function info(group, name, payload, connection) { return this.post('info', group, name, payload, connection); }; /** * Post a warning-level event to the EA server. * @param {String} group - The name of the group the event belongs to. * @param {String} name - The designated event name. * @param {?Object} [payload=null] - The payload to pass. This will be extended * onto the default payload object, if one exists. * @param {?Connection} [connection=null] - The {@link Connection} which is posting this payload. * @returns {Promise} Fulfilled if the HTTP response is 20x. */ EventPublisher.prototype.warn = function warn(group, name, payload, connection) { return this.post('warning', group, name, payload, connection); }; /** * Post an error-level event to the EA server. * @param {String} group - The name of the group the event belongs to. * @param {String} name - The designated event name. * @param {?Object} [payload=null] - The payload to pass. This will be extended * onto the default payload object, if one exists. * @param {?Connection} [connection=null] - The {@link Connection} which is posting this payload. * @returns {Promise} Fulfilled if the HTTP response is 20x. */ EventPublisher.prototype.error = function error(group, name, payload, connection) { return this.post('error', group, name, payload, connection); }; /** * Post a metrics event to the EA server. * @param {String} group - The name of the group the event belongs to. * @param {String} name - The designated event name. * @param {Array<Object>} metrics - The metrics to post. * @param {?Object} [customFields] - Custom fields to append to each payload. * @returns {Promise} Fulfilled if the HTTP response is 20x. */ EventPublisher.prototype.postMetrics = function postMetrics(group, name, metrics, customFields, connection) { return new Promise(resolve => { const samples = metrics .map(formatMetric) .map(sample => Object.assign(sample, customFields)); resolve(this._post('EndpointMetrics', 'info', group, name, samples, connection)); }); }; /** * Update the host address of the insights server to publish to. * @param {String} host - The new host address of the insights server. */ EventPublisher.prototype.setHost = function setHost(host) { this._host = host; }; /** * Update the token to use to authenticate requests. * @param {string} token * @returns {void} */ EventPublisher.prototype.setToken = function setToken(token) { this._token = token; }; /** * Enable the publishing of events. */ EventPublisher.prototype.enable = function enable() { this._isEnabled = true; }; /** * Disable the publishing of events. */ EventPublisher.prototype.disable = function disable() { this._isEnabled = false; }; function formatMetric(sample) { return { audio_codec: sample.codecName, audio_level_in: sample.audioInputLevel, audio_level_out: sample.audioOutputLevel, bytes_received: sample.bytesReceived, bytes_sent: sample.bytesSent, call_volume_input: sample.inputVolume, call_volume_output: sample.outputVolume, jitter: sample.jitter, mos: sample.mos && (Math.round(sample.mos * 100) / 100), packets_lost: sample.packetsLost, packets_lost_fraction: sample.packetsLostFraction && (Math.round(sample.packetsLostFraction * 100) / 100), packets_received: sample.packetsReceived, rtt: sample.rtt, timestamp: (new Date(sample.timestamp)).toISOString(), total_bytes_received: sample.totals.bytesReceived, total_bytes_sent: sample.totals.bytesSent, total_packets_lost: sample.totals.packetsLost, total_packets_received: sample.totals.packetsReceived, total_packets_sent: sample.totals.packetsSent, }; } export default EventPublisher; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXZlbnRwdWJsaXNoZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9saWIvdHdpbGlvL2V2ZW50cHVibGlzaGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7O0dBSUc7QUFDSCxjQUFjO0FBQ2QsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEdBQUcsTUFBTSxPQUFPLENBQUM7QUFDeEIsT0FBTyxPQUFPLE1BQU0sV0FBVyxDQUFDO0FBRWhDOzs7Ozs7Ozs7OztHQVdHO0FBQ0g7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLGNBQWUsU0FBUSxZQUFZO0lBQ3ZDLFlBQVksV0FBVyxFQUFFLEtBQUssRUFBRSxPQUFPO1FBQ3JDLEtBQUssRUFBRSxDQUFDO1FBRVIsSUFBSSxDQUFDLENBQUMsSUFBSSxZQUFZLGNBQWMsQ0FBQyxFQUFFO1lBQ3JDLE9BQU8sSUFBSSxjQUFjLENBQUMsV0FBVyxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQztTQUN4RDtRQUVELHdCQUF3QjtRQUN4QixPQUFPLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLGNBQWMsS0FBSyxPQUFPLEVBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBRXZFLElBQUksY0FBYyxHQUFHLE9BQU8sQ0FBQyxjQUFjLENBQUM7UUFFNUMsSUFBSSxPQUFPLGNBQWMsS0FBSyxVQUFVLEVBQUU7WUFDeEMsY0FBYyxHQUFHLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRyxFQUFFLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0FBQztTQUNuRTtRQUVELElBQUksU0FBUyxHQUFHLElBQUksQ0FBQztRQUNyQixNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLEVBQUUsT0FBTyxDQUFD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