@twilio/voice-sdk
Version:
Twilio's JavaScript Voice SDK
274 lines • 20.7 kB
JavaScript
/**
* @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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