UNPKG

cl-speakeasy-sdk

Version:
1,524 lines 1.01 MB
/** * @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: