cl-speakeasy-sdk
Version:
CenturyLink SpeakEasy JavaScript SDK
1,524 lines • 1.01 MB
JavaScript
/**
* @class Ctl.speakeasy.Version
* @author Andrew Kovalenko <andrew@jaybirdgroup.com>
* @docauthor Andrew Kovalenko <andrew@jaybirdgroup.com>
* @private
*
* Holds version of the Ctl.speakeasy JavaScript library.
*/
var Ctlspeakeasy_Version, Ctlspeakeasy_Config, Ctl_Logger, Ctl_Utils, Ctl_Promise, Ctl_Ajax, Ctlmodelrequest_BaseRequest, Ctlmodelrequest_SubscriptionServiceIdentitiesRequest, Ctlmodelrequest_SubscriptionServiceCatalogRequest, Ctl_Subscription, Ctl_Error, fcs, Ctlspeakeasy_Notification, Ctlmodelrequest_AccessTokenRequest, Ctlmodelrequest_RefreshTokenRequest, Ctl_Auth, Ctlspeakeasy_EventEmitter, Ctlspeakeasy_CallInfo, Ctlspeakeasy_AudiotonesManager, Ctlspeakeasy_BaseCall, Ctlspeakeasy_IncomingCall, Ctlspeakeasy_OutgoingCall, Ctlspeakeasy_CallManager, SpeakEasy;
Ctlspeakeasy_Version = '0.1.8';
Ctlspeakeasy_Config = {
useConfig: 'cert',
settings: {
intg: { 'ctlServerURL': 'https://lab.iaf.centurylink.com:8889' },
cert: { 'ctlServerURL': 'https://lab.af.centurylink.com:8889' },
'loginURI': '/token',
'getSubscriptionServiceIdentitiesURI': '/SpeakEasyProvision/v1/util/products',
'getSubscriptionServiceCatalogURI': '/SpeakEasyProvision/v1/serviceProfile/{publicId}',
'subscribeServiceURI': '/{serviceName}/{identity}@{domain}/subscription',
'unsubscribeServiceURI': '/SpeakEasy/{identity}@ctlvoip.lab.centurylink/subscription/{channelId}',
'getInstantMessageURI': '/rest/version/{versionId}/user/{userId}/notification/{notificationChannelId}',
'postImByUseridURI': '/rest/version/{versionId}/user/{userId}/instantmessage',
'postImByDeviceidURI': '/rest/version/{versionId}/device/{deviceId}/instantmessage',
'defaultOutgoingCallDomain': 'ctlvoip.lab.centurylink',
'proxyForURLPatterns': [
'/rest/version/[0-9]+/user',
'/rest/version/latest'
],
'SEProxyPrependURL': '/SpeakEasy/'
},
fcsapi: {
'intg': {
'notificationType': 'websocket',
'restUrl': 'lab.iaf.centurylink.com',
'restPort': '8889',
'callAuditTimer': '30000',
'codecsToRemove': [
'103',
'104',
'105',
'106',
'107'
],
'protocol': 'https',
'clientControlled': 'true',
'services': [
'CallControl',
'custom'
],
'websocketProtocol': 'wss',
// 'websocketIP': 'www.intg104.centurylink.com',
// 'websocketPort': '8590',
'earlyMedia': true
},
'cert': {
'notificationType': 'websocketonly',
'restUrl': 'lab.af.centurylink.com',
'restPort': '8889',
'callAuditTimer': '30000',
'codecsToRemove': [
'103',
'104',
'105',
'106',
'107'
],
'protocol': 'https',
'clientControlled': 'true',
'services': [
'CallControl',
'custom'
],
'websocketProtocol': 'wss',
// 'websocketIP': 'www.intg104.centurylink.com',
// 'websocketPort': '8590',
'earlyMedia': true
}
},
callManager: {
'localVideoContainer': 'speakeasy_localVideoContainer',
'remoteVideoContainer': 'speakeasy_remoteVideoContainer',
'videoQuality': '640x480',
'baseToneUrl': '../dist/tones'
}
};
Ctl_Logger = function () {
var name = 'Logger';
/**
* Logger for internal usage
* @class Ctl.Logger
* @author Andrew Kovalenko <andrew@jaybirdgroup.com>
* @docauthor Andrew Kovalenko <andrew@jaybirdgroup.com>
* @private
* @param {String} name Name of the logger to expose
*/
function Logger(name) {
this.logEnabled = true;
this.init(name, true);
}
/**
* Methods to use with logger
* @type {Array}
*/
Logger.METHODS = [
'log',
'error',
'warn',
'info',
'debug',
'assert',
'clear',
'count',
'dir',
'dirxml',
'exception',
'group',
'groupCollapsed',
'groupEnd',
'profile',
'profileEnd',
'table',
'time',
'timeEnd',
'trace'
];
/**
* Initialize logger
*
* @private
* @param {String} name [description]
* @param {Boolean} logEnabled [description]
*/
Logger.prototype.init = function (name, logEnabled) {
this.name = name || 'UNKNOWN';
this.logEnabled = logEnabled || true;
var addMethod = function (method) {
this[method] = this.createLogMethod(method);
}.bind(this);
Logger.METHODS.forEach(addMethod);
};
Logger.prototype.createLogMethod = function (method) {
return Logger.prototype.log.bind(this, method);
};
Logger.prototype.prefix = function (method, args) {
var prepend = '[' + method.toUpperCase() + '][' + this.name + ']:\t';
if ([
'log',
'error',
'warn',
'info',
'debug'
].indexOf(method) !== -1) {
if ('string' === typeof args[0]) {
args[0] = prepend + args[0];
} else {
args.unshift(prepend);
}
}
return args;
};
Logger.prototype.log = function () {
var args = [].slice.call(arguments);
var method = args.shift();
if (Logger.METHODS.indexOf(method) === -1) {
method = 'log';
}
if (!(this.logEnabled && console && console[method])) {
return;
}
args = this.prefix(method, args);
console[method].apply(console, args);
};
Logger.prototype.setLogEnabled = function (logEnabled) {
this.logEnabled = logEnabled || true;
};
Logger.mixin = function (destObject) {
destObject.__logger = new Logger(destObject.name || 'UNKNOWN');
var addMethod = function (method) {
if (method in destObject.prototype) {
console.warn('overwriting \'' + method + '\' on \'' + destObject.name + '\'.');
console.warn('the previous version can be found at \'_' + method + '\' on \'' + destObject.name + '\'.');
destObject.prototype['_' + method] = destObject.prototype[method];
}
destObject.prototype[method] = destObject.__logger.createLogMethod(method);
};
Logger.METHODS.forEach(addMethod);
};
return Logger;
}();
Ctl_Utils = function () {
/**
* This module is a collection of classes designed to make working with
* the CenturyLink Services API as easy as possible.
*
* @class Ctl.Utils
* @author Andrew Kovalenko <andrew@jaybirdgroup.com>
* @docauthor Andrew Kovalenko <andrew@jaybirdgroup.com>
* @private
*/
function Utils() {
var self = this;
/**
* Check if the URL is valid
* @param {[type]} url [description]
* @return {Boolean} [description]
*/
function isValidUrl(url) {
if (!url) {
return false;
}
var doc, base, anchor, isValid = false;
try {
doc = document.implementation.createHTMLDocument('');
base = doc.createElement('base');
base.href = base || window.lo;
doc.head.appendChild(base);
anchor = doc.createElement('a');
anchor.href = url;
doc.body.appendChild(anchor);
isValid = anchor.href !== '';
} catch (e) {
console.error(e);
} finally {
doc.head.removeChild(base);
doc.body.removeChild(anchor);
base = null;
anchor = null;
doc = null;
return isValid;
}
}
/**
* Method to encode the query string parameters
*
* @param {Object} params - an object of name value pairs that will be urlencoded
* @return {String} Returns the encoded string
*/
function encodeParams(params) {
var queryString;
if (params && Object.keys(params)) {
queryString = [].slice.call(arguments).reduce(function (a, b) {
return a.concat(b instanceof Array ? b : [b]);
}, []).filter(function (c) {
return 'object' === typeof c;
}).reduce(function (p, c) {
!(c instanceof Array) ? p = p.concat(Object.keys(c).map(function (key) {
return [
key,
c[key]
];
})) : p.push(c);
return p;
}, []).reduce(function (p, c) {
c.length === 2 ? p.push(c) : p = p.concat(c);
return p;
}, []).reduce(function (p, c) {
c[1] instanceof Array ? c[1].forEach(function (v) {
p.push([
c[0],
v
]);
}) : p.push(c);
return p;
}, []).map(function (c) {
c[1] = encodeURIComponent(c[1]);
return c.join('=');
}).join('&');
}
return queryString;
}
/**
* Method to determine whether or not the passed variable is a function
*
* @param {any} f - any variable
* @return {Boolean} Returns true or false
*/
function isFunction(f) {
return f && f !== null && typeof f === 'function';
}
/**
* A safe wrapper for executing a callback
*
* @param {Function} callback the passed-in callback method
* @param {Array} params an array of arguments to pass to the callback
* @param {Object} context an optional calling context for the callback
* @return Returns whatever would be returned by the callback. or false.
*/
function doCallback(callback, params, context) {
var returnValue;
if (isFunction(callback)) {
if (!params) {
params = [];
}
if (!context) {
context = this;
}
params.push(context);
//try {
returnValue = callback.apply(context, params);
}
return returnValue;
}
/**
* Save object into storage
* @param {String} key Object's key
* @param {Object} value Object's value
*/
function setObject(key, value) {
if (value) {
value = JSON.stringify(value);
}
this.set(key, value);
}
/**
* Save string into storage
* @param {String} key String key
* @param {String} value String value
*/
function set(key, value) {
var keyStore = 'ctlapi_' + key;
this[key] = value;
if (typeof Storage !== 'undefined') {
if (value) {
sessionStorage.setItem(keyStore, value);
} else {
sessionStorage.removeItem(keyStore);
}
}
}
/**
* Get object from storage
* @param {String} key Object's key
* @return {Object} value Object's value
*/
function getObject(key) {
return JSON.parse(this.get(key));
}
/**
* Get string from storage
* @param {String} key String key
* @return {String} value String value
*/
function get(key) {
var keyStore = 'ctlapi_' + key;
var value = null;
if (this[key]) {
value = this[key];
} else if (typeof Storage !== 'undefined') {
value = sessionStorage.getItem(keyStore);
}
return value;
}
/**
* Remove all data in storage
*/
function removeAll() {
for (var i = sessionStorage.length; i--;) {
// if (sessionStorage.key(i).indexOf('ctlapi_') > -1) {
sessionStorage.removeItem(sessionStorage.key(i)); // }
}
}
/**
* Merge the contents of two objects together into the first object. If target object in null will be created new one.
* @param {Object} target
* @param {Object} object1
* @returns {Object} extended object
*/
function extend(target, object1) {
if (target == null) {
target = {};
}
if (object1 != null) {
for (var i in object1) {
// check if the extended object has that property
if (object1.hasOwnProperty(i)) {
// mow check if the child is also and object so we go through it recursively
if (typeof target[i] == 'object' && target.hasOwnProperty(i) && target[i] != null) {
self.extend(target[i], object1[i]);
} else {
target[i] = object1[i];
}
}
}
}
return target;
}
/**
* Check if value is not null or undefined
* @param value
* @returns {boolean}
*/
function isNotNull(value) {
return value != null && typeof value != 'undefined';
}
/**
* Check if value is null or undefined
* @param value
* @returns {boolean}
*/
function isNull(value) {
return value == null || typeof value == 'undefined';
}
this.isValidUrl = isValidUrl;
this.encodeParams = encodeParams;
this.isFunction = isFunction;
this.doCallback = doCallback;
this.setObject = setObject;
this.set = set;
this.getObject = getObject;
this.get = get;
this.removeAll = removeAll;
this.extend = extend;
this.isNotNull = isNotNull;
this.isNull = isNull;
}
return new Utils();
}();
Ctl_Promise = function (Utils) {
/**
* Promise implementation
* @class Ctl.Promise
* @author Andrew Kovalenko <andrew@jaybirdgroup.com>
* @docauthor Andrew Kovalenko <andrew@jaybirdgroup.com>
* @private
* @requires Ctl.Utils
*/
function Promise() {
this.complete = false;
this.error = null;
this.result = null;
this.callbacks = [];
}
/**
* Promise implementation for callback
* @param {Function} callback Call once promise mits requirement
* @param {Object} context [description]
* @return {[type]} [description]
*/
Promise.prototype.then = function (callback, context) {
var f = function () {
return callback.apply(context, arguments);
};
if (this.complete) {
f(this.error, this.result);
} else {
this.callbacks.push(f);
}
};
/**
* Add handlers to be called when the Deferred object is resolved
* @param {Object} error Fired error in case of failure
* @param {Object} result Result
*/
Promise.prototype.done = function (error, result) {
this.complete = true;
this.error = error;
this.result = result;
if (this.callbacks) {
for (var i = 0; i < this.callbacks.length; i++) {
this.callbacks[i](error, result);
}
this.callbacks.length = 0;
}
};
/**
* Utility method to join promises
* @param {Ctl.Promise} promises Array of promises
* @return {Ctl.Promise} p
*/
Promise.join = function (promises) {
var p = new Promise(), total = promises.length, completed = 0, errors = [], results = [];
function notifier(i) {
return function (error, result) {
completed += 1;
errors[i] = error;
results[i] = result;
if (completed === total) {
p.done(errors, results);
}
};
}
for (var i = 0; i < total; i++) {
if (Utils.isFunction(promises[i])) {
promises[i]().then(notifier(i));
} else {
promises[i].then(notifier(i));
}
}
return p;
};
/**
* Utility method to chain Deferred
* @param {Ctl.Promise} promises Array of promises
* @param {Object} error Error if failed
* @param {Object} result Result if success
* @return {Ctl.Promise} Promise
*/
Promise.chain = function (promises, error, result) {
var p = new Promise();
if (promises === null || promises.length === 0) {
p.done(error, result);
} else {
function processPromise(err, res) {
promises.splice(0, 1);
//self.logger.info(promises.length)
if (promises) {
Promise.chain(promises, err, res).then(function (e, r) {
p.done(e, r);
});
} else {
p.done(err, res);
}
}
if (Utils.isFunction(promises[0])) {
promises[0](error, result).then(processPromise);
} else {
promises[0].then(processPromise);
}
}
return p;
};
return Promise;
}(Ctl_Utils);
Ctl_Ajax = function (Logger, Promise) {
function partial() {
var args = Array.prototype.slice.call(arguments);
var fn = args.shift();
return fn.bind(this, args);
}
/**
* Wrapper for AJAX calls
* @class Ctl.Ajax
* @author Andrew Kovalenko <andrew@jaybirdgroup.com>
* @docauthor Andrew Kovalenko <andrew@jaybirdgroup.com>
* @private
* @requires Ctl.Logger
* @requires Ctl.Promise
*/
function Ajax() {
var self = this;
var name = 'Ajax';
self.logger = new Logger(name);
/**
* Encode key-value object into URI string
*
* @private
* @param {Object/String} data Data to encode
* @return {String} Encoded string
*/
function encode(data) {
var result = '';
if (typeof data === 'string') {
result = data;
} else {
var e = encodeURIComponent;
for (var i in data) {
if (data.hasOwnProperty(i)) {
if (Object.keys(data).indexOf(i) > 0) {
result += '&';
}
result += e(i) + '=' + e(data[i]);
}
}
}
return result;
}
/**
* Request method to do AJAX calls.
* @param {String} method Request method. POST, GET, PUT, DELETE etc.
* @param {String} url Url to make request
* @param {Object} data Data to send
* @return {Ctl.Promise} p
*/
function request(method, url, data, headers) {
var p = new Promise(), timeout;
self.logger.time(method + ' ' + url);
(function (xhr) {
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
self.logger.timeEnd(method + ' ' + url);
clearTimeout(timeout);
if (this.status === 200) {
p.done(null, this);
}
if (this.status >= 400 && this.status <= 451 || this.status >= 500 && this.status <= 511) {
p.done(this, null);
}
}
};
xhr.onerror = function (response) {
clearTimeout(timeout);
p.done(response, null);
};
xhr.oncomplete = function (response) {
clearTimeout(timeout);
self.logger.timeEnd(method + ' ' + url);
self.info('%s request to %s returned %s', method, url, this.status);
};
xhr.open(method, url);
xhr.responseType = 'json';
if (headers) {
for (var i = 0; i < headers.length; i++) {
if (headers[i][1]) {
xhr.setRequestHeader(headers[i][0], headers[i][1]);
}
}
}
timeout = setTimeout(function () {
xhr.abort();
p.done({ response: 'API Call timed out.' }, null);
}, 30000);
//BACKLOG:0 stick that timeout in a config variable
xhr.send(encode(data));
}(new XMLHttpRequest()));
return p;
}
this.request = request;
/**
* @method get Do AJAX GET request
* @param {String} url Url to make request
* @param {Object} data Data to send
* @return {Ctl.Promise} p
*/
this.get = partial(request, 'GET');
/**
* @method post Do AJAX POST request
* @param {String} url Url to make request
* @param {Object} data Data to send
* @return {Ctl.Promise} p
*/
this.post = partial(request, 'POST');
/**
* @method put Do AJAX PUT request
* @param {String} url Url to make request
* @param {Object} data Data to send
* @return {Ctl.Promise} p
*/
this.put = partial(request, 'PUT');
/**
* @method delete Do AJAX DELETE request
* @param {String} url Url to make request
* @param {Object} data Data to send
* @return {Ctl.Promise} p
*/
this.delete = partial(request, 'DELETE');
}
return new Ajax();
}(Ctl_Logger, Ctl_Promise);
Ctlmodelrequest_BaseRequest = function (Config) {
/**
* @class Ctl.model.request.BaseRequest
* @private
*
* Simple class that represents a Request that will be made by any Ctl.Ajax
* subclass. All this class does is standardize the representation of a Request
* as used by any BaseRequest subclass, it does not contain any actual logic or
* perform the request itself.
*/
function BaseRequest() {
this.requestHeaders = [
[
'Content-Type',
'application/x-www-form-urlencoded'
],
[
'Accept',
'application/json'
],
[
'Authorization',
this.accessToken ? 'Bearer ' + this.accessToken : ''
]
];
}
BaseRequest.prototype = {
getRequestHeaders: function () {
return this.requestHeaders;
},
getCtlServerURL: function () {
var configSection = Config.useConfig;
var settings = Config.settings[configSection];
if (settings) {
return settings.ctlServerURL;
} else {
return Config.settings.intg.ctlServerURL;
}
}
};
return BaseRequest;
}(Ctlspeakeasy_Config);
Ctlmodelrequest_SubscriptionServiceIdentitiesRequest = function (Config, BaseRequest) {
/**
* @class Ctl.model.request.SubscriptionServiceIdentitiesRequest
* @extends Ctl.model.request.BaseRequest
* @private
*
* Get subscription service list request
*
*/
function SubscriptionServiceIdentitiesRequest() {
BaseRequest.call(this);
}
SubscriptionServiceIdentitiesRequest.prototype = Object.create(BaseRequest.prototype);
/**
* Retrieves request URL of the SubscriptionServiceIdentitiesRequest
* @return {String} string with URL for SubscriptionServiceIdentitiesRequest
*/
SubscriptionServiceIdentitiesRequest.prototype.getRequestUrl = function () {
return this.getCtlServerURL() + Config.settings.getSubscriptionServiceIdentitiesURI;
};
/**
* Type of the Ajax request
* @type {String}
*/
SubscriptionServiceIdentitiesRequest.prototype.type = 'GET';
return SubscriptionServiceIdentitiesRequest;
}(Ctlspeakeasy_Config, Ctlmodelrequest_BaseRequest);
Ctlmodelrequest_SubscriptionServiceCatalogRequest = function (Config, BaseRequest) {
/**
* @class Ctl.model.request.SubscriptionServiceCatalogRequest
* @extends Ctl.model.request.BaseRequest
* @private
*
* Get subscription service catalog request
*
* @param {String} serviceName name of the service to get info about
* @param {String} publicId public user ID
*/
function SubscriptionServiceCatalogRequest(serviceName, publicId) {
BaseRequest.call(this);
this.serviceName = serviceName;
this.publicId = publicId;
}
SubscriptionServiceCatalogRequest.prototype = Object.create(BaseRequest.prototype);
/**
* Retrieves request URL of the SubscriptionServiceCatalogRequest
* @return {String} string with URL for SubscriptionServiceCatalogRequest
*/
SubscriptionServiceCatalogRequest.prototype.getRequestUrl = function () {
return this.getCtlServerURL() + Config.settings.getSubscriptionServiceCatalogURI.replace('{service}', this.serviceName).replace('{publicId}', this.publicId);
};
/**
* Type of the Ajax request
* @type {String}
*/
SubscriptionServiceCatalogRequest.prototype.type = 'GET';
return SubscriptionServiceCatalogRequest;
}(Ctlspeakeasy_Config, Ctlmodelrequest_BaseRequest);
Ctl_Subscription = function (Logger, Promise, Ajax, Utils, SubscriptionServiceIdentitiesRequest, SubscriptionServiceCatalogRequest) {
/**
* Work with subscription services in CenturyLink
* @class Ctl.Subscription
* @author Andrew Kovalenko <andrew@jaybirdgroup.com>
* @docauthor Andrew Kovalenko <andrew@jaybirdgroup.com>
* @private
* @requires Ctl.Logger
* @requires Ctl.Promise
* @requires Ctl.Ajax
* @requires Ctl.Utils
* @requires Ctl.model.request.SubscriptionServiceIdentitiesRequest
* @requires Ctl.model.request.SubscriptionServiceCatalogRequest
*/
function Subscription() {
var logger = new Logger('Subscription');
/**
* Storage keywords for storing subscription settings
* @type {Object}
*/
var config = {
storageKeywords: {
services: 'services',
serviceName: 'serviceName',
serviceCatalog: {
publicId: 'publicId',
domain: 'domain',
id: 'id',
webSocketEndpoints: 'webSocketEndpoints',
voipTnCipherRef: 'voipTnCipherRef'
}
}
};
/**
* Retrieve list of subscribed services
* @return {Ctl.Promise} CTL promise object
*/
function getSubscriptionServices() {
var slRequest = new SubscriptionServiceIdentitiesRequest();
return Ajax.request(slRequest.type, slRequest.getRequestUrl(), null, slRequest.getRequestHeaders());
}
/**
* Retrieve details of particular service
* @param {String} serviceName Name of the service to get details about
* @param {String} publicId Moniker (public ID) tied to the service
* @return {Ctl.Promise} CTL promise object
*/
function getSubscriptionServiceDetails(serviceName, publicId) {
var seCatalogRequest = new SubscriptionServiceCatalogRequest(serviceName, publicId);
return Ajax.request(seCatalogRequest.type, seCatalogRequest.getRequestUrl(), null, seCatalogRequest.getRequestHeaders());
}
/**
* Set services into storage
* @param {Object} services Object with services
* @protected
*/
function setServices(services) {
Utils.set(config.storageKeywords.services, JSON.stringify(services));
}
/**
* Get services from the storage
* @return {Object} Object with services
* @protected
*/
function getServices() {
return Utils.get(config.storageKeywords.services);
}
/**
* Set service catalog details into storage
* @param {Object} serviceCatalog Object with service details
*/
function setServiceCatalog(serviceCatalog) {
Utils.setObject(config.storageKeywords.serviceName, serviceCatalog.productName);
Utils.setObject(config.storageKeywords.services + '_' + serviceCatalog.productName, serviceCatalog);
}
/**
* Get service details
* @return {Object} Object with service details
*/
function getServiceCatalog() {
var serviceName = Utils.getObject(config.storageKeywords.serviceName);
return Utils.getObject(config.storageKeywords.services + '_' + serviceName);
}
/**
* Set moniker (public ID) in storage
* @param {String} publicId user's public ID (moniker)
*/
function setPublicId(publicId) {
Utils.set(config.storageKeywords.serviceCatalog.publicId, publicId);
}
/**
* Get moniker (public ID) from storage
*/
function getPublicId() {
return Utils.get(config.storageKeywords.serviceCatalog.publicId);
}
this.getSubscriptionServices = getSubscriptionServices;
this.getSubscriptionServiceDetails = getSubscriptionServiceDetails;
this.getServiceCatalog = getServiceCatalog;
this.setServiceCatalog = setServiceCatalog;
this.setPublicId = setPublicId;
this.getPublicId = getPublicId;
}
return new Subscription();
}(Ctl_Logger, Ctl_Promise, Ctl_Ajax, Ctl_Utils, Ctlmodelrequest_SubscriptionServiceIdentitiesRequest, Ctlmodelrequest_SubscriptionServiceCatalogRequest);
Ctl_Error = function () {
/**
* Represent the error
* @class Ctl.Error
* @author Peter Jashchenko <peter@jaybirdgroup.com>
* @docauthor Peter Jashchenko <peter@jaybirdgroup.com>
* @param {String} type The type of error
* @param {Number} code The error code
* @param {String} message The error message
*/
function Error(type, code, message) {
this.type = type;
this.code = code;
this.message = message;
}
/**
* Error types
* @type {Object}
*/
Error.Types = {
LOGIN: 'LOGIN',
NOTIFICATION: 'NOTIFICATION',
MEDIA: 'MEDIA',
CALLING: 'CALLING',
OTHER: 'OTHER'
};
return Error;
}();
/*!
* FCS Javascript Library
*
* Copyright 2012, Genband
*/
(function (window, undefined) {
// Base64 by Kevin van Zonneveld - Public Domain
// Original Source: http://kevin.vanzonneveld.net/
function base64_encode(data) {
// http://kevin.vanzonneveld.net
// + original by: Tyler Akins (http://rumkin.com)
// + improved by: Bayron Guevara
// + improved by: Thunder.m
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Pellentesque Malesuada
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// - depends on: utf8_encode
// * example 1: base64_encode('Kevin van Zonneveld');
// * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
// mozilla has this native
// - but breaks in 2.0.0.12!
//if (typeof this.window['atob'] == 'function') {
// return atob(data);
//}
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = '', tmp_arr = [];
if (!data) {
return data;
}
data = utf8_encode(data + '');
do {
// pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 63;
h2 = bits >> 12 & 63;
h3 = bits >> 6 & 63;
h4 = bits & 63;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc;
}
function base64_decode(data) {
// http://kevin.vanzonneveld.net
// + original by: Tyler Akins (http://rumkin.com)
// + improved by: Thunder.m
// + input by: Aman Gupta
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Onno Marsman
// + bugfixed by: Pellentesque Malesuada
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + input by: Brett Zamir (http://brett-zamir.me)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// - depends on: utf8_decode
// * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
// * returns 1: 'Kevin van Zonneveld'
// mozilla has this native
// - but breaks in 2.0.0.12!
//if (typeof this.window['btoa'] == 'function') {
// return btoa(data);
//}
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = '', tmp_arr = [];
if (!data) {
return data;
}
data += '';
do {
// unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 255;
o2 = bits >> 8 & 255;
o3 = bits & 255;
if (h3 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1);
} else if (h4 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2);
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
} while (i < data.length);
dec = tmp_arr.join('');
dec = utf8_decode(dec);
return dec;
}
function utf8_encode(argString) {
// http://kevin.vanzonneveld.net
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: sowberry
// + tweaked by: Jack
// + bugfixed by: Onno Marsman
// + improved by: Yves Sucaet
// + bugfixed by: Onno Marsman
// + bugfixed by: Ulrich
// * example 1: utf8_encode('Kevin van Zonneveld');
// * returns 1: 'Kevin van Zonneveld'
var string = argString + '';
// .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
var utftext = '';
var start, end;
var stringl = 0;
start = end = 0;
stringl = string.length;
for (var n = 0; n < stringl; n++) {
var c1 = string.charCodeAt(n);
var enc = null;
if (c1 < 128) {
end++;
} else if (c1 > 127 && c1 < 2048) {
enc = String.fromCharCode(c1 >> 6 | 192) + String.fromCharCode(c1 & 63 | 128);
} else {
enc = String.fromCharCode(c1 >> 12 | 224) + String.fromCharCode(c1 >> 6 & 63 | 128) + String.fromCharCode(c1 & 63 | 128);
}
if (enc !== null) {
if (end > start) {
utftext += string.substring(start, end);
}
utftext += enc;
start = end = n + 1;
}
}
if (end > start) {
utftext += string.substring(start, string.length);
}
return utftext;
}
function utf8_decode(str_data) {
// http://kevin.vanzonneveld.net
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + input by: Aman Gupta
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Norman "zEh" Fuchs
// + bugfixed by: hitwork
// + bugfixed by: Onno Marsman
// + input by: Brett Zamir (http://brett-zamir.me)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// * example 1: utf8_decode('Kevin van Zonneveld');
// * returns 1: 'Kevin van Zonneveld'
var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0;
str_data += '';
while (i < str_data.length) {
c1 = str_data.charCodeAt(i);
if (c1 < 128) {
tmp_arr[ac++] = String.fromCharCode(c1);
i++;
} else if (c1 > 191 && c1 < 224) {
c2 = str_data.charCodeAt(i + 1);
tmp_arr[ac++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
i += 2;
} else {
c2 = str_data.charCodeAt(i + 1);
c3 = str_data.charCodeAt(i + 2);
tmp_arr[ac++] = String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
i += 3;
}
}
return tmp_arr.join('');
}
var Map = function () {
var items = {}, length = 0;
this.size = function () {
return length;
};
this.add = function (key, value) {
length++;
items[key] = value;
return this;
};
this.get = function (key) {
return items[key];
};
this.remove = function (key) {
length--;
return delete items[key];
};
this.clear = function () {
var variableKey;
for (variableKey in items) {
if (items.hasOwnProperty(variableKey)) {
if (delete items[variableKey]) {
length--;
}
}
}
};
this.entries = function () {
return items;
};
};
function extend(target, object) {
var prop;
for (prop in object) {
if (object.hasOwnProperty(prop)) {
target[prop] = object[prop];
}
}
return target;
}
function addToServiceListImpl(instance, service, manager, _fcsConfig) {
if (!_fcsConfig.serviceManagerMap) {
_fcsConfig.serviceManagerMap = new Map();
}
if (!_fcsConfig.serviceManagerMap.get(instance)) {
_fcsConfig.serviceManagerMap.add(instance, new Map());
}
_fcsConfig.serviceManagerMap.get(instance).add(service, manager);
}
function addToServiceList(instance, service, manager) {
addToServiceListImpl(instance, service, manager, fcsConfig);
}
var GlobalBroadcaster = function () {
var MAX_PRIORITY = 10, MIN_PRIORITY = 1, topics = {}, subUid = -1;
function unsubscribeFromTopic(token) {
var m, i, j;
for (m in topics) {
if (topics[m] && topics.hasOwnProperty(m)) {
j = topics[m].length;
for (i = 0; i < j; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1);
return token;
}
}
}
}
return false;
}
function subscribeToTopic(topic, func, priority, temporary) {
var token, prio = MAX_PRIORITY, temp = false;
if (typeof topic !== 'string') {
throw new Error('First parameter must be a string topic name.');
}
if (typeof func !== 'function') {
throw new Error('Second parameter must be a function.');
}
if (typeof priority !== 'undefined') {
if (typeof priority !== 'number') {
throw new Error('Priority must be a number.');
} else {
if (priority > MAX_PRIORITY || priority < MIN_PRIORITY) {
throw new Error('Priority must be between 1-10.');
} else {
prio = priority;
}
}
}
if (temporary === true) {
temp = temporary;
}
if (!topics[topic]) {
topics[topic] = [];
}
token = (++subUid).toString();
topics[topic].push({
token: token,
prio: prio,
func: func,
temp: temp
});
topics[topic].sort(function (a, b) {
return parseFloat(b.prio) - parseFloat(a.prio);
});
return token;
}
function publishTopic(topic, args) {
var subscribers, len, _args, _topic;
if (arguments.length === 0) {
throw new Error('First parameter must be a string topic name.');
}
_args = Array.prototype.slice.call(arguments);
_topic = _args.shift();
subscribers = topics[_topic];
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func.apply(null, _args);
if (subscribers[len].temp) {
unsubscribeFromTopic(subscribers[len].token);
}
}
}
/*
*
* Publish events of interest
* with a specific topic name and arguments
* such as the data to pass along
*
* @param {string} topic - Topic name.
* @param {...*} [args] - arguments.
*
* @returns {undefined}
*/
this.publish = publishTopic;
/*
*
* Subscribe to events of interest
* with a specific topic name and a
* callback function, to be executed
* when the topic/event is observed.
* Default priority 10.
* Priority must be between 1-10.
* Functions with lower priority
* will be executed first.
*
* @param {string} topic - Topic name.
* @param {type} func - function to be executed when the topic/event is observed
* @param {number} [priority] - function with higher priority will be executed first
* @param {boolean} [temporary] - if set to true, subscriber will unsubcribe automatically after first execution.
*
* @returns {string} token - reference to subscription
*/
this.subscribe = subscribeToTopic;
/*
*
* Unsubscribe from a specific
* topic, based on a tokenized reference
* to the subscription
*
* @param {string} token - reference to subscription
*
* @returns {false|string} - returns token if successfull,
* otherwise returns false.
*/
this.unsubscribe = unsubscribeFromTopic;
};
//@{fcs-jsl-prod}
var globalBroadcaster = new GlobalBroadcaster();
//@{fcs-jsl-prod}
var CONSTANTS = {
'WEBRTC': {
'PLUGIN_ID': 'fcsPlugin',
'MEDIA_STATE': {
NOT_FOUND: 'notfound',
SEND_RECEIVE: 'sendrecv',
SEND_ONLY: 'sendonly',
RECEIVE_ONLY: 'recvonly',
INACTIVE: 'inactive'
},
'RTC_SIGNALING_STATE': {
STABLE: 'stable',
HAVE_LOCAL_OFFER: 'have-local-offer',
HAVE_REMOTE_OFFER: 'have-remote-offer',
HAVE_LOCAL_PRANSWER: 'have-local-pranswer',
HAVE_REMOTE_PRANSWER: 'have-remote-pranswer',
CLOSED: 'closed'
},
'RTC_SDP_TYPE': {
'OFFER': 'offer',
'ANSWER': 'answer',
'PRANSWER': 'pranswer'
},
'ERROR': { 'ICE_ICELITE': 'ICE_ICELITE' }
},
'STRING': {
'NEW_LINE': '\n',
'CARRIAGE_RETURN': '\r',
'VIDEO': 'video',
'AUDIO': 'audio'
},
'SDP': {
'A_LINE': 'a=',
'M_LINE': 'm=',
'CRYPTO': 'crypto',
'FINGERPRINT': 'fingerprint',
'ICE_UFRAG': 'ice-ufrag:',
'ICE_PWD': 'ice-pwd:',
'NACK': 'nack',
'NACKPLI': 'nack pli',
'SETUP_ACTIVE': 'a=setup:active',
'SETUP_PASSIVE': 'a=setup:passive',
'SETUP_ACTPASS': 'a=setup:actpass'
},
'HTTP_METHOD': {
'GET': 'GET',
'POST': 'POST',
'PUT': 'PUT',
'DELETE': 'DELETE',
'OPTIONS': 'OPTIONS'
},
'WEBSOCKET': {
'PROTOCOL': {
'SECURE': 'wss',
'NONSECURE': 'ws'
},
'DEFAULT_PORT': '8581',
'STATUS': {
'OPENED': 1,
'ALREADY_OPENED': 2,
'CREATE_ERROR': 3,
'CONNECTION_ERROR': 4,
'NOT_FOUND': 5,
'CONNECTION_CLOSED': 6
}
},
'LONG_POLLING': { 'STATUS': { 'TRIGGERED_CONNECT': 1 } },
'NOTIFICATION': {
'STATUS': {
'NOT_STARTED': 3,
'CONFIGURATION_ERROR': 4,
'STOP_FOR_LP_TO_WS_UPGRADE': 5
}
},
'EVENT': {
'XHR_REQUEST_NOT_INITIALIZED': 'XHR_REQUEST_NOT_INITIALIZED',
'DEVICE_SUBSCRIPTION_STARTED': 'DEVICE_SUBSCRIPTION_STARTED',
'DEVICE_SUBSCRIPTION_ENDED': 'DEVICE_SUBSCRIPTION_ENDED',
'CONNECTION_REESTABLISHED': 'CONNECTION_REESTABLISHED',
'CONNECTION_LOST': 'CONNECTION_LOST',
'TOKEN_AUTH_STARTED': 'TOKEN_AUTH_STARTED',
'BASIC_AUTH_STARTED': 'BASIC_AUTH_STARTED',
'TOKEN_NOT_FOUND': 'TOKEN_NOT_FOUND',
'SESSION_EXPIRED': 'SESSION_EXPIRED',
'NOTIFICATION_CHANNEL_LOST': 'NOTIFICATION_CHANNEL_LOST',
'FCS_SETUP_COMPLETED': 'FCS_SETUP_COMPLETED',
'WEBSOCKET_CONNECTED': 'WEBSOCKET_CONNECTED',
'WEBSOCKET_DISCONNECTED': 'WEBSOCKET_DISCONNECTED',
'FORCE_CONNECTIVITY_CHECK': 'FORCE_CONNECTIVITY_CHECK'
},
'SUBSCRIPTION_EVENT': {
'TOKEN_OR_SESSION_LOSS': 'TOKEN_OR_SESSION_LOSS',
'SUBSCRIPTION_SUCCESS': 'SUBSCRIPTION_SUCCESS',
'EXTEND_SUCCESS': 'EXTEND_SUCCESS',
'EXTEND_FAILURE': 'EXTEND_FAILURE',
'REGULAR_EXTEND_PROCESSING': 'REGULAR_EXTEND_PROCESSING',
'STOP_SUCCESS': 'STOP_SUCCESS',
'STOP_FAILURE': 'STOP_FAILURE',
'CONNECTION_LOSS': 'CONNECTION_LOSS',
'SET_NOTIFICATION_ONERROR': 'SET_NOTIFICATION_ONERROR',
'SET_NOTIFICATION_ONSUCCESS': 'SET_NOTIFICATION_ONSUCCESS',
'TRIGGER_LONG_POLLING': 'TRIGGER_LONG_POLLING',
'RESTART_SUBSCRIPTION_REQUEST': 'RESTART_SUBSCRIPTION_REQUEST'
},
'NOTIFICATION_EVENT': {
'NOTIFICATION_SUCCESS': 'NOTIFICATION_SUCCESS',
'NOTIFICATION_FAILURE': 'NOTIFICATION_FAILURE'
},
'CACHE': {
'NOTIFYURL': 'NotificationUrl',
'NOTIFYID': 'NotificationId',
'SUBSCRIBEURL': 'SubscriptionUrl',
'SUBSCRIBEEXPIRY': 'SubscriptionExpiry',
'SUBSCRIBEEXTENDINTERVAL': 'SubscriptionExtendInterval',
'USERNAME': 'USERNAME',
'PASSWORD': 'PASSWORD',
'SESSION': 'SESSION'
},
'TIMEOUT': {
'INTERVAL_TO_PREVENT_CONFLICTS': 5000,
'DEFAULT_CONNECTIVITY_CHECK_INTERVAL': 10000
}
};
var JQrestfulImpl = function (_globalBroadcaster) {
var DEFAULT_LONGPOLLING_TOLERANCE = 30000, DEFAULT_AJAX_TIMEOUT = 40000, XHR_READY_STATE = {
REQUEST_NOT_INITIALIZED: 0,
REQUEST_DONE: 4
};
function getLogger() {
return logManager.getLogger('jQrestful');
}
function composeAjaxRequestResponseLog(context, xhr, errorThrown, data) {
var responseLog = context;
if (data) {
responseLog.data = data;
}
if (errorThrown) {
responseLog.errorThrown = errorThrown;
}
if (xhr) {
responseLog.status = xhr.status;
responseLog.statusText = xhr.statusText;
responseLog.responseText = xhr.responseText;
responseLog.readyState = xhr.readyState;
}
return responseLog;
}
function parseError(x, e) {
var returnResult, statusCode;
getLogger().error('parseError:\'' + e + '\' Status:\'' + x.status + '\' ResponseText:\'' + x.responseText + '\'');
if (x.responseText && x.responseText.search('statusCode') !== -1) {
if (JSON.parse(x.responseText).subscribeResponse !== undefined) {
statusCode = JSON.parse(x.responseText).subscribeResponse.statusCode;
} else if (JSON.parse(x.responseText).authorizationResponse !== undefined) {
statusCode = JSON.parse(x.responseText).authorizationResponse.statusCode;
}
}
statusCode = statusCode ? statusCode : x.status;
switch (statusCode) {
case 401:
returnResult = fcs.Errors.AUTH;
break;
case 403:
returnResult = fcs.Errors.INCORRECT_LOGIN_PASS;
break;
case 19:
returnResult = fcs.Errors.LOGIN_LIMIT_CLIENT;
break;
case 20:
returnResult = fcs.Errors.LOGIN_LIMIT_TABLET;
break;
case 44:
returnResult = fcs.Errors.FORCE_LOGOUT_ERROR;
break;
case 46:
returnResult = fcs.Errors.TOKEN_NOT_FOUND;
break;
case 47:
returnResult = fcs.Errors.SESSION_EXPIRED;
break;
default:
returnResult = fcs.Errors.NETWORK;
}
return returnResult;
}
// TODO tolga: remove parseError when all of the responseTypes are added
function parseErrorStatusCode(x, e, responseType) {
getLogger().error('parseErrorStatusCode:\'' + e + '\' Status:\'' + x.status + '\' ResponseText:\'' + x.responseText + '\'');
if (x.responseText && x.responseText.search('statusCode') !== -1 && JSON.parse(x.responseText)[responseType] !== undefined) {
return JSON.parse(x.responseText)[responseType].statusCode;
}
return x.status === 401 || x.status === 403 ? x.status : 400;
}
/*
* @ignore
*/
this.call = function (method, callParams, successHandler, errorHandler, successParser, errorParser, responseType, headers) {
var data, timeout = DEFAULT_AJAX_TIMEOUT, url = callParams.url, urlWithoutRestVersion = url.split('/rest/version/')[1], resourceString, logger = getLogger(), xhr, queryString, finalHeaders, headerKey, responseLogContext, handleSuccess, handleError, isSuccess, modValues;
if (callParams && callParams.data) {
data = callParams.data;
}
if (fcsConfig.polling) {
timeout = fcsConfig.polling * 1000;
if (fcsConfig.longpollingTolerans) {
timeout = timeout + fcsConfig.longpollingTolerans;
} else {
timeout = timeout + DEFAULT_LONGPOLLING_TOLERANCE;
}
}
// do not log isAlive requests
if (urlWithoutRestVersion && urlWithoutRestVersion.indexOf('isAlive') === -1) {
// extracting rest resource from url.
// ".../rest/version/<ver>/<user/anonymous>/<userName>/restResource/..."
resourceString = urlWithoutRestVersion.split('/')[3];
if (!resourceString) {
// rest resource string not found, get last string in the url
resourceString = url.substring(url.lastIndexOf('/') + 1, url.length);
}
// remove "?" if exists
resourceString = resourceString.split('?')[0];
if (data && !data.imRequest) {
logger.info('Send ajax request: ' + resourceString, data);
} else {
logger.info('Send ajax request: ' + resourceString);
}
}
if (method === 'GET') {
// Take the data parameters and append them to the URL.
queryString = utils.serialize(data);
if (queryString.length > 0) {
if (url.indexOf('?') === -1) {
url += '?' + queryString;
} else {
url += '&' + queryString;
}
}
// Remove data so that we don't add it to the body.
data = null;
}
xhr = new XMLHttpRequest();
// TODO: Kadir Goktas
// listeners below are functional expect for IE9.
// we can replace xhr.onstatechange handler
// accordingly, once IE9 is deprecated.
xhr.onload = function () {
handleSuccess(xhr);
};
xhr.onabort = function () {
logger.trace('Ajax request aborted internally. not calling failure callback');
};
xhr.onerror = function () {
logger.error('Ajax request error! Handle the error');
handleError(xhr);
};
// ajax hook to modify url and headers
if (fcsConfig.ajaxHook) {
modValues = window[fcsConfig.ajaxHook].call(xhr, window, {
type: method,
url: url,
headers: headers,
data: data
});
if (modValues) {
url = modValues.url ? modValues.url : url;
headers = modValues.headers ? modValues.headers : headers;
}
}
xhr.open(method, url, fcs.isAsync());
if (fcs.isAsync()) {
xhr.withCredentials = fcsConfig.cors ? true : false;
xhr.timeout = timeout;
}
finalHeaders = {
// Old implementation used jQuery without changing content type. Doing the same here for
// backwards compatibility.
'Content-Type': 'application/x-www-form-urlencoded',
// JQuery always adds this header by default. Adding here for backwards compatibility.
'X-Requested-With': 'XMLHttpRequest'
};
finalHeaders = extend(finalHeaders, headers);
// Set the headers.
for (headerKey in finalHeaders) {
if (finalHeaders.hasOwnProperty(headerKey)) {
xhr.setRequestHeader(headerKey, finalHeaders[headerKey]);
}
}
xhr.setRequestHeader('X-CtlRTC-SPIDR-FQDN', fcs.fcsConfig.websocketIP);
if (typeof data !== 'string') {
data = JSON.stringify(data);
}
xhr.send(data);
// Used for logging information,
responseLogContext = {
type: method,
url: url,
dataType: 'json',
async: fcs.isAsync(),
jsonp: false,
crossDomain: